django-cfg 1.4.10__py3-none-any.whl → 1.4.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +72 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/dashboard/sections/documentation.py +391 -0
  20. django_cfg/management/commands/check_endpoints.py +11 -160
  21. django_cfg/management/commands/check_settings.py +13 -348
  22. django_cfg/management/commands/clear_constance.py +13 -201
  23. django_cfg/management/commands/create_token.py +13 -321
  24. django_cfg/management/commands/generate_clients.py +23 -0
  25. django_cfg/management/commands/list_urls.py +13 -306
  26. django_cfg/management/commands/migrate_all.py +13 -126
  27. django_cfg/management/commands/migrator.py +13 -396
  28. django_cfg/management/commands/rundramatiq.py +15 -247
  29. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  30. django_cfg/management/commands/runserver_ngrok.py +15 -160
  31. django_cfg/management/commands/script.py +12 -488
  32. django_cfg/management/commands/show_config.py +12 -215
  33. django_cfg/management/commands/show_urls.py +12 -342
  34. django_cfg/management/commands/superuser.py +15 -295
  35. django_cfg/management/commands/task_clear.py +14 -217
  36. django_cfg/management/commands/task_status.py +13 -248
  37. django_cfg/management/commands/test_email.py +15 -86
  38. django_cfg/management/commands/test_telegram.py +14 -61
  39. django_cfg/management/commands/test_twilio.py +15 -105
  40. django_cfg/management/commands/tree.py +13 -383
  41. django_cfg/management/commands/validate_openapi.py +10 -0
  42. django_cfg/middleware/README.md +1 -1
  43. django_cfg/middleware/user_activity.py +3 -3
  44. django_cfg/models/__init__.py +2 -2
  45. django_cfg/models/api/drf/spectacular.py +6 -6
  46. django_cfg/models/django/__init__.py +2 -2
  47. django_cfg/models/django/openapi.py +238 -0
  48. django_cfg/modules/django_admin/management/__init__.py +0 -0
  49. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  50. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  51. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  52. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  53. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  54. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  55. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  56. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  57. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  58. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  59. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  60. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  61. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  62. django_cfg/modules/django_client/__init__.py +20 -0
  63. django_cfg/modules/django_client/apps.py +35 -0
  64. django_cfg/modules/django_client/core/__init__.py +56 -0
  65. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  66. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  67. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  68. django_cfg/modules/django_client/core/cli/main.py +235 -0
  69. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  70. django_cfg/modules/django_client/core/config/config.py +188 -0
  71. django_cfg/modules/django_client/core/config/group.py +101 -0
  72. django_cfg/modules/django_client/core/config/service.py +209 -0
  73. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  74. django_cfg/modules/django_client/core/generator/base.py +767 -0
  75. django_cfg/modules/django_client/core/generator/python.py +751 -0
  76. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  77. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  78. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  79. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  80. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  81. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  82. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  83. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  84. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  85. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  86. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  87. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  94. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  95. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  96. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  97. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  98. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  99. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  100. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  102. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  103. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  104. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  105. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  112. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  113. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  114. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  115. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  116. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  117. django_cfg/modules/django_client/core/ir/context.py +387 -0
  118. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  119. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  120. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  121. django_cfg/modules/django_client/core/parser/base.py +648 -0
  122. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  123. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  124. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  125. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  126. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  127. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  128. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  129. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  130. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  131. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  132. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  133. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  134. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  135. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  136. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  137. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  138. django_cfg/modules/django_client/management/__init__.py +3 -0
  139. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  140. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  141. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  142. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  143. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  144. django_cfg/modules/django_client/urls.py +72 -0
  145. django_cfg/modules/django_email/management/__init__.py +0 -0
  146. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  147. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  148. django_cfg/modules/django_logging/django_logger.py +6 -6
  149. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  150. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  151. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  152. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  153. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  154. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  155. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  156. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  157. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  158. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  159. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  160. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  161. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  162. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  164. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  165. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  166. django_cfg/pyproject.toml +2 -6
  167. django_cfg/registry/third_party.py +5 -7
  168. django_cfg/routing/callbacks.py +1 -1
  169. django_cfg/static/admin/css/prose-unfold.css +666 -0
  170. django_cfg/templates/admin/index.html +8 -0
  171. django_cfg/templates/admin/index_new.html +13 -0
  172. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  173. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  174. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  175. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  176. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
  177. django_cfg/management/commands/generate.py +0 -107
  178. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  179. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,12 @@
