django-cfg 1.4.11__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/core/generation/integration_generators/api.py +2 -1
- django_cfg/models/django/openapi.py +4 -80
- django_cfg/modules/django_client/core/archive/manager.py +2 -2
- django_cfg/modules/django_client/core/config/config.py +20 -0
- django_cfg/modules/django_client/core/config/service.py +1 -1
- django_cfg/modules/django_client/core/generator/__init__.py +4 -4
- django_cfg/modules/django_client/core/generator/base.py +71 -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/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
- 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/{templates/python → python/templates}/main_init.py.jinja +2 -0
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -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/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
- 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/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
- 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/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +3 -2
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -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/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_unfold/callbacks/main.py +6 -6
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/METADATA +1 -1
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/RECORD +99 -70
- django_cfg/modules/django_client/core/generator/python.py +0 -751
- django_cfg/modules/django_client/core/generator/typescript.py +0 -872
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
- /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +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}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.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/documentation.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.11.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -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 %}
|
@@ -19,10 +19,12 @@
|
|
19
19
|
* // ...
|
20
20
|
* }
|
21
21
|
*
|
22
|
-
* // Custom storage (for Electron/Node.js)
|
23
|
-
* import { MemoryStorageAdapter } from './storage';
|
22
|
+
* // Custom storage with logging (for Electron/Node.js)
|
23
|
+
* import { MemoryStorageAdapter, APILogger } from './storage';
|
24
|
+
* const logger = new APILogger({ enabled: true, logLevel: 'debug' });
|
24
25
|
* const api = new API('https://api.example.com', {
|
25
|
-
* storage: new MemoryStorageAdapter()
|
26
|
+
* storage: new MemoryStorageAdapter(logger),
|
27
|
+
* loggerConfig: { enabled: true, logLevel: 'debug' }
|
26
28
|
* });
|
27
29
|
*
|
28
30
|
* // Get OpenAPI schema
|
@@ -38,20 +40,65 @@ import {
|
|
38
40
|
CookieStorageAdapter,
|
39
41
|
MemoryStorageAdapter
|
40
42
|
} from "./storage";
|
43
|
+
import type { RetryConfig } from "./retry";
|
44
|
+
import type { LoggerConfig } from "./logger";
|
45
|
+
import { APILogger } from "./logger";
|
41
46
|
{% for tag in tags %}
|
42
47
|
export * as {{ tag.class_name }}Types from "./{{ tag.slug }}/models";
|
43
48
|
{% endfor %}
|
44
49
|
{% if has_enums %}
|
45
50
|
export * as Enums from "./enums";
|
46
51
|
{% endif %}
|
52
|
+
{% if generate_zod_schemas %}
|
47
53
|
|
48
|
-
// Re-export
|
54
|
+
// Re-export Zod schemas for runtime validation
|
55
|
+
export * as Schemas from "./_utils/schemas";
|
56
|
+
{% endif %}
|
57
|
+
{% if generate_fetchers %}
|
58
|
+
|
59
|
+
// Re-export typed fetchers for universal usage
|
60
|
+
export * as Fetchers from "./_utils/fetchers";
|
61
|
+
|
62
|
+
// Re-export API instance configuration functions
|
49
63
|
export {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
64
|
+
configureAPI,
|
65
|
+
getAPIInstance,
|
66
|
+
reconfigureAPI,
|
67
|
+
clearAPITokens,
|
68
|
+
resetAPI,
|
69
|
+
isAPIConfigured
|
70
|
+
} from "./api-instance";
|
71
|
+
{% endif %}
|
72
|
+
{% if generate_swr_hooks %}
|
73
|
+
|
74
|
+
// Re-export SWR hooks for React
|
75
|
+
export * as Hooks from "./_utils/hooks";
|
76
|
+
{% endif %}
|
77
|
+
|
78
|
+
// Re-export core client
|
79
|
+
export { APIClient };
|
80
|
+
|
81
|
+
// Re-export OpenAPI schema
|
82
|
+
export { OPENAPI_SCHEMA };
|
83
|
+
|
84
|
+
// Re-export storage adapters for convenience
|
85
|
+
export type { StorageAdapter };
|
86
|
+
export { LocalStorageAdapter, CookieStorageAdapter, MemoryStorageAdapter };
|
87
|
+
|
88
|
+
// Re-export error classes for convenience
|
89
|
+
export { APIError, NetworkError } from "./errors";
|
90
|
+
|
91
|
+
// Re-export HTTP adapters for custom implementations
|
92
|
+
export type { HttpClientAdapter, HttpRequest, HttpResponse } from "./http";
|
93
|
+
export { FetchAdapter } from "./http";
|
94
|
+
|
95
|
+
// Re-export logger types and classes
|
96
|
+
export type { LoggerConfig, RequestLog, ResponseLog, ErrorLog } from "./logger";
|
97
|
+
export { APILogger } from "./logger";
|
98
|
+
|
99
|
+
// Re-export retry configuration and utilities
|
100
|
+
export type { RetryConfig, FailedAttemptInfo } from "./retry";
|
101
|
+
export { withRetry, shouldRetry, DEFAULT_RETRY_CONFIG } from "./retry";
|
55
102
|
|
56
103
|
export const TOKEN_KEY = "auth_token";
|
57
104
|
export const REFRESH_TOKEN_KEY = "refresh_token";
|
@@ -59,6 +106,10 @@ export const REFRESH_TOKEN_KEY = "refresh_token";
|
|
59
106
|
export interface APIOptions {
|
60
107
|
/** Custom storage adapter (defaults to LocalStorageAdapter) */
|
61
108
|
storage?: StorageAdapter;
|
109
|
+
/** Retry configuration for failed requests */
|
110
|
+
retryConfig?: RetryConfig;
|
111
|
+
/** Logger configuration */
|
112
|
+
loggerConfig?: Partial<LoggerConfig>;
|
62
113
|
}
|
63
114
|
|
64
115
|
export class API {
|
@@ -67,6 +118,7 @@ export class API {
|
|
67
118
|
private _token: string | null = null;
|
68
119
|
private _refreshToken: string | null = null;
|
69
120
|
private storage: StorageAdapter;
|
121
|
+
private options?: APIOptions;
|
70
122
|
|
71
123
|
// Sub-clients
|
72
124
|
{% for tag in tags %}
|
@@ -75,7 +127,14 @@ export class API {
|
|
75
127
|
|
76
128
|
constructor(baseUrl: string, options?: APIOptions) {
|
77
129
|
this.baseUrl = baseUrl;
|
78
|
-
this.
|
130
|
+
this.options = options;
|
131
|
+
|
132
|
+
// Create logger if config provided
|
133
|
+
const logger = options?.loggerConfig ? new APILogger(options.loggerConfig) : undefined;
|
134
|
+
|
135
|
+
// Initialize storage with logger
|
136
|
+
this.storage = options?.storage || new LocalStorageAdapter(logger);
|
137
|
+
|
79
138
|
this._loadTokensFromStorage();
|
80
139
|
this._initClients();
|
81
140
|
}
|
@@ -86,7 +145,10 @@ export class API {
|
|
86
145
|
}
|
87
146
|
|
88
147
|
private _initClients(): void {
|
89
|
-
this._client = new APIClient(this.baseUrl
|
148
|
+
this._client = new APIClient(this.baseUrl, {
|
149
|
+
retryConfig: (this as any).options?.retryConfig,
|
150
|
+
loggerConfig: (this as any).options?.loggerConfig,
|
151
|
+
});
|
90
152
|
|
91
153
|
// Inject Authorization header if token exists
|
92
154
|
if (this._token) {
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"name": "{{ package_name }}",
|
3
|
+
"version": "{{ version }}",
|
4
|
+
"description": "{{ description }}",
|
5
|
+
"type": "module",
|
6
|
+
"main": "./dist/index.js",
|
7
|
+
"types": "./dist/index.d.ts",
|
8
|
+
"exports": {
|
9
|
+
".": {
|
10
|
+
"import": "./dist/index.js",
|
11
|
+
"types": "./dist/index.d.ts"
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"files": [
|
15
|
+
"dist",
|
16
|
+
"README.md"
|
17
|
+
],
|
18
|
+
"scripts": {
|
19
|
+
"build": "tsc",
|
20
|
+
"prepublishOnly": "npm run build",
|
21
|
+
"test": "vitest",
|
22
|
+
"lint": "eslint src/",
|
23
|
+
"format": "prettier --write src/"
|
24
|
+
},
|
25
|
+
"keywords": {{ keywords | tojson }},
|
26
|
+
{% if author %}"author": "{{ author }}",{% endif %}
|
27
|
+
"license": "{{ license }}",
|
28
|
+
{% if repository_url %}"repository": {
|
29
|
+
"type": "git",
|
30
|
+
"url": "{{ repository_url }}"
|
31
|
+
},{% endif %}
|
32
|
+
"dependencies": {
|
33
|
+
"p-retry": "^7.0.0",
|
34
|
+
"consola": "^3.4.2"{% if generate_zod_schemas %},
|
35
|
+
"zod": "^3.23.0"{% endif %}{% if generate_swr_hooks %},
|
36
|
+
"swr": "^2.2.0"{% endif %}
|
37
|
+
},{% if generate_swr_hooks %}
|
38
|
+
"peerDependencies": {
|
39
|
+
"react": "^18.0.0 || ^19.0.0"
|
40
|
+
},{% endif %}
|
41
|
+
"devDependencies": {
|
42
|
+
"@types/node": "^22.0.0",
|
43
|
+
"typescript": "^5.9.0",
|
44
|
+
"vitest": "^3.0.0",
|
45
|
+
"eslint": "^9.0.0",
|
46
|
+
"prettier": "^3.0.0"
|
47
|
+
},
|
48
|
+
"engines": {
|
49
|
+
"node": ">=18.0.0"
|
50
|
+
}{% if private %},
|
51
|
+
"private": true{% endif %}
|
52
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/**
|
2
|
+
* Zod Schemas - Runtime validation and type inference
|
3
|
+
*
|
4
|
+
* Auto-generated from OpenAPI specification.
|
5
|
+
* Provides runtime validation for API requests and responses.
|
6
|
+
*
|
7
|
+
* Usage:
|
8
|
+
* ```typescript
|
9
|
+
* import { UserSchema } from './schemas'
|
10
|
+
*
|
11
|
+
* // Validate data
|
12
|
+
* const user = UserSchema.parse(data)
|
13
|
+
*
|
14
|
+
* // Type inference
|
15
|
+
* type User = z.infer<typeof UserSchema>
|
16
|
+
* ```
|
17
|
+
*/
|
18
|
+
|
19
|
+
{% for schema_name in schema_names %}
|
20
|
+
export * from './{{ schema_name }}.schema'
|
21
|
+
{% endfor %}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* Zod schema for {{ schema_name }}
|
3
|
+
*
|
4
|
+
* This schema provides runtime validation and type inference.
|
5
|
+
* {% if description %}
|
6
|
+
* {{ description }}
|
7
|
+
* {% endif %}
|
8
|
+
*/
|
9
|
+
import { z } from 'zod'
|
10
|
+
{% if has_enums %}
|
11
|
+
import * as Enums from '../../enums'
|
12
|
+
{% endif %}
|
13
|
+
{% if has_refs %}
|
14
|
+
{% for ref_name in refs %}
|
15
|
+
import { {{ ref_name }}Schema } from './{{ ref_name }}.schema'
|
16
|
+
{% endfor %}
|
17
|
+
{% endif %}
|
18
|
+
|
19
|
+
{{ schema_code }}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Infer TypeScript type from Zod schema
|
23
|
+
*/
|
24
|
+
export type {{ schema_name }} = z.infer<typeof {{ schema_name }}Schema>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "ES2022",
|
4
|
+
"module": "ESNext",
|
5
|
+
"lib": ["ES2022", "DOM"],
|
6
|
+
"moduleResolution": "bundler",
|
7
|
+
"declaration": true,
|
8
|
+
"declarationMap": true,
|
9
|
+
"sourceMap": true,
|
10
|
+
"outDir": "./dist",
|
11
|
+
"rootDir": ".",
|
12
|
+
"strict": true,
|
13
|
+
"esModuleInterop": true,
|
14
|
+
"skipLibCheck": true,
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
16
|
+
"resolveJsonModule": true
|
17
|
+
},
|
18
|
+
"include": ["./**/*.ts"],
|
19
|
+
"exclude": ["node_modules", "dist"]
|
20
|
+
}
|
@@ -85,7 +85,9 @@ export class APIError extends Error {
|
|
85
85
|
const fieldErrors = this.fieldErrors;
|
86
86
|
if (fieldErrors) {
|
87
87
|
const firstField = Object.keys(fieldErrors)[0];
|
88
|
-
|
88
|
+
if (firstField) {
|
89
|
+
return `${firstField}: ${fieldErrors[firstField]?.join(', ')}`;
|
90
|
+
}
|
89
91
|
}
|
90
92
|
|
91
93
|
return this.message;
|
@@ -131,7 +131,7 @@ export class APILogger {
|
|
131
131
|
if (SENSITIVE_HEADERS.includes(lowerKey)) {
|
132
132
|
filtered[key] = '***';
|
133
133
|
} else {
|
134
|
-
filtered[key] = headers[key];
|
134
|
+
filtered[key] = headers[key] || '';
|
135
135
|
}
|
136
136
|
});
|
137
137
|
|
@@ -221,6 +221,14 @@ export class APILogger {
|
|
221
221
|
this.consola.warn(message, ...args);
|
222
222
|
}
|
223
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
|
+
|
224
232
|
/**
|
225
233
|
* Log debug
|
226
234
|
*/
|
@@ -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
|
+
}
|
@@ -7,6 +7,8 @@
|
|
7
7
|
* - Memory (Node.js/Electron/testing)
|
8
8
|
*/
|
9
9
|
|
10
|
+
import type { APILogger } from './logger';
|
11
|
+
|
10
12
|
/**
|
11
13
|
* Storage adapter interface for cross-platform token storage.
|
12
14
|
*/
|
@@ -21,13 +23,22 @@ export interface StorageAdapter {
|
|
21
23
|
* Works in modern browsers with localStorage support.
|
22
24
|
*/
|
23
25
|
export class LocalStorageAdapter implements StorageAdapter {
|
26
|
+
private logger?: APILogger;
|
27
|
+
|
28
|
+
constructor(logger?: APILogger) {
|
29
|
+
this.logger = logger;
|
30
|
+
}
|
31
|
+
|
24
32
|
getItem(key: string): string | null {
|
25
33
|
try {
|
26
34
|
if (typeof window !== 'undefined' && window.localStorage) {
|
27
|
-
|
35
|
+
const value = localStorage.getItem(key);
|
36
|
+
this.logger?.debug(`LocalStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
|
37
|
+
return value;
|
28
38
|
}
|
39
|
+
this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
|
29
40
|
} catch (error) {
|
30
|
-
|
41
|
+
this.logger?.error('LocalStorage.getItem failed:', error);
|
31
42
|
}
|
32
43
|
return null;
|
33
44
|
}
|
@@ -36,9 +47,12 @@ export class LocalStorageAdapter implements StorageAdapter {
|
|
36
47
|
try {
|
37
48
|
if (typeof window !== 'undefined' && window.localStorage) {
|
38
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');
|
39
53
|
}
|
40
54
|
} catch (error) {
|
41
|
-
|
55
|
+
this.logger?.error('LocalStorage.setItem failed:', error);
|
42
56
|
}
|
43
57
|
}
|
44
58
|
|
@@ -46,9 +60,12 @@ export class LocalStorageAdapter implements StorageAdapter {
|
|
46
60
|
try {
|
47
61
|
if (typeof window !== 'undefined' && window.localStorage) {
|
48
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');
|
49
66
|
}
|
50
67
|
} catch (error) {
|
51
|
-
|
68
|
+
this.logger?.error('LocalStorage.removeItem failed:', error);
|
52
69
|
}
|
53
70
|
}
|
54
71
|
}
|
@@ -58,16 +75,28 @@ export class LocalStorageAdapter implements StorageAdapter {
|
|
58
75
|
* Useful for Next.js, Nuxt.js, and other SSR frameworks.
|
59
76
|
*/
|
60
77
|
export class CookieStorageAdapter implements StorageAdapter {
|
78
|
+
private logger?: APILogger;
|
79
|
+
|
80
|
+
constructor(logger?: APILogger) {
|
81
|
+
this.logger = logger;
|
82
|
+
}
|
83
|
+
|
61
84
|
getItem(key: string): string | null {
|
62
85
|
try {
|
63
|
-
if (typeof document === 'undefined')
|
86
|
+
if (typeof document === 'undefined') {
|
87
|
+
this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
|
88
|
+
return null;
|
89
|
+
}
|
64
90
|
const value = `; ${document.cookie}`;
|
65
91
|
const parts = value.split(`; ${key}=`);
|
66
92
|
if (parts.length === 2) {
|
67
|
-
|
93
|
+
const result = parts.pop()?.split(';').shift() || null;
|
94
|
+
this.logger?.debug(`CookieStorage.getItem("${key}"): ${result ? 'found' : 'not found'}`);
|
95
|
+
return result;
|
68
96
|
}
|
97
|
+
this.logger?.debug(`CookieStorage.getItem("${key}"): not found`);
|
69
98
|
} catch (error) {
|
70
|
-
|
99
|
+
this.logger?.error('CookieStorage.getItem failed:', error);
|
71
100
|
}
|
72
101
|
return null;
|
73
102
|
}
|
@@ -76,9 +105,12 @@ export class CookieStorageAdapter implements StorageAdapter {
|
|
76
105
|
try {
|
77
106
|
if (typeof document !== 'undefined') {
|
78
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?)');
|
79
111
|
}
|
80
112
|
} catch (error) {
|
81
|
-
|
113
|
+
this.logger?.error('CookieStorage.setItem failed:', error);
|
82
114
|
}
|
83
115
|
}
|
84
116
|
|
@@ -86,9 +118,12 @@ export class CookieStorageAdapter implements StorageAdapter {
|
|
86
118
|
try {
|
87
119
|
if (typeof document !== 'undefined') {
|
88
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?)');
|
89
124
|
}
|
90
125
|
} catch (error) {
|
91
|
-
|
126
|
+
this.logger?.error('CookieStorage.removeItem failed:', error);
|
92
127
|
}
|
93
128
|
}
|
94
129
|
}
|
@@ -99,16 +134,25 @@ export class CookieStorageAdapter implements StorageAdapter {
|
|
99
134
|
*/
|
100
135
|
export class MemoryStorageAdapter implements StorageAdapter {
|
101
136
|
private storage: Map<string, string> = new Map();
|
137
|
+
private logger?: APILogger;
|
138
|
+
|
139
|
+
constructor(logger?: APILogger) {
|
140
|
+
this.logger = logger;
|
141
|
+
}
|
102
142
|
|
103
143
|
getItem(key: string): string | null {
|
104
|
-
|
144
|
+
const value = this.storage.get(key) || null;
|
145
|
+
this.logger?.debug(`MemoryStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
|
146
|
+
return value;
|
105
147
|
}
|
106
148
|
|
107
149
|
setItem(key: string, value: string): void {
|
108
150
|
this.storage.set(key, value);
|
151
|
+
this.logger?.debug(`MemoryStorage.setItem("${key}"): success`);
|
109
152
|
}
|
110
153
|
|
111
154
|
removeItem(key: string): void {
|
112
155
|
this.storage.delete(key);
|
156
|
+
this.logger?.debug(`MemoryStorage.removeItem("${key}"): success`);
|
113
157
|
}
|
114
158
|
}
|
@@ -356,6 +356,7 @@ class Command(BaseCommand):
|
|
356
356
|
client_structure=service.config.client_structure,
|
357
357
|
openapi_schema=schema_dict,
|
358
358
|
tag_prefix=f"{group_name}_",
|
359
|
+
generate_package_files=service.config.generate_package_files,
|
359
360
|
)
|
360
361
|
py_files = py_generator.generate()
|
361
362
|
|
@@ -377,6 +378,10 @@ class Command(BaseCommand):
|
|
377
378
|
client_structure=service.config.client_structure,
|
378
379
|
openapi_schema=schema_dict,
|
379
380
|
tag_prefix=f"{group_name}_",
|
381
|
+
generate_package_files=service.config.generate_package_files,
|
382
|
+
generate_zod_schemas=service.config.generate_zod_schemas,
|
383
|
+
generate_fetchers=service.config.generate_fetchers,
|
384
|
+
generate_swr_hooks=service.config.generate_swr_hooks,
|
380
385
|
)
|
381
386
|
ts_files = ts_generator.generate()
|
382
387
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
[pytest]
|
2
|
+
# Pytest configuration for django_client tests
|
3
|
+
|
4
|
+
testpaths = tests
|
5
|
+
|
6
|
+
python_files = test_*.py
|
7
|
+
python_classes = Test*
|
8
|
+
python_functions = test_*
|
9
|
+
|
10
|
+
# Markers
|
11
|
+
markers =
|
12
|
+
unit: Unit tests (fast, isolated)
|
13
|
+
integration: Integration tests (slower, may require setup)
|
14
|
+
slow: Slow tests (can be skipped for quick runs)
|
15
|
+
|
16
|
+
# Output options
|
17
|
+
addopts =
|
18
|
+
-v
|
19
|
+
--strict-markers
|
20
|
+
--tb=short
|
21
|
+
--color=yes
|
22
|
+
|
23
|
+
# Coverage options (if pytest-cov installed)
|
24
|
+
# addopts = --cov=django_cfg.modules.django_client --cov-report=html --cov-report=term
|
25
|
+
|
26
|
+
# Asyncio configuration
|
27
|
+
asyncio_mode = auto
|
28
|
+
|
29
|
+
# Ignore patterns
|
30
|
+
norecursedirs = .git .tox dist build *.egg __pycache__
|