Skip to content

Commit 8b8fb3c

Browse files
fa0311kota65535
authored andcommitted
[python] Fix Circular imports on inherited discriminators. (OpenAPITools#17886)
* [python] add inheritance discriminators test OpenAPITools#16808 * [python] update samples * [python] fix assert in test * [python] fix inheritance discriminators circular import * [python] update samples * [python] undo type changes related to discriminator * [python] remove extraneous processing * [python-pydantic-v1] fix inheritance discriminators circular import * [python] remove type ignore comment * [python] update samples * [python] fix avoid the empty line break * [python] update samples
1 parent 48f3a45 commit 8b8fb3c

File tree

50 files changed

+1543
-72
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1543
-72
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -927,10 +927,7 @@ private ModelsMap postProcessModelsMap(ModelsMap objs) {
927927
// if super class
928928
if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) {
929929
moduleImports.add("typing", "Union");
930-
Set<CodegenDiscriminator.MappedModel> discriminator = model.getDiscriminator().getMappedModels();
931-
for (CodegenDiscriminator.MappedModel mappedModel : discriminator) {
932-
postponedModelImports.add(mappedModel.getModelName());
933-
}
930+
moduleImports.add("importlib", "import_module");
934931
}
935932
}
936933

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonPydanticV1Codegen.java

-4
Original file line numberDiff line numberDiff line change
@@ -879,10 +879,6 @@ private ModelsMap postProcessModelsMap(ModelsMap objs) {
879879
// if super class
880880
if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) {
881881
typingImports.add("Union");
882-
Set<CodegenDiscriminator.MappedModel> discriminator = model.getDiscriminator().getMappedModels();
883-
for (CodegenDiscriminator.MappedModel mappedModel : discriminator) {
884-
postponedModelImports.add(mappedModel.getModelName());
885-
}
886882
}
887883
}
888884

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

+19-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import json
1010
{{{.}}}
1111
{{/vendorExtensions.x-py-model-imports}}
1212

