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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +73 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +162 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +208 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +838 -0
- django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
- django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
- django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
- django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
- django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
- django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
- django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
- django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
- django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +10 -0
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
- django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +21 -10
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {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 %}
|
django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja
ADDED
@@ -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 }}
|
django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja
ADDED
@@ -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,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 }}
|
django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja
ADDED
@@ -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 %}
|