1
+ """
2
+ OpenAPI Schema
3
+
4
+ This file contains the complete OpenAPI specification for this API.
5
+ It can be used for documentation, validation, or code generation.
6
+ """
7
+
8
+ from typing import Any, Dict
9
+
10
+ OPENAPI_SCHEMA: Dict[str, Any] = {{ schema_dict }}
11
+
12
+ __all__ = ["OPENAPI_SCHEMA"]
@@ -0,0 +1,2 @@
1
+ export * from "./client";
2
+ export * as Models from "./models";
@@ -0,0 +1,18 @@
1
+ import * as Models from "./models";
2
+
3
+
4
+ /**
5
+ * API endpoints for {{ tag }}.
6
+ */
7
+ export class {{ class_name }} {
8
+ private client: any;
9
+
10
+ constructor(client: any) {
11
+ this.client = client;
12
+ }
13
+
14
+ {% for operation in operations %}
15
+ {{ operation }}
16
+
17
+ {% endfor %}
18
+ }
@@ -0,0 +1,327 @@
1
+ {% if sub_clients %}
2
+ {# Namespaced client with sub-clients #}
3
+ {% if include_imports %}
4
+ {% for tag in tags %}
5
+ import { {{ tag.class_name }} } from "./{{ tag.slug }}";
6
+ {% endfor %}
7
+ import { HttpClientAdapter, FetchAdapter } from "./http";
8
+ import { APIError, NetworkError } from "./errors";
9
+ import { APILogger, type LoggerConfig } from "./logger";
10
+
11
+
12
+ {% endif %}
13
+ /**
14
+ * Async API client for {{ info.title }}.
15
+ *
16
+ * Usage:
17
+ * ```typescript
18
+ * const client = new APIClient('https://api.example.com');
19
+ * const users = await client.users.list();
20
+ * const post = await client.posts.create(newPost);
21
+ *
22
+ * // Custom HTTP adapter (e.g., Axios)
23
+ * const client = new APIClient('https://api.example.com', {
24
+ * httpClient: new AxiosAdapter()
25
+ * });
26
+ * ```
27
+ */
28
+ export class APIClient {
29
+ private baseUrl: string;
30
+ private httpClient: HttpClientAdapter;
31
+ private logger: APILogger | null = null;
32
+
33
+ // Sub-clients
34
+ {% for tag in tags %}
35
+ public {{ tag.property }}: {{ tag.class_name }};
36
+ {% endfor %}
37
+
38
+ constructor(
39
+ baseUrl: string,
40
+ options?: {
41
+ httpClient?: HttpClientAdapter;
42
+ loggerConfig?: Partial<LoggerConfig>;
43
+ }
44
+ ) {
45
+ this.baseUrl = baseUrl.replace(/\/$/, '');
46
+ this.httpClient = options?.httpClient || new FetchAdapter();
47
+
48
+ // Initialize logger if config provided
49
+ if (options?.loggerConfig !== undefined) {
50
+ this.logger = new APILogger(options.loggerConfig);
51
+ }
52
+
53
+ // Initialize sub-clients
54
+ {% for tag in tags %}
55
+ this.{{ tag.property }} = new {{ tag.class_name }}(this);
56
+ {% endfor %}
57
+ }
58
+
59
+ /**
60
+ * Get CSRF token from cookies.
61
+ */
62
+ getCsrfToken(): string | null {
63
+ const name = 'csrftoken';
64
+ const value = `; ${document.cookie}`;
65
+ const parts = value.split(`; ${name}=`);
66
+ if (parts.length === 2) {
67
+ return parts.pop()?.split(';').shift() || null;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Make HTTP request with Django CSRF and session handling.
74
+ */
75
+ async request<T>(
76
+ method: string,
77
+ path: string,
78
+ options?: {
79
+ params?: Record<string, any>;
80
+ body?: any;
81
+ formData?: FormData;
82
+ }
83
+ ): Promise<T> {
84
+ const url = new URL(path, this.baseUrl);
85
+ const startTime = Date.now();
86
+
87
+ // Build headers
88
+ const headers: Record<string, string> = {};
89
+
90
+ // Don't set Content-Type for FormData (browser will set it with boundary)
91
+ if (!options?.formData) {
92
+ headers['Content-Type'] = 'application/json';
93
+ }
94
+
95
+ // Add CSRF token for non-GET requests
96
+ if (method !== 'GET') {
97
+ const csrfToken = this.getCsrfToken();
98
+ if (csrfToken) {
99
+ headers['X-CSRFToken'] = csrfToken;
100
+ }
101
+ }
102
+
103
+ // Log request
104
+ if (this.logger) {
105
+ this.logger.logRequest({
106
+ method,
107
+ url: url.toString(),
108
+ headers,
109
+ body: options?.formData || options?.body,
110
+ timestamp: startTime,
111
+ });
112
+ }
113
+
114
+ try {
115
+ // Make request via HTTP adapter
116
+ const response = await this.httpClient.request<T>({
117
+ method,
118
+ url: url.toString(),
119
+ headers,
120
+ params: options?.params,
121
+ body: options?.body,
122
+ formData: options?.formData,
123
+ });
124
+
125
+ const duration = Date.now() - startTime;
126
+
127
+ // Check for HTTP errors
128
+ if (response.status >= 400) {
129
+ const error = new APIError(
130
+ response.status,
131
+ response.statusText,
132
+ response.data,
133
+ url.toString()
134
+ );
135
+
136
+ // Log error
137
+ if (this.logger) {
138
+ this.logger.logError(
139
+ {
140
+ method,
141
+ url: url.toString(),
142
+ headers,
143
+ body: options?.formData || options?.body,
144
+ timestamp: startTime,
145
+ },
146
+ {
147
+ message: error.message,
148
+ statusCode: response.status,
149
+ duration,
150
+ timestamp: Date.now(),
151
+ }
152
+ );
153
+ }
154
+
155
+ throw error;
156
+ }
157
+
158
+ // Log successful response
159
+ if (this.logger) {
160
+ this.logger.logResponse(
161
+ {
162
+ method,
163
+ url: url.toString(),
164
+ headers,
165
+ body: options?.formData || options?.body,
166
+ timestamp: startTime,
167
+ },
168
+ {
169
+ status: response.status,
170
+ statusText: response.statusText,
171
+ data: response.data,
172
+ duration,
173
+ timestamp: Date.now(),
174
+ }
175
+ );
176
+ }
177
+
178
+ return response.data as T;
179
+ } catch (error) {
180
+ const duration = Date.now() - startTime;
181
+
182
+ // Re-throw APIError as-is
183
+ if (error instanceof APIError) {
184
+ throw error;
185
+ }
186
+
187
+ // Wrap other errors as NetworkError
188
+ const networkError = error instanceof Error
189
+ ? new NetworkError(error.message, url.toString(), error)
190
+ : new NetworkError('Unknown error', url.toString());
191
+
192
+ // Log network error
193
+ if (this.logger) {
194
+ this.logger.logError(
195
+ {
196
+ method,
197
+ url: url.toString(),
198
+ headers,
199
+ body: options?.formData || options?.body,
200
+ timestamp: startTime,
201
+ },
202
+ {
203
+ message: networkError.message,
204
+ duration,
205
+ timestamp: Date.now(),
206
+ }
207
+ );
208
+ }
209
+
210
+ throw networkError;
211
+ }
212
+ }
213
+ }
214
+ {% else %}
215
+ {# Flat client without sub-clients #}
216
+ import * as Enums from "./enums";
217
+ import { HttpClientAdapter, FetchAdapter } from "./http";
218
+ import { APIError, NetworkError } from "./errors";
219
+
220
+
221
+ /**
222
+ * Async API client for {{ info.title }}.
223
+ *
224
+ * Usage:
225
+ * ```typescript
226
+ * const client = new APIClient('https://api.example.com');
227
+ * const users = await client.usersList();
228
+ *
229
+ * // Custom HTTP adapter
230
+ * const client = new APIClient('https://api.example.com', {
231
+ * httpClient: new AxiosAdapter()
232
+ * });
233
+ * ```
234
+ */
235
+ export class APIClient {
236
+ private baseUrl: string;
237
+ private httpClient: HttpClientAdapter;
238
+
239
+ constructor(
240
+ baseUrl: string,
241
+ options?: { httpClient?: HttpClientAdapter }
242
+ ) {
243
+ this.baseUrl = baseUrl.replace(/\/$/, '');
244
+ this.httpClient = options?.httpClient || new FetchAdapter();
245
+ }
246
+
247
+ /**
248
+ * Get CSRF token from cookies.
249
+ */
250
+ private getCsrfToken(): string | null {
251
+ const name = 'csrftoken';
252
+ const value = `; ${document.cookie}`;
253
+ const parts = value.split(`; ${name}=`);
254
+ if (parts.length === 2) {
255
+ return parts.pop()?.split(';').shift() || null;
256
+ }
257
+ return null;
258
+ }
259
+
260
+ /**
261
+ * Make HTTP request with Django CSRF and session handling.
262
+ */
263
+ private async request<T>(
264
+ method: string,
265
+ path: string,
266
+ options?: {
267
+ params?: Record<string, any>;
268
+ body?: any;
269
+ formData?: FormData;
270
+ }
271
+ ): Promise<T> {
272
+ const url = new URL(path, this.baseUrl);
273
+
274
+ // Build headers
275
+ const headers: Record<string, string> = {};
276
+
277
+ // Don't set Content-Type for FormData (browser will set it with boundary)
278
+ if (!options?.formData) {
279
+ headers['Content-Type'] = 'application/json';
280
+ }
281
+
282
+ // Add CSRF token for non-GET requests
283
+ if (method !== 'GET') {
284
+ const csrfToken = this.getCsrfToken();
285
+ if (csrfToken) {
286
+ headers['X-CSRFToken'] = csrfToken;
287
+ }
288
+ }
289
+
290
+ try {
291
+ // Make request via HTTP adapter
292
+ const response = await this.httpClient.request<T>({
293
+ method,
294
+ url: url.toString(),
295
+ headers,
296
+ params: options?.params,
297
+ body: options?.body,
298
+ formData: options?.formData,
299
+ });
300
+
301
+ // Check for HTTP errors
302
+ if (response.status >= 400) {
303
+ throw new APIError(
304
+ response.status,
305
+ response.statusText,
306
+ response.data,
307
+ url.toString()
308
+ );
309
+ }
310
+
311
+ return response.data as T;
312
+ } catch (error) {
313
+ // Re-throw APIError as-is
314
+ if (error instanceof APIError) {
315
+ throw error;
316
+ }
317
+
318
+ // Wrap other errors as NetworkError
319
+ throw error instanceof Error
320
+ ? new NetworkError(error.message, url.toString(), error)
321
+ : new NetworkError('Unknown error', url.toString());
322
+ }
323
+ }
324
+
325
+ {# Operations will be added here by generator #}
326
+ }
327
+ {% endif %}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Async API client for {{ api_title }}.
3
+ *
4
+ * Usage:
5
+ * ```typescript
6
+ * const client = new APIClient('https://api.example.com');
7
+ * const users = await client.usersList();
8
+ *
9
+ * // Custom HTTP adapter
10
+ * const client = new APIClient('https://api.example.com', {
11
+ * httpClient: new AxiosAdapter()
12
+ * });
13
+ * ```
14
+ */
15
+ export class APIClient {
16
+ private baseUrl: string;
17
+ private httpClient: HttpClientAdapter;
18
+
19
+ constructor(
20
+ baseUrl: string,
21
+ options?: { httpClient?: HttpClientAdapter }
22
+ ) {
23
+ this.baseUrl = baseUrl.replace(/\/$/, '');
24
+ this.httpClient = options?.httpClient || new FetchAdapter();
25
+ }
26
+
27
+ /**
28
+ * Get CSRF token from cookies.
29
+ */
30
+ private getCsrfToken(): string | null {
31
+ const name = 'csrftoken';
32
+ const value = `; ${document.cookie}`;
33
+ const parts = value.split(`; ${name}=`);
34
+ if (parts.length === 2) {
35
+ return parts.pop()?.split(';').shift() || null;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Make HTTP request with Django CSRF and session handling.
42
+ */
43
+ private async request<T>(
44
+ method: string,
45
+ path: string,
46
+ options?: {
47
+ params?: Record<string, any>;
48
+ body?: any;
49
+ formData?: FormData;
50
+ }
51
+ ): Promise<T> {
52
+ const url = new URL(path, this.baseUrl);
53
+
54
+ // Build headers
55
+ const headers: Record<string, string> = {};
56
+
57
+ // Don't set Content-Type for FormData (browser will set it with boundary)
58
+ if (!options?.formData) {
59
+ headers['Content-Type'] = 'application/json';
60
+ }
61
+
62
+ // Add CSRF token for non-GET requests
63
+ if (method !== 'GET') {
64
+ const csrfToken = this.getCsrfToken();
65
+ if (csrfToken) {
66
+ headers['X-CSRFToken'] = csrfToken;
67
+ }
68
+ }
69
+
70
+ try {
71
+ // Make request via HTTP adapter
72
+ const response = await this.httpClient.request<T>({
73
+ method,
74
+ url: url.toString(),
75
+ headers,
76
+ params: options?.params,
77
+ body: options?.body,
78
+ formData: options?.formData,
79
+ });
80
+
81
+ // Check for HTTP errors
82
+ if (response.status >= 400) {
83
+ throw new APIError(
84
+ response.status,
85
+ response.statusText,
86
+ response.data,
87
+ url.toString()
88
+ );
89
+ }
90
+
91
+ return response.data as T;
92
+ } catch (error) {
93
+ // Re-throw APIError as-is
94
+ if (error instanceof APIError) {
95
+ throw error;
96
+ }
97
+
98
+ // Wrap other errors as NetworkError
99
+ throw error instanceof Error
100
+ ? new NetworkError(error.message, url.toString(), error)
101
+ : new NetworkError('Unknown error', url.toString());
102
+ }
103
+ }
104
+
105
+ {% for operation in operations %}
106
+ {{ operation }}
107
+
108
+ {% endfor %}
109
+ }
@@ -0,0 +1,9 @@
1
+ {% for tag in tags %}
2
+ import { {{ tag.class_name }} } from "./{{ tag.slug }}";
3
+ {% endfor %}
4
+ import { HttpClientAdapter, FetchAdapter } from "./http";
5
+ import { APIError, NetworkError } from "./errors";
6
+ import { APILogger, type LoggerConfig } from "./logger";
7
+
8
+
9
+ {{ client_code }}
@@ -0,0 +1,61 @@
1
+ {# Macro for generating TypeScript operation method #}
2
+ {% macro render_operation(operation, in_subclient=False) %}
3
+ {%- set request_prefix = "this.client" if in_subclient else "this" -%}
4
+
5
+ {%- if operation.summary or operation.description -%}
6
+ /**
7
+ {%- if operation.summary %}
8
+ * {{ operation.summary }}
9
+ {%- endif %}
10
+ {%- if operation.description %}
11
+ *
12
+ {%- for line in operation.description.split('\n') %}
13
+ * {{ line }}
14
+ {%- endfor %}
15
+ {%- endif %}
16
+ */
17
+ {%- endif %}
18
+ async {{ operation.method_name }}(
19
+ {%- for param in operation.params -%}
20
+ {{ param.name }}{{ '?' if param.optional else '' }}: {{ param.type }}{{ ', ' if not loop.last else '' }}
21
+ {%- endfor -%}
22
+ ): Promise<{{ operation.return_type }}> {
23
+ {%- if operation.is_multipart %}
24
+ const formData = new FormData();
25
+ {%- for field in operation.multipart_fields %}
26
+ {%- if field.is_file %}
27
+ formData.append('{{ field.name }}', {{ field.name }});
28
+ {%- else %}
29
+ {%- if field.optional %}
30
+ if ({{ field.name }} !== undefined) formData.append('{{ field.name }}', String({{ field.name }}));
31
+ {%- else %}
32
+ formData.append('{{ field.name }}', String({{ field.name }}));
33
+ {%- endif %}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- endif %}
37
+ const response = await {{ request_prefix }}.request<{{ operation.return_type }}>(
38
+ '{{ operation.http_method }}',
39
+ {{ operation.path_expr }}
40
+ {%- if operation.has_options -%}
41
+ , {
42
+ {%- if operation.query_params %}
43
+ params: { {{ operation.query_params }} }{{ ',' if operation.has_body or operation.is_multipart else '' }}
44
+ {%- endif %}
45
+ {%- if operation.is_multipart %}
46
+ formData
47
+ {%- elif operation.has_body %}
48
+ body: data
49
+ {%- endif %}
50
+ }
51
+ {%- endif -%}
52
+ );
53
+ {%- if operation.is_list %}
54
+ return (response as any).results || [];
55
+ {%- elif operation.return_type != 'void' %}
56
+ return response;
57
+ {%- else %}
58
+ return;
59
+ {%- endif %}
60
+ }
61
+ {% endmacro %}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * API endpoints for {{ tag }}.
3
+ */
4
+ class {{ class_name }} {
5
+ private client: APIClient;
6
+
7
+ constructor(client: APIClient) {
8
+ this.client = client;
9
+ }
10
+
11
+ {% for operation in operations %}
12
+ {{ operation }}
13
+
14
+ {% endfor %}
15
+ }
@@ -0,0 +1,9 @@
1
+ {% if has_enums %}
2
+ import * as Enums from "./enums";
3
+ {% endif %}
4
+ import { HttpClientAdapter, FetchAdapter } from "./http";
5
+ import { APIError, NetworkError } from "./errors";
6
+ import { APILogger, type LoggerConfig } from "./logger";
7
+
8
+
9
+ {{ client_code }}
@@ -0,0 +1,5 @@
1
+ export * from "./client";
2
+ export * as Models from "./models";
3
+ {% if has_enums %}
4
+ export * as Enums from "./enums";
5
+ {% endif %}