ab-openapi-python-generator 2.1.4__py3-none-any.whl → 2.1.4.dev1768280320__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.
- ab_openapi_python_generator/__init__.py +10 -14
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.1.4.dev1768280320.dist-info}/METADATA +27 -21
- ab_openapi_python_generator-2.1.4.dev1768280320.dist-info/RECORD +6 -0
- {ab_openapi_python_generator-2.1.4.dist-info → ab_openapi_python_generator-2.1.4.dev1768280320.dist-info}/WHEEL +1 -1
- ab_openapi_python_generator-2.1.4.dev1768280320.dist-info/entry_points.txt +3 -0
- ab_openapi_python_generator/__main__.py +0 -85
- ab_openapi_python_generator/common.py +0 -58
- ab_openapi_python_generator/generate_data.py +0 -235
- ab_openapi_python_generator/language_converters/__init__.py +0 -0
- ab_openapi_python_generator/language_converters/python/__init__.py +0 -0
- ab_openapi_python_generator/language_converters/python/api_config_generator.py +0 -35
- ab_openapi_python_generator/language_converters/python/common.py +0 -58
- ab_openapi_python_generator/language_converters/python/generator.py +0 -54
- ab_openapi_python_generator/language_converters/python/jinja_config.py +0 -38
- ab_openapi_python_generator/language_converters/python/model_generator.py +0 -896
- ab_openapi_python_generator/language_converters/python/service_generator.py +0 -540
- ab_openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 +0 -49
- ab_openapi_python_generator/language_converters/python/templates/alias_union.jinja2 +0 -17
- ab_openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 +0 -38
- ab_openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 +0 -42
- ab_openapi_python_generator/language_converters/python/templates/discriminator_enum.jinja2 +0 -7
- ab_openapi_python_generator/language_converters/python/templates/enum.jinja2 +0 -11
- ab_openapi_python_generator/language_converters/python/templates/httpx.jinja2 +0 -126
- ab_openapi_python_generator/language_converters/python/templates/models.jinja2 +0 -24
- ab_openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 +0 -28
- ab_openapi_python_generator/language_converters/python/templates/requests.jinja2 +0 -50
- ab_openapi_python_generator/language_converters/python/templates/service.jinja2 +0 -12
- ab_openapi_python_generator/models.py +0 -101
- ab_openapi_python_generator/parsers/__init__.py +0 -13
- ab_openapi_python_generator/parsers/openapi_30.py +0 -65
- ab_openapi_python_generator/parsers/openapi_31.py +0 -65
- ab_openapi_python_generator/py.typed +0 -0
- ab_openapi_python_generator/version_detector.py +0 -70
- ab_openapi_python_generator-2.1.4.dist-info/RECORD +0 -34
- ab_openapi_python_generator-2.1.4.dist-info/entry_points.txt +0 -2
- {ab_openapi_python_generator-2.1.4.dist-info/licenses → ab_openapi_python_generator-2.1.4.dev1768280320.dist-info}/LICENSE +0 -0
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
|
3
|
-
|
|
4
|
-
import click
|
|
5
|
-
from openapi_pydantic.v3 import (
|
|
6
|
-
Operation,
|
|
7
|
-
PathItem,
|
|
8
|
-
Reference,
|
|
9
|
-
Response,
|
|
10
|
-
Schema,
|
|
11
|
-
)
|
|
12
|
-
from openapi_pydantic.v3.v3_0 import (
|
|
13
|
-
MediaType as MediaType30,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
# Import version-specific types for isinstance checks
|
|
17
|
-
from openapi_pydantic.v3.v3_0 import (
|
|
18
|
-
Reference as Reference30,
|
|
19
|
-
)
|
|
20
|
-
from openapi_pydantic.v3.v3_0 import (
|
|
21
|
-
Response as Response30,
|
|
22
|
-
)
|
|
23
|
-
from openapi_pydantic.v3.v3_0 import (
|
|
24
|
-
Schema as Schema30,
|
|
25
|
-
)
|
|
26
|
-
from openapi_pydantic.v3.v3_0.parameter import Parameter as Parameter30
|
|
27
|
-
from openapi_pydantic.v3.v3_1 import (
|
|
28
|
-
MediaType as MediaType31,
|
|
29
|
-
)
|
|
30
|
-
from openapi_pydantic.v3.v3_1 import (
|
|
31
|
-
Reference as Reference31,
|
|
32
|
-
)
|
|
33
|
-
from openapi_pydantic.v3.v3_1 import (
|
|
34
|
-
Response as Response31,
|
|
35
|
-
)
|
|
36
|
-
from openapi_pydantic.v3.v3_1 import (
|
|
37
|
-
Schema as Schema31,
|
|
38
|
-
)
|
|
39
|
-
from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31
|
|
40
|
-
|
|
41
|
-
from ab_openapi_python_generator.language_converters.python import common
|
|
42
|
-
from ab_openapi_python_generator.language_converters.python.common import normalize_symbol
|
|
43
|
-
from ab_openapi_python_generator.language_converters.python.jinja_config import (
|
|
44
|
-
create_jinja_env,
|
|
45
|
-
)
|
|
46
|
-
from ab_openapi_python_generator.language_converters.python.model_generator import (
|
|
47
|
-
type_converter,
|
|
48
|
-
)
|
|
49
|
-
from ab_openapi_python_generator.models import (
|
|
50
|
-
LibraryConfig,
|
|
51
|
-
OpReturnType,
|
|
52
|
-
Service,
|
|
53
|
-
ServiceOperation,
|
|
54
|
-
TypeConversion,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Helper functions for isinstance checks across OpenAPI versions
|
|
59
|
-
def is_response_type(obj) -> bool:
|
|
60
|
-
"""Check if object is a Response from any OpenAPI version"""
|
|
61
|
-
return isinstance(obj, (Response30, Response31))
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def create_media_type_for_reference(
|
|
65
|
-
reference_obj: Union[Response30, Reference30, Response31, Reference31],
|
|
66
|
-
):
|
|
67
|
-
"""Create a MediaType wrapper for a reference object, using the correct version"""
|
|
68
|
-
# Check which version the reference object belongs to
|
|
69
|
-
if isinstance(reference_obj, Reference30):
|
|
70
|
-
return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics
|
|
71
|
-
elif isinstance(reference_obj, Reference31):
|
|
72
|
-
return MediaType31(schema=reference_obj) # type: ignore - pydantic issue with generics
|
|
73
|
-
else:
|
|
74
|
-
# Fallback to v3.0 for generic Reference
|
|
75
|
-
return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def is_media_type(obj) -> bool:
|
|
79
|
-
"""Check if object is a MediaType from any OpenAPI version"""
|
|
80
|
-
return isinstance(obj, (MediaType30, MediaType31))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def is_reference_type(obj: Any) -> bool:
|
|
84
|
-
"""Check if object is a Reference type across different versions."""
|
|
85
|
-
return isinstance(obj, (Reference, Reference30, Reference31))
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def is_schema_type(obj: Any) -> bool:
|
|
89
|
-
"""Check if object is a Schema from any OpenAPI version"""
|
|
90
|
-
return isinstance(obj, (Schema30, Schema31))
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def operation_is_sse(op: Operation) -> bool:
|
|
94
|
-
"""Detect if an Operation advertises Server-Sent-Events (text/event-stream) in any 2xx response."""
|
|
95
|
-
if not getattr(op, "responses", None):
|
|
96
|
-
return False
|
|
97
|
-
|
|
98
|
-
for status_code, resp in op.responses.items():
|
|
99
|
-
try:
|
|
100
|
-
if not str(status_code).startswith("2"):
|
|
101
|
-
continue
|
|
102
|
-
except Exception:
|
|
103
|
-
continue
|
|
104
|
-
|
|
105
|
-
# Concrete Response object
|
|
106
|
-
if is_response_type(resp):
|
|
107
|
-
content = getattr(resp, "content", None)
|
|
108
|
-
if isinstance(content, dict) and "text/event-stream" in content:
|
|
109
|
-
return True
|
|
110
|
-
|
|
111
|
-
# Reference responses could be resolved externally; skip for now
|
|
112
|
-
if is_reference_type(resp):
|
|
113
|
-
# If you need supporting $ref'ed SSE responses, resolve via components
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
HTTP_OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def generate_body_param(operation: Operation) -> Union[str, None]:
|
|
123
|
-
if operation.requestBody is None:
|
|
124
|
-
return None
|
|
125
|
-
else:
|
|
126
|
-
if isinstance(operation.requestBody, Reference30) or isinstance(
|
|
127
|
-
operation.requestBody, Reference31
|
|
128
|
-
):
|
|
129
|
-
return "data.dict()"
|
|
130
|
-
|
|
131
|
-
if operation.requestBody.content is None:
|
|
132
|
-
return None # pragma: no cover
|
|
133
|
-
|
|
134
|
-
if operation.requestBody.content.get("application/json") is None:
|
|
135
|
-
return None # pragma: no cover
|
|
136
|
-
|
|
137
|
-
media_type = operation.requestBody.content.get("application/json")
|
|
138
|
-
|
|
139
|
-
if media_type is None:
|
|
140
|
-
return None # pragma: no cover
|
|
141
|
-
|
|
142
|
-
if isinstance(
|
|
143
|
-
media_type.media_type_schema, (Reference, Reference30, Reference31)
|
|
144
|
-
):
|
|
145
|
-
return "data.dict()"
|
|
146
|
-
elif hasattr(media_type.media_type_schema, "ref"):
|
|
147
|
-
# Handle Reference objects from different OpenAPI versions
|
|
148
|
-
return "data.dict()"
|
|
149
|
-
elif isinstance(media_type.media_type_schema, (Schema, Schema30, Schema31)):
|
|
150
|
-
schema = media_type.media_type_schema
|
|
151
|
-
if schema.type == "array":
|
|
152
|
-
return "[i.dict() for i in data]"
|
|
153
|
-
elif schema.type == "object":
|
|
154
|
-
return "data"
|
|
155
|
-
else:
|
|
156
|
-
raise Exception(
|
|
157
|
-
f"Unsupported schema type for request body: {schema.type}"
|
|
158
|
-
) # pragma: no cover
|
|
159
|
-
else:
|
|
160
|
-
raise Exception(
|
|
161
|
-
f"Unsupported schema type for request body: {type(media_type.media_type_schema)}"
|
|
162
|
-
) # pragma: no cover
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def generate_params(operation: Operation) -> str:
|
|
166
|
-
def _generate_params_from_content(content: Any):
|
|
167
|
-
# Accept reference from either 3.0 or 3.1
|
|
168
|
-
if isinstance(content, (Reference, Reference30, Reference31)):
|
|
169
|
-
return f"data : {content.ref.split('/')[-1]}" # type: ignore
|
|
170
|
-
elif isinstance(content, (Schema, Schema30, Schema31)):
|
|
171
|
-
return f"data : {type_converter(content, True).converted_type}" # type: ignore
|
|
172
|
-
else: # pragma: no cover
|
|
173
|
-
raise Exception(f"Unsupported request body schema type: {type(content)}")
|
|
174
|
-
|
|
175
|
-
if operation.parameters is None and operation.requestBody is None:
|
|
176
|
-
return ""
|
|
177
|
-
|
|
178
|
-
params = ""
|
|
179
|
-
default_params = ""
|
|
180
|
-
if operation.parameters is not None:
|
|
181
|
-
for param in operation.parameters:
|
|
182
|
-
if not isinstance(param, (Parameter30, Parameter31)):
|
|
183
|
-
continue # pragma: no cover
|
|
184
|
-
converted_result = ""
|
|
185
|
-
required = False
|
|
186
|
-
param_name_cleaned = common.normalize_symbol(param.name)
|
|
187
|
-
|
|
188
|
-
if isinstance(param.param_schema, Schema30) or isinstance(
|
|
189
|
-
param.param_schema, Schema31
|
|
190
|
-
):
|
|
191
|
-
converted_result = (
|
|
192
|
-
f"{param_name_cleaned} : {type_converter(param.param_schema, param.required).converted_type}"
|
|
193
|
-
+ ("" if param.required else " = None")
|
|
194
|
-
)
|
|
195
|
-
required = param.required
|
|
196
|
-
elif isinstance(param.param_schema, Reference30) or isinstance(
|
|
197
|
-
param.param_schema, Reference31
|
|
198
|
-
):
|
|
199
|
-
converted_result = (
|
|
200
|
-
f"{param_name_cleaned} : {param.param_schema.ref.split('/')[-1] }"
|
|
201
|
-
+ (
|
|
202
|
-
""
|
|
203
|
-
if isinstance(param, Reference30)
|
|
204
|
-
or isinstance(param, Reference31)
|
|
205
|
-
or param.required
|
|
206
|
-
else " = None"
|
|
207
|
-
)
|
|
208
|
-
)
|
|
209
|
-
required = isinstance(param, Reference) or param.required
|
|
210
|
-
|
|
211
|
-
if required:
|
|
212
|
-
params += f"{converted_result}, "
|
|
213
|
-
else:
|
|
214
|
-
default_params += f"{converted_result}, "
|
|
215
|
-
|
|
216
|
-
operation_request_body_types = [
|
|
217
|
-
"application/json",
|
|
218
|
-
"text/plain",
|
|
219
|
-
"multipart/form-data",
|
|
220
|
-
"application/octet-stream",
|
|
221
|
-
]
|
|
222
|
-
|
|
223
|
-
if operation.requestBody is not None and not is_reference_type(
|
|
224
|
-
operation.requestBody
|
|
225
|
-
):
|
|
226
|
-
# Safe access only if it's a concrete RequestBody object
|
|
227
|
-
rb_content = getattr(operation.requestBody, "content", None)
|
|
228
|
-
if isinstance(rb_content, dict) and any(
|
|
229
|
-
rb_content.get(i) is not None for i in operation_request_body_types
|
|
230
|
-
):
|
|
231
|
-
get_keyword = [
|
|
232
|
-
i for i in operation_request_body_types if rb_content.get(i)
|
|
233
|
-
][0]
|
|
234
|
-
content = rb_content.get(get_keyword)
|
|
235
|
-
if content is not None and hasattr(content, "media_type_schema"):
|
|
236
|
-
mts = getattr(content, "media_type_schema", None)
|
|
237
|
-
if isinstance(
|
|
238
|
-
mts,
|
|
239
|
-
(Reference, Reference30, Reference31, Schema, Schema30, Schema31),
|
|
240
|
-
):
|
|
241
|
-
params += f"{_generate_params_from_content(mts)}, "
|
|
242
|
-
else: # pragma: no cover
|
|
243
|
-
raise Exception(
|
|
244
|
-
f"Unsupported media type schema for {str(operation)}: {type(mts)}"
|
|
245
|
-
)
|
|
246
|
-
# else: silently ignore unsupported body shapes (could extend later)
|
|
247
|
-
# Replace - with _ in params
|
|
248
|
-
params = params.replace("-", "_")
|
|
249
|
-
default_params = default_params.replace("-", "_")
|
|
250
|
-
|
|
251
|
-
return params + default_params
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def generate_operation_id(
|
|
255
|
-
operation: Operation, http_op: str, path_name: Optional[str] = None
|
|
256
|
-
) -> str:
|
|
257
|
-
if operation.operationId is not None:
|
|
258
|
-
return common.normalize_symbol(operation.operationId)
|
|
259
|
-
elif path_name is not None:
|
|
260
|
-
return common.normalize_symbol(f"{http_op}_{path_name}")
|
|
261
|
-
else:
|
|
262
|
-
raise Exception(
|
|
263
|
-
f"OperationId is not defined for {http_op} of path_name {path_name} --> {operation.summary}"
|
|
264
|
-
) # pragma: no cover
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def _generate_params(
|
|
268
|
-
operation: Operation, param_in: Literal["query", "header"] = "query"
|
|
269
|
-
):
|
|
270
|
-
if operation.parameters is None:
|
|
271
|
-
return []
|
|
272
|
-
|
|
273
|
-
params = []
|
|
274
|
-
for param in operation.parameters:
|
|
275
|
-
if isinstance(param, (Parameter30, Parameter31)) and param.param_in == param_in:
|
|
276
|
-
param_name_cleaned = common.normalize_symbol(param.name)
|
|
277
|
-
params.append(f"{param.name!r} : {param_name_cleaned}")
|
|
278
|
-
|
|
279
|
-
return params
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def generate_query_params(operation: Operation) -> List[str]:
|
|
283
|
-
return _generate_params(operation, "query")
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def generate_header_params(operation: Operation) -> List[str]:
|
|
287
|
-
return _generate_params(operation, "header")
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def generate_return_type(operation: Operation) -> OpReturnType:
|
|
291
|
-
if operation.responses is None:
|
|
292
|
-
return OpReturnType(type=None, status_code=200, complex_type=False)
|
|
293
|
-
|
|
294
|
-
good_responses: List[Tuple[int, Union[Response, Reference]]] = [
|
|
295
|
-
(int(status_code), response)
|
|
296
|
-
for status_code, response in operation.responses.items()
|
|
297
|
-
if status_code.startswith("2")
|
|
298
|
-
]
|
|
299
|
-
if len(good_responses) == 0:
|
|
300
|
-
return OpReturnType(type=None, status_code=200, complex_type=False)
|
|
301
|
-
|
|
302
|
-
chosen_response = good_responses[0][1]
|
|
303
|
-
media_type_schema = None
|
|
304
|
-
|
|
305
|
-
if is_response_type(chosen_response):
|
|
306
|
-
# It's a Response type, access content safely
|
|
307
|
-
if hasattr(chosen_response, "content") and chosen_response.content is not None: # type: ignore
|
|
308
|
-
content = chosen_response.content # type: ignore
|
|
309
|
-
# Prefer application/json, then text/event-stream, then first available
|
|
310
|
-
if isinstance(content, dict):
|
|
311
|
-
media_type_schema = (
|
|
312
|
-
content.get("application/json")
|
|
313
|
-
or content.get("text/event-stream")
|
|
314
|
-
or next(iter(content.values()), None)
|
|
315
|
-
)
|
|
316
|
-
else:
|
|
317
|
-
media_type_schema = None
|
|
318
|
-
elif is_reference_type(chosen_response):
|
|
319
|
-
media_type_schema = create_media_type_for_reference(chosen_response)
|
|
320
|
-
|
|
321
|
-
if media_type_schema is None:
|
|
322
|
-
return OpReturnType(
|
|
323
|
-
type=None, status_code=good_responses[0][0], complex_type=False
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
if is_media_type(media_type_schema):
|
|
327
|
-
inner_schema = getattr(media_type_schema, "media_type_schema", None)
|
|
328
|
-
if is_reference_type(inner_schema):
|
|
329
|
-
type_conv = TypeConversion(
|
|
330
|
-
original_type=inner_schema.ref, # type: ignore
|
|
331
|
-
converted_type=inner_schema.ref.split("/")[-1], # type: ignore
|
|
332
|
-
import_types=[inner_schema.ref.split("/")[-1]], # type: ignore
|
|
333
|
-
)
|
|
334
|
-
return OpReturnType(
|
|
335
|
-
type=type_conv,
|
|
336
|
-
status_code=good_responses[0][0],
|
|
337
|
-
complex_type=True,
|
|
338
|
-
)
|
|
339
|
-
elif is_schema_type(inner_schema):
|
|
340
|
-
converted_result = type_converter(inner_schema, True) # type: ignore
|
|
341
|
-
if "array" in converted_result.original_type and isinstance(
|
|
342
|
-
converted_result.import_types, list
|
|
343
|
-
):
|
|
344
|
-
matched = re.findall(r"List\[(.+)\]", converted_result.converted_type)
|
|
345
|
-
if len(matched) > 0:
|
|
346
|
-
list_type = matched[0]
|
|
347
|
-
else: # pragma: no cover
|
|
348
|
-
raise Exception(
|
|
349
|
-
f"Unable to parse list type from {converted_result.converted_type}"
|
|
350
|
-
)
|
|
351
|
-
else:
|
|
352
|
-
list_type = None
|
|
353
|
-
return OpReturnType(
|
|
354
|
-
type=converted_result,
|
|
355
|
-
status_code=good_responses[0][0],
|
|
356
|
-
complex_type=bool(
|
|
357
|
-
converted_result.import_types
|
|
358
|
-
and len(converted_result.import_types) > 0
|
|
359
|
-
),
|
|
360
|
-
list_type=list_type,
|
|
361
|
-
)
|
|
362
|
-
else: # pragma: no cover
|
|
363
|
-
raise Exception("Unknown media type schema type")
|
|
364
|
-
elif media_type_schema is None:
|
|
365
|
-
return OpReturnType(
|
|
366
|
-
type=None,
|
|
367
|
-
status_code=good_responses[0][0],
|
|
368
|
-
complex_type=False,
|
|
369
|
-
)
|
|
370
|
-
else:
|
|
371
|
-
raise Exception("Unknown media type schema type") # pragma: no cover
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def generate_services(
|
|
375
|
-
paths: Dict[str, PathItem], library_config: LibraryConfig
|
|
376
|
-
) -> List[Service]:
|
|
377
|
-
"""
|
|
378
|
-
Generates services from a paths object.
|
|
379
|
-
:param paths: paths object to be converted
|
|
380
|
-
:return: List of services
|
|
381
|
-
"""
|
|
382
|
-
jinja_env = create_jinja_env()
|
|
383
|
-
|
|
384
|
-
def generate_service_operation(
|
|
385
|
-
op: Operation, path_name: str, async_type: bool
|
|
386
|
-
) -> ServiceOperation:
|
|
387
|
-
# Merge path-level parameters (always required by spec) into the
|
|
388
|
-
# operation-level parameters so they get turned into function args.
|
|
389
|
-
try:
|
|
390
|
-
path_level_params = []
|
|
391
|
-
if hasattr(path, "parameters") and path.parameters is not None: # type: ignore
|
|
392
|
-
path_level_params = [p for p in path.parameters if p is not None] # type: ignore
|
|
393
|
-
if path_level_params:
|
|
394
|
-
existing_names = set()
|
|
395
|
-
if op.parameters is not None:
|
|
396
|
-
for p in op.parameters: # type: ignore
|
|
397
|
-
if isinstance(p, (Parameter30, Parameter31)):
|
|
398
|
-
existing_names.add(p.name)
|
|
399
|
-
for p in path_level_params:
|
|
400
|
-
if (
|
|
401
|
-
isinstance(p, (Parameter30, Parameter31))
|
|
402
|
-
and p.name not in existing_names
|
|
403
|
-
):
|
|
404
|
-
if op.parameters is None:
|
|
405
|
-
op.parameters = [] # type: ignore
|
|
406
|
-
op.parameters.append(p) # type: ignore
|
|
407
|
-
except Exception: # pragma: no cover
|
|
408
|
-
print(
|
|
409
|
-
f"Error merging path-level parameters for {path_name}"
|
|
410
|
-
) # pragma: no cover
|
|
411
|
-
pass
|
|
412
|
-
|
|
413
|
-
params = generate_params(op)
|
|
414
|
-
# Fallback: ensure all {placeholders} in path are present as function params
|
|
415
|
-
try:
|
|
416
|
-
placeholder_names = [
|
|
417
|
-
m.group(1) for m in re.finditer(r"\{([^}/]+)\}", path_name)
|
|
418
|
-
]
|
|
419
|
-
existing_param_names = {
|
|
420
|
-
p.split(":")[0].strip() for p in params.split(",") if ":" in p
|
|
421
|
-
}
|
|
422
|
-
for ph in placeholder_names:
|
|
423
|
-
norm_ph = common.normalize_symbol(ph)
|
|
424
|
-
if norm_ph not in existing_param_names and norm_ph:
|
|
425
|
-
params = f"{norm_ph}: Any, " + params
|
|
426
|
-
except Exception: # pragma: no cover
|
|
427
|
-
print(
|
|
428
|
-
f"Error ensuring path placeholders in params for {path_name}"
|
|
429
|
-
) # pragma: no cover
|
|
430
|
-
pass
|
|
431
|
-
operation_id = generate_operation_id(op, http_operation, path_name)
|
|
432
|
-
query_params = generate_query_params(op)
|
|
433
|
-
header_params = generate_header_params(op)
|
|
434
|
-
return_type = generate_return_type(op)
|
|
435
|
-
body_param = generate_body_param(op)
|
|
436
|
-
|
|
437
|
-
so = ServiceOperation(
|
|
438
|
-
params=params,
|
|
439
|
-
operation_id=operation_id,
|
|
440
|
-
query_params=query_params,
|
|
441
|
-
header_params=header_params,
|
|
442
|
-
return_type=return_type,
|
|
443
|
-
operation=op,
|
|
444
|
-
pathItem=path,
|
|
445
|
-
content="",
|
|
446
|
-
async_client=async_type,
|
|
447
|
-
body_param=body_param,
|
|
448
|
-
path_name=path_name,
|
|
449
|
-
method=http_operation,
|
|
450
|
-
is_sse=operation_is_sse(op),
|
|
451
|
-
use_orjson=common.get_use_orjson(),
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
so.content = jinja_env.get_template(library_config.template_name).render(
|
|
455
|
-
**so.model_dump()
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
if op.tags is not None and len(op.tags) > 0:
|
|
459
|
-
so.tag = normalize_symbol(op.tags[0])
|
|
460
|
-
|
|
461
|
-
try:
|
|
462
|
-
compile(so.content, "<string>", "exec")
|
|
463
|
-
except SyntaxError as e: # pragma: no cover
|
|
464
|
-
click.echo(f"Error in service {so.operation_id}: {e}") # pragma: no cover
|
|
465
|
-
|
|
466
|
-
return so
|
|
467
|
-
|
|
468
|
-
services = []
|
|
469
|
-
service_ops = []
|
|
470
|
-
for path_name, path in paths.items():
|
|
471
|
-
clean_path_name = clean_up_path_name(path_name)
|
|
472
|
-
for http_operation in HTTP_OPERATIONS:
|
|
473
|
-
op = path.__getattribute__(http_operation)
|
|
474
|
-
if op is None:
|
|
475
|
-
continue
|
|
476
|
-
|
|
477
|
-
if library_config.include_sync:
|
|
478
|
-
sync_so = generate_service_operation(op, clean_path_name, False)
|
|
479
|
-
service_ops.append(sync_so)
|
|
480
|
-
|
|
481
|
-
if library_config.include_async:
|
|
482
|
-
async_so = generate_service_operation(op, clean_path_name, True)
|
|
483
|
-
service_ops.append(async_so)
|
|
484
|
-
|
|
485
|
-
# Ensure every operation has a tag; fallback to "default" for untagged operations
|
|
486
|
-
for so in service_ops:
|
|
487
|
-
if not so.tag:
|
|
488
|
-
so.tag = "default"
|
|
489
|
-
|
|
490
|
-
tags = list({so.tag for so in service_ops})
|
|
491
|
-
|
|
492
|
-
for tag in tags:
|
|
493
|
-
services.append(
|
|
494
|
-
Service(
|
|
495
|
-
file_name=f"{tag}_service",
|
|
496
|
-
operations=[
|
|
497
|
-
so for so in service_ops if so.tag == tag and not so.async_client
|
|
498
|
-
],
|
|
499
|
-
content="\n".join(
|
|
500
|
-
[
|
|
501
|
-
so.content
|
|
502
|
-
for so in service_ops
|
|
503
|
-
if so.tag == tag and not so.async_client
|
|
504
|
-
]
|
|
505
|
-
),
|
|
506
|
-
async_client=False,
|
|
507
|
-
library_import=library_config.library_name,
|
|
508
|
-
use_orjson=common.get_use_orjson(),
|
|
509
|
-
)
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
for tag in tags:
|
|
513
|
-
services.append(
|
|
514
|
-
Service(
|
|
515
|
-
file_name=f"async_{tag}_service",
|
|
516
|
-
operations=[
|
|
517
|
-
so for so in service_ops if so.tag == tag and so.async_client
|
|
518
|
-
],
|
|
519
|
-
content="\n".join(
|
|
520
|
-
[
|
|
521
|
-
so.content
|
|
522
|
-
for so in service_ops
|
|
523
|
-
if so.tag == tag and so.async_client
|
|
524
|
-
]
|
|
525
|
-
),
|
|
526
|
-
async_client=True,
|
|
527
|
-
library_import=library_config.library_name,
|
|
528
|
-
use_orjson=common.get_use_orjson(),
|
|
529
|
-
)
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
return services
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
def clean_up_path_name(path_name: str) -> str:
|
|
536
|
-
# Clean up path name: only replace dashes inside curly brackets for f-string compatibility, keep other dashes
|
|
537
|
-
def _replace_bracket_dashes(match):
|
|
538
|
-
return "{" + match.group(1).replace("-", "_") + "}"
|
|
539
|
-
|
|
540
|
-
return re.sub(r"\{([^}/]+)\}", _replace_bracket_dashes, path_name)
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
async def {{ operation_id }}(api_config_override : Optional[APIConfig] = None{% if params.strip() %}, *, {{ params.rstrip(', ') }}{% endif %}) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}:
|
|
2
|
-
api_config = api_config_override if api_config_override else APIConfig()
|
|
3
|
-
|
|
4
|
-
base_path = api_config.base_path
|
|
5
|
-
path = f'{{ path_name }}'
|
|
6
|
-
headers = {
|
|
7
|
-
'Content-Type': 'application/json',
|
|
8
|
-
'Accept': 'application/json',
|
|
9
|
-
'Authorization': f'Bearer { api_config.get_access_token() }',
|
|
10
|
-
{{ header_params | join(',\n') | safe }}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
query_params : Dict[str,Any] = {
|
|
14
|
-
{% if query_params|length > 0 %}
|
|
15
|
-
{{ query_params | join(',\n') | safe }}
|
|
16
|
-
{% endif %}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
query_params = {key:value for (key,value) in query_params.items() if value is not None}
|
|
20
|
-
|
|
21
|
-
async with aiohttp.ClientSession(headers=headers) as session:
|
|
22
|
-
async with session.request(
|
|
23
|
-
'{{ method }}',
|
|
24
|
-
base_path + path,
|
|
25
|
-
params=query_params,
|
|
26
|
-
{% if body_param %}
|
|
27
|
-
{% if use_orjson %}
|
|
28
|
-
data=orjson.dumps({{ body_param }})
|
|
29
|
-
{% else %}
|
|
30
|
-
json = {{ body_param }}
|
|
31
|
-
{% endif %}
|
|
32
|
-
{% endif %}
|
|
33
|
-
) as initial_response:
|
|
34
|
-
if initial_response.status != {{ return_type.status_code }}:
|
|
35
|
-
raise HTTPException(initial_response.status, f'{{ operation_id }} failed with status code: {initial_response.status}')
|
|
36
|
-
# Only parse JSON when a body is expected (avoid errors on 204 No Content)
|
|
37
|
-
body = None if {{ return_type.status_code }} == 204 else await initial_response.json()
|
|
38
|
-
|
|
39
|
-
{% if return_type.type is none or return_type.type.converted_type is none %}
|
|
40
|
-
return None
|
|
41
|
-
{% elif return_type.complex_type %}
|
|
42
|
-
{%- if return_type.list_type is none %}
|
|
43
|
-
return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}()
|
|
44
|
-
{%- else %}
|
|
45
|
-
return [{{ return_type.list_type }}(**item) for item in body]
|
|
46
|
-
{%- endif %}
|
|
47
|
-
{% else %}
|
|
48
|
-
return body
|
|
49
|
-
{% endif %}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{# Renders ONE standalone python module that defines a named Union/Annotated alias #}
|
|
2
|
-
{% if discriminator_key -%}
|
|
3
|
-
from typing import Annotated, Union
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
{% else -%}
|
|
6
|
-
from typing import Union
|
|
7
|
-
{% endif %}
|
|
8
|
-
|
|
9
|
-
{% for imp in member_imports -%}
|
|
10
|
-
{{ imp }}
|
|
11
|
-
{% endfor %}
|
|
12
|
-
|
|
13
|
-
{% if discriminator_key -%}
|
|
14
|
-
{{ alias_name }} = Annotated[{{ union_type }}, Field(discriminator="{{ discriminator_key }}")]
|
|
15
|
-
{% else -%}
|
|
16
|
-
{{ alias_name }} = {{ union_type }}
|
|
17
|
-
{% endif -%}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from typing import Optional, Union
|
|
5
|
-
|
|
6
|
-
class APIConfig(BaseModel):
|
|
7
|
-
base_path: str = {% if servers|length > 0 %} '{{ servers[0].url }}' {% else %} 'NO SERVER' {% endif %}
|
|
8
|
-
|
|
9
|
-
verify: Union[bool, str] = True
|
|
10
|
-
{% if env_token_name is none %}
|
|
11
|
-
access_token : Optional[str] = None
|
|
12
|
-
{% endif %}
|
|
13
|
-
|
|
14
|
-
def get_access_token(self) -> Optional[str]:
|
|
15
|
-
{% if env_token_name is not none %}
|
|
16
|
-
try:
|
|
17
|
-
return os.environ['{{ env_token_name }}']
|
|
18
|
-
except KeyError:
|
|
19
|
-
return None
|
|
20
|
-
{% else %}
|
|
21
|
-
return self.access_token
|
|
22
|
-
{% endif %}
|
|
23
|
-
|
|
24
|
-
def set_access_token(self, value : str):
|
|
25
|
-
{% if env_token_name is not none %}
|
|
26
|
-
raise Exception("This client was generated with an environment variable for the access token. Please set the environment variable '{{ env_token_name }}' to the access token.")
|
|
27
|
-
{% else %}
|
|
28
|
-
self.access_token = value
|
|
29
|
-
{% endif %}
|
|
30
|
-
|
|
31
|
-
class HTTPException(Exception):
|
|
32
|
-
def __init__(self, status_code: int, message: str):
|
|
33
|
-
self.status_code = status_code
|
|
34
|
-
self.message = message
|
|
35
|
-
super().__init__(f"{status_code} {message}")
|
|
36
|
-
|
|
37
|
-
def __str__(self):
|
|
38
|
-
return f"{self.status_code} {self.message}"
|
ab_openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{% if env_token_name is not none %}import os{% endif %}
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from typing import Optional, Union
|
|
5
|
-
|
|
6
|
-
class APIConfig(BaseModel):
|
|
7
|
-
model_config = {
|
|
8
|
-
"validate_assignment": True
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
base_path: str = {% if servers|length > 0 %} '{{ servers[0].url }}' {% else %} 'NO SERVER' {% endif %}
|
|
12
|
-
|
|
13
|
-
verify: Union[bool, str] = True
|
|
14
|
-
{% if env_token_name is none %}
|
|
15
|
-
access_token : Optional[str] = None
|
|
16
|
-
{% endif %}
|
|
17
|
-
|
|
18
|
-
def get_access_token(self) -> Optional[str]:
|
|
19
|
-
{% if env_token_name is not none %}
|
|
20
|
-
try:
|
|
21
|
-
return os.environ['{{ env_token_name }}']
|
|
22
|
-
except KeyError:
|
|
23
|
-
return None
|
|
24
|
-
{% else %}
|
|
25
|
-
return self.access_token
|
|
26
|
-
{% endif %}
|
|
27
|
-
|
|
28
|
-
def set_access_token(self, value : str):
|
|
29
|
-
{% if env_token_name is not none %}
|
|
30
|
-
raise Exception("This client was generated with an environment variable for the access token. Please set the environment variable '{{ env_token_name }}' to the access token.")
|
|
31
|
-
{% else %}
|
|
32
|
-
self.access_token = value
|
|
33
|
-
{% endif %}
|
|
34
|
-
|
|
35
|
-
class HTTPException(Exception):
|
|
36
|
-
def __init__(self, status_code: int, message: str):
|
|
37
|
-
self.status_code = status_code
|
|
38
|
-
self.message = message
|
|
39
|
-
super().__init__(f"{status_code} {message}")
|
|
40
|
-
|
|
41
|
-
def __str__(self):
|
|
42
|
-
return f"{self.status_code} {self.message}"
|