Skip to content

Commit 74da896

Browse files
committed
refactor: allOf support to happen in build_model_property
1 parent a7b6d47 commit 74da896

File tree

7 files changed

+34
-246
lines changed

7 files changed

+34
-246
lines changed

end_to_end_tests/golden-record-custom/custom_e2e/models/model_from_all_of.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,34 @@
1111
class ModelFromAllOf:
1212
""" """
1313

14-
another_sub_property: Union[Unset, str] = UNSET
1514
a_sub_property: Union[Unset, str] = UNSET
15+
another_sub_property: Union[Unset, str] = UNSET
1616
additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
1717

1818
def to_dict(self) -> Dict[str, Any]:
19-
another_sub_property = self.another_sub_property
2019
a_sub_property = self.a_sub_property
20+
another_sub_property = self.another_sub_property
2121

2222
field_dict: Dict[str, Any] = {}
2323
field_dict.update(self.additional_properties)
2424
field_dict.update({})
25-
if another_sub_property is not UNSET:
26-
field_dict["another_sub_property"] = another_sub_property
2725
if a_sub_property is not UNSET:
2826
field_dict["a_sub_property"] = a_sub_property
27+
if another_sub_property is not UNSET:
28+
field_dict["another_sub_property"] = another_sub_property
2929

3030
return field_dict
3131

3232
@classmethod
3333
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
3434
d = src_dict.copy()
35-
another_sub_property = d.pop("another_sub_property", UNSET)
36-
3735
a_sub_property = d.pop("a_sub_property", UNSET)
3836

37+
another_sub_property = d.pop("another_sub_property", UNSET)
38+
3939
model_from_all_of = cls(
40-
another_sub_property=another_sub_property,
4140
a_sub_property=a_sub_property,
41+
another_sub_property=another_sub_property,
4242
)
4343

4444
model_from_all_of.additional_properties = d

end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,34 @@
1111
class ModelFromAllOf:
1212
""" """
1313

14-
another_sub_property: Union[Unset, str] = UNSET
1514
a_sub_property: Union[Unset, str] = UNSET
15+
another_sub_property: Union[Unset, str] = UNSET
1616
additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
1717

1818
def to_dict(self) -> Dict[str, Any]:
19-
another_sub_property = self.another_sub_property
2019
a_sub_property = self.a_sub_property
20+
another_sub_property = self.another_sub_property
2121

2222
field_dict: Dict[str, Any] = {}
2323
field_dict.update(self.additional_properties)
2424
field_dict.update({})
25-
if another_sub_property is not UNSET:
26-
field_dict["another_sub_property"] = another_sub_property
2725
if a_sub_property is not UNSET:
2826
field_dict["a_sub_property"] = a_sub_property
27+
if another_sub_property is not UNSET:
28+
field_dict["another_sub_property"] = another_sub_property
2929

3030
return field_dict
3131

3232
@classmethod
3333
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
3434
d = src_dict.copy()
35-
another_sub_property = d.pop("another_sub_property", UNSET)
36-
3735
a_sub_property = d.pop("a_sub_property", UNSET)
3836

37+
another_sub_property = d.pop("another_sub_property", UNSET)
38+
3939
model_from_all_of = cls(
40-
another_sub_property=another_sub_property,
4140
a_sub_property=a_sub_property,
41+
another_sub_property=another_sub_property,
4242
)
4343

4444
model_from_all_of.additional_properties = d

