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,15 +1,30 @@
1
1
  import itertools
2
2
  import re
3
- from typing import List
3
+ from typing import List, Union
4
4
  from typing import Optional
5
5
 
6
6
  import click
7
- from openapi_pydantic.v3.v3_0 import Schema, Reference, Components
7
+ from openapi_pydantic.v3.v3_0 import (
8
+ Schema as Schema30,
9
+ Reference as Reference30,
10
+ Components as Components30,
11
+ )
12
+ from openapi_pydantic.v3.v3_1 import (
13
+ Schema as Schema31,
14
+ Reference as Reference31,
15
+ Components as Components31,
16
+ )
17
+
18
+ # Type aliases for compatibility
19
+ Schema = Union[Schema30, Schema31]
20
+ Reference = Union[Reference30, Reference31]
21
+ Components = Union[Components30, Components31]
8
22
 
9
23
  from openapi_python_generator.common import PydanticVersion
10
24
  from openapi_python_generator.language_converters.python import common
11
25
  from openapi_python_generator.language_converters.python.jinja_config import (
12
- ENUM_TEMPLATE, MODELS_TEMPLATE_PYDANTIC_V2,
26
+ ENUM_TEMPLATE,
27
+ MODELS_TEMPLATE_PYDANTIC_V2,
13
28
  )
14
29
  from openapi_python_generator.language_converters.python.jinja_config import (
15
30
  MODELS_TEMPLATE,
@@ -23,17 +38,35 @@ from openapi_python_generator.models import TypeConversion
23
38
 
24
39
 
25
40
  def type_converter( # noqa: C901
26
- schema: Schema,
27
- required: bool = False,
28
- model_name: Optional[str] = None,
41
+ schema: Union[Schema, Reference],
42
+ required: bool = False,
43
+ model_name: Optional[str] = None,
29
44
  ) -> TypeConversion:
30
45
  """
31
46
  Converts an OpenAPI type to a Python type.
32
- :param schema: Schema containing the type to be converted
47
+ :param schema: Schema or Reference containing the type to be converted
33
48
  :param model_name: Name of the original model on which the type is defined
34
49
  :param required: Flag indicating if the type is required by the class
35
50
  :return: The converted type
36
51
  """
52
+ # Handle Reference objects by converting them to type references
53
+ if isinstance(schema, Reference):
54
+ import_type = common.normalize_symbol(schema.ref.split("/")[-1])
55
+ if required:
56
+ converted_type = import_type
57
+ else:
58
+ converted_type = f"Optional[{import_type}]"
59
+
60
+ return TypeConversion(
61
+ original_type=schema.ref,
62
+ converted_type=converted_type,
63
+ import_types=(
64
+ [f"from .{import_type} import {import_type}"]
65
+ if import_type != model_name
66
+ else None
67
+ ),
68
+ )
69
+
37
70
  if required:
38
71
  pre_type = ""
39
72
  post_type = ""
@@ -41,7 +74,11 @@ def type_converter( # noqa: C901
41
74
  pre_type = "Optional["
42
75
  post_type = "]"
43
76
 
44
- original_type = schema.type.value if schema.type is not None else "object"
77
+ original_type = (
78
+ schema.type.value
79
+ if hasattr(schema.type, "value") and schema.type is not None
80
+ else str(schema.type) if schema.type is not None else "object"
81
+ )
45
82
  import_types: Optional[List[str]] = None
46
83
 
47
84
  if schema.allOf is not None:
@@ -70,19 +107,22 @@ def type_converter( # noqa: C901
70
107
  )
71
108
 
72
109
  original_type = (
73
- "tuple<" + ",".join([i.original_type for i in conversions]) + ">"
110
+ "tuple<" + ",".join([i.original_type for i in conversions]) + ">"
74
111
  )
75
112
  if len(conversions) == 1:
76
113
  converted_type = conversions[0].converted_type
77
114
  else:
78
115
  converted_type = (
79
- "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]"
116
+ "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]"
80
117
  )
81
118
 
82
119
  converted_type = pre_type + converted_type + post_type
120
+ # Collect first import from referenced sub-schemas only (skip empty lists)
83
121
  import_types = [
84
- i.import_types[0] for i in conversions if i.import_types is not None
85
- ]
122
+ i.import_types[0]
123
+ for i in conversions
124
+ if i.import_types is not None and len(i.import_types) > 0
125
+ ] or None
86
126
 
87
127
  elif schema.oneOf is not None or schema.anyOf is not None:
88
128
  used = schema.oneOf if schema.oneOf is not None else schema.anyOf
@@ -102,14 +142,14 @@ def type_converter( # noqa: C901
102
142
  )
103
143
  )
