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,536 @@
|
|
1
|
+
"""
|
2
|
+
SWR Hooks Generator - Generates React hooks for data fetching.
|
3
|
+
|
4
|
+
This generator creates SWR-based React hooks from IR:
|
5
|
+
- Query hooks (GET operations) using useSWR
|
6
|
+
- Mutation hooks (POST/PUT/PATCH/DELETE) using useSWRConfig
|
7
|
+
- Automatic key generation
|
8
|
+
- Type-safe parameters and responses
|
9
|
+
- Optimistic updates support
|
10
|
+
|
11
|
+
Architecture:
|
12
|
+
- Query hooks: useSWR with automatic key management
|
13
|
+
- Mutation hooks: Custom hooks with revalidation
|
14
|
+
- Works only in React client components
|
15
|
+
"""
|
16
|
+
|
17
|
+
from __future__ import annotations
|
18
|
+
|
19
|
+
from jinja2 import Environment
|
20
|
+
from ..base import BaseGenerator, GeneratedFile
|
21
|
+
from ...ir import IROperationObject, IRContext
|
22
|
+
|
23
|
+
|
24
|
+
class HooksGenerator:
|
25
|
+
"""
|
26
|
+
SWR hooks generator for React.
|
27
|
+
|
28
|
+
Generates:
|
29
|
+
- useResource() hooks for GET operations
|
30
|
+
- useCreateResource() hooks for POST
|
31
|
+
- useUpdateResource() hooks for PUT/PATCH
|
32
|
+
- useDeleteResource() hooks for DELETE
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self, jinja_env: Environment, context: IRContext, base: BaseGenerator):
|
36
|
+
self.jinja_env = jinja_env
|
37
|
+
self.context = context
|
38
|
+
self.base = base
|
39
|
+
|
40
|
+
def generate_query_hook(self, operation: IROperationObject) -> str:
|
41
|
+
"""
|
42
|
+
Generate useSWR hook for GET operation.
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
>>> generate_query_hook(users_list)
|
46
|
+
export function useShopProducts(params?: { page?: number }) {
|
47
|
+
return useSWR(
|
48
|
+
params ? ['shop-products', params] : 'shop-products',
|
49
|
+
() => Fetchers.getShopProducts(params)
|
50
|
+
)
|
51
|
+
}
|
52
|
+
"""
|
53
|
+
# Get hook name
|
54
|
+
hook_name = self._operation_to_hook_name(operation)
|
55
|
+
|
56
|
+
# Get fetcher function name
|
57
|
+
fetcher_name = self._operation_to_fetcher_name(operation)
|
58
|
+
|
59
|
+
# Get parameters
|
60
|
+
param_info = self._get_param_info(operation)
|
61
|
+
|
62
|
+
# Get response type
|
63
|
+
response_type = self._get_response_type(operation)
|
64
|
+
|
65
|
+
# Get SWR key
|
66
|
+
swr_key = self._generate_swr_key(operation)
|
67
|
+
|
68
|
+
# Build hook
|
69
|
+
lines = []
|
70
|
+
|
71
|
+
# JSDoc
|
72
|
+
lines.append("/**")
|
73
|
+
if operation.summary:
|
74
|
+
lines.append(f" * {operation.summary}")
|
75
|
+
lines.append(" *")
|
76
|
+
lines.append(f" * @method {operation.http_method}")
|
77
|
+
lines.append(f" * @path {operation.path}")
|
78
|
+
lines.append(" */")
|
79
|
+
|
80
|
+
# Hook signature
|
81
|
+
if param_info['func_params']:
|
82
|
+
lines.append(f"export function {hook_name}({param_info['func_params']}) {{")
|
83
|
+
else:
|
84
|
+
lines.append(f"export function {hook_name}() {{")
|
85
|
+
|
86
|
+
# useSWR call
|
87
|
+
fetcher_params = param_info['fetcher_params']
|
88
|
+
if fetcher_params:
|
89
|
+
lines.append(f" return useSWR<{response_type}>(")
|
90
|
+
lines.append(f" {swr_key},")
|
91
|
+
lines.append(f" () => Fetchers.{fetcher_name}({fetcher_params})")
|
92
|
+
lines.append(" )")
|
93
|
+
else:
|
94
|
+
lines.append(f" return useSWR<{response_type}>(")
|
95
|
+
lines.append(f" {swr_key},")
|
96
|
+
lines.append(f" () => Fetchers.{fetcher_name}()")
|
97
|
+
lines.append(" )")
|
98
|
+
|
99
|
+
lines.append("}")
|
100
|
+
|
101
|
+
return "\n".join(lines)
|
102
|
+
|
103
|
+
def generate_mutation_hook(self, operation: IROperationObject) -> str:
|
104
|
+
"""
|
105
|
+
Generate mutation hook for POST/PUT/PATCH/DELETE.
|
106
|
+
|
107
|
+
Examples:
|
108
|
+
>>> generate_mutation_hook(users_create)
|
109
|
+
export function useCreateShopProduct() {
|
110
|
+
const { mutate } = useSWRConfig()
|
111
|
+
|
112
|
+
return async (data: ProductCreateRequest) => {
|
113
|
+
const result = await Fetchers.createShopProduct(data)
|
114
|
+
mutate('shop-products')
|
115
|
+
return result
|
116
|
+
}
|
117
|
+
}
|
118
|
+
"""
|
119
|
+
# Get hook name
|
120
|
+
hook_name = self._operation_to_hook_name(operation)
|
121
|
+
|
122
|
+
# Get fetcher function name
|
123
|
+
fetcher_name = self._operation_to_fetcher_name(operation)
|
124
|
+
|
125
|
+
# Get parameters
|
126
|
+
param_info = self._get_param_info(operation)
|
127
|
+
|
128
|
+
# Get response type
|
129
|
+
response_type = self._get_response_type(operation)
|
130
|
+
|
131
|
+
# Get revalidation keys
|
132
|
+
revalidation_keys = self._get_revalidation_keys(operation)
|
133
|
+
|
134
|
+
# Build hook
|
135
|
+
lines = []
|
136
|
+
|
137
|
+
# JSDoc
|
138
|
+
lines.append("/**")
|
139
|
+
if operation.summary:
|
140
|
+
lines.append(f" * {operation.summary}")
|
141
|
+
lines.append(" *")
|
142
|
+
lines.append(f" * @method {operation.http_method}")
|
143
|
+
lines.append(f" * @path {operation.path}")
|
144
|
+
lines.append(" */")
|
145
|
+
|
146
|
+
# Hook signature
|
147
|
+
lines.append(f"export function {hook_name}() {{")
|
148
|
+
lines.append(" const { mutate } = useSWRConfig()")
|
149
|
+
lines.append("")
|
150
|
+
|
151
|
+
# Return async function
|
152
|
+
if param_info['func_params']:
|
153
|
+
lines.append(f" return async ({param_info['func_params']}): Promise<{response_type}> => {{")
|
154
|
+
else:
|
155
|
+
lines.append(f" return async (): Promise<{response_type}> => {{")
|
156
|
+
|
157
|
+
# Call fetcher
|
158
|
+
fetcher_params = param_info['fetcher_params']
|
159
|
+
if fetcher_params:
|
160
|
+
lines.append(f" const result = await Fetchers.{fetcher_name}({fetcher_params})")
|
161
|
+
else:
|
162
|
+
lines.append(f" const result = await Fetchers.{fetcher_name}()")
|
163
|
+
|
164
|
+
# Revalidate
|
165
|
+
if revalidation_keys:
|
166
|
+
lines.append("")
|
167
|
+
lines.append(" // Revalidate related queries")
|
168
|
+
for key in revalidation_keys:
|
169
|
+
lines.append(f" mutate('{key}')")
|
170
|
+
|
171
|
+
lines.append("")
|
172
|
+
lines.append(" return result")
|
173
|
+
lines.append(" }")
|
174
|
+
lines.append("}")
|
175
|
+
|
176
|
+
return "\n".join(lines)
|
177
|
+
|
178
|
+
def _operation_to_hook_name(self, operation: IROperationObject) -> str:
|
179
|
+
"""
|
180
|
+
Convert operation to hook name.
|
181
|
+
|
182
|
+
Examples:
|
183
|
+
users_list (GET) -> useUsers
|
184
|
+
users_retrieve (GET) -> useUser
|
185
|
+
users_create (POST) -> useCreateUser
|
186
|
+
users_update (PUT) -> useUpdateUser
|
187
|
+
users_partial_update (PATCH) -> useUpdateUser
|
188
|
+
users_destroy (DELETE) -> useDeleteUser
|
189
|
+
"""
|
190
|
+
op_id = operation.operation_id
|
191
|
+
|
192
|
+
if op_id.endswith("_list"):
|
193
|
+
resource = op_id.replace("_list", "")
|
194
|
+
# Plural form
|
195
|
+
return f"use{self._to_pascal_case(resource)}"
|
196
|
+
elif op_id.endswith("_retrieve"):
|
197
|
+
resource = op_id.replace("_retrieve", "")
|
198
|
+
# Singular form (remove trailing 's')
|
199
|
+
resource_singular = resource.rstrip('s') if resource.endswith('s') and len(resource) > 1 else resource
|
200
|
+
return f"use{self._to_pascal_case(resource_singular)}"
|
201
|
+
elif op_id.endswith("_create"):
|
202
|
+
resource = op_id.removesuffix("_create")
|
203
|
+
return f"useCreate{self._to_pascal_case(resource)}"
|
204
|
+
elif op_id.endswith("_partial_update"):
|
205
|
+
resource = op_id.removesuffix("_partial_update")
|
206
|
+
return f"usePartialUpdate{self._to_pascal_case(resource)}"
|
207
|
+
elif op_id.endswith("_update"):
|
208
|
+
resource = op_id.removesuffix("_update")
|
209
|
+
return f"useUpdate{self._to_pascal_case(resource)}"
|
210
|
+
elif op_id.endswith("_destroy"):
|
211
|
+
resource = op_id.removesuffix("_destroy")
|
212
|
+
return f"useDelete{self._to_pascal_case(resource)}"
|
213
|
+
else:
|
214
|
+
# Custom action
|
215
|
+
return f"use{self._to_pascal_case(op_id)}"
|
216
|
+
|
217
|
+
def _operation_to_fetcher_name(self, operation: IROperationObject) -> str:
|
218
|
+
"""Get corresponding fetcher function name."""
|
219
|
+
op_id = operation.operation_id
|
220
|
+
|
221
|
+
# Remove only suffix, not all occurrences (same logic as fetchers_generator)
|
222
|
+
if op_id.endswith("_list"):
|
223
|
+
resource = op_id.removesuffix("_list")
|
224
|
+
return f"get{self._to_pascal_case(resource)}"
|
225
|
+
elif op_id.endswith("_retrieve"):
|
226
|
+
resource = op_id.removesuffix("_retrieve")
|
227
|
+
# Singular
|
228
|
+
resource_singular = resource.rstrip('s') if resource.endswith('s') else resource
|
229
|
+
return f"get{self._to_pascal_case(resource_singular)}"
|
230
|
+
elif op_id.endswith("_create"):
|
231
|
+
resource = op_id.removesuffix("_create")
|
232
|
+
return f"create{self._to_pascal_case(resource)}"
|
233
|
+
elif op_id.endswith("_partial_update"):
|
234
|
+
resource = op_id.removesuffix("_partial_update")
|
235
|
+
return f"partialUpdate{self._to_pascal_case(resource)}"
|
236
|
+
elif op_id.endswith("_update"):
|
237
|
+
resource = op_id.removesuffix("_update")
|
238
|
+
return f"update{self._to_pascal_case(resource)}"
|
239
|
+
elif op_id.endswith("_destroy"):
|
240
|
+
resource = op_id.removesuffix("_destroy")
|
241
|
+
return f"delete{self._to_pascal_case(resource)}"
|
242
|
+
else:
|
243
|
+
return f"{operation.http_method.lower()}{self._to_pascal_case(op_id)}"
|
244
|
+
|
245
|
+
def _get_param_info(self, operation: IROperationObject) -> dict:
|
246
|
+
"""
|
247
|
+
Get parameter info for hook.
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
{
|
251
|
+
'func_params': Function parameters for hook signature
|
252
|
+
'fetcher_params': Parameters to pass to fetcher
|
253
|
+
}
|
254
|
+
"""
|
255
|
+
func_params = []
|
256
|
+
fetcher_params = []
|
257
|
+
|
258
|
+
# Path parameters
|
259
|
+
if operation.path_parameters:
|
260
|
+
for param in operation.path_parameters:
|
261
|
+
param_type = self._map_param_type(param.schema_type)
|
262
|
+
func_params.append(f"{param.name}: {param_type}")
|
263
|
+
fetcher_params.append(param.name)
|
264
|
+
|
265
|
+
# Query parameters
|
266
|
+
if operation.query_parameters:
|
267
|
+
query_fields = []
|
268
|
+
all_required = all(param.required for param in operation.query_parameters)
|
269
|
+
|
270
|
+
for param in operation.query_parameters:
|
271
|
+
param_type = self._map_param_type(param.schema_type)
|
272
|
+
optional = "?" if not param.required else ""
|
273
|
+
query_fields.append(f"{param.name}{optional}: {param_type}")
|
274
|
+
|
275
|
+
if query_fields:
|
276
|
+
params_optional = "" if all_required else "?"
|
277
|
+
func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
|
278
|
+
fetcher_params.append("params")
|
279
|
+
|
280
|
+
# Request body
|
281
|
+
if operation.request_body:
|
282
|
+
schema_name = operation.request_body.schema_name
|
283
|
+
# Use schema only if it exists as a component (not inline)
|
284
|
+
if schema_name and schema_name in self.context.schemas:
|
285
|
+
body_type = schema_name
|
286
|
+
else:
|
287
|
+
body_type = "any"
|
288
|
+
func_params.append(f"data: {body_type}")
|
289
|
+
fetcher_params.append("data")
|
290
|
+
|
291
|
+
return {
|
292
|
+
'func_params': ", ".join(func_params) if func_params else "",
|
293
|
+
'fetcher_params': ", ".join(fetcher_params) if fetcher_params else ""
|
294
|
+
}
|
295
|
+
|
296
|
+
def _map_param_type(self, param_type: str) -> str:
|
297
|
+
"""Map OpenAPI param type to TypeScript type."""
|
298
|
+
type_map = {
|
299
|
+
"integer": "number",
|
300
|
+
"number": "number",
|
301
|
+
"string": "string",
|
302
|
+
"boolean": "boolean",
|
303
|
+
"array": "any[]",
|
304
|
+
"object": "any",
|
305
|
+
}
|
306
|
+
return type_map.get(param_type, "any")
|
307
|
+
|
308
|
+
def _get_response_type(self, operation: IROperationObject) -> str:
|
309
|
+
"""Get response type for hook."""
|
310
|
+
# Get 2xx response
|
311
|
+
for status_code in [200, 201, 202, 204]:
|
312
|
+
if status_code in operation.responses:
|
313
|
+
response = operation.responses[status_code]
|
314
|
+
if response.schema_name:
|
315
|
+
return response.schema_name
|
316
|
+
|
317
|
+
# No response or void
|
318
|
+
if 204 in operation.responses or operation.http_method == "DELETE":
|
319
|
+
return "void"
|
320
|
+
|
321
|
+
return "any"
|
322
|
+
|
323
|
+
def _generate_swr_key(self, operation: IROperationObject) -> str:
|
324
|
+
"""
|
325
|
+
Generate SWR key for query.
|
326
|
+
|
327
|
+
Examples:
|
328
|
+
GET /products/ -> 'shop-products'
|
329
|
+
GET /products/{id}/ -> ['shop-product', id]
|
330
|
+
GET /products/?category=5 -> ['shop-products', params]
|
331
|
+
"""
|
332
|
+
# Get resource name from operation_id
|
333
|
+
op_id = operation.operation_id
|
334
|
+
|
335
|
+
# Determine if list or detail
|
336
|
+
is_list = op_id.endswith("_list")
|
337
|
+
is_detail = op_id.endswith("_retrieve")
|
338
|
+
|
339
|
+
# Remove common suffixes
|
340
|
+
resource = op_id.replace("_list", "").replace("_retrieve", "")
|
341
|
+
|
342
|
+
# For detail views, use singular form
|
343
|
+
if is_detail:
|
344
|
+
resource = resource.rstrip('s') if resource.endswith('s') and len(resource) > 1 else resource
|
345
|
+
|
346
|
+
# Convert to kebab-case
|
347
|
+
key_base = resource.replace("_", "-")
|
348
|
+
|
349
|
+
# Check if has path params or query params
|
350
|
+
has_path_params = bool(operation.path_parameters)
|
351
|
+
has_query_params = bool(operation.query_parameters)
|
352
|
+
|
353
|
+
if has_path_params:
|
354
|
+
# Single resource: ['shop-product', id]
|
355
|
+
param_name = operation.path_parameters[0].name
|
356
|
+
return f"['{key_base}', {param_name}]"
|
357
|
+
elif has_query_params:
|
358
|
+
# List with params: params ? ['shop-products', params] : 'shop-products'
|
359
|
+
return f"params ? ['{key_base}', params] : '{key_base}'"
|
360
|
+
else:
|
361
|
+
# Simple key: 'shop-products'
|
362
|
+
return f"'{key_base}'"
|
363
|
+
|
364
|
+
def _get_revalidation_keys(self, operation: IROperationObject) -> list[str]:
|
365
|
+
"""
|
366
|
+
Get SWR keys that should be revalidated after mutation.
|
367
|
+
|
368
|
+
Examples:
|
369
|
+
POST /products/ -> ['shop-products']
|
370
|
+
PUT /products/{id}/ -> ['shop-products', 'shop-product']
|
371
|
+
DELETE /products/{id}/ -> ['shop-products']
|
372
|
+
"""
|
373
|
+
keys = []
|
374
|
+
|
375
|
+
op_id = operation.operation_id
|
376
|
+
resource = op_id.replace("_create", "").replace("_update", "").replace("_partial_update", "").replace("_destroy", "")
|
377
|
+
|
378
|
+
# List key (for revalidating lists)
|
379
|
+
list_key = f"{resource.replace('_', '-')}"
|
380
|
+
keys.append(list_key)
|
381
|
+
|
382
|
+
# Detail key (for update/delete operations)
|
383
|
+
if operation.http_method in ("PUT", "PATCH", "DELETE"):
|
384
|
+
detail_key = f"{resource.replace('_', '-').rstrip('s')}"
|
385
|
+
if detail_key != list_key:
|
386
|
+
keys.append(detail_key)
|
387
|
+
|
388
|
+
return keys
|
389
|
+
|
390
|
+
def _to_pascal_case(self, snake_str: str) -> str:
|
391
|
+
"""Convert snake_case to PascalCase."""
|
392
|
+
return ''.join(word.capitalize() for word in snake_str.split('_'))
|
393
|
+
|
394
|
+
def generate_tag_hooks_file(
|
395
|
+
self,
|
396
|
+
tag: str,
|
397
|
+
operations: list[IROperationObject],
|
398
|
+
) -> GeneratedFile:
|
399
|
+
"""
|
400
|
+
Generate hooks file for a specific tag/resource.
|
401
|
+
|
402
|
+
Args:
|
403
|
+
tag: Tag name (e.g., "shop_products")
|
404
|
+
operations: List of operations for this tag
|
405
|
+
|
406
|
+
Returns:
|
407
|
+
GeneratedFile with hooks
|
408
|
+
"""
|
409
|
+
# Separate queries and mutations & collect schema names
|
410
|
+
query_hooks = []
|
411
|
+
mutation_hooks = []
|
412
|
+
schema_names = set()
|
413
|
+
|
414
|
+
for operation in operations:
|
415
|
+
# Collect schemas used in this operation (only if they exist as components)
|
416
|
+
if operation.request_body and operation.request_body.schema_name:
|
417
|
+
if operation.request_body.schema_name in self.context.schemas:
|
418
|
+
schema_names.add(operation.request_body.schema_name)
|
419
|
+
if operation.patch_request_body and operation.patch_request_body.schema_name:
|
420
|
+
if operation.patch_request_body.schema_name in self.context.schemas:
|
421
|
+
schema_names.add(operation.patch_request_body.schema_name)
|
422
|
+
|
423
|
+
# Get response schema
|
424
|
+
response = operation.primary_success_response
|
425
|
+
if response and response.schema_name:
|
426
|
+
schema_names.add(response.schema_name)
|
427
|
+
|
428
|
+
# Generate hook
|
429
|
+
if operation.http_method == "GET":
|
430
|
+
query_hooks.append(self.generate_query_hook(operation))
|
431
|
+
else:
|
432
|
+
mutation_hooks.append(self.generate_mutation_hook(operation))
|
433
|
+
|
434
|
+
# Get display name for documentation
|
435
|
+
tag_display_name = self.base.tag_to_display_name(tag)
|
436
|
+
|
437
|
+
# Build file content
|
438
|
+
lines = []
|
439
|
+
|
440
|
+
# Header
|
441
|
+
lines.append("/**")
|
442
|
+
lines.append(f" * SWR Hooks for {tag_display_name}")
|
443
|
+
lines.append(" *")
|
444
|
+
lines.append(" * Auto-generated React hooks for data fetching with SWR.")
|
445
|
+
lines.append(" *")
|
446
|
+
lines.append(" * Setup:")
|
447
|
+
lines.append(" * ```typescript")
|
448
|
+
lines.append(" * // Configure API once (in your app root)")
|
449
|
+
lines.append(" * import { configureAPI } from '../../api-instance'")
|
450
|
+
lines.append(" * configureAPI({ baseUrl: 'https://api.example.com' })")
|
451
|
+
lines.append(" * ```")
|
452
|
+
lines.append(" *")
|
453
|
+
lines.append(" * Usage:")
|
454
|
+
lines.append(" * ```typescript")
|
455
|
+
lines.append(" * // Query hook")
|
456
|
+
lines.append(" * const { data, error, mutate } = useShopProducts({ page: 1 })")
|
457
|
+
lines.append(" *")
|
458
|
+
lines.append(" * // Mutation hook")
|
459
|
+
lines.append(" * const createProduct = useCreateShopProduct()")
|
460
|
+
lines.append(" * await createProduct({ name: 'Product', price: 99 })")
|
461
|
+
lines.append(" * ```")
|
462
|
+
lines.append(" */")
|
463
|
+
|
464
|
+
# Import types from schemas
|
465
|
+
for schema_name in sorted(schema_names):
|
466
|
+
lines.append(f"import type {{ {schema_name} }} from '../schemas/{schema_name}.schema'")
|
467
|
+
|
468
|
+
lines.append("import useSWR from 'swr'")
|
469
|
+
lines.append("import { useSWRConfig } from 'swr'")
|
470
|
+
lines.append("import * as Fetchers from '../fetchers'")
|
471
|
+
lines.append("")
|
472
|
+
|
473
|
+
# Query hooks
|
474
|
+
if query_hooks:
|
475
|
+
lines.append("// ===== Query Hooks (GET) =====")
|
476
|
+
lines.append("")
|
477
|
+
for hook in query_hooks:
|
478
|
+
lines.append(hook)
|
479
|
+
lines.append("")
|
480
|
+
|
481
|
+
# Mutation hooks
|
482
|
+
if mutation_hooks:
|
483
|
+
lines.append("// ===== Mutation Hooks (POST/PUT/PATCH/DELETE) =====")
|
484
|
+
lines.append("")
|
485
|
+
for hook in mutation_hooks:
|
486
|
+
lines.append(hook)
|
487
|
+
lines.append("")
|
488
|
+
|
489
|
+
content = "\n".join(lines)
|
490
|
+
|
491
|
+
# Get file path (use same naming as APIClient)
|
492
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
493
|
+
file_path = f"_utils/hooks/{folder_name}.ts"
|
494
|
+
|
495
|
+
return GeneratedFile(
|
496
|
+
path=file_path,
|
497
|
+
content=content,
|
498
|
+
description=f"SWR hooks for {tag_display_name}",
|
499
|
+
)
|
500
|
+
|
501
|
+
def generate_hooks_index_file(self, module_names: list[str]) -> GeneratedFile:
|
502
|
+
"""Generate index.ts for hooks folder."""
|
503
|
+
lines = []
|
504
|
+
|
505
|
+
lines.append("/**")
|
506
|
+
lines.append(" * SWR Hooks - React hooks for data fetching")
|
507
|
+
lines.append(" *")
|
508
|
+
lines.append(" * Auto-generated from OpenAPI specification.")
|
509
|
+
lines.append(" * These hooks use SWR for data fetching and caching.")
|
510
|
+
lines.append(" *")
|
511
|
+
lines.append(" * Usage:")
|
512
|
+
lines.append(" * ```typescript")
|
513
|
+
lines.append(" * import { useShopProducts } from './_utils/hooks'")
|
514
|
+
lines.append(" *")
|
515
|
+
lines.append(" * function ProductsPage() {")
|
516
|
+
lines.append(" * const { data, error } = useShopProducts({ page: 1 })")
|
517
|
+
lines.append(" * if (error) return <Error />")
|
518
|
+
lines.append(" * if (!data) return <Loading />")
|
519
|
+
lines.append(" * return <ProductList products={data.results} />")
|
520
|
+
lines.append(" * }")
|
521
|
+
lines.append(" * ```")
|
522
|
+
lines.append(" */")
|
523
|
+
lines.append("")
|
524
|
+
|
525
|
+
for module_name in module_names:
|
526
|
+
lines.append(f"export * from './{module_name}'")
|
527
|
+
|
528
|
+
lines.append("")
|
529
|
+
|
530
|
+
content = "\n".join(lines)
|
531
|
+
|
532
|
+
return GeneratedFile(
|
533
|
+
path="_utils/hooks/index.ts",
|
534
|
+
content=content,
|
535
|
+
description="Index file for SWR hooks",
|
536
|
+
)
|