django-cfg 1.4.10__py3-none-any.whl → 1.4.13__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 (225) 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 +73 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/management/commands/check_endpoints.py +11 -160
  20. django_cfg/management/commands/check_settings.py +13 -348
  21. django_cfg/management/commands/clear_constance.py +13 -201
  22. django_cfg/management/commands/create_token.py +13 -321
  23. django_cfg/management/commands/generate_clients.py +23 -0
  24. django_cfg/management/commands/list_urls.py +13 -306
  25. django_cfg/management/commands/migrate_all.py +13 -126
  26. django_cfg/management/commands/migrator.py +13 -396
  27. django_cfg/management/commands/rundramatiq.py +15 -247
  28. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  29. django_cfg/management/commands/runserver_ngrok.py +15 -160
  30. django_cfg/management/commands/script.py +12 -488
  31. django_cfg/management/commands/show_config.py +12 -215
  32. django_cfg/management/commands/show_urls.py +12 -342
  33. django_cfg/management/commands/superuser.py +15 -295
  34. django_cfg/management/commands/task_clear.py +14 -217
  35. django_cfg/management/commands/task_status.py +13 -248
  36. django_cfg/management/commands/test_email.py +15 -86
  37. django_cfg/management/commands/test_telegram.py +14 -61
  38. django_cfg/management/commands/test_twilio.py +15 -105
  39. django_cfg/management/commands/tree.py +13 -383
  40. django_cfg/management/commands/validate_openapi.py +10 -0
  41. django_cfg/middleware/README.md +1 -1
  42. django_cfg/middleware/user_activity.py +3 -3
  43. django_cfg/models/__init__.py +2 -2
  44. django_cfg/models/api/drf/spectacular.py +6 -6
  45. django_cfg/models/django/__init__.py +2 -2
  46. django_cfg/models/django/openapi.py +162 -0
  47. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  48. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  49. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  50. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  51. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  52. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  53. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  54. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  55. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  56. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  57. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  58. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  59. django_cfg/modules/django_client/__init__.py +20 -0
  60. django_cfg/modules/django_client/apps.py +35 -0
  61. django_cfg/modules/django_client/core/__init__.py +56 -0
  62. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  63. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  64. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  65. django_cfg/modules/django_client/core/cli/main.py +235 -0
  66. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  67. django_cfg/modules/django_client/core/config/config.py +208 -0
  68. django_cfg/modules/django_client/core/config/group.py +101 -0
  69. django_cfg/modules/django_client/core/config/service.py +209 -0
  70. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  71. django_cfg/modules/django_client/core/generator/base.py +838 -0
  72. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  73. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  74. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  75. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  76. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  77. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  78. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  79. django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
  80. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
  81. django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
  82. django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
  83. django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
  84. django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
  85. django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
  86. django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
  87. django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
  88. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  89. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  90. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  91. django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
  92. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
  93. django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
  94. django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
  95. django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
  96. django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
  97. django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
  98. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  99. django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
  100. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  101. django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
  102. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  103. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  104. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  105. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  106. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  107. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  108. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  109. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  110. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  111. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  112. django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
  113. django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
  114. django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
  115. django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
  116. django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
  117. django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
  118. django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
  119. django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
  120. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  121. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  122. django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
  123. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
  124. django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
  125. django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
  126. django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
  127. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  128. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  129. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  130. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  131. django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
  132. django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
  133. django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
  134. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  135. django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
  136. django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
  137. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  138. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  139. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  140. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  141. django_cfg/modules/django_client/core/ir/context.py +387 -0
  142. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  143. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  144. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  145. django_cfg/modules/django_client/core/parser/base.py +648 -0
  146. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  147. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  148. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  149. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  150. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  151. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  152. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  153. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  154. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  155. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  156. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  157. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  158. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  159. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  160. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  161. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  162. django_cfg/modules/django_client/management/__init__.py +3 -0
  163. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  164. django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
  165. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  166. django_cfg/modules/django_client/pytest.ini +30 -0
  167. django_cfg/modules/django_client/spectacular/__init__.py +10 -0
  168. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  169. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  170. django_cfg/modules/django_client/urls.py +72 -0
  171. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  172. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  173. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  174. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  175. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  176. django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
  177. django_cfg/modules/django_email/management/__init__.py +0 -0
  178. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  179. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  180. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  181. django_cfg/modules/django_logging/django_logger.py +6 -6
  182. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  183. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  184. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  185. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  186. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  187. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  188. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  189. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  190. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  191. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  192. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  193. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  194. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  195. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  196. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  197. django_cfg/modules/django_unfold/callbacks/main.py +21 -10
  198. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  199. django_cfg/pyproject.toml +2 -6
  200. django_cfg/registry/third_party.py +5 -7
  201. django_cfg/routing/callbacks.py +1 -1
  202. django_cfg/static/admin/css/prose-unfold.css +666 -0
  203. django_cfg/templates/admin/index.html +8 -0
  204. django_cfg/templates/admin/index_new.html +13 -0
  205. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  206. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  207. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  208. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
  209. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
  210. django_cfg/management/commands/generate.py +0 -107
  211. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  212. /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
  213. /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
  214. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  215. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  216. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  217. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  218. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  219. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  220. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  221. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  222. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  223. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  224. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  225. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,403 @@
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
+ import { withRetry, type RetryConfig } from "./retry";
11
+
12
+
13
+ {% endif %}
14
+ /**
15
+ * Async API client for {{ info.title }}.
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * const client = new APIClient('https://api.example.com');
20
+ * const users = await client.users.list();
21
+ * const post = await client.posts.create(newPost);
22
+ *
23
+ * // Custom HTTP adapter (e.g., Axios)
24
+ * const client = new APIClient('https://api.example.com', {
25
+ * httpClient: new AxiosAdapter()
26
+ * });
27
+ * ```
28
+ */
29
+ export class APIClient {
30
+ private baseUrl: string;
31
+ private httpClient: HttpClientAdapter;
32
+ private logger: APILogger | null = null;
33
+ private retryConfig: RetryConfig | null = null;
34
+
35
+ // Sub-clients
36
+ {% for tag in tags %}
37
+ public {{ tag.property }}: {{ tag.class_name }};
38
+ {% endfor %}
39
+
40
+ constructor(
41
+ baseUrl: string,
42
+ options?: {
43
+ httpClient?: HttpClientAdapter;
44
+ loggerConfig?: Partial<LoggerConfig>;
45
+ retryConfig?: RetryConfig;
46
+ }
47
+ ) {
48
+ this.baseUrl = baseUrl.replace(/\/$/, '');
49
+ this.httpClient = options?.httpClient || new FetchAdapter();
50
+
51
+ // Initialize logger if config provided
52
+ if (options?.loggerConfig !== undefined) {
53
+ this.logger = new APILogger(options.loggerConfig);
54
+ }
55
+
56
+ // Store retry configuration
57
+ if (options?.retryConfig !== undefined) {
58
+ this.retryConfig = options.retryConfig;
59
+ }
60
+
61
+ // Initialize sub-clients
62
+ {% for tag in tags %}
63
+ this.{{ tag.property }} = new {{ tag.class_name }}(this);
64
+ {% endfor %}
65
+ }
66
+
67
+ /**
68
+ * Get CSRF token from cookies.
69
+ */
70
+ getCsrfToken(): string | null {
71
+ const name = 'csrftoken';
72
+ const value = `; ${document.cookie}`;
73
+ const parts = value.split(`; ${name}=`);
74
+ if (parts.length === 2) {
75
+ return parts.pop()?.split(';').shift() || null;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * Make HTTP request with Django CSRF and session handling.
82
+ * Automatically retries on network errors and 5xx server errors.
83
+ */
84
+ async request<T>(
85
+ method: string,
86
+ path: string,
87
+ options?: {
88
+ params?: Record<string, any>;
89
+ body?: any;
90
+ formData?: FormData;
91
+ }
92
+ ): Promise<T> {
93
+ // Wrap request in retry logic if configured
94
+ if (this.retryConfig) {
95
+ return withRetry(() => this._makeRequest<T>(method, path, options), {
96
+ ...this.retryConfig,
97
+ onFailedAttempt: (info) => {
98
+ // Log retry attempts
99
+ if (this.logger) {
100
+ this.logger.warn(
101
+ `Retry attempt ${info.attemptNumber}/${info.retriesLeft + info.attemptNumber} ` +
102
+ `for ${method} ${path}: ${info.error.message}`
103
+ );
104
+ }
105
+ // Call user's onFailedAttempt if provided
106
+ this.retryConfig?.onFailedAttempt?.(info);
107
+ },
108
+ });
109
+ }
110
+
111
+ // No retry configured, make request directly
112
+ return this._makeRequest<T>(method, path, options);
113
+ }
114
+
115
+ /**
116
+ * Internal request method (without retry wrapper).
117
+ * Used by request() method with optional retry logic.
118
+ */
119
+ private async _makeRequest<T>(
120
+ method: string,
121
+ path: string,
122
+ options?: {
123
+ params?: Record<string, any>;
124
+ body?: any;
125
+ formData?: FormData;
126
+ }
127
+ ): Promise<T> {
128
+ const url = new URL(path, this.baseUrl);
129
+ const startTime = Date.now();
130
+
131
+ // Build headers
132
+ const headers: Record<string, string> = {};
133
+
134
+ // Don't set Content-Type for FormData (browser will set it with boundary)
135
+ if (!options?.formData) {
136
+ headers['Content-Type'] = 'application/json';
137
+ }
138
+
139
+ // Add CSRF token for non-GET requests
140
+ if (method !== 'GET') {
141
+ const csrfToken = this.getCsrfToken();
142
+ if (csrfToken) {
143
+ headers['X-CSRFToken'] = csrfToken;
144
+ }
145
+ }
146
+
147
+ // Log request
148
+ if (this.logger) {
149
+ this.logger.logRequest({
150
+ method,
151
+ url: url.toString(),
152
+ headers,
153
+ body: options?.formData || options?.body,
154
+ timestamp: startTime,
155
+ });
156
+ }
157
+
158
+ try {
159
+ // Make request via HTTP adapter
160
+ const response = await this.httpClient.request<T>({
161
+ method,
162
+ url: url.toString(),
163
+ headers,
164
+ params: options?.params,
165
+ body: options?.body,
166
+ formData: options?.formData,
167
+ });
168
+
169
+ const duration = Date.now() - startTime;
170
+
171
+ // Check for HTTP errors
172
+ if (response.status >= 400) {
173
+ const error = new APIError(
174
+ response.status,
175
+ response.statusText,
176
+ response.data,
177
+ url.toString()
178
+ );
179
+
180
+ // Log error
181
+ if (this.logger) {
182
+ this.logger.logError(
183
+ {
184
+ method,
185
+ url: url.toString(),
186
+ headers,
187
+ body: options?.formData || options?.body,
188
+ timestamp: startTime,
189
+ },
190
+ {
191
+ message: error.message,
192
+ statusCode: response.status,
193
+ duration,
194
+ timestamp: Date.now(),
195
+ }
196
+ );
197
+ }
198
+
199
+ throw error;
200
+ }
201
+
202
+ // Log successful response
203
+ if (this.logger) {
204
+ this.logger.logResponse(
205
+ {
206
+ method,
207
+ url: url.toString(),
208
+ headers,
209
+ body: options?.formData || options?.body,
210
+ timestamp: startTime,
211
+ },
212
+ {
213
+ status: response.status,
214
+ statusText: response.statusText,
215
+ data: response.data,
216
+ duration,
217
+ timestamp: Date.now(),
218
+ }
219
+ );
220
+ }
221
+
222
+ return response.data as T;
223
+ } catch (error) {
224
+ const duration = Date.now() - startTime;
225
+
226
+ // Re-throw APIError as-is
227
+ if (error instanceof APIError) {
228
+ throw error;
229
+ }
230
+
231
+ // Wrap other errors as NetworkError
232
+ const networkError = error instanceof Error
233
+ ? new NetworkError(error.message, url.toString(), error)
234
+ : new NetworkError('Unknown error', url.toString());
235
+
236
+ // Log network error
237
+ if (this.logger) {
238
+ this.logger.logError(
239
+ {
240
+ method,
241
+ url: url.toString(),
242
+ headers,
243
+ body: options?.formData || options?.body,
244
+ timestamp: startTime,
245
+ },
246
+ {
247
+ message: networkError.message,
248
+ duration,
249
+ timestamp: Date.now(),
250
+ }
251
+ );
252
+ }
253
+
254
+ throw networkError;
255
+ }
256
+ }
257
+ }
258
+ {% else %}
259
+ {# Flat client without sub-clients #}
260
+ import * as Enums from "./enums";
261
+ import { HttpClientAdapter, FetchAdapter } from "./http";
262
+ import { APIError, NetworkError } from "./errors";
263
+ import { withRetry, type RetryConfig } from "./retry";
264
+
265
+
266
+ /**
267
+ * Async API client for {{ info.title }}.
268
+ *
269
+ * Usage:
270
+ * ```typescript
271
+ * const client = new APIClient('https://api.example.com');
272
+ * const users = await client.usersList();
273
+ *
274
+ * // Custom HTTP adapter
275
+ * const client = new APIClient('https://api.example.com', {
276
+ * httpClient: new AxiosAdapter()
277
+ * });
278
+ * ```
279
+ */
280
+ export class APIClient {
281
+ private baseUrl: string;
282
+ private httpClient: HttpClientAdapter;
283
+ private retryConfig: RetryConfig | null = null;
284
+
285
+ constructor(
286
+ baseUrl: string,
287
+ options?: {
288
+ httpClient?: HttpClientAdapter;
289
+ retryConfig?: RetryConfig;
290
+ }
291
+ ) {
292
+ this.baseUrl = baseUrl.replace(/\/$/, '');
293
+ this.httpClient = options?.httpClient || new FetchAdapter();
294
+
295
+ // Store retry configuration
296
+ if (options?.retryConfig !== undefined) {
297
+ this.retryConfig = options.retryConfig;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Get CSRF token from cookies.
303
+ */
304
+ private getCsrfToken(): string | null {
305
+ const name = 'csrftoken';
306
+ const value = `; ${document.cookie}`;
307
+ const parts = value.split(`; ${name}=`);
308
+ if (parts.length === 2) {
309
+ return parts.pop()?.split(';').shift() || null;
310
+ }
311
+ return null;
312
+ }
313
+
314
+ /**
315
+ * Make HTTP request with Django CSRF and session handling.
316
+ * Automatically retries on network errors and 5xx server errors.
317
+ */
318
+ private async request<T>(
319
+ method: string,
320
+ path: string,
321
+ options?: {
322
+ params?: Record<string, any>;
323
+ body?: any;
324
+ formData?: FormData;
325
+ }
326
+ ): Promise<T> {
327
+ // Wrap request in retry logic if configured
328
+ if (this.retryConfig) {
329
+ return withRetry(() => this._makeRequest<T>(method, path, options), this.retryConfig);
330
+ }
331
+
332
+ // No retry configured, make request directly
333
+ return this._makeRequest<T>(method, path, options);
334
+ }
335
+
336
+ /**
337
+ * Internal request method (without retry wrapper).
338
+ */
339
+ private async _makeRequest<T>(
340
+ method: string,
341
+ path: string,
342
+ options?: {
343
+ params?: Record<string, any>;
344
+ body?: any;
345
+ formData?: FormData;
346
+ }
347
+ ): Promise<T> {
348
+ const url = new URL(path, this.baseUrl);
349
+
350
+ // Build headers
351
+ const headers: Record<string, string> = {};
352
+
353
+ // Don't set Content-Type for FormData (browser will set it with boundary)
354
+ if (!options?.formData) {
355
+ headers['Content-Type'] = 'application/json';
356
+ }
357
+
358
+ // Add CSRF token for non-GET requests
359
+ if (method !== 'GET') {
360
+ const csrfToken = this.getCsrfToken();
361
+ if (csrfToken) {
362
+ headers['X-CSRFToken'] = csrfToken;
363
+ }
364
+ }
365
+
366
+ try {
367
+ // Make request via HTTP adapter
368
+ const response = await this.httpClient.request<T>({
369
+ method,
370
+ url: url.toString(),
371
+ headers,
372
+ params: options?.params,
373
+ body: options?.body,
374
+ formData: options?.formData,
375
+ });
376
+
377
+ // Check for HTTP errors
378
+ if (response.status >= 400) {
379
+ throw new APIError(
380
+ response.status,
381
+ response.statusText,
382
+ response.data,
383
+ url.toString()
384
+ );
385
+ }
386
+
387
+ return response.data as T;
388
+ } catch (error) {
389
+ // Re-throw APIError as-is
390
+ if (error instanceof APIError) {
391
+ throw error;
392
+ }
393
+
394
+ // Wrap other errors as NetworkError
395
+ throw error instanceof Error
396
+ ? new NetworkError(error.message, url.toString(), error)
397
+ : new NetworkError('Unknown error', url.toString());
398
+ }
399
+ }
400
+
401
+ {# Operations will be added here by generator #}
402
+ }
403
+ {% 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,10 @@
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
+ import { withRetry, type RetryConfig } from "./retry";
8
+
9
+
10
+ {{ 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: any;
6
+
7
+ constructor(client: any) {
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,45 @@
1
+ /**
2
+ * Typed fetchers for {{ tag_display_name }}
3
+ *
4
+ * Universal functions that work in any environment:
5
+ * - Next.js (App Router / Pages Router / Server Components)
6
+ * - React Native
7
+ * - Node.js backend
8
+ *
9
+ * These fetchers use Zod schemas for runtime validation.
10
+ *
11
+ * Usage:
12
+ * ```typescript
13
+ * // Configure API once (in your app entry point)
14
+ * import { configureAPI } from '../../api-instance'
15
+ * configureAPI({ baseUrl: 'https://api.example.com' })
16
+ *
17
+ * // Then use fetchers anywhere
18
+ * const users = await getUsers({ page: 1 })
19
+ *
20
+ * // With SWR
21
+ * const { data } = useSWR(['users', params], () => getUsers(params))
22
+ *
23
+ * // With React Query
24
+ * const { data } = useQuery(['users', params], () => getUsers(params))
25
+ *
26
+ * // In Server Component or SSR (pass custom client)
27
+ * import { API } from '../../index'
28
+ * const api = new API('https://api.example.com')
29
+ * const users = await getUsers({ page: 1 }, api)
30
+ * ```
31
+ */
32
+ {% if has_schemas %}
33
+ {% for schema_name in schema_names %}
34
+ import { {{ schema_name }}Schema, type {{ schema_name }} } from '../schemas/{{ schema_name }}.schema'
35
+ {% endfor %}
36
+ {% endif %}
37
+ {% if has_client %}
38
+ import { getAPIInstance } from '../../api-instance'
39
+ import type { API } from '../../index'
40
+ {% endif %}
41
+
42
+ {% for fetcher in fetchers %}
43
+ {{ fetcher }}
44
+
45
+ {% endfor %}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Typed Fetchers - Universal API functions
3
+ *
4
+ * Auto-generated from OpenAPI specification.
5
+ * These functions work in any JavaScript environment.
6
+ *
7
+ * Features:
8
+ * - Runtime validation with Zod
9
+ * - Type-safe parameters and responses
10
+ * - Works with any data-fetching library (SWR, React Query, etc)
11
+ * - Server Component compatible
12
+ *
13
+ * Usage:
14
+ * ```typescript
15
+ * import * as fetchers from './fetchers'
16
+ *
17
+ * // Direct usage
18
+ * const user = await fetchers.getUser(1)
19
+ *
20
+ * // With SWR
21
+ * const { data } = useSWR('user-1', () => fetchers.getUser(1))
22
+ *
23
+ * // With React Query
24
+ * const { data } = useQuery(['user', 1], () => fetchers.getUser(1))
25
+ * ```
26
+ */
27
+
28
+ {% for module_name in modules %}
29
+ export * from './{{ module_name }}'
30
+ {% endfor %}
@@ -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 %}