From 745915566ec874639fa1445edc21d177a2947a72 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 10 May 2021 17:50:27 -0400 Subject: [PATCH 01/12] Allof enum and string or int --- .../my_test_api_client/models/__init__.py | 1 + .../models/all_of_sub_model.py | 7 ++++ .../models/another_all_of_sub_model.py | 13 ++++++++ .../models/another_all_of_sub_model_type.py | 8 +++++ .../models/model_from_all_of.py | 14 ++++++++ end_to_end_tests/openapi.json | 7 ++++ .../parser/properties/model_property.py | 32 +++++++++++++++---- 7 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 5bbd77d7d..e297b8f49 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -6,6 +6,7 @@ from .an_enum import AnEnum from .an_int_enum import AnIntEnum from .another_all_of_sub_model import AnotherAllOfSubModel +from .another_all_of_sub_model_type import AnotherAllOfSubModelType from .body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost from .different_enum import DifferentEnum from .free_form_model import FreeFormModel diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py index 8945c70ab..d08c10e7a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py @@ -12,16 +12,20 @@ class AllOfSubModel: """ """ a_sub_property: Union[Unset, str] = UNSET + type: Union[Unset, str] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: a_sub_property = self.a_sub_property + type = self.type field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if a_sub_property is not UNSET: field_dict["a_sub_property"] = a_sub_property + if type is not UNSET: + field_dict["type"] = type return field_dict @@ -30,8 +34,11 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() a_sub_property = d.pop("a_sub_property", UNSET) + type = d.pop("type", UNSET) + all_of_sub_model = cls( a_sub_property=a_sub_property, + type=type, ) all_of_sub_model.additional_properties = d diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py index 2ecc464a8..8bea3bebc 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py @@ -2,6 +2,7 @@ import attr +from ..models.another_all_of_sub_model_type import AnotherAllOfSubModelType from ..types import UNSET, Unset T = TypeVar("T", bound="AnotherAllOfSubModel") @@ -12,16 +13,22 @@ class AnotherAllOfSubModel: """ """ another_sub_property: Union[Unset, str] = UNSET + type: Union[Unset, AnotherAllOfSubModelType] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: another_sub_property = self.another_sub_property + type: Union[Unset, str] = UNSET + if not isinstance(self.type, Unset): + type = self.type.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if another_sub_property is not UNSET: field_dict["another_sub_property"] = another_sub_property + if type is not UNSET: + field_dict["type"] = type return field_dict @@ -30,8 +37,14 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() another_sub_property = d.pop("another_sub_property", UNSET) + type: Union[Unset, AnotherAllOfSubModelType] = UNSET + _type = d.pop("type", UNSET) + if not isinstance(_type, Unset): + type = AnotherAllOfSubModelType(_type) + another_all_of_sub_model = cls( another_sub_property=another_sub_property, + type=type, ) another_all_of_sub_model.additional_properties = d diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type.py b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type.py new file mode 100644 index 000000000..b2e82aa7c --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class AnotherAllOfSubModelType(str, Enum): + SUBMODEL = "submodel" + + def __str__(self) -> str: + return str(self.value) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py index 60406f46d..2ae83b25b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py @@ -2,6 +2,7 @@ import attr +from ..models.another_all_of_sub_model_type import AnotherAllOfSubModelType from ..types import UNSET, Unset T = TypeVar("T", bound="ModelFromAllOf") @@ -12,11 +13,16 @@ class ModelFromAllOf: """ """ a_sub_property: Union[Unset, str] = UNSET + type: Union[Unset, AnotherAllOfSubModelType] = UNSET another_sub_property: Union[Unset, str] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: a_sub_property = self.a_sub_property + type: Union[Unset, str] = UNSET + if not isinstance(self.type, Unset): + type = self.type.value + another_sub_property = self.another_sub_property field_dict: Dict[str, Any] = {} @@ -24,6 +30,8 @@ def to_dict(self) -> Dict[str, Any]: field_dict.update({}) if a_sub_property is not UNSET: field_dict["a_sub_property"] = a_sub_property + if type is not UNSET: + field_dict["type"] = type if another_sub_property is not UNSET: field_dict["another_sub_property"] = another_sub_property @@ -34,10 +42,16 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() a_sub_property = d.pop("a_sub_property", UNSET) + type: Union[Unset, AnotherAllOfSubModelType] = UNSET + _type = d.pop("type", UNSET) + if not isinstance(_type, Unset): + type = AnotherAllOfSubModelType(_type) + another_sub_property = d.pop("another_sub_property", UNSET) model_from_all_of = cls( a_sub_property=a_sub_property, + type=type, another_sub_property=another_sub_property, ) diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 585b97cde..a954a576c 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -1173,6 +1173,9 @@ "properties": { "a_sub_property": { "type": "string" + }, + "type": { + "type": "string" } } }, @@ -1182,6 +1185,10 @@ "properties": { "another_sub_property": { "type": "string" + }, + "type": { + "type": "string", + "enum": ["submodel"] } } }, diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index a40460886..f5aa5be42 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -7,6 +7,7 @@ from ... import schema as oai from ... import utils from ..errors import ParseError, PropertyError +from .enum_property import EnumProperty from .property import Property from .schemas import Class, Schemas, parse_reference_path @@ -49,15 +50,32 @@ def get_imports(self, *, prefix: str) -> Set[str]: def _merge_properties(first: Property, second: Property) -> Union[Property, PropertyError]: - if first.__class__ != second.__class__: - return PropertyError(header="Cannot merge properties", detail="Properties are two different types") nullable = first.nullable and second.nullable required = first.required or second.required - first = attr.evolve(first, nullable=nullable, required=required) - second = attr.evolve(second, nullable=nullable, required=required) - if first != second: - return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") - return first + + if first.__class__ == second.__class__: + first = attr.evolve(first, nullable=nullable, required=required) + second = attr.evolve(second, nullable=nullable, required=required) + if first != second: + return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") + return first + elif first.__class__.__name__ == "StringProperty" and second.__class__ == EnumProperty and second.value_type == str: + second = attr.evolve(second, nullable=nullable, required=required) + return second + elif second.__class__.__name__ == "StringProperty" and first.__class__ == EnumProperty and first.value_type == str: + first = attr.evolve(first, nullable=nullable, required=required) + return first + elif first.__class__.__name__ == "IntProperty" and second.__class__ == EnumProperty and second.value_type == int: + second = attr.evolve(second, nullable=nullable, required=required) + return second + elif second.__class__.__name__ == "IntProperty" and first.__class__ == EnumProperty and first.value_type == int: + first = attr.evolve(first, nullable=nullable, required=required) + return first + else: + return PropertyError( + header="Cannot merge properties", + detail=f"{first.__class__}, {second.__class__}Properties have incompatible types", + ) class _PropertyData(NamedTuple): From 39ace8ce69ace747888f44ac63cf91cfef01cd92 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 10 May 2021 17:57:28 -0400 Subject: [PATCH 02/12] Combine statements --- .../parser/properties/model_property.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index f5aa5be42..f27a49d1f 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -7,7 +7,6 @@ from ... import schema as oai from ... import utils from ..errors import ParseError, PropertyError -from .enum_property import EnumProperty from .property import Property from .schemas import Class, Schemas, parse_reference_path @@ -59,16 +58,24 @@ def _merge_properties(first: Property, second: Property) -> Union[Property, Prop if first != second: return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") return first - elif first.__class__.__name__ == "StringProperty" and second.__class__ == EnumProperty and second.value_type == str: + elif ( + first.__class__.__name__ == "StringProperty" + and second.__class__.__name__ == "EnumProperty" + and second.value_type == str + or first.__class__.__name__ == "IntProperty" + and second.__class__.__name__ == "EnumProperty" + and second.value_type == int + ): second = attr.evolve(second, nullable=nullable, required=required) return second - elif second.__class__.__name__ == "StringProperty" and first.__class__ == EnumProperty and first.value_type == str: - first = attr.evolve(first, nullable=nullable, required=required) - return first - elif first.__class__.__name__ == "IntProperty" and second.__class__ == EnumProperty and second.value_type == int: - second = attr.evolve(second, nullable=nullable, required=required) - return second - elif second.__class__.__name__ == "IntProperty" and first.__class__ == EnumProperty and first.value_type == int: + elif ( + second.__class__.__name__ == "StringProperty" + and first.__class__.__name__ == "EnumProperty" + and first.value_type == str + or second.__class__.__name__ == "IntProperty" + and first.__class__.__name__ == "EnumProperty" + and first.value_type == int + ): first = attr.evolve(first, nullable=nullable, required=required) return first else: From 49753faed9a9524fc3ace3124fe6c2d3f37d04e9 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 10 May 2021 18:00:20 -0400 Subject: [PATCH 03/12] Another style --- .../parser/properties/model_property.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index f27a49d1f..65f7bf859 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -48,6 +48,18 @@ def get_imports(self, *, prefix: str) -> Set[str]: return imports +def _is_subtype(first: Property, second: Property) -> bool: + return ( + first.__class__.__name__ == "EnumProperty" + and first.value_type == str + and second.__class__.__name__ == "StringProperty" + ) or ( + first.__class__.__name__ == "EnumProperty" + and first.value_type == int + and second.__class__.__name__ == "IntProperty" + ) + + def _merge_properties(first: Property, second: Property) -> Union[Property, PropertyError]: nullable = first.nullable and second.nullable required = first.required or second.required @@ -58,26 +70,12 @@ def _merge_properties(first: Property, second: Property) -> Union[Property, Prop if first != second: return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") return first - elif ( - first.__class__.__name__ == "StringProperty" - and second.__class__.__name__ == "EnumProperty" - and second.value_type == str - or first.__class__.__name__ == "IntProperty" - and second.__class__.__name__ == "EnumProperty" - and second.value_type == int - ): - second = attr.evolve(second, nullable=nullable, required=required) - return second - elif ( - second.__class__.__name__ == "StringProperty" - and first.__class__.__name__ == "EnumProperty" - and first.value_type == str - or second.__class__.__name__ == "IntProperty" - and first.__class__.__name__ == "EnumProperty" - and first.value_type == int - ): + elif _is_subtype(first, second): first = attr.evolve(first, nullable=nullable, required=required) return first + elif _is_subtype(second, first): + second = attr.evolve(second, nullable=nullable, required=required) + return second else: return PropertyError( header="Cannot merge properties", From 598478b17af12a7bd8a393a9616536e107b4da4c Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 10 May 2021 18:08:17 -0400 Subject: [PATCH 04/12] Support enums --- .../my_test_api_client/models/__init__.py | 2 + .../models/all_of_sub_model.py | 13 +++++++ .../models/all_of_sub_model_type_enum.py | 9 +++++ .../models/another_all_of_sub_model.py | 14 +++++++ .../another_all_of_sub_model_type_enum.py | 8 ++++ .../models/model_from_all_of.py | 14 +++++++ end_to_end_tests/openapi.json | 8 ++++ .../parser/properties/model_property.py | 39 ++++++++++++------- 8 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model_type_enum.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type_enum.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index e297b8f49..fb1d98a8e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -2,11 +2,13 @@ from .a_model import AModel from .all_of_sub_model import AllOfSubModel +from .all_of_sub_model_type_enum import AllOfSubModelTypeEnum from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum from .an_int_enum import AnIntEnum from .another_all_of_sub_model import AnotherAllOfSubModel from .another_all_of_sub_model_type import AnotherAllOfSubModelType +from .another_all_of_sub_model_type_enum import AnotherAllOfSubModelTypeEnum from .body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost from .different_enum import DifferentEnum from .free_form_model import FreeFormModel diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py index d08c10e7a..2095f0d40 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py @@ -2,6 +2,7 @@ import attr +from ..models.all_of_sub_model_type_enum import AllOfSubModelTypeEnum from ..types import UNSET, Unset T = TypeVar("T", bound="AllOfSubModel") @@ -13,11 +14,15 @@ class AllOfSubModel: a_sub_property: Union[Unset, str] = UNSET type: Union[Unset, str] = UNSET + type_enum: Union[Unset, AllOfSubModelTypeEnum] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: a_sub_property = self.a_sub_property type = self.type + type_enum: Union[Unset, int] = UNSET + if not isinstance(self.type_enum, Unset): + type_enum = self.type_enum.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -26,6 +31,8 @@ def to_dict(self) -> Dict[str, Any]: field_dict["a_sub_property"] = a_sub_property if type is not UNSET: field_dict["type"] = type + if type_enum is not UNSET: + field_dict["type_enum"] = type_enum return field_dict @@ -36,9 +43,15 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: type = d.pop("type", UNSET) + type_enum: Union[Unset, AllOfSubModelTypeEnum] = UNSET + _type_enum = d.pop("type_enum", UNSET) + if not isinstance(_type_enum, Unset): + type_enum = AllOfSubModelTypeEnum(_type_enum) + all_of_sub_model = cls( a_sub_property=a_sub_property, type=type, + type_enum=type_enum, ) all_of_sub_model.additional_properties = d diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model_type_enum.py b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model_type_enum.py new file mode 100644 index 000000000..817e0eb7c --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model_type_enum.py @@ -0,0 +1,9 @@ +from enum import IntEnum + + +class AllOfSubModelTypeEnum(IntEnum): + VALUE_0 = 0 + VALUE_1 = 1 + + def __str__(self) -> str: + return str(self.value) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py index 8bea3bebc..82476e2b7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py @@ -3,6 +3,7 @@ import attr from ..models.another_all_of_sub_model_type import AnotherAllOfSubModelType +from ..models.another_all_of_sub_model_type_enum import AnotherAllOfSubModelTypeEnum from ..types import UNSET, Unset T = TypeVar("T", bound="AnotherAllOfSubModel") @@ -14,6 +15,7 @@ class AnotherAllOfSubModel: another_sub_property: Union[Unset, str] = UNSET type: Union[Unset, AnotherAllOfSubModelType] = UNSET + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: @@ -22,6 +24,10 @@ def to_dict(self) -> Dict[str, Any]: if not isinstance(self.type, Unset): type = self.type.value + type_enum: Union[Unset, int] = UNSET + if not isinstance(self.type_enum, Unset): + type_enum = self.type_enum.value + field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) @@ -29,6 +35,8 @@ def to_dict(self) -> Dict[str, Any]: field_dict["another_sub_property"] = another_sub_property if type is not UNSET: field_dict["type"] = type + if type_enum is not UNSET: + field_dict["type_enum"] = type_enum return field_dict @@ -42,9 +50,15 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: if not isinstance(_type, Unset): type = AnotherAllOfSubModelType(_type) + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET + _type_enum = d.pop("type_enum", UNSET) + if not isinstance(_type_enum, Unset): + type_enum = AnotherAllOfSubModelTypeEnum(_type_enum) + another_all_of_sub_model = cls( another_sub_property=another_sub_property, type=type, + type_enum=type_enum, ) another_all_of_sub_model.additional_properties = d diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type_enum.py b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type_enum.py new file mode 100644 index 000000000..d54ed9dde --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model_type_enum.py @@ -0,0 +1,8 @@ +from enum import IntEnum + + +class AnotherAllOfSubModelTypeEnum(IntEnum): + VALUE_0 = 0 + + def __str__(self) -> str: + return str(self.value) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py index 2ae83b25b..0cca1941b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py @@ -3,6 +3,7 @@ import attr from ..models.another_all_of_sub_model_type import AnotherAllOfSubModelType +from ..models.another_all_of_sub_model_type_enum import AnotherAllOfSubModelTypeEnum from ..types import UNSET, Unset T = TypeVar("T", bound="ModelFromAllOf") @@ -14,6 +15,7 @@ class ModelFromAllOf: a_sub_property: Union[Unset, str] = UNSET type: Union[Unset, AnotherAllOfSubModelType] = UNSET + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET another_sub_property: Union[Unset, str] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) @@ -23,6 +25,10 @@ def to_dict(self) -> Dict[str, Any]: if not isinstance(self.type, Unset): type = self.type.value + type_enum: Union[Unset, int] = UNSET + if not isinstance(self.type_enum, Unset): + type_enum = self.type_enum.value + another_sub_property = self.another_sub_property field_dict: Dict[str, Any] = {} @@ -32,6 +38,8 @@ def to_dict(self) -> Dict[str, Any]: field_dict["a_sub_property"] = a_sub_property if type is not UNSET: field_dict["type"] = type + if type_enum is not UNSET: + field_dict["type_enum"] = type_enum if another_sub_property is not UNSET: field_dict["another_sub_property"] = another_sub_property @@ -47,11 +55,17 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: if not isinstance(_type, Unset): type = AnotherAllOfSubModelType(_type) + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET + _type_enum = d.pop("type_enum", UNSET) + if not isinstance(_type_enum, Unset): + type_enum = AnotherAllOfSubModelTypeEnum(_type_enum) + another_sub_property = d.pop("another_sub_property", UNSET) model_from_all_of = cls( a_sub_property=a_sub_property, type=type, + type_enum=type_enum, another_sub_property=another_sub_property, ) diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index a954a576c..3b098d3c6 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -1176,6 +1176,10 @@ }, "type": { "type": "string" + }, + "type_enum": { + "type": "int", + "enum": [0, 1] } } }, @@ -1189,6 +1193,10 @@ "type": { "type": "string", "enum": ["submodel"] + }, + "type_enum": { + "type": "int", + "enum": [0] } } }, diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 65f7bf859..910d2361a 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -48,15 +48,24 @@ def get_imports(self, *, prefix: str) -> Set[str]: return imports +def _is_string_enum(prop: Property) -> bool: + return prop.__class__.__name__ == "EnumProperty" and prop.value_type == str + + +def _is_int_enum(prop: Property) -> bool: + return prop.__class__.__name__ == "EnumProperty" and prop.value_type == int + + def _is_subtype(first: Property, second: Property) -> bool: - return ( - first.__class__.__name__ == "EnumProperty" - and first.value_type == str - and second.__class__.__name__ == "StringProperty" - ) or ( - first.__class__.__name__ == "EnumProperty" - and first.value_type == int - and second.__class__.__name__ == "IntProperty" + return any( + [ + _is_string_enum(first) and second.__class__.__name__ == "StringProperty", + _is_int_enum(first) and second.__class__.__name__ == "IntProperty", + _is_string_enum(first) + and _is_string_enum(second) + and set(first.values.items()) <= set(second.values.items()), + _is_int_enum(first) and _is_int_enum(second) and set(first.values.items()) <= set(second.values.items()), + ] ) @@ -64,18 +73,18 @@ def _merge_properties(first: Property, second: Property) -> Union[Property, Prop nullable = first.nullable and second.nullable required = first.required or second.required - if first.__class__ == second.__class__: - first = attr.evolve(first, nullable=nullable, required=required) - second = attr.evolve(second, nullable=nullable, required=required) - if first != second: - return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") - return first - elif _is_subtype(first, second): + if _is_subtype(first, second): first = attr.evolve(first, nullable=nullable, required=required) return first elif _is_subtype(second, first): second = attr.evolve(second, nullable=nullable, required=required) return second + elif first.__class__ == second.__class__: + first = attr.evolve(first, nullable=nullable, required=required) + second = attr.evolve(second, nullable=nullable, required=required) + if first != second: + return PropertyError(header="Cannot merge properties", detail="Properties has conflicting values") + return first else: return PropertyError( header="Cannot merge properties", From 7ed16e402c896de5deacccf089db07821610b555 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 10 May 2021 18:13:36 -0400 Subject: [PATCH 05/12] Style --- .../parser/properties/model_property.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 910d2361a..1c724698e 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -7,6 +7,7 @@ from ... import schema as oai from ... import utils from ..errors import ParseError, PropertyError +from .enum_property import EnumProperty from .property import Property from .schemas import Class, Schemas, parse_reference_path @@ -49,18 +50,20 @@ def get_imports(self, *, prefix: str) -> Set[str]: def _is_string_enum(prop: Property) -> bool: - return prop.__class__.__name__ == "EnumProperty" and prop.value_type == str + return isinstance(prop, EnumProperty) and prop.value_type == str def _is_int_enum(prop: Property) -> bool: - return prop.__class__.__name__ == "EnumProperty" and prop.value_type == int + return isinstance(prop, EnumProperty) and prop.value_type == int def _is_subtype(first: Property, second: Property) -> bool: + from . import IntProperty, StringProperty + return any( [ - _is_string_enum(first) and second.__class__.__name__ == "StringProperty", - _is_int_enum(first) and second.__class__.__name__ == "IntProperty", + _is_string_enum(first) and isinstance(second, StringProperty), + _is_int_enum(first) and isinstance(second, IntProperty), _is_string_enum(first) and _is_string_enum(second) and set(first.values.items()) <= set(second.values.items()), From 7561c01ab10540ba22cfd036ec763cebf7b05ba6 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 10:52:26 -0400 Subject: [PATCH 06/12] Fix bug --- openapi_python_client/parser/properties/model_property.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 59cab364b..f5522427a 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -67,8 +67,10 @@ def _is_subtype(first: Property, second: Property) -> bool: _is_int_enum(first) and isinstance(second, IntProperty), _is_string_enum(first) and _is_string_enum(second) - and set(first.values.items()) <= set(second.values.items()), - _is_int_enum(first) and _is_int_enum(second) and set(first.values.items()) <= set(second.values.items()), + and set(e.value for e in first) <= set(e.value for e in second), + _is_int_enum(first) + and _is_int_enum(second) + and set(e.value for e in first) <= set(e.value for e in second), ] ) From e90720fd38f73ae9fed89c3bb672a01a83e73092 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 10:54:23 -0400 Subject: [PATCH 07/12] Revert "Fix bug" This reverts commit 7561c01ab10540ba22cfd036ec763cebf7b05ba6. --- openapi_python_client/parser/properties/model_property.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index f5522427a..59cab364b 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -67,10 +67,8 @@ def _is_subtype(first: Property, second: Property) -> bool: _is_int_enum(first) and isinstance(second, IntProperty), _is_string_enum(first) and _is_string_enum(second) - and set(e.value for e in first) <= set(e.value for e in second), - _is_int_enum(first) - and _is_int_enum(second) - and set(e.value for e in first) <= set(e.value for e in second), + and set(first.values.items()) <= set(second.values.items()), + _is_int_enum(first) and _is_int_enum(second) and set(first.values.items()) <= set(second.values.items()), ] ) From 5b95eb4b9318da761d06ba4d6196ea3e1bde08b7 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 10:56:06 -0400 Subject: [PATCH 08/12] Fix bug --- openapi_python_client/parser/properties/model_property.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 59cab364b..9a9e6064c 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -58,6 +58,10 @@ def _is_int_enum(prop: Property) -> bool: return isinstance(prop, EnumProperty) and prop.value_type == int +def values_are_subset(first: EnumProperty, second: EnumProperty) -> bool: + return set(first.values.items()) <= set(second.values.items()) + + def _is_subtype(first: Property, second: Property) -> bool: from . import IntProperty, StringProperty @@ -67,8 +71,8 @@ def _is_subtype(first: Property, second: Property) -> bool: _is_int_enum(first) and isinstance(second, IntProperty), _is_string_enum(first) and _is_string_enum(second) - and set(first.values.items()) <= set(second.values.items()), - _is_int_enum(first) and _is_int_enum(second) and set(first.values.items()) <= set(second.values.items()), + and values_are_subset(first, second), + _is_int_enum(first) and _is_int_enum(second) and values_are_subset(first, second), ] ) From f50fd2bc1e2f6fe1f353e23d93ac227db5630478 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 10:58:01 -0400 Subject: [PATCH 09/12] Lint --- openapi_python_client/parser/properties/model_property.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 9a9e6064c..1c9ae80dd 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -69,9 +69,7 @@ def _is_subtype(first: Property, second: Property) -> bool: [ _is_string_enum(first) and isinstance(second, StringProperty), _is_int_enum(first) and isinstance(second, IntProperty), - _is_string_enum(first) - and _is_string_enum(second) - and values_are_subset(first, second), + _is_string_enum(first) and _is_string_enum(second) and values_are_subset(first, second), _is_int_enum(first) and _is_int_enum(second) and values_are_subset(first, second), ] ) From 70d663a00436cbec363c8a7693518d076e4626d9 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 11:00:55 -0400 Subject: [PATCH 10/12] Fix bug --- .../parser/properties/model_property.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 1c9ae80dd..239a90427 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import ClassVar, Dict, List, NamedTuple, Optional, Set, Tuple, Union +from typing import ClassVar, Dict, List, NamedTuple, Optional, Set, Tuple, Union, cast import attr @@ -69,8 +69,12 @@ def _is_subtype(first: Property, second: Property) -> bool: [ _is_string_enum(first) and isinstance(second, StringProperty), _is_int_enum(first) and isinstance(second, IntProperty), - _is_string_enum(first) and _is_string_enum(second) and values_are_subset(first, second), - _is_int_enum(first) and _is_int_enum(second) and values_are_subset(first, second), + _is_string_enum(first) and _is_string_enum(second) + # cast because MyPy fails to deduce type + and values_are_subset(cast(EnumProperty, first), cast(EnumProperty, second)), + _is_int_enum(first) and _is_int_enum(second) + # cast because MyPy fails to deduce type + and values_are_subset(cast(EnumProperty, first), cast(EnumProperty, second)), ] ) From 46997d3b9669970214e821d50c58a141b1369441 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Mon, 28 Jun 2021 13:51:42 -0400 Subject: [PATCH 11/12] Regen --- .../my_test_api_client/models/all_of_sub_model.py | 6 ++++-- .../models/another_all_of_sub_model.py | 12 ++++++++---- .../my_test_api_client/models/model_from_all_of.py | 12 ++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py index 2095f0d40..515374d19 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/all_of_sub_model.py @@ -43,9 +43,11 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: type = d.pop("type", UNSET) - type_enum: Union[Unset, AllOfSubModelTypeEnum] = UNSET _type_enum = d.pop("type_enum", UNSET) - if not isinstance(_type_enum, Unset): + type_enum: Union[Unset, AllOfSubModelTypeEnum] + if isinstance(_type_enum, Unset): + type_enum = UNSET + else: type_enum = AllOfSubModelTypeEnum(_type_enum) all_of_sub_model = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py index 82476e2b7..5fabb03e4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/another_all_of_sub_model.py @@ -45,14 +45,18 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() another_sub_property = d.pop("another_sub_property", UNSET) - type: Union[Unset, AnotherAllOfSubModelType] = UNSET _type = d.pop("type", UNSET) - if not isinstance(_type, Unset): + type: Union[Unset, AnotherAllOfSubModelType] + if isinstance(_type, Unset): + type = UNSET + else: type = AnotherAllOfSubModelType(_type) - type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET _type_enum = d.pop("type_enum", UNSET) - if not isinstance(_type_enum, Unset): + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] + if isinstance(_type_enum, Unset): + type_enum = UNSET + else: type_enum = AnotherAllOfSubModelTypeEnum(_type_enum) another_all_of_sub_model = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py index 0cca1941b..415f27486 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py @@ -50,14 +50,18 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() a_sub_property = d.pop("a_sub_property", UNSET) - type: Union[Unset, AnotherAllOfSubModelType] = UNSET _type = d.pop("type", UNSET) - if not isinstance(_type, Unset): + type: Union[Unset, AnotherAllOfSubModelType] + if isinstance(_type, Unset): + type = UNSET + else: type = AnotherAllOfSubModelType(_type) - type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] = UNSET _type_enum = d.pop("type_enum", UNSET) - if not isinstance(_type_enum, Unset): + type_enum: Union[Unset, AnotherAllOfSubModelTypeEnum] + if isinstance(_type_enum, Unset): + type_enum = UNSET + else: type_enum = AnotherAllOfSubModelTypeEnum(_type_enum) another_sub_property = d.pop("another_sub_property", UNSET) From 8cebdecac77729c68e9795c1ac4e72f5aac1db36 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Tue, 29 Jun 2021 08:56:18 -0400 Subject: [PATCH 12/12] Add tests --- .../test_properties/test_model_property.py | 160 +++++++++++++++++- 1 file changed, 159 insertions(+), 1 deletion(-) diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index abe0e5323..09baef9ce 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -6,7 +6,15 @@ import openapi_python_client.schema as oai from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError -from openapi_python_client.parser.properties import DateTimeProperty, ModelProperty, StringProperty +from openapi_python_client.parser.properties import ( + Class, + DateTimeProperty, + EnumProperty, + IntProperty, + ModelProperty, + StringProperty, + enum_property, +) def get_class(): @@ -275,6 +283,156 @@ def test_conflicting_properties_same_types(self, model_property_factory): assert isinstance(result, PropertyError) + def test_allof_string_and_string_enum(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) + enum_property = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": "foo"}, + class_info=Class(name="AnEnum", module_name="an_enum"), + value_type=str, + default=None, + ) + schemas = Schemas( + classes_by_reference={ + "/First": model_property_factory(optional_properties=[string_property()]), + "/Second": model_property_factory(optional_properties=[enum_property]), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + assert result.optional_props[0] == enum_property + + def test_allof_string_enum_and_string(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) + enum_property = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": "foo"}, + class_info=Class(name="AnEnum", module_name="an_enum"), + value_type=str, + default=None, + ) + schemas = Schemas( + classes_by_reference={ + "/First": model_property_factory(optional_properties=[enum_property]), + "/Second": model_property_factory(optional_properties=[string_property()]), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + assert result.optional_props[0] == enum_property + + def test_allof_int_and_int_enum(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) + enum_property = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": 1}, + class_info=Class(name="AnEnum", module_name="an_enum"), + value_type=int, + default=None, + ) + schemas = Schemas( + classes_by_reference={ + "/First": model_property_factory( + optional_properties=[IntProperty(name="", required=True, nullable=True, default=None)] + ), + "/Second": model_property_factory(optional_properties=[enum_property]), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + assert result.optional_props[0] == enum_property + + def test_allof_string_enums(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) + enum_property1 = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": "foo", "bar": "bar"}, + class_info=Class(name="AnEnum1", module_name="an_enum1"), + value_type=str, + default=None, + ) + enum_property2 = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": "foo"}, + class_info=Class(name="AnEnum2", module_name="an_enum2"), + value_type=str, + default=None, + ) + schemas = Schemas( + classes_by_reference={ + "/First": model_property_factory(optional_properties=[enum_property1]), + "/Second": model_property_factory(optional_properties=[enum_property2]), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + assert result.optional_props[0] == enum_property2 + + def test_allof_int_enums(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) + enum_property1 = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": 1, "bar": 2}, + class_info=Class(name="AnEnum1", module_name="an_enum1"), + value_type=int, + default=None, + ) + enum_property2 = EnumProperty( + name="", + required=True, + nullable=True, + values={"foo": 1}, + class_info=Class(name="AnEnum2", module_name="an_enum2"), + value_type=int, + default=None, + ) + schemas = Schemas( + classes_by_reference={ + "/First": model_property_factory(optional_properties=[enum_property1]), + "/Second": model_property_factory(optional_properties=[enum_property2]), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + assert result.optional_props[0] == enum_property2 + def test_duplicate_properties(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties