openapi-python-generator 1.1.2.dev1734089718__py3-none-any.whl → 1.2.1.dev1757310194__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. openapi_python_generator/__init__.py +2 -1
  2. openapi_python_generator/__main__.py +21 -6
  3. openapi_python_generator/common.py +15 -0
  4. openapi_python_generator/generate_data.py +112 -65
  5. openapi_python_generator/language_converters/python/api_config_generator.py +12 -6
  6. openapi_python_generator/language_converters/python/generator.py +8 -4
  7. openapi_python_generator/language_converters/python/model_generator.py +191 -54
  8. openapi_python_generator/language_converters/python/service_generator.py +159 -62
  9. openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 +10 -9
  10. openapi_python_generator/language_converters/python/templates/httpx.jinja2 +8 -5
  11. openapi_python_generator/language_converters/python/templates/models.jinja2 +5 -3
  12. openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 +5 -3
  13. openapi_python_generator/language_converters/python/templates/requests.jinja2 +8 -5
  14. openapi_python_generator/language_converters/python/templates/service.jinja2 +2 -1
  15. openapi_python_generator/models.py +16 -2
  16. openapi_python_generator/parsers/__init__.py +13 -0
  17. openapi_python_generator/parsers/openapi_30.py +65 -0
  18. openapi_python_generator/parsers/openapi_31.py +65 -0
  19. openapi_python_generator/py.typed +0 -0
  20. openapi_python_generator/version_detector.py +70 -0
  21. {openapi_python_generator-1.1.2.dev1734089718.dist-info → openapi_python_generator-1.2.1.dev1757310194.dist-info}/METADATA +3 -3
  22. openapi_python_generator-1.2.1.dev1757310194.dist-info/RECORD +32 -0
  23. openapi_python_generator-1.1.2.dev1734089718.dist-info/RECORD +0 -27
  24. {openapi_python_generator-1.1.2.dev1734089718.dist-info → openapi_python_generator-1.2.1.dev1757310194.dist-info}/LICENSE +0 -0
  25. {openapi_python_generator-1.1.2.dev1734089718.dist-info → openapi_python_generator-1.2.1.dev1757310194.dist-info}/WHEEL +0 -0
  26. {openapi_python_generator-1.1.2.dev1734089718.dist-info → openapi_python_generator-1.2.1.dev1757310194.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import re
2
+ from typing import Any
2
3
  from typing import Dict
3
4
  from typing import List
4
5
  from typing import Literal
@@ -7,7 +8,28 @@ from typing import Tuple
7
8
  from typing import Union
8
9
 
9
10
  import click
10
- from openapi_pydantic.v3.v3_0 import Reference, Schema, Operation, Parameter, RequestBody, Response, MediaType, PathItem
11
+ from openapi_pydantic.v3 import (
12
+ Reference,
13
+ Schema,
14
+ Operation,
15
+ Parameter,
16
+ Response,
17
+ PathItem,
18
+ )
19
+
20
+ # Import version-specific types for isinstance checks
21
+ from openapi_pydantic.v3.v3_0 import (
22
+ Reference as Reference30,
23
+ Schema as Schema30,
24
+ Response as Response30,
25
+ MediaType as MediaType30,
26
+ )
27
+ from openapi_pydantic.v3.v3_1 import (
28
+ Reference as Reference31,
29
+ Schema as Schema31,
30
+ Response as Response31,
31
+ MediaType as MediaType31,
32
+ )
11
33
 
12
34
  from openapi_python_generator.language_converters.python import common
13
35
  from openapi_python_generator.language_converters.python.common import normalize_symbol
@@ -24,6 +46,39 @@ from openapi_python_generator.models import ServiceOperation
24
46
  from openapi_python_generator.models import TypeConversion
25
47
 
26
48
 
49
+ # Helper functions for isinstance checks across OpenAPI versions
50
+ def is_response_type(obj) -> bool:
51
+ """Check if object is a Response from any OpenAPI version"""
52
+ return isinstance(obj, (Response30, Response31))
53
+
54
+
55
+ def create_media_type_for_reference(reference_obj):
56
+ """Create a MediaType wrapper for a reference object, using the correct version"""
57
+ # Check which version the reference object belongs to
58
+ if isinstance(reference_obj, Reference30):
59
+ return MediaType30(schema=reference_obj)
60
+ elif isinstance(reference_obj, Reference31):
61
+ return MediaType31(schema=reference_obj)
62
+ else:
63
+ # Fallback to v3.0 for generic Reference
64
+ return MediaType30(schema=reference_obj)
65
+
66
+
67
+ def is_media_type(obj) -> bool:
68
+ """Check if object is a MediaType from any OpenAPI version"""
69
+ return isinstance(obj, (MediaType30, MediaType31))
70
+
71
+
72
+ def is_reference_type(obj: Any) -> bool:
73
+ """Check if object is a Reference type across different versions."""
74
+ return isinstance(obj, (Reference, Reference30, Reference31))
75
+
76
+
77
+ def is_schema_type(obj: Any) -> bool:
78
+ """Check if object is a Schema from any OpenAPI version"""
79
+ return isinstance(obj, (Schema30, Schema31))
80
+
81
+
27
82
  HTTP_OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"]
28
83
 
29
84
 
@@ -45,9 +100,14 @@ def generate_body_param(operation: Operation) -> Union[str, None]:
45
100
  if media_type is None:
46
101
  return None # pragma: no cover
47
102
 
48
- if isinstance(media_type.media_type_schema, Reference):
103
+ if isinstance(
104
+ media_type.media_type_schema, (Reference, Reference30, Reference31)
105
+ ):
106
+ return "data.dict()"
107
+ elif hasattr(media_type.media_type_schema, "ref"):
108
+ # Handle Reference objects from different OpenAPI versions
49
109
  return "data.dict()"
50
- elif isinstance(media_type.media_type_schema, Schema):
110
+ elif isinstance(media_type.media_type_schema, (Schema, Schema30, Schema31)):
51
111
  schema = media_type.media_type_schema
52
112
  if schema.type == "array":
53
113
  return "[i.dict() for i in data]"
@@ -64,11 +124,14 @@ def generate_body_param(operation: Operation) -> Union[str, None]:
64
124
 
65
125
 
66
126
  def generate_params(operation: Operation) -> str:
67
- def _generate_params_from_content(content: Union[Reference, Schema]):
68
- if isinstance(content, Reference):
69
- return f"data : {content.ref.split('/')[-1]}"
70
- else:
71
- return f"data : {type_converter(content, True).converted_type}"
127
+ def _generate_params_from_content(content: Any):
128
+ # Accept reference from either 3.0 or 3.1
129
+ if isinstance(content, (Reference, Reference30, Reference31)):
130
+ return f"data : {content.ref.split('/')[-1]}" # type: ignore
131
+ elif isinstance(content, (Schema, Schema30, Schema31)):
132
+ return f"data : {type_converter(content, True).converted_type}" # type: ignore
133
+ else: # pragma: no cover
134
+ raise Exception(f"Unsupported request body schema type: {type(content)}")
72
135
 
73
136
  if operation.parameters is None and operation.requestBody is None:
74
137
  return ""
@@ -109,40 +172,30 @@ def generate_params(operation: Operation) -> str:
109
172
  "application/json",
110
173
  "text/plain",
111
174
  "multipart/form-data",
175
+ "application/octet-stream",
112
176
  ]
