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,259 @@
1
+ /**
2
+ * API Logger with Consola
3
+ * Beautiful console logging for API requests and responses
4
+ *
5
+ * Installation:
6
+ * npm install consola
7
+ */
8
+
9
+ import { type ConsolaInstance, createConsola } from 'consola';
10
+
11
+ /**
12
+ * Request log data
13
+ */
14
+ export interface RequestLog {
15
+ method: string;
16
+ url: string;
17
+ headers?: Record<string, string>;
18
+ body?: any;
19
+ timestamp: number;
20
+ }
21
+
22
+ /**
23
+ * Response log data
24
+ */
25
+ export interface ResponseLog {
26
+ status: number;
27
+ statusText: string;
28
+ data?: any;
29
+ duration: number;
30
+ timestamp: number;
31
+ }
32
+
33
+ /**
34
+ * Error log data
35
+ */
36
+ export interface ErrorLog {
37
+ message: string;
38
+ statusCode?: number;
39
+ fieldErrors?: Record<string, string[]>;
40
+ duration: number;
41
+ timestamp: number;
42
+ }
43
+
44
+ /**
45
+ * Logger configuration
46
+ */
47
+ export interface LoggerConfig {
48
+ /** Enable logging */
49
+ enabled: boolean;
50
+ /** Log requests */
51
+ logRequests: boolean;
52
+ /** Log responses */
53
+ logResponses: boolean;
54
+ /** Log errors */
55
+ logErrors: boolean;
56
+ /** Log request/response bodies */
57
+ logBodies: boolean;
58
+ /** Log headers (excluding sensitive ones) */
59
+ logHeaders: boolean;
60
+ /** Custom consola instance */
61
+ consola?: ConsolaInstance;
62
+ }
63
+
64
+ /**
65
+ * Default logger configuration
66
+ */
67
+ const DEFAULT_CONFIG: LoggerConfig = {
68
+ enabled: process.env.NODE_ENV !== 'production',
69
+ logRequests: true,
70
+ logResponses: true,
71
+ logErrors: true,
72
+ logBodies: true,
73
+ logHeaders: false,
74
+ };
75
+
76
+ /**
77
+ * Sensitive header names to filter out
78
+ */
79
+ const SENSITIVE_HEADERS = [
80
+ 'authorization',
81
+ 'cookie',
82
+ 'set-cookie',
83
+ 'x-api-key',
84
+ 'x-csrf-token',
85
+ ];
86
+
87
+ /**
88
+ * API Logger class
89
+ */
90
+ export class APILogger {
91
+ private config: LoggerConfig;
92
+ private consola: ConsolaInstance;
93
+
94
+ constructor(config: Partial<LoggerConfig> = {}) {
95
+ this.config = { ...DEFAULT_CONFIG, ...config };
96
+ this.consola = config.consola || createConsola({
97
+ level: this.config.enabled ? 4 : 0,
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Enable logging
103
+ */
104
+ enable(): void {
105
+ this.config.enabled = true;
106
+ }
107
+
108
+ /**
109
+ * Disable logging
110
+ */
111
+ disable(): void {
112
+ this.config.enabled = false;
113
+ }
114
+
115
+ /**
116
+ * Update configuration
117
+ */
118
+ setConfig(config: Partial<LoggerConfig>): void {
119
+ this.config = { ...this.config, ...config };
120
+ }
121
+
122
+ /**
123
+ * Filter sensitive headers
124
+ */
125
+ private filterHeaders(headers?: Record<string, string>): Record<string, string> {
126
+ if (!headers) return {};
127
+
128
+ const filtered: Record<string, string> = {};
129
+ Object.keys(headers).forEach((key) => {
130
+ const lowerKey = key.toLowerCase();
131
+ if (SENSITIVE_HEADERS.includes(lowerKey)) {
132
+ filtered[key] = '***';
133
+ } else {
134
+ filtered[key] = headers[key] || '';
135
+ }
136
+ });
137
+
138
+ return filtered;
139
+ }
140
+
141
+ /**
142
+ * Log request
143
+ */
144
+ logRequest(request: RequestLog): void {
145
+ if (!this.config.enabled || !this.config.logRequests) return;
146
+
147
+ const { method, url, headers, body } = request;
148
+
149
+ this.consola.start(`${method} ${url}`);
150
+
151
+ if (this.config.logHeaders && headers) {
152
+ this.consola.debug('Headers:', this.filterHeaders(headers));
153
+ }
154
+
155
+ if (this.config.logBodies && body) {
156
+ this.consola.debug('Body:', body);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Log response
162
+ */
163
+ logResponse(request: RequestLog, response: ResponseLog): void {
164
+ if (!this.config.enabled || !this.config.logResponses) return;
165
+
166
+ const { method, url } = request;
167
+ const { status, statusText, data, duration } = response;
168
+
169
+ const statusColor = status >= 500 ? 'red'
170
+ : status >= 400 ? 'yellow'
171
+ : status >= 300 ? 'cyan'
172
+ : 'green';
173
+
174
+ this.consola.success(
175
+ `${method} ${url} ${status} ${statusText} (${duration}ms)`
176
+ );
177
+
178
+ if (this.config.logBodies && data) {
179
+ this.consola.debug('Response:', data);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Log error
185
+ */
186
+ logError(request: RequestLog, error: ErrorLog): void {
187
+ if (!this.config.enabled || !this.config.logErrors) return;
188
+
189
+ const { method, url } = request;
190
+ const { message, statusCode, fieldErrors, duration } = error;
191
+
192
+ this.consola.error(
193
+ `${method} ${url} ${statusCode || 'Network'} Error (${duration}ms)`
194
+ );
195
+
196
+ this.consola.error('Message:', message);
197
+
198
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
199
+ this.consola.error('Field Errors:');
200
+ Object.entries(fieldErrors).forEach(([field, errors]) => {
201
+ errors.forEach((err) => {
202
+ this.consola.error(` • ${field}: ${err}`);
203
+ });
204
+ });
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Log general info
210
+ */
211
+ info(message: string, ...args: any[]): void {
212
+ if (!this.config.enabled) return;
213
+ this.consola.info(message, ...args);
214
+ }
215
+
216
+ /**
217
+ * Log warning
218
+ */
219
+ warn(message: string, ...args: any[]): void {
220
+ if (!this.config.enabled) return;
221
+ this.consola.warn(message, ...args);
222
+ }
223
+
224
+ /**
225
+ * Log error
226
+ */
227
+ error(message: string, ...args: any[]): void {
228
+ if (!this.config.enabled) return;
229
+ this.consola.error(message, ...args);
230
+ }
231
+
232
+ /**
233
+ * Log debug
234
+ */
235
+ debug(message: string, ...args: any[]): void {
236
+ if (!this.config.enabled) return;
237
+ this.consola.debug(message, ...args);
238
+ }
239
+
240
+ /**
241
+ * Log success
242
+ */
243
+ success(message: string, ...args: any[]): void {
244
+ if (!this.config.enabled) return;
245
+ this.consola.success(message, ...args);
246
+ }
247
+
248
+ /**
249
+ * Create a sub-logger with prefix
250
+ */
251
+ withTag(tag: string): ConsolaInstance {
252
+ return this.consola.withTag(tag);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Default logger instance
258
+ */
259
+ export const defaultLogger = new APILogger();
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Retry Configuration and Utilities
3
+ *
4
+ * Provides automatic retry logic for failed HTTP requests using p-retry.
5
+ * Retries only on network errors and server errors (5xx), not client errors (4xx).
6
+ */
7
+
8
+ import pRetry, { AbortError } from 'p-retry';
9
+ import { APIError, NetworkError } from './errors';
10
+
11
+ /**
12
+ * Information about a failed retry attempt.
13
+ */
14
+ export interface FailedAttemptInfo {
15
+ /** The error that caused the failure */
16
+ error: Error;
17
+ /** The attempt number (1-indexed) */
18
+ attemptNumber: number;
19
+ /** Number of retries left */
20
+ retriesLeft: number;
21
+ }
22
+
23
+ /**
24
+ * Retry configuration options.
25
+ *
26
+ * Uses exponential backoff with jitter by default to avoid thundering herd.
27
+ */
28
+ export interface RetryConfig {
29
+ /**
30
+ * Maximum number of retry attempts.
31
+ * @default 3
32
+ */
33
+ retries?: number;
34
+
35
+ /**
36
+ * Exponential backoff factor.
37
+ * @default 2
38
+ */
39
+ factor?: number;
40
+
41
+ /**
42
+ * Minimum wait time between retries (ms).
43
+ * @default 1000
44
+ */
45
+ minTimeout?: number;
46
+
47
+ /**
48
+ * Maximum wait time between retries (ms).
49
+ * @default 60000
50
+ */
51
+ maxTimeout?: number;
52
+
53
+ /**
54
+ * Add randomness to wait times (jitter).
55
+ * Helps avoid thundering herd problem.
56
+ * @default true
57
+ */
58
+ randomize?: boolean;
59
+
60
+ /**
61
+ * Callback called on each failed attempt.
62
+ */
63
+ onFailedAttempt?: (info: FailedAttemptInfo) => void;
64
+ }
65
+
66
+ /**
67
+ * Default retry configuration.
68
+ */
69
+ export const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
70
+ retries: 3,
71
+ factor: 2,
72
+ minTimeout: 1000,
73
+ maxTimeout: 60000,
74
+ randomize: true,
75
+ onFailedAttempt: () => {},
76
+ };
77
+
78
+ /**
79
+ * Determine if an error should trigger a retry.
80
+ *
81
+ * Retries on:
82
+ * - Network errors (connection refused, timeout, etc.)
83
+ * - Server errors (5xx status codes)
84
+ * - Rate limiting (429 status code)
85
+ *
86
+ * Does NOT retry on:
87
+ * - Client errors (4xx except 429)
88
+ * - Authentication errors (401, 403)
89
+ * - Not found (404)
90
+ *
91
+ * @param error - The error to check
92
+ * @returns true if should retry, false otherwise
93
+ */
94
+ export function shouldRetry(error: any): boolean {
95
+ // Always retry network errors
96
+ if (error instanceof NetworkError) {
97
+ return true;
98
+ }
99
+
100
+ // For API errors, check status code
101
+ if (error instanceof APIError) {
102
+ const status = error.statusCode;
103
+
104
+ // Retry on 5xx server errors
105
+ if (status >= 500 && status < 600) {
106
+ return true;
107
+ }
108
+
109
+ // Retry on 429 (rate limit)
110
+ if (status === 429) {
111
+ return true;
112
+ }
113
+
114
+ // Do NOT retry on 4xx client errors
115
+ return false;
116
+ }
117
+
118
+ // Retry on unknown errors (might be network issues)
119
+ return true;
120
+ }
121
+
122
+ /**
123
+ * Wrap a function with retry logic.
124
+ *
125
+ * @param fn - Async function to retry
126
+ * @param config - Retry configuration
127
+ * @returns Result of the function
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const result = await withRetry(
132
+ * async () => fetch('https://api.example.com/users'),
133
+ * { retries: 5, minTimeout: 2000 }
134
+ * );
135
+ * ```
136
+ */
137
+ export async function withRetry<T>(
138
+ fn: () => Promise<T>,
139
+ config?: RetryConfig
140
+ ): Promise<T> {
141
+ const finalConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
142
+
143
+ return pRetry(
144
+ async () => {
145
+ try {
146
+ return await fn();
147
+ } catch (error) {
148
+ // Check if we should retry this error
149
+ if (!shouldRetry(error)) {
150
+ // Abort retry immediately for non-retryable errors
151
+ throw new AbortError(error as Error);
152
+ }
153
+
154
+ // Re-throw error to trigger retry
155
+ throw error;
156
+ }
157
+ },
158
+ {
159
+ retries: finalConfig.retries,
160
+ factor: finalConfig.factor,
161
+ minTimeout: finalConfig.minTimeout,
162
+ maxTimeout: finalConfig.maxTimeout,
163
+ randomize: finalConfig.randomize,
164
+ onFailedAttempt: finalConfig.onFailedAttempt ? (error) => {
165
+ // Adapt p-retry's FailedAttemptError to our FailedAttemptInfo
166
+ const pRetryError = error as any; // p-retry's internal type
167
+ finalConfig.onFailedAttempt!({
168
+ error: pRetryError as Error,
169
+ attemptNumber: pRetryError.attemptNumber,
170
+ retriesLeft: pRetryError.retriesLeft,
171
+ });
172
+ } : undefined,
173
+ }
174
+ );
175
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * OpenAPI Schema Export
3
+ *
4
+ * Contains the complete OpenAPI specification for runtime access.
5
+ */
6
+
7
+ export const OPENAPI_SCHEMA = {{ schema | tojson(indent=2) }};
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Storage adapters for cross-platform token storage.
3
+ *
4
+ * Supports:
5
+ * - LocalStorage (browser)
6
+ * - Cookies (SSR/browser)
7
+ * - Memory (Node.js/Electron/testing)
8
+ */
9
+
10
+ import type { APILogger } from './logger';
11
+
12
+ /**
13
+ * Storage adapter interface for cross-platform token storage.
14
+ */
15
+ export interface StorageAdapter {
16
+ getItem(key: string): string | null;
17
+ setItem(key: string, value: string): void;
18
+ removeItem(key: string): void;
19
+ }
20
+
21
+ /**
22
+ * LocalStorage adapter with safe try-catch for browser environments.
23
+ * Works in modern browsers with localStorage support.
24
+ */
25
+ export class LocalStorageAdapter implements StorageAdapter {
26
+ private logger?: APILogger;
27
+
28
+ constructor(logger?: APILogger) {
29
+ this.logger = logger;
30
+ }
31
+
32
+ getItem(key: string): string | null {
33
+ try {
34
+ if (typeof window !== 'undefined' && window.localStorage) {
35
+ const value = localStorage.getItem(key);
36
+ this.logger?.debug(`LocalStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
37
+ return value;
38
+ }
39
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
40
+ } catch (error) {
41
+ this.logger?.error('LocalStorage.getItem failed:', error);
42
+ }
43
+ return null;
44
+ }
45
+
46
+ setItem(key: string, value: string): void {
47
+ try {
48
+ if (typeof window !== 'undefined' && window.localStorage) {
49
+ localStorage.setItem(key, value);
50
+ this.logger?.debug(`LocalStorage.setItem("${key}"): success`);
51
+ } else {
52
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
53
+ }
54
+ } catch (error) {
55
+ this.logger?.error('LocalStorage.setItem failed:', error);
56
+ }
57
+ }
58
+
59
+ removeItem(key: string): void {
60
+ try {
61
+ if (typeof window !== 'undefined' && window.localStorage) {
62
+ localStorage.removeItem(key);
63
+ this.logger?.debug(`LocalStorage.removeItem("${key}"): success`);
64
+ } else {
65
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
66
+ }
67
+ } catch (error) {
68
+ this.logger?.error('LocalStorage.removeItem failed:', error);
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Cookie-based storage adapter for SSR and browser environments.
75
+ * Useful for Next.js, Nuxt.js, and other SSR frameworks.
76
+ */
77
+ export class CookieStorageAdapter implements StorageAdapter {
78
+ private logger?: APILogger;
79
+
80
+ constructor(logger?: APILogger) {
81
+ this.logger = logger;
82
+ }
83
+
84
+ getItem(key: string): string | null {
85
+ try {
86
+ if (typeof document === 'undefined') {
87
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
88
+ return null;
89
+ }
90
+ const value = `; ${document.cookie}`;
91
+ const parts = value.split(`; ${key}=`);
92
+ if (parts.length === 2) {
93
+ const result = parts.pop()?.split(';').shift() || null;
94
+ this.logger?.debug(`CookieStorage.getItem("${key}"): ${result ? 'found' : 'not found'}`);
95
+ return result;
96
+ }
97
+ this.logger?.debug(`CookieStorage.getItem("${key}"): not found`);
98
+ } catch (error) {
99
+ this.logger?.error('CookieStorage.getItem failed:', error);
100
+ }
101
+ return null;
102
+ }
103
+
104
+ setItem(key: string, value: string): void {
105
+ try {
106
+ if (typeof document !== 'undefined') {
107
+ document.cookie = `${key}=${value}; path=/; max-age=31536000`;
108
+ this.logger?.debug(`CookieStorage.setItem("${key}"): success`);
109
+ } else {
110
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
111
+ }
112
+ } catch (error) {
113
+ this.logger?.error('CookieStorage.setItem failed:', error);
114
+ }
115
+ }
116
+
117
+ removeItem(key: string): void {
118
+ try {
119
+ if (typeof document !== 'undefined') {
120
+ document.cookie = `${key}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
121
+ this.logger?.debug(`CookieStorage.removeItem("${key}"): success`);
122
+ } else {
123
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
124
+ }
125
+ } catch (error) {
126
+ this.logger?.error('CookieStorage.removeItem failed:', error);
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * In-memory storage adapter for Node.js, Electron, and testing environments.
133
+ * Data is stored in RAM and cleared when process exits.
134
+ */
135
+ export class MemoryStorageAdapter implements StorageAdapter {
136
+ private storage: Map<string, string> = new Map();
137
+ private logger?: APILogger;
138
+
139
+ constructor(logger?: APILogger) {
140
+ this.logger = logger;
141
+ }
142
+
143
+ getItem(key: string): string | null {
144
+ const value = this.storage.get(key) || null;
145
+ this.logger?.debug(`MemoryStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
146
+ return value;
147
+ }
148
+
149
+ setItem(key: string, value: string): void {
150
+ this.storage.set(key, value);
151
+ this.logger?.debug(`MemoryStorage.setItem("${key}"): success`);
152
+ }
153
+
154
+ removeItem(key: string): void {
155
+ this.storage.delete(key);
156
+ this.logger?.debug(`MemoryStorage.removeItem("${key}"): success`);
157
+ }
158
+ }
@@ -0,0 +1,13 @@
1
+ """
2
+ Application Grouping Module.
3
+
4
+ Smart grouping of Django apps into separate OpenAPI schemas.
5
+ """
6
+
7
+ from .manager import GroupManager
8
+ from .detector import GroupDetector
9
+
10
+ __all__ = [
11
+ "GroupManager",
12
+ "GroupDetector",
13
+ ]