Skip to content

Commit 7a23ac7

Browse files
YousefHaggyyugi
andauthored
[Python] Bug Fix - model_generic templates to have valid imports for polymorphism (#20273)
* fix model_generic python templates to have valid imports for polymorphism * update samples * update samples --------- Co-authored-by: yugi <[email protected]>
1 parent 3d65786 commit 7a23ac7

File tree

35 files changed

+850
-70
lines changed

35 files changed

+850
-70
lines changed

modules/openapi-generator/src/main/resources/python-pydantic-v1/model_generic.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING
1717
from importlib import import_module
1818
if TYPE_CHECKING:
1919
{{#mappedModels}}
20-
from {{packageName}}.models.{{model.classVarName}} import {{modelName}}
20+
from {{packageName}}.models.{{model.classFilename}} import {{modelName}}
2121
{{/mappedModels}}
2222

2323
{{/discriminator}}
@@ -238,7 +238,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
238238
object_type = cls.get_discriminator_value(obj)
239239
{{#mappedModels}}
240240
if object_type == '{{{modelName}}}':
241-
return import_module("{{packageName}}.models.{{model.classVarName}}").{{modelName}}.from_dict(obj)
241+
return import_module("{{packageName}}.models.{{model.classFilename}}").{{modelName}}.from_dict(obj)
242242
{{/mappedModels}}
243243
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +
244244
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +

modules/openapi-generator/src/main/resources/python/model_generic.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ from typing_extensions import Self
1818
from typing import TYPE_CHECKING
1919
if TYPE_CHECKING:
2020
{{#mappedModels}}
21-
from {{packageName}}.models.{{model.classVarName}} import {{modelName}}
21+
from {{packageName}}.models.{{model.classFilename}} import {{modelName}}
2222
{{/mappedModels}}
2323

2424
{{/discriminator}}
@@ -261,7 +261,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
261261
object_type = cls.get_discriminator_value(obj)
262262
{{#mappedModels}}
263263
if object_type == '{{{modelName}}}':
264-
return import_module("{{packageName}}.models.{{model.classVarName}}").{{modelName}}.from_dict(obj)
264+
return import_module("{{packageName}}.models.{{model.classFilename}}").{{modelName}}.from_dict(obj)
265265
{{/mappedModels}}
266266

267267
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +

modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,13 @@ components:
19501950
properties:
19511951
_class:
19521952
type: string
1953+
Hunting__Dog:
1954+
allOf:
1955+
- $ref: '#/components/schemas/Creature'
1956+
- type: object
1957+
properties:
1958+
isTrained:
1959+
type: boolean
19531960
Dog:
19541961
allOf:
19551962
- $ref: '#/components/schemas/Animal'

samples/openapi3/client/petstore/python-aiohttp/.openapi-generator/FILES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ docs/FooGetDefaultResponse.md
5353
docs/FormatTest.md
5454
docs/HasOnlyReadOnly.md
5555
docs/HealthCheckResult.md
56+
docs/HuntingDog.md
5657
docs/ImportTestDatetimeApi.md
5758
docs/Info.md
5859
docs/InnerDictWithProperty.md
@@ -178,6 +179,7 @@ petstore_api/models/foo_get_default_response.py
178179
petstore_api/models/format_test.py
179180
petstore_api/models/has_only_read_only.py
180181
petstore_api/models/health_check_result.py
182+
petstore_api/models/hunting_dog.py
181183
petstore_api/models/info.py
182184
petstore_api/models/inner_dict_with_property.py
183185
petstore_api/models/input_all_of.py

samples/openapi3/client/petstore/python-aiohttp/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ Class | Method | HTTP request | Description
197197
- [FormatTest](docs/FormatTest.md)
198198
- [HasOnlyReadOnly](docs/HasOnlyReadOnly.md)
199199
- [HealthCheckResult](docs/HealthCheckResult.md)
200+
- [HuntingDog](docs/HuntingDog.md)
200201
- [Info](docs/Info.md)
201202
- [InnerDictWithProperty](docs/InnerDictWithProperty.md)
202203
- [InputAllOf](docs/InputAllOf.md)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# HuntingDog
2+
3+
4+
## Properties
5+
6+
Name | Type | Description | Notes
7+
------------ | ------------- | ------------- | -------------
8+
**is_trained** | **bool** | | [optional]
9+
10+
## Example
11+
12+
```python
13+
from petstore_api.models.hunting_dog import HuntingDog
14+
15+
# TODO update the JSON string below
16+
json = "{}"
17+
# create an instance of HuntingDog from a JSON string
18+
hunting_dog_instance = HuntingDog.from_json(json)
19+
# print the JSON string representation of the object
20+
print(HuntingDog.to_json())
21+
22+
# convert the object into a dict
23+
hunting_dog_dict = hunting_dog_instance.to_dict()
24+
# create an instance of HuntingDog from a dict
25+
hunting_dog_from_dict = HuntingDog.from_dict(hunting_dog_dict)
26+
```
27+
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
28+
29+

samples/openapi3/client/petstore/python-aiohttp/petstore_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
from petstore_api.models.format_test import FormatTest
8686
from petstore_api.models.has_only_read_only import HasOnlyReadOnly
8787
from petstore_api.models.health_check_result import HealthCheckResult
88+
from petstore_api.models.hunting_dog import HuntingDog
8889
from petstore_api.models.info import Info
8990
from petstore_api.models.inner_dict_with_property import InnerDictWithProperty
9091
from petstore_api.models.input_all_of import InputAllOf

samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from petstore_api.models.format_test import FormatTest
6161
from petstore_api.models.has_only_read_only import HasOnlyReadOnly
6262
from petstore_api.models.health_check_result import HealthCheckResult
63+
from petstore_api.models.hunting_dog import HuntingDog
6364
from petstore_api.models.info import Info
6465
from petstore_api.models.inner_dict_with_property import InnerDictWithProperty
6566
from petstore_api.models.input_all_of import InputAllOf

samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/creature.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
1717
import re # noqa: F401
1818
import json
1919

20+
from importlib import import_module
2021
from pydantic import BaseModel, ConfigDict, StrictStr
21-
from typing import Any, ClassVar, Dict, List
22+
from typing import Any, ClassVar, Dict, List, Union
2223
from petstore_api.models.creature_info import CreatureInfo
2324
from typing import Optional, Set
2425
from typing_extensions import Self
2526

27+
from typing import TYPE_CHECKING
28+
if TYPE_CHECKING:
29+
from petstore_api.models.hunting_dog import HuntingDog
30+
2631
class Creature(BaseModel):
2732
"""
2833
Creature
@@ -38,6 +43,23 @@ class Creature(BaseModel):
3843
)
3944

4045

46+
# JSON field name that stores the object type
47+
__discriminator_property_name: ClassVar[str] = 'type'
48+
49+
# discriminator mappings
50+
__discriminator_value_class_map: ClassVar[Dict[str, str]] = {
51+
'Hunting__Dog': 'HuntingDog'
52+
}
53+
54+
@classmethod
55+
def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
56+
"""Returns the discriminator value (object type) of the data"""
57+
discriminator_value = obj[cls.__discriminator_property_name]
58+
if discriminator_value:
59+
return cls.__discriminator_value_class_map.get(discriminator_value)
60+
else:
61+
return None
62+
4163
def to_str(self) -> str:
4264
"""Returns the string representation of the model using alias"""
4365
return pprint.pformat(self.model_dump(by_alias=True))
@@ -48,7 +70,7 @@ def to_json(self) -> str:
4870
return json.dumps(self.to_dict())
4971

5072
@classmethod
51-
def from_json(cls, json_str: str) -> Optional[Self]:
73+
def from_json(cls, json_str: str) -> Optional[Union[HuntingDog]]:
5274
"""Create an instance of Creature from a JSON string"""
5375
return cls.from_dict(json.loads(json_str))
5476

@@ -76,18 +98,15 @@ def to_dict(self) -> Dict[str, Any]:
7698
return _dict
7799

78100
@classmethod
79-
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
101+
def from_dict(cls, obj: Dict[str, Any]) -> Optional[Union[HuntingDog]]:
80102
"""Create an instance of Creature from a dict"""
81-
if obj is None:
82-
return None
83-
84-
if not isinstance(obj, dict):
85-
return cls.model_validate(obj)
86-
87-
_obj = cls.model_validate({
88-
"info": CreatureInfo.from_dict(obj["info"]) if obj.get("info") is not None else None,
89-
"type": obj.get("type")
90-
})
91-
return _obj
103+
# look up the object type based on discriminator mapping
104+
object_type = cls.get_discriminator_value(obj)
105+
if object_type == 'HuntingDog':
106+
return import_module("petstore_api.models.hunting_dog").HuntingDog.from_dict(obj)
107+
108+
raise ValueError("Creature failed to lookup discriminator value from " +
109+
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
110+
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
92111

93112

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# coding: utf-8
2+
3+
"""
4+
OpenAPI Petstore
5+
6+
This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\
7+
8+
The version of the OpenAPI document: 1.0.0
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import ConfigDict, Field, StrictBool
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from petstore_api.models.creature import Creature
23+
from petstore_api.models.creature_info import CreatureInfo
24+
from typing import Optional, Set
25+
from typing_extensions import Self
26+
27+
class HuntingDog(Creature):
28+
"""
29+
HuntingDog
30+
""" # noqa: E501
31+
is_trained: Optional[StrictBool] = Field(default=None, alias="isTrained")
32+
__properties: ClassVar[List[str]] = ["info", "type", "isTrained"]
33+
34+
model_config = ConfigDict(
35+
populate_by_name=True,
36+
validate_assignment=True,
37+
protected_namespaces=(),
38+
)
39+
40+
41+
def to_str(self) -> str:
42+
"""Returns the string representation of the model using alias"""
43+
return pprint.pformat(self.model_dump(by_alias=True))
44+
45+
def to_json(self) -> str:
46+
"""Returns the JSON representation of the model using alias"""
47+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
48+
return json.dumps(self.to_dict())
49+
50+
@classmethod
51+
def from_json(cls, json_str: str) -> Optional[Self]:
52+
"""Create an instance of HuntingDog from a JSON string"""
53+
return cls.from_dict(json.loads(json_str))
54+
55+
def to_dict(self) -> Dict[str, Any]:
56+
"""Return the dictionary representation of the model using alias.
57+
58+
This has the following differences from calling pydantic's
59+
`self.model_dump(by_alias=True)`:
60+
61+
* `None` is only added to the output dict for nullable fields that
62+
were set at model initialization. Other fields with value `None`
63+
are ignored.
64+
"""
65+
excluded_fields: Set[str] = set([
66+
])
67+
68+
_dict = self.model_dump(
69+
by_alias=True,
70+
exclude=excluded_fields,
71+
exclude_none=True,
72+
)
73+
# override the default output from pydantic by calling `to_dict()` of info
74+
if self.info:
75+
_dict['info'] = self.info.to_dict()
76+
return _dict
77+
78+
@classmethod
79+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
80+
"""Create an instance of HuntingDog from a dict"""
81+
if obj is None:
82+
return None
83+
84+
if not isinstance(obj, dict):
85+
return cls.model_validate(obj)
86+
87+
_obj = cls.model_validate({
88+
"info": CreatureInfo.from_dict(obj["info"]) if obj.get("info") is not None else None,
89+
"type": obj.get("type"),
90+
"isTrained": obj.get("isTrained")
91+
})
92+
return _obj
93+
94+

0 commit comments

Comments
 (0)