13+
{{#hasChildren}}
14+
{{#discriminator}}
15+
{{! If this model is a super class, importlib is used. So import the necessary modules for the type here. }}
16+
from typing import TYPE_CHECKING
17+
from importlib import import_module
18+
if TYPE_CHECKING:
19+
{{#mappedModels}}
20+
from {{packageName}}.models.{{model.classVarName}} import {{modelName}}
21+
{{/mappedModels}}
22+
23+
{{/discriminator}}
24+
{{/hasChildren}}
1325
class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):
1426
"""
1527
{{#description}}{{{description}}} # noqa: E501{{/description}}{{^description}}{{{classname}}}{{/description}}
@@ -222,13 +234,13 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
222234
{{#discriminator}}
223235
# look up the object type based on discriminator mapping
224236
object_type = cls.get_discriminator_value(obj)
225-
if object_type:
226-
klass = globals()[object_type]
227-
return klass.from_dict(obj)
228-
else:
229-
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +
230-
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
231-
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
237+
{{#mappedModels}}
238+
if object_type == '{{{mappingName}}}':
239+
return import_module("{{packageName}}.models.{{model.classVarName}}").{{modelName}}.from_dict(obj)
240+
{{/mappedModels}}
241+
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +
242+
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
243+
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
232244
{{/discriminator}}
233245
{{/hasChildren}}
234246
{{^hasChildren}}

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

+21-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ import json
1212
from typing import Optional, Set
1313
from typing_extensions import Self
1414

15+
{{#hasChildren}}
16+
{{#discriminator}}
17+
{{! If this model is a super class, importlib is used. So import the necessary modules for the type here. }}
18+
from typing import TYPE_CHECKING
19+
if TYPE_CHECKING:
20+
{{#mappedModels}}
21+
from {{packageName}}.models.{{model.classVarName}} import {{modelName}}
22+
{{/mappedModels}}
23+
24+
{{/discriminator}}
25+
{{/hasChildren}}
1526
class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):
1627
"""
1728
{{#description}}{{{description}}}{{/description}}{{^description}}{{{classname}}}{{/description}}
@@ -113,7 +124,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
113124
return json.dumps(self.to_dict())
114125

115126
@classmethod
116-
def from_json(cls, json_str: str) -> Optional[{{^hasChildren}}Self{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union[{{#children}}Self{{^-last}}, {{/-last}}{{/children}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}{{/hasChildren}}]:
127+
def from_json(cls, json_str: str) -> Optional[{{^hasChildren}}Self{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union[{{#mappedModels}}{{{modelName}}}{{^-last}}, {{/-last}}{{/mappedModels}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}{{/hasChildren}}]:
117128
"""Create an instance of {{{classname}}} from a JSON string"""
118129
return cls.from_dict(json.loads(json_str))
119130

@@ -236,18 +247,19 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
236247

237248
{{#hasChildren}}
238249
@classmethod
239-
def from_dict(cls, obj: Dict[str, Any]) -> Optional[{{#discriminator}}Union[{{#children}}Self{{^-last}}, {{/-last}}{{/children}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}]:
250+
def from_dict(cls, obj: Dict[str, Any]) -> Optional[{{#discriminator}}Union[{{#mappedModels}}{{{modelName}}}{{^-last}}, {{/-last}}{{/mappedModels}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}]:
240251
"""Create an instance of {{{classname}}} from a dict"""
241252
{{#discriminator}}
242253
# look up the object type based on discriminator mapping
243254
object_type = cls.get_discriminator_value(obj)
244-
if object_type:
245-
klass = globals()[object_type]
246-
return klass.from_dict(obj)
247-
else:
248-
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +
249-
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
250-
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
255+
{{#mappedModels}}
256+
if object_type == '{{{mappingName}}}':
257+
return import_module("{{packageName}}.models.{{model.classVarName}}").{{modelName}}.from_dict(obj)
258+
{{/mappedModels}}
259+
260+
raise ValueError("{{{classname}}} failed to lookup discriminator value from " +
261+
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
262+
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
251263
{{/discriminator}}
252264
{{/hasChildren}}
253265
{{^hasChildren}}

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

+14
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,20 @@ components:
16401640
required:
16411641
- id
16421642
- activity
1643+
DiscriminatorAllOfSuper:
1644+
type: object
1645+
required:
1646+
- elementType
1647+
discriminator:
1648+
propertyName: elementType
1649+
properties:
1650+
elementType:
1651+
type: string
1652+
DiscriminatorAllOfSub:
1653+
allOf:
1654+
- $ref: '#/components/schemas/DiscriminatorAllOfSuper'
1655+
- type: object
1656+
properties: {}
16431657
Pet:
16441658
type: object
16451659
required:

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

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ docs/CreatureInfo.md
3030
docs/DanishPig.md
3131
docs/DefaultApi.md
3232
docs/DeprecatedObject.md
33+
docs/DiscriminatorAllOfSub.md
34+
docs/DiscriminatorAllOfSuper.md
3335
docs/Dog.md
3436
docs/DummyModel.md
3537
docs/EnumArrays.md
@@ -140,6 +142,8 @@ petstore_api/models/creature.py
140142
petstore_api/models/creature_info.py
141143
petstore_api/models/danish_pig.py
142144
petstore_api/models/deprecated_object.py
145+
petstore_api/models/discriminator_all_of_sub.py
146+
petstore_api/models/discriminator_all_of_super.py
143147
petstore_api/models/dog.py
144148
petstore_api/models/dummy_model.py
145149
petstore_api/models/enum_arrays.py

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

+2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ Class | Method | HTTP request | Description
164164
- [CreatureInfo](docs/CreatureInfo.md)
165165
- [DanishPig](docs/DanishPig.md)
166166
- [DeprecatedObject](docs/DeprecatedObject.md)
167+
- [DiscriminatorAllOfSub](docs/DiscriminatorAllOfSub.md)
168+
- [DiscriminatorAllOfSuper](docs/DiscriminatorAllOfSuper.md)
167169
- [Dog](docs/Dog.md)
168170
- [DummyModel](docs/DummyModel.md)
169171
- [EnumArrays](docs/EnumArrays.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# DiscriminatorAllOfSub
2+
3+
4+
## Properties
5+
6+
Name | Type | Description | Notes
7+
------------ | ------------- | ------------- | -------------
8+
9+
## Example
10+
11+
```python
12+
from petstore_api.models.discriminator_all_of_sub import DiscriminatorAllOfSub
13+
14+
# TODO update the JSON string below
15+
json = "{}"
16+
# create an instance of DiscriminatorAllOfSub from a JSON string
17+
discriminator_all_of_sub_instance = DiscriminatorAllOfSub.from_json(json)
18+
# print the JSON string representation of the object
19+
print DiscriminatorAllOfSub.to_json()
20+
21+
# convert the object into a dict
22+
discriminator_all_of_sub_dict = discriminator_all_of_sub_instance.to_dict()
23+
# create an instance of DiscriminatorAllOfSub from a dict
24+
discriminator_all_of_sub_form_dict = discriminator_all_of_sub.from_dict(discriminator_all_of_sub_dict)
25+
```
26+
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
27+
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# DiscriminatorAllOfSuper
2+
3+
4+
## Properties
5+
6+
Name | Type | Description | Notes
7+
------------ | ------------- | ------------- | -------------
8+
**element_type** | **str** | |
9+
10+
## Example
11+
12+
```python
13+
from petstore_api.models.discriminator_all_of_super import DiscriminatorAllOfSuper
14+
15+
# TODO update the JSON string below
16+
json = "{}"
17+
# create an instance of DiscriminatorAllOfSuper from a JSON string
18+
discriminator_all_of_super_instance = DiscriminatorAllOfSuper.from_json(json)
19+
# print the JSON string representation of the object
20+
print DiscriminatorAllOfSuper.to_json()
21+
22+
# convert the object into a dict
23+
discriminator_all_of_super_dict = discriminator_all_of_super_instance.to_dict()
24+
# create an instance of DiscriminatorAllOfSuper from a dict
25+
discriminator_all_of_super_form_dict = discriminator_all_of_super.from_dict(discriminator_all_of_super_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

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
from petstore_api.models.creature_info import CreatureInfo
6464
from petstore_api.models.danish_pig import DanishPig
6565
from petstore_api.models.deprecated_object import DeprecatedObject
66+
from petstore_api.models.discriminator_all_of_sub import DiscriminatorAllOfSub
67+
from petstore_api.models.discriminator_all_of_super import DiscriminatorAllOfSuper
6668
from petstore_api.models.dog import Dog
6769
from petstore_api.models.dummy_model import DummyModel
6870
from petstore_api.models.enum_arrays import EnumArrays

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

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from petstore_api.models.creature_info import CreatureInfo
4040
from petstore_api.models.danish_pig import DanishPig
4141
from petstore_api.models.deprecated_object import DeprecatedObject
42+
from petstore_api.models.discriminator_all_of_sub import DiscriminatorAllOfSub
43+
from petstore_api.models.discriminator_all_of_super import DiscriminatorAllOfSuper
4244
from petstore_api.models.dog import Dog
4345
from petstore_api.models.dummy_model import DummyModel
4446
from petstore_api.models.enum_arrays import EnumArrays

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

+17-14
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@
1717
import re # noqa: F401
1818
import json
1919

20+
from importlib import import_module
2021
from pydantic import BaseModel, Field, StrictStr
2122
from typing import Any, ClassVar, Dict, List, Optional, Union
2223
from typing import Optional, Set
2324
from typing_extensions import Self
2425

26+
from typing import TYPE_CHECKING
27+
if TYPE_CHECKING:
28+
from petstore_api.models.cat import Cat
29+
from petstore_api.models.dog import Dog
30+
2531
class Animal(BaseModel):
2632
"""
2733
Animal
@@ -64,7 +70,7 @@ def to_json(self) -> str:
6470
return json.dumps(self.to_dict())
6571

6672
@classmethod
67-
def from_json(cls, json_str: str) -> Optional[Union[Self, Self]]:
73+
def from_json(cls, json_str: str) -> Optional[Union[Cat, Dog]]:
6874
"""Create an instance of Animal from a JSON string"""
6975
return cls.from_dict(json.loads(json_str))
7076

@@ -89,20 +95,17 @@ def to_dict(self) -> Dict[str, Any]:
8995
return _dict
9096

9197
@classmethod
92-
def from_dict(cls, obj: Dict[str, Any]) -> Optional[Union[Self, Self]]:
98+
def from_dict(cls, obj: Dict[str, Any]) -> Optional[Union[Cat, Dog]]:
9399
"""Create an instance of Animal from a dict"""
94100
# look up the object type based on discriminator mapping
95101
object_type = cls.get_discriminator_value(obj)
96-
if object_type:
97-
klass = globals()[object_type]
98-
return klass.from_dict(obj)
99-
else:
100-
raise ValueError("Animal failed to lookup discriminator value from " +
101-
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
102-
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
103-
104-
from petstore_api.models.cat import Cat
105-
from petstore_api.models.dog import Dog
106-
# TODO: Rewrite to not use raise_errors
107-
Animal.model_rebuild(raise_errors=False)
102+
if object_type == 'Cat':
103+
return import_module("petstore_api.models.cat").Cat.from_dict(obj)
104+
if object_type == 'Dog':
105+
return import_module("petstore_api.models.dog").Dog.from_dict(obj)
106+
107+
raise ValueError("Animal failed to lookup discriminator value from " +
108+
json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name +
109+
", mapping: " + json.dumps(cls.__discriminator_value_class_map))
110+
108111

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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 typing import Any, ClassVar, Dict, List
21+
from petstore_api.models.discriminator_all_of_super import DiscriminatorAllOfSuper
22+
from typing import Optional, Set
23+
from typing_extensions import Self
24+
25+
class DiscriminatorAllOfSub(DiscriminatorAllOfSuper):
26+
"""
27+
DiscriminatorAllOfSub
28+
""" # noqa: E501
29+
__properties: ClassVar[List[str]] = ["elementType"]
30+
31+
model_config = {
32+
"populate_by_name": True,
33+
"validate_assignment": True,
34+
"protected_namespaces": (),
35+
}
36+
37+
38+
def to_str(self) -> str:
39+
"""Returns the string representation of the model using alias"""
40+
return pprint.pformat(self.model_dump(by_alias=True))
41+
42+
def to_json(self) -> str:
43+
"""Returns the JSON representation of the model using alias"""
44+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
45+
return json.dumps(self.to_dict())
46+
47+
@classmethod
48+
def from_json(cls, json_str: str) -> Optional[Self]:
49+
"""Create an instance of DiscriminatorAllOfSub from a JSON string"""
50+
return cls.from_dict(json.loads(json_str))
51+
52+
def to_dict(self) -> Dict[str, Any]:
53+
"""Return the dictionary representation of the model using alias.
54+
55+
This has the following differences from calling pydantic's
56+
`self.model_dump(by_alias=True)`:
57+
58+
* `None` is only added to the output dict for nullable fields that
59+
were set at model initialization. Other fields with value `None`
60+
are ignored.
61+
"""
62+
excluded_fields: Set[str] = set([
63+
])
64+
65+
_dict = self.model_dump(
66+
by_alias=True,
67+
exclude=excluded_fields,
68+
exclude_none=True,
69+
)
70+
return _dict
71+
72+
@classmethod
73+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
74+
"""Create an instance of DiscriminatorAllOfSub from a dict"""
75+
if obj is None:
76+
return None
77+
78+
if not isinstance(obj, dict):
79+
return cls.model_validate(obj)
80+
81+
_obj = cls.model_validate({
82+
"elementType": obj.get("elementType")
83+
})
84+
return _obj
85+
86+

0 commit comments

Comments
 (0)