From 159f747a5577af7955b2d9bf743909913db64bc1 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:51:40 +0300 Subject: [PATCH 01/11] Add the creation of empty classes. It allows to reference in unprocessed classes. --- .../parser/properties/__init__.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 839ef05ff..b7cca9e77 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -9,6 +9,7 @@ "property_from_data", ] +from copy import deepcopy from itertools import chain from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union @@ -592,6 +593,13 @@ def build_schemas( if isinstance(ref_path, ParseError): schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue + + indirect_references = _get_indirect_references(data=data, schemas=schemas) + # Create classes wi can be re + schemas = build_classes_without_properties( + indirect_references=indirect_references, components=components, schemas=schemas, config=config + ) + schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, PropertyError): next_round.append((name, data)) @@ -603,3 +611,37 @@ def build_schemas( schemas.errors.extend(errors) return schemas + + +def _get_indirect_references(data: oai.Schema, schemas: Schemas) -> Set[str]: + """Gets references to unprocessed classes.""" + if data.properties: + processed_references: Set[str] = set(schemas.classes_by_reference.keys()) + prop_references: Set[str] = set(prop.ref for prop in data.properties.values() if isinstance(prop, oai.Reference)) + indirect_references: Set[str] = set(ref.strip('#') for ref in prop_references) - processed_references + return indirect_references + return set() + + +def build_classes_without_properties( + indirect_references: Set[str], components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config +) -> Union[Schemas, PropertyError]: + """Adds classes with empty properties. This allows to reference these classes. + Properties will be filled in later. + """ + for reference in indirect_references: + prop_name = reference.rsplit(sep='/', maxsplit=1)[1] + if prop_name not in components: + return PropertyError(detail=f"Could not find reference in parsed models or enums: #/components/schemas/{prop_name}") + + prop_ref_path = parse_reference_path(f"#/components/schemas/{prop_name}") + data = deepcopy(components.get(prop_name)) + # Add model for reference without properties. Properties will be added later by order. + data.properties = None + + schemas_or_err = update_schemas_with_data(ref_path=prop_ref_path, data=data, schemas=schemas, config=config) + if isinstance(schemas_or_err, ParseError): + schemas.errors.append(schemas_or_err) + continue + schemas = schemas_or_err + return schemas From fa899031737c3668141c3d949ffdd2dd44f68b20 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:53:13 +0300 Subject: [PATCH 02/11] Remove `Attempted to generate duplicate models` check --- openapi_python_client/parser/properties/model_property.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index aab642f0d..eec1bb64a 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -255,9 +255,6 @@ def build_model_property( additional_properties=additional_properties, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), ) - if class_info.name in schemas.classes_by_name: - error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"') - return error, schemas schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop}) return prop, schemas From c34169eb07b1679b832ce2f75f81b14993514c0b Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:53:21 +0300 Subject: [PATCH 03/11] Remove `Attempted to generate duplicate models` test --- .../test_properties/test_model_property.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 0b5a729d1..a610b0170 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -127,19 +127,6 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_ additional_properties=True, ) - def test_model_name_conflict(self): - from openapi_python_client.parser.properties import Schemas, build_model_property - - data = oai.Schema.construct() - schemas = Schemas(classes_by_name={"OtherModel": None}) - - err, new_schemas = build_model_property( - data=data, name="OtherModel", schemas=schemas, required=True, parent_name=None, config=Config() - ) - - assert new_schemas == schemas - assert err == PropertyError(detail='Attempted to generate duplicate models with name "OtherModel"', data=data) - def test_bad_props_return_error(self): from openapi_python_client.parser.properties import Schemas, build_model_property From 3ccec6afc0172f6ffd2c85d1c431c4d5be2f871a Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:33:01 +0300 Subject: [PATCH 04/11] Add support for recursive references --- openapi_python_client/parser/properties/model_property.py | 3 +++ openapi_python_client/templates/model.py.jinja | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index eec1bb64a..f4c438154 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -171,6 +171,9 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: optional_properties.append(prop) relative_imports.update(prop.get_imports(prefix="..")) + # Except circular import + relative_imports = {relative_import for relative_import in relative_imports if "import " + class_name + "\n" not in relative_import} + return _PropertyData( optional_props=optional_properties, required_props=required_properties, diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja index c4c23c878..306ed4527 100644 --- a/openapi_python_client/templates/model.py.jinja +++ b/openapi_python_client/templates/model.py.jinja @@ -31,12 +31,12 @@ class {{ class_name }}: """ {{ model.description }} """ {% for property in model.required_properties + model.optional_properties %} {% if property.default is none and property.required %} - {{ property.to_string() }} + {{ property.to_string().replace(" " + class_name + "]", " T]") }} {% endif %} {% endfor %} {% for property in model.required_properties + model.optional_properties %} {% if property.default is not none or not property.required %} - {{ property.to_string() }} + {{ property.to_string().replace(" " + class_name + "]", " T]") }} {% endif %} {% endfor %} {% if model.additional_properties %} From 3d6515d4224997f4f91715a319dcb90f20890949 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:50:29 +0300 Subject: [PATCH 05/11] Fix circular import --- openapi_python_client/parser/properties/model_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index f4c438154..4aa75ba92 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -172,7 +172,7 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: relative_imports.update(prop.get_imports(prefix="..")) # Except circular import - relative_imports = {relative_import for relative_import in relative_imports if "import " + class_name + "\n" not in relative_import} + relative_imports = {relative_import for relative_import in relative_imports if not relative_import.endswith("import " + class_name)} return _PropertyData( optional_props=optional_properties, From 98dc19abe6135edbf09a22b206319d7e1fdae37f Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:51:40 +0300 Subject: [PATCH 06/11] Add the creation of empty classes. It allows to reference in unprocessed classes. --- .../parser/properties/__init__.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index b1701959f..1dbcdb728 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -9,6 +9,7 @@ "property_from_data", ] +from copy import deepcopy from itertools import chain from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union @@ -654,6 +655,13 @@ def build_schemas( if isinstance(ref_path, ParseError): schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue + + indirect_references = _get_indirect_references(data=data, schemas=schemas) + # Create classes wi can be re + schemas = build_classes_without_properties( + indirect_references=indirect_references, components=components, schemas=schemas, config=config + ) + schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, PropertyError): next_round.append((name, data)) @@ -665,3 +673,37 @@ def build_schemas( schemas.errors.extend(errors) return schemas + + +def _get_indirect_references(data: oai.Schema, schemas: Schemas) -> Set[str]: + """Gets references to unprocessed classes.""" + if data.properties: + processed_references: Set[str] = set(schemas.classes_by_reference.keys()) + prop_references: Set[str] = set(prop.ref for prop in data.properties.values() if isinstance(prop, oai.Reference)) + indirect_references: Set[str] = set(ref.strip('#') for ref in prop_references) - processed_references + return indirect_references + return set() + + +def build_classes_without_properties( + indirect_references: Set[str], components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config +) -> Union[Schemas, PropertyError]: + """Adds classes with empty properties. This allows to reference these classes. + Properties will be filled in later. + """ + for reference in indirect_references: + prop_name = reference.rsplit(sep='/', maxsplit=1)[1] + if prop_name not in components: + return PropertyError(detail=f"Could not find reference in parsed models or enums: #/components/schemas/{prop_name}") + + prop_ref_path = parse_reference_path(f"#/components/schemas/{prop_name}") + data = deepcopy(components.get(prop_name)) + # Add model for reference without properties. Properties will be added later by order. + data.properties = None + + schemas_or_err = update_schemas_with_data(ref_path=prop_ref_path, data=data, schemas=schemas, config=config) + if isinstance(schemas_or_err, ParseError): + schemas.errors.append(schemas_or_err) + continue + schemas = schemas_or_err + return schemas From d832f3f91b7540678c332a09e6d0fa1ab9a09978 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:53:13 +0300 Subject: [PATCH 07/11] Remove `Attempted to generate duplicate models` check --- openapi_python_client/parser/properties/model_property.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 0cfb7a902..d5649c88a 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -257,9 +257,6 @@ def build_model_property( additional_properties=additional_properties, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), ) - if class_info.name in schemas.classes_by_name: - error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"') - return error, schemas schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop}) return prop, schemas From 6747f07b40543bf5b12355c4cadbadbf807345cf Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:53:21 +0300 Subject: [PATCH 08/11] Remove `Attempted to generate duplicate models` test --- .../test_properties/test_model_property.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 5f179eab2..757f2c99d 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -127,19 +127,6 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_ additional_properties=True, ) - def test_model_name_conflict(self): - from openapi_python_client.parser.properties import Schemas, build_model_property - - data = oai.Schema.construct() - schemas = Schemas(classes_by_name={"OtherModel": None}) - - err, new_schemas = build_model_property( - data=data, name="OtherModel", schemas=schemas, required=True, parent_name=None, config=Config() - ) - - assert new_schemas == schemas - assert err == PropertyError(detail='Attempted to generate duplicate models with name "OtherModel"', data=data) - def test_bad_props_return_error(self): from openapi_python_client.parser.properties import Schemas, build_model_property From e926ad67c6437e96281e6a835910e710e3c28adc Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:33:01 +0300 Subject: [PATCH 09/11] Add support for recursive references --- openapi_python_client/parser/properties/model_property.py | 3 +++ openapi_python_client/templates/model.py.jinja | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index d5649c88a..15b467ee1 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -173,6 +173,9 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: optional_properties.append(prop) relative_imports.update(prop.get_imports(prefix="..")) + # Except circular import + relative_imports = {relative_import for relative_import in relative_imports if "import " + class_name + "\n" not in relative_import} + return _PropertyData( optional_props=optional_properties, required_props=required_properties, diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja index c4c23c878..306ed4527 100644 --- a/openapi_python_client/templates/model.py.jinja +++ b/openapi_python_client/templates/model.py.jinja @@ -31,12 +31,12 @@ class {{ class_name }}: """ {{ model.description }} """ {% for property in model.required_properties + model.optional_properties %} {% if property.default is none and property.required %} - {{ property.to_string() }} + {{ property.to_string().replace(" " + class_name + "]", " T]") }} {% endif %} {% endfor %} {% for property in model.required_properties + model.optional_properties %} {% if property.default is not none or not property.required %} - {{ property.to_string() }} + {{ property.to_string().replace(" " + class_name + "]", " T]") }} {% endif %} {% endfor %} {% if model.additional_properties %} From f11e4374c9979bb7026824fea36decf5b26fdcc2 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:50:29 +0300 Subject: [PATCH 10/11] Fix circular import --- openapi_python_client/parser/properties/model_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 15b467ee1..91db268ed 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -174,7 +174,7 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: relative_imports.update(prop.get_imports(prefix="..")) # Except circular import - relative_imports = {relative_import for relative_import in relative_imports if "import " + class_name + "\n" not in relative_import} + relative_imports = {relative_import for relative_import in relative_imports if not relative_import.endswith("import " + class_name)} return _PropertyData( optional_props=optional_properties, From 559bc881dd6849cc64bb9e1d52011a2fc24d86c0 Mon Sep 17 00:00:00 2001 From: Matvey <38750524+mtovts@users.noreply.github.com> Date: Tue, 17 Aug 2021 14:08:09 +0300 Subject: [PATCH 11/11] Refactor: create empty models then adds properties --- .../parser/properties/__init__.py | 88 +++++++------------ .../parser/properties/model_property.py | 75 ++++++++++------ 2 files changed, 80 insertions(+), 83 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 1dbcdb728..315284184 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -11,7 +11,7 @@ from copy import deepcopy from itertools import chain -from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union +from typing import Any, ClassVar, Dict, Generic, Iterator, List, Optional, Set, Tuple, TypeVar, Union import attr @@ -637,71 +637,49 @@ def build_schemas( *, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config ) -> Schemas: """Get a list of Schemas from an OpenAPI dict""" - to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() - still_making_progress = True errors: List[PropertyError] = [] - # References could have forward References so keep going as long as we are making progress - while still_making_progress: - still_making_progress = False - errors = [] - next_round = [] - # Only accumulate errors from the last round, since we might fix some along the way - for name, data in to_process: - if isinstance(data, oai.Reference): - schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported.")) - continue - ref_path = parse_reference_path(f"#/components/schemas/{name}") - if isinstance(ref_path, ParseError): - schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) - continue - - indirect_references = _get_indirect_references(data=data, schemas=schemas) - # Create classes wi can be re - schemas = build_classes_without_properties( - indirect_references=indirect_references, components=components, schemas=schemas, config=config - ) - - schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) - if isinstance(schemas_or_err, PropertyError): - next_round.append((name, data)) - errors.append(schemas_or_err) - continue - schemas = schemas_or_err - still_making_progress = True - to_process = next_round + # Create classes witch can be referenced + schemas = build_schemas_without_properties(components=components, schemas=schemas, config=config) - schemas.errors.extend(errors) - return schemas + for name, data in components.items(): + if isinstance(data, oai.Reference): + schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported.")) + continue + ref_path = parse_reference_path(f"#/components/schemas/{name}") + if isinstance(ref_path, ParseError): + schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) + continue + schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) + if isinstance(schemas_or_err, PropertyError): + errors.append(schemas_or_err) + continue + schemas = schemas_or_err -def _get_indirect_references(data: oai.Schema, schemas: Schemas) -> Set[str]: - """Gets references to unprocessed classes.""" - if data.properties: - processed_references: Set[str] = set(schemas.classes_by_reference.keys()) - prop_references: Set[str] = set(prop.ref for prop in data.properties.values() if isinstance(prop, oai.Reference)) - indirect_references: Set[str] = set(ref.strip('#') for ref in prop_references) - processed_references - return indirect_references - return set() + return schemas -def build_classes_without_properties( - indirect_references: Set[str], components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config -) -> Union[Schemas, PropertyError]: +def build_schemas_without_properties( + components: Dict[str, Union[oai.Reference, oai.Schema]], + schemas: Schemas, + config: Config, +) -> Schemas: """Adds classes with empty properties. This allows to reference these classes. Properties will be filled in later. """ - for reference in indirect_references: - prop_name = reference.rsplit(sep='/', maxsplit=1)[1] - if prop_name not in components: - return PropertyError(detail=f"Could not find reference in parsed models or enums: #/components/schemas/{prop_name}") - - prop_ref_path = parse_reference_path(f"#/components/schemas/{prop_name}") - data = deepcopy(components.get(prop_name)) - # Add model for reference without properties. Properties will be added later by order. - data.properties = None + for name, schema in components.items(): + data = deepcopy(schema) + if isinstance(data, oai.Reference): + # Unsupported now. Adds to errors in the top loop. + continue + ref_path = parse_reference_path(f"#/components/schemas/{name}") + if isinstance(ref_path, ParseError): + continue - schemas_or_err = update_schemas_with_data(ref_path=prop_ref_path, data=data, schemas=schemas, config=config) + # Add model for reference without properties. Properties will be added later. + data.properties = None + schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, ParseError): schemas.errors.append(schemas_or_err) continue diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 91db268ed..9b5410f5e 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -173,8 +173,10 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: optional_properties.append(prop) relative_imports.update(prop.get_imports(prefix="..")) - # Except circular import - relative_imports = {relative_import for relative_import in relative_imports if not relative_import.endswith("import " + class_name)} + # Except self import + relative_imports = { + relative_import for relative_import in relative_imports if not relative_import.endswith("import " + class_name) + } return _PropertyData( optional_props=optional_properties, @@ -214,6 +216,15 @@ def _get_additional_properties( return additional_properties, schemas +def _get_empty_properties(schemas: Schemas) -> _PropertyData: + return _PropertyData( + optional_props=[], + required_props=[], + relative_imports=set(), + schemas=schemas, + ) + + def build_model_property( *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str], config: Config ) -> Tuple[Union[ModelProperty, PropertyError], Schemas]: @@ -234,32 +245,40 @@ def build_model_property( class_string = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_string)}" class_info = Class.from_string(string=class_string, config=config) - property_data = _process_properties(data=data, schemas=schemas, class_name=class_info.name, config=config) - if isinstance(property_data, PropertyError): - return property_data, schemas - schemas = property_data.schemas - - additional_properties, schemas = _get_additional_properties( - schema_additional=data.additionalProperties, schemas=schemas, class_name=class_info.name, config=config - ) - if isinstance(additional_properties, Property): - property_data.relative_imports.update(additional_properties.get_imports(prefix="..")) - elif isinstance(additional_properties, PropertyError): - return additional_properties, schemas - - prop = ModelProperty( - class_info=class_info, - required_properties=property_data.required_props, - optional_properties=property_data.optional_props, - relative_imports=property_data.relative_imports, - description=data.description or "", - default=None, - nullable=data.nullable, - required=required, - name=name, - additional_properties=additional_properties, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - ) + existing = schemas.classes_by_name.get(class_info.name) + if isinstance(existing, ModelProperty): + property_data = _process_properties(data=data, schemas=schemas, class_name=class_info.name, config=config) + if isinstance(property_data, PropertyError): + return property_data, schemas + schemas = property_data.schemas + prop = attr.evolve( + existing, + required_properties=property_data.required_props, + optional_properties=property_data.optional_props, + ) + else: + property_data = _get_empty_properties(schemas=schemas) + additional_properties, schemas = _get_additional_properties( + schema_additional=data.additionalProperties, schemas=schemas, class_name=class_info.name, config=config + ) + if isinstance(additional_properties, Property): + property_data.relative_imports.update(additional_properties.get_imports(prefix="..")) + elif isinstance(additional_properties, PropertyError): + return additional_properties, schemas + + prop = ModelProperty( + class_info=class_info, + required_properties=property_data.required_props, + optional_properties=property_data.optional_props, + relative_imports=property_data.relative_imports, + description=data.description or "", + default=None, + nullable=data.nullable, + required=required, + name=name, + additional_properties=additional_properties, + python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + ) schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop}) return prop, schemas