104
144
  original_type = (
105
- "union<" + ",".join([i.original_type for i in conversions]) + ">"
145
+ "union<" + ",".join([i.original_type for i in conversions]) + ">"
106
146
  )
107
147
 
108
148
  if len(conversions) == 1:
109
149
  converted_type = conversions[0].converted_type
110
150
  else:
111
151
  converted_type = (
112
- "Union[" + ",".join([i.converted_type for i in conversions]) + "]"
152
+ "Union[" + ",".join([i.converted_type for i in conversions]) + "]"
113
153
  )
114
154
 
115
155
  converted_type = pre_type + converted_type + post_type
@@ -120,14 +160,15 @@ def type_converter( # noqa: C901
120
160
  )
121
161
  # We only want to auto convert to datetime if orjson is used throghout the code, otherwise we can not
122
162
  # serialize it to JSON.
123
- elif schema.type == "string" and (
124
- schema.schema_format is None or not common.get_use_orjson()
163
+ elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and (
164
+ schema.schema_format is None or not common.get_use_orjson()
125
165
  ):
126
166
  converted_type = pre_type + "str" + post_type
127
167
  elif (
128
- schema.type == "string"
129
- and schema.schema_format.startswith("uuid")
130
- and common.get_use_orjson()
168
+ (schema.type == "string" or str(schema.type) == "DataType.STRING")
169
+ and schema.schema_format is not None
170
+ and schema.schema_format.startswith("uuid")
171
+ and common.get_use_orjson()
131
172
  ):
132
173
  if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric():
133
174
  uuid_type = schema.schema_format.upper()
@@ -136,41 +177,131 @@ def type_converter( # noqa: C901
136
177
  else:
137
178
  converted_type = pre_type + "UUID" + post_type
138
179
  import_types = ["from uuid import UUID"]
139
- elif schema.type == "string" and schema.schema_format == "date-time":
180
+ elif (
181
+ schema.type == "string" or str(schema.type) == "DataType.STRING"
182
+ ) and schema.schema_format == "date-time":
140
183
  converted_type = pre_type + "datetime" + post_type
141
184
  import_types = ["from datetime import datetime"]
142
- elif schema.type == "integer":
185
+ elif schema.type == "integer" or str(schema.type) == "DataType.INTEGER":
143
186
  converted_type = pre_type + "int" + post_type
144
- elif schema.type == "number":
187
+ elif schema.type == "number" or str(schema.type) == "DataType.NUMBER":
145
188
  converted_type = pre_type + "float" + post_type
146
- elif schema.type == "boolean":
189
+ elif schema.type == "boolean" or str(schema.type) == "DataType.BOOLEAN":
147
190
  converted_type = pre_type + "bool" + post_type
148
- elif schema.type == "array":
191
+ elif schema.type == "array" or str(schema.type) == "DataType.ARRAY":
149
192
  retVal = pre_type + "List["
150
193
  if isinstance(schema.items, Reference):
151
194
  converted_reference = _generate_property_from_reference(
152
- model_name, "", schema.items, schema, required
195
+ model_name or "", "", schema.items, schema, required
153
196
  )
154
197
  import_types = converted_reference.type.import_types
155
198
  original_type = "array<" + converted_reference.type.original_type + ">"
156
199
  retVal += converted_reference.type.converted_type
157
200
  elif isinstance(schema.items, Schema):
158
- original_type = "array<" + (
159
- str(schema.items.type.value) if schema.items.type is not None else "unknown") + ">"
201
+ type_str = schema.items.type
202
+ if hasattr(type_str, "value"):
203
+ type_value = str(type_str.value) if type_str is not None else "unknown"
204
+ else:
205
+ type_value = str(type_str) if type_str is not None else "unknown"
206
+ original_type = "array<" + type_value + ">"
160
207
  retVal += type_converter(schema.items, True).converted_type
161
208
  else:
162
209
  original_type = "array<unknown>"
163
210
  retVal += "Any"
164
211
 
165
212
  converted_type = retVal + "]" + post_type
166
- elif schema.type == "object":
213
+ elif schema.type == "object" or str(schema.type) == "DataType.OBJECT":
167
214
  converted_type = pre_type + "Dict[str, Any]" + post_type
168
- elif schema.type == "null":
215
+ elif schema.type == "null" or str(schema.type) == "DataType.NULL":
169
216
  converted_type = pre_type + "None" + post_type
170
217
  elif schema.type is None:
171
218
  converted_type = pre_type + "Any" + post_type
172
219
  else:
173
- raise TypeError(f"Unknown type: {schema.type}")
220
+ # Handle DataType enum types as strings
221
+ if hasattr(schema.type, "value"):
222
+ # Single DataType enum
223
+ if schema.type.value == "string":
224
+ # Check for UUID format first
225
+ if (
226
+ schema.schema_format is not None
227
+ and schema.schema_format.startswith("uuid")
228
+ and common.get_use_orjson()
229
+ ):
230
+ if (
231
+ len(schema.schema_format) > 4
232
+ and schema.schema_format[4].isnumeric()
233
+ ):
234
+ uuid_type = schema.schema_format.upper()
235
+ converted_type = pre_type + uuid_type + post_type
236
+ import_types = ["from pydantic import " + uuid_type]
237
+ else:
238
+ converted_type = pre_type + "UUID" + post_type
239
+ import_types = ["from uuid import UUID"]
240
+ # Check for date-time format
241
+ elif schema.schema_format == "date-time":
242
+ converted_type = pre_type + "datetime" + post_type
243
+ import_types = ["from datetime import datetime"]
244
+ else:
245
+ converted_type = pre_type + "str" + post_type
246
+ elif schema.type.value == "integer":
247
+ converted_type = pre_type + "int" + post_type
248
+ elif schema.type.value == "number":
249
+ converted_type = pre_type + "float" + post_type
250
+ elif schema.type.value == "boolean":
251
+ converted_type = pre_type + "bool" + post_type
252
+ elif schema.type.value == "array":
253
+ converted_type = pre_type + "List[Any]" + post_type
254
+ elif schema.type.value == "object":
255
+ converted_type = pre_type + "Dict[str, Any]" + post_type
256
+ elif schema.type.value == "null":
257
+ converted_type = pre_type + "None" + post_type
258
+ else:
259
+ converted_type = pre_type + "str" + post_type # Default fallback
260
+ elif isinstance(schema.type, list) and len(schema.type) > 0:
261
+ # List of DataType enums - use first one
262
+ first_type = schema.type[0]
263
+ if hasattr(first_type, "value"):
264
+ if first_type.value == "string":
265
+ # Check for UUID format first
266
+ if (
267
+ schema.schema_format is not None
268
+ and schema.schema_format.startswith("uuid")
269
+ and common.get_use_orjson()
270
+ ):
271
+ if (
272
+ len(schema.schema_format) > 4
273
+ and schema.schema_format[4].isnumeric()
274
+ ):
275
+ uuid_type = schema.schema_format.upper()
276
+ converted_type = pre_type + uuid_type + post_type
277
+ import_types = ["from pydantic import " + uuid_type]
278
+ else:
279
+ converted_type = pre_type + "UUID" + post_type
280
+ import_types = ["from uuid import UUID"]
281
+ # Check for date-time format
282
+ elif schema.schema_format == "date-time":
283
+ converted_type = pre_type + "datetime" + post_type
284
+ import_types = ["from datetime import datetime"]
285
+ else:
286
+ converted_type = pre_type + "str" + post_type
287
+ elif first_type.value == "integer":
288
+ converted_type = pre_type + "int" + post_type
289
+ elif first_type.value == "number":
290
+ converted_type = pre_type + "float" + post_type
291
+ elif first_type.value == "boolean":
292
+ converted_type = pre_type + "bool" + post_type
293
+ elif first_type.value == "array":
294
+ converted_type = pre_type + "List[Any]" + post_type
295
+ elif first_type.value == "object":
296
+ converted_type = pre_type + "Dict[str, Any]" + post_type
297
+ elif first_type.value == "null":
298
+ converted_type = pre_type + "None" + post_type
299
+ else:
300
+ converted_type = pre_type + "str" + post_type # Default fallback
301
+ else:
302
+ converted_type = pre_type + "str" + post_type # Default fallback
303
+ else:
304
+ converted_type = pre_type + "str" + post_type # Default fallback
174
305
 
175
306
  return TypeConversion(
176
307
  original_type=original_type,
@@ -180,7 +311,7 @@ def type_converter( # noqa: C901
180
311
 
181
312
 
182
313
  def _generate_property_from_schema(
183
- model_name: str, name: str, schema: Schema, parent_schema: Optional[Schema] = None
314
+ model_name: str, name: str, schema: Schema, parent_schema: Optional[Schema] = None
184
315
  ) -> Property:
185
316
  """
186
317
  Generates a property from a schema. It takes the type of the schema and converts it to a python type, and then
@@ -192,9 +323,9 @@ def _generate_property_from_schema(
192
323
  :return: Property
193
324
  """
194
325
  required = (
195
- parent_schema is not None
196
- and parent_schema.required is not None
197
- and name in parent_schema.required
326
+ parent_schema is not None
327
+ and parent_schema.required is not None
328
+ and name in parent_schema.required
198
329
  )
199
330
 
200
331
  import_type = None
@@ -211,11 +342,11 @@ def _generate_property_from_schema(
211
342
 
212
343
 
213
344
  def _generate_property_from_reference(
214
- model_name: str,
215
- name: str,
216
- reference: Reference,
217
- parent_schema: Optional[Schema] = None,
218
- force_required: bool = False,
345
+ model_name: str,
346
+ name: str,
347
+ reference: Reference,
348
+ parent_schema: Optional[Schema] = None,
349
+ force_required: bool = False,
219
350
  ) -> Property:
220
351
  """
221
352
  Generates a property from a reference. It takes the name of the reference as the type, and then
@@ -227,26 +358,26 @@ def _generate_property_from_reference(
227
358
  :return: Property and model to be imported by the file
228
359
  """
229
360
  required = (
230
- parent_schema is not None
231
- and parent_schema.required is not None
232
- and name in parent_schema.required
233
- ) or force_required
361
+ parent_schema is not None
362
+ and parent_schema.required is not None
363
+ and name in parent_schema.required
364
+ ) or force_required
234
365
  import_model = common.normalize_symbol(reference.ref.split("/")[-1])
235
366
 
236
367
  if import_model == model_name:
237
368
  type_conv = TypeConversion(
238
369
  original_type=reference.ref,
239
- converted_type=import_model
240
- if required
241
- else 'Optional["' + import_model + '"]',
370
+ converted_type=(
371
+ import_model if required else 'Optional["' + import_model + '"]'
372
+ ),
242
373
  import_types=None,
243
374
  )
244
375
  else:
245
376
  type_conv = TypeConversion(
246
377
  original_type=reference.ref,
247
- converted_type=import_model
248
- if required
249
- else "Optional[" + import_model + "]",
378
+ converted_type=(
379
+ import_model if required else "Optional[" + import_model + "]"
380
+ ),
250
381
  import_types=[f"from .{import_model} import {import_model}"],
251
382
  )
252
383
  return Property(
@@ -258,13 +389,15 @@ def _generate_property_from_reference(
258
389
  )
259
390
 
260
391
 
261
- def generate_models(components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2) -> List[Model]:
392
+ def generate_models(
393
+ components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2
394
+ ) -> List[Model]:
262
395
  """
263
- Receives components from an OpenAPI 3.0 specification and generates the models from it.
396
+ Receives components from an OpenAPI 3.0+ specification and generates the models from it.
264
397
  It does so, by iterating over the components.schemas dictionary. For each schema, it checks if
265
398
  it is a normal schema (i.e. simple type like string, integer, etc.), a reference to another schema, or
266
399
  an array of types/references. It then computes pydantic models from it using jinja2
267
- :param components: The components from an OpenAPI 3.0 specification.
400
+ :param components: The components from an OpenAPI 3.0+ specification.
268
401
  :param pydantic_version: The version of pydantic to use.
269
402
  :return: A list of models.
270
403
  """
@@ -277,7 +410,7 @@ def generate_models(components: Components, pydantic_version: PydanticVersion =
277
410
  for schema_name, schema_or_reference in components.schemas.items():
278
411
  name = common.normalize_symbol(schema_name)
279
412
  if schema_or_reference.enum is not None:
280
- value_dict = schema_or_reference.dict()
413
+ value_dict = schema_or_reference.model_dump()
281
414
  regex = re.compile(r"[\s\/=\*\+]+")
282
415
  value_dict["enum"] = [
283
416
  re.sub(regex, "_", i) if isinstance(i, str) else f"value_{i}"
@@ -316,7 +449,11 @@ def generate_models(components: Components, pydantic_version: PydanticVersion =
316
449
  )
317
450
  properties.append(conv_property)
318
451
 
319
- template_name = MODELS_TEMPLATE_PYDANTIC_V2 if pydantic_version == PydanticVersion.V2 else MODELS_TEMPLATE
452
+ template_name = (
453
+ MODELS_TEMPLATE_PYDANTIC_V2
454
+ if pydantic_version == PydanticVersion.V2
455
+ else MODELS_TEMPLATE
456
+ )
320
457
 
321
458
  generated_content = jinja_env.get_template(template_name).render(
322
459
  schema_name=name, schema=schema_or_reference, properties=properties