Skip to content

Commit bdcbc89

Browse files
committed
imptove py sdk anyOf / allOf
1 parent 96bd85e commit bdcbc89

31 files changed

Lines changed: 253 additions & 1636 deletions

File tree

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

Lines changed: 25 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from __future__ import annotations
2-
from inspect import getfullargspec
3-
import json
42
import pprint
53
import re # noqa: F401
64
{{#vendorExtensions.x-py-other-imports}}
@@ -11,167 +9,57 @@ import re # noqa: F401
119
{{/vendorExtensions.x-py-model-imports}}
1210
from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict
1311
from typing_extensions import Literal, Self
14-
from pydantic import Field
12+
from pydantic import Field, RootModel
1513

1614
{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS = [{{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}}]
1715

18-
class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):
16+
17+
class {{classname}}(RootModel[Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]):
1918
"""
2019
{{{description}}}{{^description}}{{{classname}}}{{/description}}
2120
"""
2221

23-
{{#composedSchemas.anyOf}}
24-
# data type: {{{dataType}}}
25-
{{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}}
26-
{{/composedSchemas.anyOf}}
27-
if TYPE_CHECKING:
28-
actual_instance: Optional[Union[{{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}]] = None
29-
else:
30-
actual_instance: Any = None
31-
any_of_schemas: Set[str] = { {{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}} }
32-
33-
model_config = {
34-
"validate_assignment": True,
35-
"protected_namespaces": (),
36-
}
37-
{{#discriminator}}
22+
root: Union[{{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}{{#isNullable}}, None{{/isNullable}}] = Field(
23+
{{#isNullable}}None{{/isNullable}}{{^isNullable}}...{{/isNullable}}{{#discriminator}}, discriminator="{{discriminatorName}}"{{/discriminator}}
24+
)
3825

39-
discriminator_value_class_map: Dict[str, str] = {
40-
{{#children}}
41-
'{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}},{{/-last}}
42-
{{/children}}
43-
}
44-
{{/discriminator}}
26+
def __getattr__(self, name):
27+
"""
28+
Delegate attribute access to the root model if the attribute
29+
doesn't exist on the main class.
30+
"""
4531

46-
def __init__(self, *args, **kwargs) -> None:
47-
if args:
48-
if len(args) > 1:
49-
raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`")
50-
if kwargs:
51-
raise ValueError("If a position argument is used, keyword arguments cannot be used.")
52-
super().__init__(actual_instance=args[0])
53-
else:
54-
super().__init__(**kwargs)
32+
if name in self.__dict__:
33+
return super().__getattribute__(name)
5534

56-
@field_validator('actual_instance')
57-
def actual_instance_must_validate_anyof(cls, v):
58-
{{#isNullable}}
59-
if v is None:
60-
return v
35+
root = self.__dict__.get('root')
36+
if root is not None:
37+
return getattr(root, name)
6138

62-
{{/isNullable}}
63-
instance = {{{classname}}}.model_construct()
64-
error_messages = []
65-
{{#composedSchemas.anyOf}}
66-
# validate data type: {{{dataType}}}
67-
{{#isContainer}}
68-
try:
69-
instance.{{vendorExtensions.x-py-name}} = v
70-
return v
71-
except (ValidationError, ValueError) as e:
72-
error_messages.append(str(e))
73-
{{/isContainer}}
74-
{{^isContainer}}
75-
{{#isPrimitiveType}}
76-
try:
77-
instance.{{vendorExtensions.x-py-name}} = v
78-
return v
79-
except (ValidationError, ValueError) as e:
80-
error_messages.append(str(e))
81-
{{/isPrimitiveType}}
82-
{{^isPrimitiveType}}
83-
if not isinstance(v, {{{dataType}}}):
84-
error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`")
85-
else:
86-
return v
87-
88-
{{/isPrimitiveType}}
89-
{{/isContainer}}
90-
{{/composedSchemas.anyOf}}
91-
if error_messages:
92-
# no match
93-
raise ValueError("No match found when setting the actual_instance in {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages))
94-
else:
95-
return v
39+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
9640

9741
@classmethod
9842
def from_dict(cls, obj: Dict[str, Any]) -> Self:
99-
return cls.from_json(json.dumps(obj))
43+
"""Returns the object represented by the Dict"""
44+
return cls.model_validate(obj, strict=True)
10045

10146
@classmethod
10247
def from_json(cls, json_str: str) -> Self:
10348
"""Returns the object represented by the json string"""
104-
instance = cls.model_construct()
105-
{{#isNullable}}
106-
if json_str is None:
107-
return instance
108-
109-
{{/isNullable}}
110-
error_messages = []
111-
{{#composedSchemas.anyOf}}
112-
{{#isContainer}}
113-
# deserialize data into {{{dataType}}}
114-
try:
115-
# validation
116-
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
117-
# assign value to actual_instance
118-
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
119-
return instance
120-
except (ValidationError, ValueError) as e:
121-
error_messages.append(str(e))
122-
{{/isContainer}}
123-
{{^isContainer}}
124-
{{#isPrimitiveType}}
125-
# deserialize data into {{{dataType}}}
126-
try:
127-
# validation
128-
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
129-
# assign value to actual_instance
130-
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
131-
return instance
132-
except (ValidationError, ValueError) as e:
133-
error_messages.append(str(e))
134-
{{/isPrimitiveType}}
135-
{{^isPrimitiveType}}
136-
# {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}}
137-
try:
138-
instance.actual_instance = {{{dataType}}}.from_json(json_str)
139-
return instance
140-
except (ValidationError, ValueError) as e:
141-
error_messages.append(str(e))
142-
{{/isPrimitiveType}}
143-
{{/isContainer}}
144-
{{/composedSchemas.anyOf}}
145-
146-
if error_messages:
147-
# no match
148-
raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages))
149-
else:
150-
return instance
49+
return cls.model_validate_json(json_str)
15150

15251
def to_json(self) -> str:
15352
"""Returns the JSON representation of the actual instance"""
154-
if self.actual_instance is None:
155-
return "null"
156-
157-
if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json):
158-
return self.actual_instance.to_json()
159-
else:
160-
return json.dumps(self.actual_instance)
53+
return self.model_dump_json(by_alias=True, exclude_none=True)
16154

162-
def to_dict(self) -> Optional[Union[Dict[str, Any], {{#anyOf}}{{.}}{{^-last}}, {{/-last}}{{/anyOf}}]]:
55+
def to_dict(self) -> Dict[str, Any]:
16356
"""Returns the dict representation of the actual instance"""
164-
if self.actual_instance is None:
165-
return None
166-
167-
if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict):
168-
return self.actual_instance.to_dict()
169-
else:
170-
return self.actual_instance
57+
return self.model_dump(by_alias=True)
17158

17259
def to_str(self) -> str:
17360
"""Returns the string representation of the actual instance"""
174-
return pprint.pformat(self.model_dump())
61+
return pprint.pformat(self.model_dump(by_alias=True, mode="json"))
62+
17563

17664
{{#vendorExtensions.x-py-postponed-model-imports.size}}
17765
{{#vendorExtensions.x-py-postponed-model-imports}}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ from {{modelPackage}}.{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}} im
1919
# TODO update the JSON string below
2020
json = "{}"
2121
# create an instance of {{classname}} from a JSON string
22-
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.from_json(json)
22+
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.model_validate_json(json)
2323
# print the JSON string representation of the object
24-
print({{classname}}.to_json())
24+
print({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.model_dump_json(by_alias=True, exclude_unset=True))
2525

2626
# convert the object into a dict
27-
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.to_dict()
27+
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.model_dump(by_alias=True)
2828
# create an instance of {{classname}} from a dict
29-
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_from_dict = {{classname}}.from_dict({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict)
29+
{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_from_dict = {{classname}}.model_validate({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict)
3030
```
3131
{{/isEnum}}
3232
{{#isEnum}}

0 commit comments

Comments
 (0)