Skip to content

Commit ae4b667

Browse files
committed
feat: support class inheritance
1 parent dda53f5 commit ae4b667

File tree

4 files changed

+81
-25
lines changed

4 files changed

+81
-25
lines changed

src/openapi_python_generator/language_converters/python/model_generator.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from openapi_python_generator.models import Model
2121
from openapi_python_generator.models import Property
2222
from openapi_python_generator.models import TypeConversion
23+
from openapi_python_generator.models import ParentModel
2324

2425

2526
def type_converter( # noqa: C901
@@ -281,6 +282,32 @@ def _generate_property_from_reference(
281282
import_type=[import_model],
282283
)
283284

285+
def _generate_property(
286+
model_name: str,
287+
name: str,
288+
schema_or_reference: Schema | Reference,
289+
parent_schema: Optional[Schema] = None,
290+
) -> Property:
291+
if isinstance(schema_or_reference, Reference):
292+
return _generate_property_from_reference(
293+
model_name, name, schema_or_reference, parent_schema
294+
)
295+
296+
return _generate_property_from_schema(
297+
model_name, name, schema_or_reference, parent_schema
298+
)
299+
300+
def _collect_properties_from_schema(model_name: str, parent_schema: Schema):
301+
property_iterator = (
302+
parent_schema.properties.items()
303+
if parent_schema.properties is not None
304+
else {}
305+
)
306+
for name, schema_or_reference in property_iterator:
307+
conv_property = _generate_property(
308+
model_name, name, schema_or_reference, parent_schema
309+
)
310+
yield conv_property
284311

285312
def generate_models(components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2) -> List[Model]:
286313
"""
@@ -323,27 +350,39 @@ def generate_models(components: Components, pydantic_version: PydanticVersion =
323350

324351
continue # pragma: no cover
325352

353+
# Enumerate properties for this model
326354
properties = []
327-
property_iterator = (
328-
schema_or_reference.properties.items()
329-
if schema_or_reference.properties is not None
330-
else {}
331-
)
332-
for prop_name, property in property_iterator:
333-
if isinstance(property, Reference):
334-
conv_property = _generate_property_from_reference(
335-
name, prop_name, property, schema_or_reference
336-
)
337-
else:
338-
conv_property = _generate_property_from_schema(
339-
name, prop_name, property, schema_or_reference
340-
)
355+
for conv_property in _collect_properties_from_schema(name, schema_or_reference):
341356
properties.append(conv_property)
342357

358+
# Enumerate union types that compose this model (if any) from allOf, oneOf, anyOf
359+
parent_components = []
360+
components_iterator = (
361+
(schema_or_reference.allOf or []) + (schema_or_reference.oneOf or []) + (schema_or_reference.anyOf or [])
362+
)
363+
for parent_component in components_iterator:
364+
# For references, instead of importing properties, record inherited components
365+
if isinstance(parent_component, Reference):
366+
ref = parent_component.ref
367+
parent_name = common.normalize_symbol(ref.split("/")[-1])
368+
parent_components.append(ParentModel(
369+
ref = ref,
370+
name = parent_name,
371+
import_type = f"from .{parent_name} import {parent_name}"
372+
))
373+
374+
# Collect inline properties
375+
if isinstance(parent_component, Schema):
376+
for conv_property in _collect_properties_from_schema(name, parent_component):
377+
properties.append(conv_property)
378+
343379
template_name = MODELS_TEMPLATE_PYDANTIC_V2 if pydantic_version == PydanticVersion.V2 else MODELS_TEMPLATE
344380

345381
generated_content = jinja_env.get_template(template_name).render(
346-
schema_name=name, schema=schema_or_reference, properties=properties
382+
schema_name=name,
383+
schema=schema_or_reference,
384+
properties=properties,
385+
parent_components=parent_components
347386
)
348387

349388
try:
@@ -357,6 +396,7 @@ def generate_models(components: Components, pydantic_version: PydanticVersion =
357396
content=generated_content,
358397
openapi_object=schema_or_reference,
359398
properties=properties,
399+
parent_components=parent_components
360400
)
361401
)
362402

src/openapi_python_generator/language_converters/python/templates/models.jinja2

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ from pydantic import BaseModel, Field
77
{% endfor %}
88
{% endif %}
99
{% endfor %}
10+
{% for parent_component in parent_components %}
11+
{% if parent_component.import_type is not none %}
12+
{{ parent_component.import_type }}
13+
{% endif %}
14+
{% endfor %}
1015

11-
class {{ schema_name }}(BaseModel):
16+
class {{ schema_name }}({% for parent_component in parent_components %}{{ parent_component.name }},{% endfor %}BaseModel):
1217
"""
1318
{{ schema.title }} model
1419
{% if schema.description != None %}
1520
{{ schema.description }}
1621
{% endif %}
17-
1822
"""
1923
{% for property in properties %}
2024

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
from typing import *
2-
from pydantic import BaseModel, Field
2+
from pydantic import BaseModel, Field, ConfigDict
33
{% for property in properties %}
44
{% if property.type.import_types is not none %}
55
{% for import_type in property.type.import_types %}
66
{{ import_type }}
77
{% endfor %}
88
{% endif %}
99
{% endfor %}
10+
{% for parent_component in parent_components %}
11+
{% if parent_component.import_type is not none %}
12+
{{ parent_component.import_type }}
13+
{% endif %}
14+
{% endfor %}
1015

11-
class {{ schema_name }}(BaseModel):
16+
class {{ schema_name }}({% for parent_component in parent_components %}{{ parent_component.name }},{% endfor %}BaseModel):
1217
"""
1318
{{ schema.title }} model
1419
{% if schema.description != None %}
1520
{{ schema.description }}
1621
{% endif %}
17-
1822
"""
19-
model_config = {
20-
"populate_by_name": True,
21-
"validate_assignment": True
22-
}
23+
model_config = ConfigDict(
24+
populate_by_name= True,
25+
validate_assignment=True,
26+
from_attributes=True
27+
)
2328
{% for property in properties %}
2429

2530
{{ property.name | replace("@","") | replace("-","_") }} : {{ property.type.converted_type | safe }} = Field(validation_alias="{{ property.name }}" {% if not property.required %}, default = {{ property.default }} {% endif %})
26-
{% endfor %}
31+
{% endfor %}

src/openapi_python_generator/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ class Property(BaseModel):
5151
import_type: Optional[List[str]] = None
5252

5353

54+
class ParentModel(BaseModel):
55+
ref: str
56+
name: str
57+
import_type: Optional[str] = None
58+
59+
5460
class Model(BaseModel):
5561
file_name: str
5662
content: str
5763
openapi_object: Schema
5864
properties: List[Property] = []
65+
parent_components: List[ParentModel] = []
5966

6067

6168
class Service(BaseModel):

0 commit comments

Comments
 (0)