openapi_python_client/parser/properties/__init__.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -250,21 +250,25 @@ def build_model_property(
250250
required_properties: List[Property] = []
251251
optional_properties: List[Property] = []
252252
relative_imports: Set[str] = set()
253-
references: List[oai.Reference] = []
254253

255254
class_name = data.title or name
256255
if parent_name:
257256
class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
258257
ref = Reference.from_ref(class_name)
259258

260259
all_props = data.properties or {}
261-
if not isinstance(data, oai.Reference) and data.allOf:
262-
for sub_prop in data.allOf:
263-
if isinstance(sub_prop, oai.Reference):
264-
references += [sub_prop]
265-
else:
266-
all_props.update(sub_prop.properties or {})
267-
required_set.update(sub_prop.required or [])
260+
for sub_prop in data.allOf or []:
261+
if isinstance(sub_prop, oai.Reference):
262+
source_name = Reference.from_ref(sub_prop.ref).class_name
263+
sub_model = schemas.models.get(source_name)
264+
if sub_model is None:
265+
return PropertyError(f"Reference {sub_prop.ref} not found"), schemas
266+
required_properties.extend(sub_model.required_properties)
267+
optional_properties.extend(sub_model.optional_properties)
268+
relative_imports.update(sub_model.relative_imports)
269+
else:
270+
all_props.update(sub_prop.properties or {})
271+
required_set.update(sub_prop.required or [])
268272

269273
for key, value in all_props.items():
270274
prop_required = key in required_set
@@ -302,7 +306,6 @@ def build_model_property(
302306

303307
prop = ModelProperty(
304308
reference=ref,
305-
references=references,
306309
required_properties=required_properties,
307310
optional_properties=optional_properties,
308311
relative_imports=relative_imports,
@@ -555,15 +558,5 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
555558
processing = True # We made some progress this round, do another after it's done
556559
to_process = next_round
557560

558-
resolve_errors: List[PropertyError] = []
559-
models = list(schemas.models.values())
560-
for model in models:
561-
schemas_or_err = model.resolve_references(components=components, schemas=schemas)
562-
if isinstance(schemas_or_err, PropertyError):
563-
resolve_errors.append(schemas_or_err)
564-
else:
565-
schemas = schemas_or_err
566-
567561
schemas.errors.extend(errors)
568-
schemas.errors.extend(resolve_errors)
569562
return schemas

openapi_python_client/parser/properties/model_property.py

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
from collections.abc import Iterable
2-
from typing import ClassVar, Dict, List, Set, Union
1+
from typing import ClassVar, List, Set, Union
32

43
import attr
54

6-
from ... import schema as oai
7-
from ..errors import PropertyError
85
from ..reference import Reference
96
from .property import Property
10-
from .schemas import Schemas
117

128

139
@attr.s(auto_attribs=True, frozen=True)
1410
class ModelProperty(Property):
1511
""" A property which refers to another Schema """
1612

1713
reference: Reference
18-
references: List[oai.Reference]
1914
required_properties: List[Property]
2015
optional_properties: List[Property]
2116
description: str
@@ -24,49 +19,6 @@ class ModelProperty(Property):
2419

2520
template: ClassVar[str] = "model_property.pyi"
2621

27-
def resolve_references(
28-
self, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas
29-
) -> Union[Schemas, PropertyError]:
30-
from ..properties import property_from_data
31-
32-
required_set = set()
33-
props = {}
34-
while self.references:
35-
reference = self.references.pop()
36-
source_name = Reference.from_ref(reference.ref).class_name
37-
referenced_prop = components[source_name]
38-
assert isinstance(referenced_prop, oai.Schema)
39-
for p, val in (referenced_prop.properties or {}).items():
40-
props[p] = (val, source_name)
41-
for sub_prop in referenced_prop.allOf or []:
42-
if isinstance(sub_prop, oai.Reference):
43-
self.references.append(sub_prop)
44-
else:
45-
for p, val in (sub_prop.properties or {}).items():
46-
props[p] = (val, source_name)
47-
if isinstance(referenced_prop.required, Iterable):
48-
for sub_prop_name in referenced_prop.required:
49-
required_set.add(sub_prop_name)
50-
51-
for key, (value, source_name) in (props or {}).items():
52-
required = key in required_set
53-
prop, schemas = property_from_data(
54-
name=key, required=required, data=value, schemas=schemas, parent_name=source_name
55-
)
56-
if isinstance(prop, PropertyError):
57-
return prop
58-
if required:
59-
self.required_properties.append(prop)
60-
# Remove the optional version
61-
new_optional_props = [op for op in self.optional_properties if op.name != prop.name]
62-
self.optional_properties.clear()
63-
self.optional_properties.extend(new_optional_props)
64-
elif not any(ep for ep in (self.optional_properties + self.required_properties) if ep.name == prop.name):
65-
self.optional_properties.append(prop)
66-
self.relative_imports.update(prop.get_imports(prefix=".."))
67-
68-
return schemas
69-
7022
def get_type_string(self, no_optional: bool = False) -> str:
7123
""" Get a string representation of type that should be used when declaring this property """
7224
type_string = self.reference.class_name

openapi_python_client/parser/properties/schemas.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55
import attr
66

77
from ..errors import ParseError
8-
9-
# Avoid circular import with forward reference
10-
from . import model_property
118
from .enum_property import EnumProperty
9+
from .model_property import ModelProperty
1210

1311

1412
@attr.s(auto_attribs=True, frozen=True)
1513
class Schemas:
1614
""" Structure for containing all defined, shareable, and resuabled schemas (attr classes and Enums) """
1715

1816
enums: Dict[str, EnumProperty] = attr.ib(factory=dict)
19-
models: Dict[str, "model_property.ModelProperty"] = attr.ib(factory=dict)
17+
models: Dict[str, ModelProperty] = attr.ib(factory=dict)
2018
errors: List[ParseError] = attr.ib(factory=list)

tests/test_parser/test_properties/test_init.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,6 @@ def test_property_from_data_ref_model(self):
586586
nullable=False,
587587
default=None,
588588
reference=Reference(class_name=class_name, module_name="my_model"),
589-
references=[],
590589
required_properties=[],
591590
optional_properties=[],
592591
description="",
@@ -603,7 +602,6 @@ def test_property_from_data_ref_model(self):
603602
nullable=False,
604603
default=None,
605604
reference=Reference(class_name=class_name, module_name="my_model"),
606-
references=[],
607605
required_properties=[],
608606
optional_properties=[],
609607
description="",
@@ -990,25 +988,19 @@ def test__string_based_property_unsupported_format(self, mocker):
990988
def test_build_schemas(mocker):
991989
build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property")
992990
in_data = {"1": mocker.MagicMock(enum=None), "2": mocker.MagicMock(enum=None), "3": mocker.MagicMock(enum=None)}
993-
994991
model_1 = mocker.MagicMock()
995992
schemas_1 = mocker.MagicMock()
996993
model_2 = mocker.MagicMock()
997994
schemas_2 = mocker.MagicMock(errors=[])
998-
schemas_2.models = {"1": model_1, "2": model_2}
999-
error_1 = PropertyError()
995+
error = PropertyError()
1000996
schemas_3 = mocker.MagicMock()
1001-
schemas_4 = mocker.MagicMock(errors=[])
1002-
model_1.resolve_references.return_value = schemas_4
1003-
error_2 = PropertyError()
1004-
model_2.resolve_references.return_value = error_2
1005997

1006998
# This loops through one for each, then again to retry the error
1007999
build_model_property.side_effect = [
10081000
(model_1, schemas_1),
10091001
(model_2, schemas_2),
1010-
(error_1, schemas_3),
1011-
(error_1, schemas_3),
1002+
(error, schemas_3),
1003+
(error, schemas_3),
10121004
]
10131005

10141006
from openapi_python_client.parser.properties import Schemas, build_schemas
@@ -1024,12 +1016,8 @@ def test_build_schemas(mocker):
10241016
]
10251017
)
10261018
# schemas_3 was the last to come back from build_model_property, but it should be ignored because it's an error
1027-
model_1.resolve_references.assert_called_once_with(components=in_data, schemas=schemas_2)
1028-
# schemas_4 came from resolving model_1
1029-
model_2.resolve_references.assert_called_once_with(components=in_data, schemas=schemas_4)
1030-
# resolving model_2 resulted in err, so no schemas_5
1031-
assert result == schemas_4
1032-
assert result.errors == [error_1, error_2]
1019+
assert result == schemas_2
1020+
assert result.errors == [error]
10331021

10341022

10351023
def test_build_parse_error_on_reference():
@@ -1103,7 +1091,6 @@ def test_build_model_property(additional_properties_schema, expected_additional_
11031091
nullable=False,
11041092
default=None,
11051093
reference=Reference(class_name="ParentMyModel", module_name="parent_my_model"),
1106-
references=[],
11071094
required_properties=[StringProperty(name="req", required=True, nullable=False, default=None)],
11081095
optional_properties=[DateTimeProperty(name="opt", required=False, nullable=False, default=None)],
11091096
description=data.description,

0 commit comments

Comments
 (0)