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.
Files changed (101) hide show
  1. django_cfg/core/generation/integration_generators/api.py +2 -1
  2. django_cfg/models/django/openapi.py +4 -80
  3. django_cfg/modules/django_client/core/archive/manager.py +2 -2
  4. django_cfg/modules/django_client/core/config/config.py +20 -0
  5. django_cfg/modules/django_client/core/config/service.py +1 -1
  6. django_cfg/modules/django_client/core/generator/__init__.py +4 -4
  7. django_cfg/modules/django_client/core/generator/base.py +71 -0
  8. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  9. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  10. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  11. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  12. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  13. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  14. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  15. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
  16. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
  17. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
  18. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
  19. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
  20. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  21. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  22. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  23. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
  24. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
  25. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
  26. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  27. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  28. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  29. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  30. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  31. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  32. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  33. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  34. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  35. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  36. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  37. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  38. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
  39. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
  40. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
  41. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
  42. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  43. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  44. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
  45. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  46. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  47. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  48. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  49. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
  50. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
  51. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  52. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
  53. django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
  54. django_cfg/modules/django_client/pytest.ini +30 -0
  55. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  56. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  57. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  58. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  59. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  60. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  61. django_cfg/modules/django_unfold/callbacks/main.py +6 -6
  62. django_cfg/pyproject.toml +1 -1
  63. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/METADATA +1 -1
  64. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/RECORD +99 -70
  65. django_cfg/modules/django_client/core/generator/python.py +0 -751
  66. django_cfg/modules/django_client/core/generator/typescript.py +0 -872
  67. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
  68. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
  69. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
  70. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
  71. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
  72. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
  73. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
  74. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
  75. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
  76. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
  77. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
  78. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
  79. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
  80. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
  81. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
  82. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
  83. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
  84. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
  85. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
  86. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
  87. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  88. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  89. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  90. /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
  91. /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
  92. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  93. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  94. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  95. /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
  96. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  97. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  98. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  99. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  100. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  101. {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 storage adapters for convenience
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
- StorageAdapter,
51
- LocalStorageAdapter,
52
- CookieStorageAdapter,
53
- MemoryStorageAdapter
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.storage = options?.storage || new LocalStorageAdapter();
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
- return `${firstField}: ${fieldErrors[firstField].join(', ')}`;
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
- return localStorage.getItem(key);
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
- console.warn('LocalStorage not available:', error);
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
- console.warn('LocalStorage not available:', error);
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
- console.warn('LocalStorage not available:', error);
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') return null;
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
- return parts.pop()?.split(';').shift() || null;
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
- console.warn('Cookies not available:', error);
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
- console.warn('Cookies not available:', error);
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
- console.warn('Cookies not available:', error);
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
- return this.storage.get(key) || null;
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__