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,278 @@
|
|
1
|
+
"""
|
2
|
+
Operations Generator - Generates operation methods for async and sync clients.
|
3
|
+
|
4
|
+
Handles:
|
5
|
+
- Async operation methods (async def with await)
|
6
|
+
- Sync operation methods (def without await)
|
7
|
+
- Path parameters, query parameters, request bodies
|
8
|
+
- Response parsing and validation
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
from jinja2 import Environment
|
14
|
+
|
15
|
+
from ...ir import IROperationObject
|
16
|
+
|
17
|
+
|
18
|
+
class OperationsGenerator:
|
19
|
+
"""Generates operation methods for Python clients."""
|
20
|
+
|
21
|
+
def __init__(self, jinja_env: Environment, base_generator):
|
22
|
+
"""
|
23
|
+
Initialize operations generator.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
jinja_env: Jinja2 environment for templates
|
27
|
+
base_generator: Reference to base generator for utility methods
|
28
|
+
"""
|
29
|
+
self.jinja_env = jinja_env
|
30
|
+
self.base = base_generator
|
31
|
+
|
32
|
+
def generate_async_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False) -> str:
|
33
|
+
"""Generate async method for operation."""
|
34
|
+
# Get method name
|
35
|
+
method_name = operation.operation_id
|
36
|
+
if remove_tag_prefix and operation.tags:
|
37
|
+
# Remove tag prefix using base class method
|
38
|
+
tag = operation.tags[0]
|
39
|
+
method_name = self.base.remove_tag_prefix(method_name, tag)
|
40
|
+
|
41
|
+
# Method signature
|
42
|
+
params = ["self"]
|
43
|
+
|
44
|
+
# Add path parameters
|
45
|
+
for param in operation.path_parameters:
|
46
|
+
param_type = self._map_param_type(param.schema_type)
|
47
|
+
params.append(f"{param.name}: {param_type}")
|
48
|
+
|
49
|
+
# Add request body parameter
|
50
|
+
if operation.request_body:
|
51
|
+
params.append(f"data: {operation.request_body.schema_name}")
|
52
|
+
elif operation.patch_request_body:
|
53
|
+
params.append(f"data: {operation.patch_request_body.schema_name} | None = None")
|
54
|
+
|
55
|
+
# Add query parameters
|
56
|
+
for param in operation.query_parameters:
|
57
|
+
param_type = self._map_param_type(param.schema_type)
|
58
|
+
if not param.required:
|
59
|
+
param_type = f"{param_type} | None = None"
|
60
|
+
params.append(f"{param.name}: {param_type}")
|
61
|
+
|
62
|
+
# Return type
|
63
|
+
primary_response = operation.primary_success_response
|
64
|
+
if primary_response and primary_response.schema_name:
|
65
|
+
if operation.is_list_operation:
|
66
|
+
return_type = f"list[{primary_response.schema_name}]"
|
67
|
+
else:
|
68
|
+
return_type = primary_response.schema_name
|
69
|
+
else:
|
70
|
+
return_type = "None"
|
71
|
+
|
72
|
+
signature = f"async def {method_name}({', '.join(params)}) -> {return_type}:"
|
73
|
+
|
74
|
+
# Docstring
|
75
|
+
docstring_lines = []
|
76
|
+
if operation.summary:
|
77
|
+
docstring_lines.append(operation.summary)
|
78
|
+
if operation.description:
|
79
|
+
if docstring_lines:
|
80
|
+
docstring_lines.append("")
|
81
|
+
docstring_lines.extend(self.base.wrap_comment(operation.description, 72))
|
82
|
+
|
83
|
+
docstring = "\n".join(docstring_lines) if docstring_lines else None
|
84
|
+
|
85
|
+
# Method body
|
86
|
+
body_lines = []
|
87
|
+
|
88
|
+
# Build URL
|
89
|
+
url_expr = f'"{operation.path}"'
|
90
|
+
if operation.path_parameters:
|
91
|
+
# Replace {id} with f-string {id}
|
92
|
+
url_expr = f'f"{operation.path}"'
|
93
|
+
|
94
|
+
body_lines.append(f"url = {url_expr}")
|
95
|
+
|
96
|
+
# Build request
|
97
|
+
request_kwargs = []
|
98
|
+
|
99
|
+
# Query params
|
100
|
+
if operation.query_parameters:
|
101
|
+
query_items = []
|
102
|
+
for param in operation.query_parameters:
|
103
|
+
if param.required:
|
104
|
+
query_items.append(f'"{param.name}": {param.name}')
|
105
|
+
else:
|
106
|
+
query_items.append(f'"{param.name}": {param.name} if {param.name} is not None else None')
|
107
|
+
|
108
|
+
query_dict = "{" + ", ".join(query_items) + "}"
|
109
|
+
request_kwargs.append(f"params={query_dict}")
|
110
|
+
|
111
|
+
# JSON body
|
112
|
+
if operation.request_body:
|
113
|
+
# Required body
|
114
|
+
request_kwargs.append("json=data.model_dump()")
|
115
|
+
elif operation.patch_request_body:
|
116
|
+
# Optional PATCH body - check for None
|
117
|
+
request_kwargs.append("json=data.model_dump() if data is not None else None")
|
118
|
+
|
119
|
+
# Make request
|
120
|
+
method_lower = operation.http_method.lower()
|
121
|
+
request_line = f"response = await self._client.{method_lower}(url"
|
122
|
+
if request_kwargs:
|
123
|
+
request_line += ", " + ", ".join(request_kwargs)
|
124
|
+
request_line += ")"
|
125
|
+
|
126
|
+
body_lines.append(request_line)
|
127
|
+
|
128
|
+
# Handle response
|
129
|
+
body_lines.append("response.raise_for_status()")
|
130
|
+
|
131
|
+
if return_type != "None":
|
132
|
+
if operation.is_list_operation:
|
133
|
+
# Paginated list response - extract results
|
134
|
+
body_lines.append(f"data = response.json()")
|
135
|
+
body_lines.append(f'return [{primary_response.schema_name}.model_validate(item) for item in data.get("results", [])]')
|
136
|
+
else:
|
137
|
+
body_lines.append(f"return {primary_response.schema_name}.model_validate(response.json())")
|
138
|
+
else:
|
139
|
+
body_lines.append("return None")
|
140
|
+
|
141
|
+
template = self.jinja_env.get_template('client/operation_method.py.jinja')
|
142
|
+
return template.render(
|
143
|
+
method_name=method_name,
|
144
|
+
params=params,
|
145
|
+
return_type=return_type,
|
146
|
+
docstring=docstring,
|
147
|
+
body_lines=body_lines
|
148
|
+
)
|
149
|
+
|
150
|
+
def generate_sync_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False) -> str:
|
151
|
+
"""Generate sync method for operation (mirrors async generate_operation)."""
|
152
|
+
# Get method name
|
153
|
+
method_name = operation.operation_id
|
154
|
+
if remove_tag_prefix and operation.tags:
|
155
|
+
# Remove tag prefix using base class method
|
156
|
+
tag = operation.tags[0]
|
157
|
+
method_name = self.base.remove_tag_prefix(method_name, tag)
|
158
|
+
|
159
|
+
# Method signature
|
160
|
+
params = ["self"]
|
161
|
+
|
162
|
+
# Add path parameters
|
163
|
+
for param in operation.path_parameters:
|
164
|
+
param_type = self._map_param_type(param.schema_type)
|
165
|
+
params.append(f"{param.name}: {param_type}")
|
166
|
+
|
167
|
+
# Add request body parameter
|
168
|
+
if operation.request_body:
|
169
|
+
params.append(f"data: {operation.request_body.schema_name}")
|
170
|
+
elif operation.patch_request_body:
|
171
|
+
params.append(f"data: {operation.patch_request_body.schema_name} | None = None")
|
172
|
+
|
173
|
+
# Add query parameters
|
174
|
+
for param in operation.query_parameters:
|
175
|
+
param_type = self._map_param_type(param.schema_type)
|
176
|
+
if not param.required:
|
177
|
+
param_type = f"{param_type} | None = None"
|
178
|
+
params.append(f"{param.name}: {param_type}")
|
179
|
+
|
180
|
+
# Return type
|
181
|
+
primary_response = operation.primary_success_response
|
182
|
+
if primary_response and primary_response.schema_name:
|
183
|
+
if operation.is_list_operation:
|
184
|
+
return_type = f"list[{primary_response.schema_name}]"
|
185
|
+
else:
|
186
|
+
return_type = primary_response.schema_name
|
187
|
+
else:
|
188
|
+
return_type = "None"
|
189
|
+
|
190
|
+
# Docstring
|
191
|
+
docstring_lines = []
|
192
|
+
if operation.summary:
|
193
|
+
docstring_lines.append(operation.summary)
|
194
|
+
if operation.description:
|
195
|
+
if docstring_lines:
|
196
|
+
docstring_lines.append("")
|
197
|
+
docstring_lines.extend(self.base.wrap_comment(operation.description, 72))
|
198
|
+
|
199
|
+
docstring = "\n".join(docstring_lines) if docstring_lines else None
|
200
|
+
|
201
|
+
# Method body
|
202
|
+
body_lines = []
|
203
|
+
|
204
|
+
# Build URL
|
205
|
+
url_expr = f'"{operation.path}"'
|
206
|
+
if operation.path_parameters:
|
207
|
+
# Replace {id} with f-string {id}
|
208
|
+
url_expr = f'f"{operation.path}"'
|
209
|
+
|
210
|
+
body_lines.append(f"url = {url_expr}")
|
211
|
+
|
212
|
+
# Build request
|
213
|
+
request_kwargs = []
|
214
|
+
|
215
|
+
# Query params
|
216
|
+
if operation.query_parameters:
|
217
|
+
query_items = []
|
218
|
+
for param in operation.query_parameters:
|
219
|
+
if param.required:
|
220
|
+
query_items.append(f'"{param.name}": {param.name}')
|
221
|
+
else:
|
222
|
+
query_items.append(f'"{param.name}": {param.name} if {param.name} is not None else None')
|
223
|
+
|
224
|
+
query_dict = "{" + ", ".join(query_items) + "}"
|
225
|
+
request_kwargs.append(f"params={query_dict}")
|
226
|
+
|
227
|
+
# JSON body
|
228
|
+
if operation.request_body:
|
229
|
+
# Required body
|
230
|
+
request_kwargs.append("json=data.model_dump(exclude_unset=True)")
|
231
|
+
elif operation.patch_request_body:
|
232
|
+
# Optional PATCH body - check for None
|
233
|
+
request_kwargs.append("json=data.model_dump(exclude_unset=True) if data is not None else None")
|
234
|
+
|
235
|
+
# HTTP method
|
236
|
+
method_lower = operation.http_method.lower()
|
237
|
+
|
238
|
+
# Build request call (sync version - no await)
|
239
|
+
if request_kwargs:
|
240
|
+
request_call = f'self._client.{method_lower}(url, {", ".join(request_kwargs)})'
|
241
|
+
else:
|
242
|
+
request_call = f'self._client.{method_lower}(url)'
|
243
|
+
|
244
|
+
body_lines.append(f"response = {request_call}")
|
245
|
+
body_lines.append("response.raise_for_status()")
|
246
|
+
|
247
|
+
# Parse response
|
248
|
+
if return_type != "None":
|
249
|
+
if operation.is_list_operation:
|
250
|
+
# List response - validate each item (paginated)
|
251
|
+
primary_schema = primary_response.schema_name
|
252
|
+
body_lines.append(f"data = response.json()")
|
253
|
+
body_lines.append(f'return [{primary_schema}.model_validate(item) for item in data.get("results", [])]')
|
254
|
+
else:
|
255
|
+
# Single object response
|
256
|
+
body_lines.append(f"return {return_type}.model_validate(response.json())")
|
257
|
+
|
258
|
+
# Render template
|
259
|
+
template = self.jinja_env.get_template('client/sync_operation_method.py.jinja')
|
260
|
+
return template.render(
|
261
|
+
method_name=method_name,
|
262
|
+
params=params,
|
263
|
+
return_type=return_type,
|
264
|
+
body_lines=body_lines,
|
265
|
+
docstring=docstring
|
266
|
+
)
|
267
|
+
|
268
|
+
def _map_param_type(self, schema_type: str) -> str:
|
269
|
+
"""Map parameter schema type to Python type."""
|
270
|
+
type_map = {
|
271
|
+
"string": "str",
|
272
|
+
"integer": "int",
|
273
|
+
"number": "float",
|
274
|
+
"boolean": "bool",
|
275
|
+
"array": "list",
|
276
|
+
"object": "dict",
|
277
|
+
}
|
278
|
+
return type_map.get(schema_type, "str")
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""
|
2
|
+
Sync Client Generator - Generates sync Python clients.
|
3
|
+
|
4
|
+
Handles:
|
5
|
+
- Main SyncAPIClient class (httpx.Client)
|
6
|
+
- Sync sub-client classes (per tag/app)
|
7
|
+
- Mirrors async client structure without async/await
|
8
|
+
"""
|
9
|
+
|
10
|
+
from __future__ import annotations
|
11
|
+
|
12
|
+
from jinja2 import Environment
|
13
|
+
|
14
|
+
from ..base import GeneratedFile
|
15
|
+
from ...ir import IROperationObject
|
16
|
+
|
17
|
+
|
18
|
+
class SyncClientGenerator:
|
19
|
+
"""Generates sync Python client files."""
|
20
|
+
|
21
|
+
def __init__(self, jinja_env: Environment, base_generator, operations_gen):
|
22
|
+
"""
|
23
|
+
Initialize sync client generator.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
jinja_env: Jinja2 environment for templates
|
27
|
+
base_generator: Reference to base generator
|
28
|
+
operations_gen: Operations generator instance
|
29
|
+
"""
|
30
|
+
self.jinja_env = jinja_env
|
31
|
+
self.base = base_generator
|
32
|
+
self.operations_gen = operations_gen
|
33
|
+
|
34
|
+
def generate_app_sync_client_file(self, tag: str, operations: list[IROperationObject]) -> GeneratedFile:
|
35
|
+
"""Generate sync_client.py for a specific app."""
|
36
|
+
class_name = self.base.tag_to_class_name(tag)
|
37
|
+
|
38
|
+
# Generate sync methods
|
39
|
+
method_codes = []
|
40
|
+
for operation in operations:
|
41
|
+
method_codes.append(self.operations_gen.generate_sync_operation(operation, remove_tag_prefix=True))
|
42
|
+
|
43
|
+
template = self.jinja_env.get_template('client/sync_sub_client.py.jinja')
|
44
|
+
content = template.render(
|
45
|
+
tag=self.base.tag_to_display_name(tag),
|
46
|
+
class_name=class_name,
|
47
|
+
operations=method_codes
|
48
|
+
)
|
49
|
+
|
50
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
51
|
+
return GeneratedFile(
|
52
|
+
path=f"{folder_name}/sync_client.py",
|
53
|
+
content=content,
|
54
|
+
description=f"Sync API client for {tag}",
|
55
|
+
)
|
56
|
+
|
57
|
+
def generate_sync_main_client_file(self, ops_by_tag: dict) -> GeneratedFile:
|
58
|
+
"""Generate sync_client.py with SyncAPIClient."""
|
59
|
+
tags = sorted(ops_by_tag.keys())
|
60
|
+
|
61
|
+
# Prepare tags data for template (with Sync prefix for imports)
|
62
|
+
tags_data = [
|
63
|
+
{
|
64
|
+
"class_name": f"Sync{self.base.tag_to_class_name(tag)}", # Add Sync prefix
|
65
|
+
"slug": f"{self.base.tag_and_app_to_folder_name(tag, ops_by_tag[tag])}.sync_client", # Import from sync_client module
|
66
|
+
}
|
67
|
+
for tag in tags
|
68
|
+
]
|
69
|
+
|
70
|
+
# Generate sync APIClient class
|
71
|
+
sync_client_code = self._generate_sync_main_client_class(ops_by_tag)
|
72
|
+
|
73
|
+
template = self.jinja_env.get_template('client/main_client_file.py.jinja')
|
74
|
+
content = template.render(
|
75
|
+
tags=tags_data,
|
76
|
+
client_code=sync_client_code
|
77
|
+
)
|
78
|
+
|
79
|
+
return GeneratedFile(
|
80
|
+
path="sync_client.py",
|
81
|
+
content=content,
|
82
|
+
description="Main sync API client",
|
83
|
+
)
|
84
|
+
|
85
|
+
def _generate_sync_main_client_class(self, ops_by_tag: dict) -> str:
|
86
|
+
"""Generate main SyncAPIClient with sync sub-clients."""
|
87
|
+
tags = sorted(ops_by_tag.keys())
|
88
|
+
|
89
|
+
# Prepare tags data for template
|
90
|
+
tags_data = [
|
91
|
+
{
|
92
|
+
"class_name": self.base.tag_to_class_name(tag),
|
93
|
+
"property": self.base.tag_to_property_name(tag),
|
94
|
+
}
|
95
|
+
for tag in tags
|
96
|
+
]
|
97
|
+
|
98
|
+
template = self.jinja_env.get_template('client/sync_main_client.py.jinja')
|
99
|
+
return template.render(
|
100
|
+
api_title=self.base.context.openapi_info.title,
|
101
|
+
tags=tags_data
|
102
|
+
)
|
@@ -6,24 +6,42 @@ class API:
|
|
6
6
|
- Thread-safe JWT token storage
|
7
7
|
- Automatic Authorization header injection
|
8
8
|
- Context manager support for async operations
|
9
|
+
- Optional retry and logging configuration
|
9
10
|
|
10
11
|
Example:
|
11
12
|
>>> api = API('https://api.example.com')
|
12
13
|
>>> api.set_token('jwt-token')
|
13
14
|
>>> async with api:
|
14
15
|
... users = await api.users.list()
|
16
|
+
>>>
|
17
|
+
>>> # With retry and logging
|
18
|
+
>>> api = API(
|
19
|
+
... 'https://api.example.com',
|
20
|
+
... retry_config=RetryConfig(max_attempts=5),
|
21
|
+
... logger_config=LoggerConfig(enabled=True)
|
22
|
+
... )
|
15
23
|
"""
|
16
24
|
|
17
|
-
def __init__(
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
base_url: str,
|
28
|
+
logger_config: LoggerConfig | None = None,
|
29
|
+
retry_config: RetryConfig | None = None,
|
30
|
+
**kwargs: Any
|
31
|
+
):
|
18
32
|
"""
|
19
33
|
Initialize API client.
|
20
34
|
|
21
35
|
Args:
|
22
36
|
base_url: Base API URL (e.g., 'https://api.example.com')
|
37
|
+
logger_config: Logger configuration (None to disable logging)
|
38
|
+
retry_config: Retry configuration (None to disable retry)
|
23
39
|
**kwargs: Additional httpx.AsyncClient kwargs
|
24
40
|
"""
|
25
41
|
self.base_url = base_url.rstrip('/')
|
26
42
|
self._kwargs = kwargs
|
43
|
+
self._logger_config = logger_config
|
44
|
+
self._retry_config = retry_config
|
27
45
|
self._token: str | None = None
|
28
46
|
self._refresh_token: str | None = None
|
29
47
|
self._lock = threading.Lock()
|
@@ -42,7 +60,12 @@ class API:
|
|
42
60
|
kwargs['headers'] = headers
|
43
61
|
|
44
62
|
# Create new APIClient
|
45
|
-
self._client = APIClient(
|
63
|
+
self._client = APIClient(
|
64
|
+
self.base_url,
|
65
|
+
logger_config=self._logger_config,
|
66
|
+
retry_config=self._retry_config,
|
67
|
+
**kwargs
|
68
|
+
)
|
46
69
|
|
47
70
|
{% for prop in properties %}
|
48
71
|
@property
|
@@ -6,12 +6,18 @@ class APIClient:
|
|
6
6
|
>>> async with APIClient(base_url='https://api.example.com') as client:
|
7
7
|
... users = await client.users.list()
|
8
8
|
... post = await client.posts.create(data=new_post)
|
9
|
+
>>>
|
10
|
+
>>> # With retry configuration
|
11
|
+
>>> retry_config = RetryConfig(max_attempts=5, min_wait=2.0)
|
12
|
+
>>> async with APIClient(base_url='https://api.example.com', retry_config=retry_config) as client:
|
13
|
+
... users = await client.users.list()
|
9
14
|
"""
|
10
15
|
|
11
16
|
def __init__(
|
12
17
|
self,
|
13
18
|
base_url: str,
|
14
19
|
logger_config: Optional[LoggerConfig] = None,
|
20
|
+
retry_config: Optional[RetryConfig] = None,
|
15
21
|
**kwargs: Any,
|
16
22
|
):
|
17
23
|
"""
|
@@ -20,14 +26,25 @@ class APIClient:
|
|
20
26
|
Args:
|
21
27
|
base_url: Base API URL (e.g., 'https://api.example.com')
|
22
28
|
logger_config: Logger configuration (None to disable logging)
|
29
|
+
retry_config: Retry configuration (None to disable retry)
|
23
30
|
**kwargs: Additional httpx.AsyncClient kwargs
|
24
31
|
"""
|
25
32
|
self.base_url = base_url.rstrip('/')
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
|
34
|
+
# Create HTTP client with or without retry
|
35
|
+
if retry_config is not None:
|
36
|
+
self._client = RetryAsyncClient(
|
37
|
+
base_url=self.base_url,
|
38
|
+
retry_config=retry_config,
|
39
|
+
timeout=30.0,
|
40
|
+
**kwargs,
|
41
|
+
)
|
42
|
+
else:
|
43
|
+
self._client = httpx.AsyncClient(
|
44
|
+
base_url=self.base_url,
|
45
|
+
timeout=30.0,
|
46
|
+
**kwargs,
|
47
|
+
)
|
31
48
|
|
32
49
|
# Initialize logger
|
33
50
|
self.logger: Optional[APILogger] = None
|
@@ -40,10 +57,11 @@ class APIClient:
|
|
40
57
|
{% endfor %}
|
41
58
|
|
42
59
|
async def __aenter__(self) -> 'APIClient':
|
60
|
+
await self._client.__aenter__()
|
43
61
|
return self
|
44
62
|
|
45
63
|
async def __aexit__(self, *args: Any) -> None:
|
46
|
-
await self._client.
|
64
|
+
await self._client.__aexit__(*args)
|
47
65
|
|
48
66
|
async def close(self) -> None:
|
49
67
|
"""Close HTTP client."""
|
@@ -1,3 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from .models import *
|
6
|
+
|
7
|
+
|
1
8
|
class {{ class_name }}:
|
2
9
|
"""API endpoints for {{ tag }}."""
|
3
10
|
|
@@ -6,6 +13,6 @@ class {{ class_name }}:
|
|
6
13
|
self._client = client
|
7
14
|
|
8
15
|
{% for operation in operations %}
|
9
|
-
|
16
|
+
{{ operation | indent(4, first=True) }}
|
10
17
|
|
11
18
|
{% endfor %}
|
django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
class SyncAPIClient:
|
2
|
+
"""
|
3
|
+
Synchronous API client for {{ api_title }}.
|
4
|
+
|
5
|
+
Usage:
|
6
|
+
>>> with SyncAPIClient(base_url='https://api.example.com') as client:
|
7
|
+
... users = client.users.list()
|
8
|
+
... post = client.posts.create(data=new_post)
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
base_url: str,
|
14
|
+
logger_config: Optional[LoggerConfig] = None,
|
15
|
+
**kwargs: Any,
|
16
|
+
):
|
17
|
+
"""
|
18
|
+
Initialize sync API client.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
base_url: Base API URL (e.g., 'https://api.example.com')
|
22
|
+
logger_config: Logger configuration (None to disable logging)
|
23
|
+
**kwargs: Additional httpx.Client kwargs
|
24
|
+
"""
|
25
|
+
self.base_url = base_url.rstrip('/')
|
26
|
+
self._client = httpx.Client(
|
27
|
+
base_url=self.base_url,
|
28
|
+
timeout=30.0,
|
29
|
+
**kwargs,
|
30
|
+
)
|
31
|
+
|
32
|
+
# Initialize logger
|
33
|
+
self.logger: Optional[APILogger] = None
|
34
|
+
if logger_config is not None:
|
35
|
+
self.logger = APILogger(logger_config)
|
36
|
+
|
37
|
+
# Initialize sub-clients
|
38
|
+
{% for tag in tags %}
|
39
|
+
self.{{ tag.property }} = Sync{{ tag.class_name }}(self._client)
|
40
|
+
{% endfor %}
|
41
|
+
|
42
|
+
def __enter__(self) -> 'SyncAPIClient':
|
43
|
+
return self
|
44
|
+
|
45
|
+
def __exit__(self, *args: Any) -> None:
|
46
|
+
self._client.close()
|
47
|
+
|
48
|
+
def close(self) -> None:
|
49
|
+
"""Close HTTP client."""
|
50
|
+
self._client.close()
|
django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from .models import *
|
6
|
+
|
7
|
+
|
8
|
+
class Sync{{ class_name }}:
|
9
|
+
"""Synchronous API endpoints for {{ tag }}."""
|
10
|
+
|
11
|
+
def __init__(self, client: httpx.Client):
|
12
|
+
"""Initialize sync sub-client with shared httpx client."""
|
13
|
+
self._client = client
|
14
|
+
|
15
|
+
{% for operation in operations %}
|
16
|
+
{{ operation | indent(4, first=True) }}
|
17
|
+
|
18
|
+
{% endfor %}
|