113
177
 
114
- if operation.requestBody is not None:
115
- if (
116
- isinstance(operation.requestBody, RequestBody)
117
- and isinstance(operation.requestBody.content, dict)
118
- and any(
119
- [
120
- operation.requestBody.content.get(i) is not None
121
- for i in operation_request_body_types
122
- ]
123
- )
178
+ if operation.requestBody is not None and not is_reference_type(
179
+ operation.requestBody
180
+ ):
181
+ # Safe access only if it's a concrete RequestBody object
182
+ rb_content = getattr(operation.requestBody, "content", None)
183
+ if isinstance(rb_content, dict) and any(
184
+ rb_content.get(i) is not None for i in operation_request_body_types
124
185
  ):
125
- get_keyword = [
126
- i
127
- for i in operation_request_body_types
128
- if operation.requestBody.content.get(i) is not None
129
- ][0]
130
- content = operation.requestBody.content.get(get_keyword)
131
- if content is not None and (
132
- isinstance(content.media_type_schema, Schema)
133
- or isinstance(content.media_type_schema, Reference)
134
- ):
135
- params += (
136
- f"{_generate_params_from_content(content.media_type_schema)}, "
137
- )
138
- else:
139
- raise Exception(
140
- f"Unsupported media type schema for {str(operation)}"
141
- ) # pragma: no cover
142
- else:
143
- raise Exception(
144
- f"Unsupported request body type: {type(operation.requestBody)}"
145
- )
186
+ get_keyword = [i for i in operation_request_body_types if rb_content.get(i)][
187
+ 0
188
+ ]
189
+ content = rb_content.get(get_keyword)
190
+ if content is not None and hasattr(content, "media_type_schema"):
191
+ mts = getattr(content, "media_type_schema", None)
192
+ if isinstance(mts, (Reference, Reference30, Reference31, Schema, Schema30, Schema31)):
193
+ params += f"{_generate_params_from_content(mts)}, "
194
+ else: # pragma: no cover
195
+ raise Exception(
196
+ f"Unsupported media type schema for {str(operation)}: {type(mts)}"
197
+ )
198
+ # else: silently ignore unsupported body shapes (could extend later)
146
199
  # Replace - with _ in params
147
200
  params = params.replace("-", "_")
148
201
  default_params = default_params.replace("-", "_")
@@ -199,53 +252,59 @@ def generate_return_type(operation: Operation) -> OpReturnType:
199
252
  return OpReturnType(type=None, status_code=200, complex_type=False)
200
253
 
201
254
  chosen_response = good_responses[0][1]
255
+ media_type_schema = None
202
256
 
203
- if isinstance(chosen_response, Response) and chosen_response.content is not None:
204
- media_type_schema = chosen_response.content.get("application/json")
205
- elif isinstance(chosen_response, Reference):
206
- media_type_schema = MediaType(
207
- media_type_schema=chosen_response
208
- ) # pragma: no cover
209
- else:
257
+ if is_response_type(chosen_response):
258
+ # It's a Response type, access content safely
259
+ if hasattr(chosen_response, "content") and getattr(chosen_response, "content") is not None: # type: ignore
260
+ media_type_schema = getattr(chosen_response, "content").get("application/json") # type: ignore
261
+ elif is_reference_type(chosen_response):
262
+ media_type_schema = create_media_type_for_reference(chosen_response)
263
+
264
+ if media_type_schema is None:
210
265
  return OpReturnType(
211
266
  type=None, status_code=good_responses[0][0], complex_type=False
212
267
  )
213
268
 
214
- if isinstance(media_type_schema, MediaType):
215
- if isinstance(media_type_schema.media_type_schema, Reference):
269
+ if is_media_type(media_type_schema):
270
+ inner_schema = getattr(media_type_schema, "media_type_schema", None)
271
+ if is_reference_type(inner_schema):
216
272
  type_conv = TypeConversion(
217
- original_type=media_type_schema.media_type_schema.ref,
218
- converted_type=media_type_schema.media_type_schema.ref.split("/")[-1],
219
- import_types=[media_type_schema.media_type_schema.ref.split("/")[-1]],
273
+ original_type=inner_schema.ref, # type: ignore
274
+ converted_type=inner_schema.ref.split("/")[-1], # type: ignore
275
+ import_types=[inner_schema.ref.split("/")[-1]], # type: ignore
220
276
  )
221
277
  return OpReturnType(
222
278
  type=type_conv,
223
279
  status_code=good_responses[0][0],
224
280
  complex_type=True,
225
281
  )
226
- elif isinstance(media_type_schema.media_type_schema, Schema):
227
- converted_result = type_converter(media_type_schema.media_type_schema, True)
228
- if "array" in converted_result.original_type and isinstance(
229
- converted_result.import_types, list
282
+ elif is_schema_type(inner_schema):
283
+ converted_result = type_converter(inner_schema, True) # type: ignore
284
+ if (
285
+ "array" in converted_result.original_type
286
+ and isinstance(converted_result.import_types, list)
230
287
  ):
231
288
  matched = re.findall(r"List\[(.+)\]", converted_result.converted_type)
232
289
  if len(matched) > 0:
233
290
  list_type = matched[0]
234
- else:
291
+ else: # pragma: no cover
235
292
  raise Exception(
236
293
  f"Unable to parse list type from {converted_result.converted_type}"
237
- ) # pragma: no cover
294
+ )
238
295
  else:
239
296
  list_type = None
240
297
  return OpReturnType(
241
298
  type=converted_result,
242
299
  status_code=good_responses[0][0],
243
- complex_type=converted_result.import_types is not None
244
- and len(converted_result.import_types) > 0,
300
+ complex_type=bool(
301
+ converted_result.import_types
302
+ and len(converted_result.import_types) > 0
303
+ ),
245
304
  list_type=list_type,
246
305
  )
247
- else:
248
- raise Exception("Unknown media type schema type") # pragma: no cover
306
+ else: # pragma: no cover
307
+ raise Exception("Unknown media type schema type")
249
308
  elif media_type_schema is None:
250
309
  return OpReturnType(
251
310
  type=None,
@@ -269,7 +328,40 @@ def generate_services(
269
328
  def generate_service_operation(
270
329
  op: Operation, path_name: str, async_type: bool
271
330
  ) -> ServiceOperation:
331
+ # Merge path-level parameters (always required by spec) into the
332
+ # operation-level parameters so they get turned into function args.
333
+ try:
334
+ path_level_params = []
335
+ if hasattr(path, "parameters") and getattr(path, "parameters") is not None: # type: ignore
336
+ path_level_params = [p for p in getattr(path, "parameters") if p is not None] # type: ignore
337
+ if path_level_params:
338
+ existing_names = set()
339
+ if op.parameters is not None:
340
+ for p in op.parameters: # type: ignore
341
+ if isinstance(p, Parameter):
342
+ existing_names.add(p.name)
343
+ for p in path_level_params:
344
+ if isinstance(p, Parameter) and p.name not in existing_names:
345
+ if op.parameters is None:
346
+ op.parameters = [] # type: ignore
347
+ op.parameters.append(p) # type: ignore
348
+ except Exception: # pragma: no cover
349
+ pass
350
+
272
351
  params = generate_params(op)
352
+ # Fallback: ensure all {placeholders} in path are present as function params
353
+ try:
354
+ placeholder_names = [m.group(1) for m in re.finditer(r"\{([^}/]+)\}", path_name)]
355
+ existing_param_names = {
356
+ p.split(":")[0].strip()
357
+ for p in params.split(",") if ":" in p
358
+ }
359
+ for ph in placeholder_names:
360
+ norm_ph = common.normalize_symbol(ph)
361
+ if norm_ph not in existing_param_names and norm_ph:
362
+ params = f"{norm_ph}: Any, " + params
363
+ except Exception: # pragma: no cover
364
+ pass
273
365
  operation_id = generate_operation_id(op, http_operation, path_name)
274
366
  query_params = generate_query_params(op)
275
367
  header_params = generate_header_params(op)
@@ -293,7 +385,7 @@ def generate_services(
293
385
  )
294
386
 
295
387
  so.content = jinja_env.get_template(library_config.template_name).render(
296
- **so.dict()
388
+ **so.model_dump()
297
389
  )
298
390
 
299
391
  if op.tags is not None and len(op.tags) > 0:
@@ -322,6 +414,11 @@ def generate_services(
322
414
  async_so = generate_service_operation(op, path_name, True)
323
415
  service_ops.append(async_so)
324
416
 
417
+ # Ensure every operation has a tag; fallback to "default" for untagged operations
418
+ for so in service_ops:
419
+ if not so.tag:
420
+ so.tag = "default"
421
+
325
422
  tags = set([so.tag for so in service_ops])
326
423
 
327
424
  for tag in tags:
@@ -1,4 +1,4 @@
1
- async def {{ operation_id }}({{ params }} api_config_override : Optional[APIConfig] = None) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}:
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
2
  api_config = api_config_override if api_config_override else APIConfig()
3
3
 
4
4
  base_path = api_config.base_path
@@ -30,19 +30,20 @@ async def {{ operation_id }}({{ params }} api_config_override : Optional[APIConf
30
30
  json = {{ body_param }}
31
31
  {% endif %}
32
32
  {% endif %}
33
- ) as inital_response:
34
- if inital_response.status != {{ return_type.status_code }}:
35
- raise HTTPException(inital_response.status, f'{{ operationId }} failed with status code: {inital_response.status}')
36
- response = await inital_response.json()
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()
37
38
 
38
39
  {% if return_type.type is none or return_type.type.converted_type is none %}
39
- return None
40
+ return None
40
41
  {% elif return_type.complex_type %}
41
42
  {%- if return_type.list_type is none %}
42
- return {{ return_type.type.converted_type }}(**response) if response is not None else {{ return_type.type.converted_type }}()
43
+ return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}()
43
44
  {%- else %}
44
- return [{{ return_type.list_type }}(**item) for item in response]
45
+ return [{{ return_type.list_type }}(**item) for item in body]
45
46
  {%- endif %}
46
47
  {% else %}
47
- return response
48
+ return body
48
49
  {% endif %}
@@ -1,4 +1,4 @@
1
- {% if async_client %}async {% endif %}def {{ operation_id }}({{ params }} api_config_override : Optional[APIConfig] = None) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}:
1
+ {% if async_client %}async {% endif %}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
2
  api_config = api_config_override if api_config_override else APIConfig()
3
3
 
4
4
  base_path = api_config.base_path
@@ -38,16 +38,19 @@ with httpx.Client(base_url=base_path, verify=api_config.verify) as client:
38
38
  )
39
39
 
40
40
  if response.status_code != {{ return_type.status_code }}:
41
- raise HTTPException(response.status_code, f'{{ operationId }} failed with status code: {response.status_code}')
41
+ raise HTTPException(response.status_code, f'{{ operation_id }} failed with status code: {response.status_code}')
42
+ else:
43
+ {# Conditional body parsing: avoid calling .json() for 204 #}
44
+ body = None if {{ return_type.status_code }} == 204 else response.json()
42
45
 
43
46
  {% if return_type.type is none or return_type.type.converted_type is none %}
44
47
  return None
45
48
  {% elif return_type.complex_type %}
46
49
  {%- if return_type.list_type is none %}
47
- return {{ return_type.type.converted_type }}(**response.json()) if response.json() is not None else {{ return_type.type.converted_type }}()
50
+ return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}()
48
51
  {%- else %}
49
- return [{{ return_type.list_type }}(**item) for item in response.json()]
52
+ return [{{ return_type.list_type }}(**item) for item in body]
50
53
  {%- endif %}
51
54
  {% else %}
52
- return response.json()
55
+ return body
53
56
  {% endif %}
@@ -10,11 +10,13 @@ from pydantic import BaseModel, Field
10
10
 
11
11
  class {{ schema_name }}(BaseModel):
12
12
  """
13
- {{ schema.title }} model
14
- {% if schema.description != None %}
13
+ {% if schema.title %}{{ schema.title }}{% else %}{{ schema_name }}{% endif %} model
14
+ {% if schema.description %}
15
15
  {{ schema.description }}
16
16
  {% endif %}
17
-
17
+ {% if parent_comment %}
18
+ {{ parent_comment }}
19
+ {% endif %}
18
20
  """
19
21
  {% for property in properties %}
20
22
 
@@ -10,11 +10,13 @@ from pydantic import BaseModel, Field
10
10
 
11
11
  class {{ schema_name }}(BaseModel):
12
12
  """
13
- {{ schema.title }} model
14
- {% if schema.description != None %}
13
+ {% if schema.title %}{{ schema.title }}{% else %}{{ schema_name }}{% endif %} model
14
+ {% if schema.description %}
15
15
  {{ schema.description }}
16
16
  {% endif %}
17
-
17
+ {% if parent_comment %}
18
+ {{ parent_comment }}
19
+ {% endif %}
18
20
  """
19
21
  model_config = {
20
22
  "populate_by_name": True,
@@ -1,4 +1,4 @@
1
- def {{ operation_id }}({{ params }} api_config_override : Optional[APIConfig] = None) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}:
1
+ 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
2
  api_config = api_config_override if api_config_override else APIConfig()
3
3
 
4
4
  base_path = api_config.base_path
@@ -32,16 +32,19 @@ def {{ operation_id }}({{ params }} api_config_override : Optional[APIConfig] =
32
32
  {% endif %}
33
33
  )
34
34
  if response.status_code != {{ return_type.status_code }}:
35
- raise HTTPException(response.status_code, f'{{ operationId }} failed with status code: {response.status_code}')
35
+ raise HTTPException(response.status_code, f'{{ operation_id }} failed with status code: {response.status_code}')
36
+ else:
37
+ {# Conditional body parsing: avoid calling .json() for 204 #}
38
+ body = None if {{ return_type.status_code }} == 204 else response.json()
36
39
 
37
40
  {% if return_type.type is none or return_type.type.converted_type is none %}
38
41
  return None
39
42
  {% elif return_type.complex_type %}
40
43
  {%- if return_type.list_type is none %}
41
- return {{ return_type.type.converted_type }}(**response.json()) if response.json() is not None else {{ return_type.type.converted_type }}()
44
+ return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}()
42
45
  {%- else %}
43
- return [{{ return_type.list_type }}(**item) for item in response.json()]
46
+ return [{{ return_type.list_type }}(**item) for item in body]
44
47
  {%- endif %}
45
48
  {% else %}
46
- return response.json()
49
+ return body
47
50
  {% endif %}
@@ -1,8 +1,9 @@
1
1
  from typing import *
2
2
  import {{ library_import }}
3
- import json
3
+
4
4
  {% if use_orjson %}
5
5
  import orjson
6
+ from uuid import UUID
6
7
  {% endif %}
7
8
 
8
9
  from ..models import *
@@ -1,9 +1,23 @@
1
- from typing import List
1
+ from typing import List, Union
2
2
  from typing import Optional
3
3
 
4
- from openapi_pydantic.v3.v3_0 import Operation, PathItem, Schema
4
+ from openapi_pydantic.v3.v3_0 import (
5
+ Operation as Operation30,
6
+ PathItem as PathItem30,
7
+ Schema as Schema30,
8
+ )
9
+ from openapi_pydantic.v3.v3_1 import (
10
+ Operation as Operation31,
11
+ PathItem as PathItem31,
12
+ Schema as Schema31,
13
+ )
5
14
  from pydantic import BaseModel
6
15
 
16
+ # Type unions for compatibility with both OpenAPI 3.0 and 3.1
17
+ Operation = Union[Operation30, Operation31]
18
+ PathItem = Union[PathItem30, PathItem31]
19
+ Schema = Union[Schema30, Schema31]
20
+
7
21
 
8
22
  class LibraryConfig(BaseModel):
9
23
  name: str
@@ -0,0 +1,13 @@
1
+ """
2
+ OpenAPI parsers for different specification versions.
3
+ """
4
+
5
+ from .openapi_30 import parse_openapi_3_0, generate_code_3_0
6
+ from .openapi_31 import parse_openapi_3_1, generate_code_3_1
7
+
8
+ __all__ = [
9
+ "parse_openapi_3_0",
10
+ "generate_code_3_0",
11
+ "parse_openapi_3_1",
12
+ "generate_code_3_1",
13
+ ]
@@ -0,0 +1,65 @@
1
+ """
2
+ OpenAPI 3.0 specific parsing and generation.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from openapi_pydantic.v3.v3_0 import OpenAPI
8
+
9
+ from openapi_python_generator.common import HTTPLibrary, PydanticVersion
10
+ from openapi_python_generator.language_converters.python.generator import (
11
+ generator as base_generator,
12
+ )
13
+ from openapi_python_generator.models import ConversionResult
14
+
15
+
16
+ def parse_openapi_3_0(spec_data: dict) -> OpenAPI:
17
+ """
18
+ Parse OpenAPI 3.0 specification data.
19
+
20
+ Args:
21
+ spec_data: Dictionary containing OpenAPI 3.0 specification
22
+
23
+ Returns:
24
+ OpenAPI: Parsed OpenAPI 3.0 specification object
25
+
26
+ Raises:
27
+ ValidationError: If the specification is invalid
28
+ """
29
+ return OpenAPI(**spec_data)
30
+
31
+
32
+ def generate_code_3_0(
33
+ data: OpenAPI,
34
+ library: HTTPLibrary = HTTPLibrary.httpx,
35
+ env_token_name: Optional[str] = None,
36
+ use_orjson: bool = False,
37
+ custom_template_path: Optional[str] = None,
38
+ pydantic_version: PydanticVersion = PydanticVersion.V2,
39
+ ) -> ConversionResult:
40
+ """
41
+ Generate Python code from OpenAPI 3.0 specification.
42
+
43
+ Args:
44
+ data: OpenAPI 3.0 specification object
45
+ library: HTTP library to use
46
+ env_token_name: Environment variable name for token
47
+ use_orjson: Whether to use orjson for serialization
48
+ custom_template_path: Custom template path
49
+ pydantic_version: Pydantic version to use
50
+
51
+ Returns:
52
+ ConversionResult: Generated code and metadata
53
+ """
54
+ from openapi_python_generator.common import library_config_dict
55
+
56
+ library_config = library_config_dict[library]
57
+
58
+ return base_generator(
59
+ data=data,
60
+ library_config=library_config,
61
+ env_token_name=env_token_name,
62
+ use_orjson=use_orjson,
63
+ custom_template_path=custom_template_path,
64
+ pydantic_version=pydantic_version,
65
+ )
@@ -0,0 +1,65 @@
1
+ """
2
+ OpenAPI 3.1 specific parsing and generation.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from openapi_pydantic.v3.v3_1 import OpenAPI
8
+
9
+ from openapi_python_generator.common import HTTPLibrary, PydanticVersion
10
+ from openapi_python_generator.language_converters.python.generator import (
11
+ generator as base_generator,
12
+ )
13
+ from openapi_python_generator.models import ConversionResult
14
+
15
+
16
+ def parse_openapi_3_1(spec_data: dict) -> OpenAPI:
17
+ """
18
+ Parse OpenAPI 3.1 specification data.
19
+
20
+ Args:
21
+ spec_data: Dictionary containing OpenAPI 3.1 specification
22
+
23
+ Returns:
24
+ OpenAPI: Parsed OpenAPI 3.1 specification object
25
+
26
+ Raises:
27
+ ValidationError: If the specification is invalid
28
+ """
29
+ return OpenAPI(**spec_data)
30
+
31
+
32
+ def generate_code_3_1(
33
+ data: OpenAPI,
34
+ library: HTTPLibrary = HTTPLibrary.httpx,
35
+ env_token_name: Optional[str] = None,
36
+ use_orjson: bool = False,
37
+ custom_template_path: Optional[str] = None,
38
+ pydantic_version: PydanticVersion = PydanticVersion.V2,
39
+ ) -> ConversionResult:
40
+ """
41
+ Generate Python code from OpenAPI 3.1 specification.
42
+
43
+ Args:
44
+ data: OpenAPI 3.1 specification object
45
+ library: HTTP library to use
46
+ env_token_name: Environment variable name for token
47
+ use_orjson: Whether to use orjson for serialization
48
+ custom_template_path: Custom template path
49
+ pydantic_version: Pydantic version to use
50
+
51
+ Returns:
52
+ ConversionResult: Generated code and metadata
53
+ """
54
+ from openapi_python_generator.common import library_config_dict
55
+
56
+ library_config = library_config_dict[library]
57
+
58
+ return base_generator(
59
+ data=data,
60
+ library_config=library_config,
61
+ env_token_name=env_token_name,
62
+ use_orjson=use_orjson,
63
+ custom_template_path=custom_template_path,
64
+ pydantic_version=pydantic_version,
65
+ )
File without changes