diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py
index 5eb7ec0f0..4d9d0bec0 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py
@@ -24,8 +24,8 @@ def _get_kwargs(
     union_prop: Union[float, str] = "not a float",
     union_prop_with_ref: Union[AnEnum, None, Unset, float] = 0.6,
     enum_prop: AnEnum,
-    model_prop: ModelWithUnionProperty,
-    required_model_prop: ModelWithUnionProperty,
+    model_prop: "ModelWithUnionProperty",
+    required_model_prop: "ModelWithUnionProperty",
 ) -> Dict[str, Any]:
     url = "{}/tests/defaults".format(client.base_url)
 
@@ -130,8 +130,8 @@ def sync_detailed(
     union_prop: Union[float, str] = "not a float",
     union_prop_with_ref: Union[AnEnum, None, Unset, float] = 0.6,
     enum_prop: AnEnum,
-    model_prop: ModelWithUnionProperty,
-    required_model_prop: ModelWithUnionProperty,
+    model_prop: "ModelWithUnionProperty",
+    required_model_prop: "ModelWithUnionProperty",
 ) -> Response[Union[Any, HTTPValidationError]]:
     """Defaults
 
@@ -187,8 +187,8 @@ def sync(
     union_prop: Union[float, str] = "not a float",
     union_prop_with_ref: Union[AnEnum, None, Unset, float] = 0.6,
     enum_prop: AnEnum,
-    model_prop: ModelWithUnionProperty,
-    required_model_prop: ModelWithUnionProperty,
+    model_prop: "ModelWithUnionProperty",
+    required_model_prop: "ModelWithUnionProperty",
 ) -> Optional[Union[Any, HTTPValidationError]]:
     """Defaults
 
@@ -237,8 +237,8 @@ async def asyncio_detailed(
     union_prop: Union[float, str] = "not a float",
     union_prop_with_ref: Union[AnEnum, None, Unset, float] = 0.6,
     enum_prop: AnEnum,
-    model_prop: ModelWithUnionProperty,
-    required_model_prop: ModelWithUnionProperty,
+    model_prop: "ModelWithUnionProperty",
+    required_model_prop: "ModelWithUnionProperty",
 ) -> Response[Union[Any, HTTPValidationError]]:
     """Defaults
 
@@ -292,8 +292,8 @@ async def asyncio(
     union_prop: Union[float, str] = "not a float",
     union_prop_with_ref: Union[AnEnum, None, Unset, float] = 0.6,
     enum_prop: AnEnum,
-    model_prop: ModelWithUnionProperty,
-    required_model_prop: ModelWithUnionProperty,
+    model_prop: "ModelWithUnionProperty",
+    required_model_prop: "ModelWithUnionProperty",
 ) -> Optional[Union[Any, HTTPValidationError]]:
     """Defaults
 
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py
index ba0a3351e..0fd856e12 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py
@@ -69,7 +69,7 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[HTTPValidationError, List[AModel]]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[HTTPValidationError, List["AModel"]]]:
     if response.status_code == HTTPStatus.OK:
         response_200 = []
         _response_200 = response.json()
@@ -90,7 +90,7 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[HTTPValidatio
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[HTTPValidationError, List[AModel]]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[HTTPValidationError, List["AModel"]]]:
     return Response(
         status_code=HTTPStatus(response.status_code),
         content=response.content,
@@ -106,7 +106,7 @@ def sync_detailed(
     an_enum_value_with_null: List[Optional[AnEnumWithNull]],
     an_enum_value_with_only_null: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[HTTPValidationError, List[AModel]]]:
+) -> Response[Union[HTTPValidationError, List["AModel"]]]:
     """Get List
 
      Get a list of things
@@ -118,7 +118,7 @@ def sync_detailed(
         some_date (Union[datetime.date, datetime.datetime]):
 
     Returns:
-        Response[Union[HTTPValidationError, List[AModel]]]
+        Response[Union[HTTPValidationError, List['AModel']]]
     """
 
     kwargs = _get_kwargs(
@@ -144,7 +144,7 @@ def sync(
     an_enum_value_with_null: List[Optional[AnEnumWithNull]],
     an_enum_value_with_only_null: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[HTTPValidationError, List[AModel]]]:
+) -> Optional[Union[HTTPValidationError, List["AModel"]]]:
     """Get List
 
      Get a list of things
@@ -156,7 +156,7 @@ def sync(
         some_date (Union[datetime.date, datetime.datetime]):
 
     Returns:
-        Response[Union[HTTPValidationError, List[AModel]]]
+        Response[Union[HTTPValidationError, List['AModel']]]
     """
 
     return sync_detailed(
@@ -175,7 +175,7 @@ async def asyncio_detailed(
     an_enum_value_with_null: List[Optional[AnEnumWithNull]],
     an_enum_value_with_only_null: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[HTTPValidationError, List[AModel]]]:
+) -> Response[Union[HTTPValidationError, List["AModel"]]]:
     """Get List
 
      Get a list of things
@@ -187,7 +187,7 @@ async def asyncio_detailed(
         some_date (Union[datetime.date, datetime.datetime]):
 
     Returns:
-        Response[Union[HTTPValidationError, List[AModel]]]
+        Response[Union[HTTPValidationError, List['AModel']]]
     """
 
     kwargs = _get_kwargs(
@@ -211,7 +211,7 @@ async def asyncio(
     an_enum_value_with_null: List[Optional[AnEnumWithNull]],
     an_enum_value_with_only_null: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[HTTPValidationError, List[AModel]]]:
+) -> Optional[Union[HTTPValidationError, List["AModel"]]]:
     """Get List
 
      Get a list of things
@@ -223,7 +223,7 @@ async def asyncio(
         some_date (Union[datetime.date, datetime.datetime]):
 
     Returns:
-        Response[Union[HTTPValidationError, List[AModel]]]
+        Response[Union[HTTPValidationError, List['AModel']]]
     """
 
     return (
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 04344ee19..4216f0b71 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,18 @@
 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_array_with_a_circular_ref_in_items_object_a_item import AnArrayWithACircularRefInItemsObjectAItem
+from .an_array_with_a_circular_ref_in_items_object_additional_properties_a_item import (
+    AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem,
+)
+from .an_array_with_a_circular_ref_in_items_object_additional_properties_b_item import (
+    AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem,
+)
+from .an_array_with_a_circular_ref_in_items_object_b_item import AnArrayWithACircularRefInItemsObjectBItem
+from .an_array_with_a_recursive_ref_in_items_object_additional_properties_item import (
+    AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem,
+)
+from .an_array_with_a_recursive_ref_in_items_object_item import AnArrayWithARecursiveRefInItemsObjectItem
 from .an_enum import AnEnum
 from .an_enum_with_null import AnEnumWithNull
 from .an_int_enum import AnIntEnum
@@ -33,10 +45,16 @@
 from .model_with_additional_properties_refed import ModelWithAdditionalPropertiesRefed
 from .model_with_any_json_properties import ModelWithAnyJsonProperties
 from .model_with_any_json_properties_additional_property_type_0 import ModelWithAnyJsonPropertiesAdditionalPropertyType0
+from .model_with_circular_ref_a import ModelWithCircularRefA
+from .model_with_circular_ref_b import ModelWithCircularRefB
+from .model_with_circular_ref_in_additional_properties_a import ModelWithCircularRefInAdditionalPropertiesA
+from .model_with_circular_ref_in_additional_properties_b import ModelWithCircularRefInAdditionalPropertiesB
 from .model_with_date_time_property import ModelWithDateTimeProperty
 from .model_with_primitive_additional_properties import ModelWithPrimitiveAdditionalProperties
 from .model_with_primitive_additional_properties_a_date_holder import ModelWithPrimitiveAdditionalPropertiesADateHolder
 from .model_with_property_ref import ModelWithPropertyRef
+from .model_with_recursive_ref import ModelWithRecursiveRef
+from .model_with_recursive_ref_in_additional_properties import ModelWithRecursiveRefInAdditionalProperties
 from .model_with_union_property import ModelWithUnionProperty
 from .model_with_union_property_inlined import ModelWithUnionPropertyInlined
 from .model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
@@ -58,6 +76,12 @@
     "AModel",
     "AModelWithPropertiesReferenceThatAreNotObject",
     "AnAllOfEnum",
+    "AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem",
+    "AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem",
+    "AnArrayWithACircularRefInItemsObjectAItem",
+    "AnArrayWithACircularRefInItemsObjectBItem",
+    "AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem",
+    "AnArrayWithARecursiveRefInItemsObjectItem",
     "AnEnum",
     "AnEnumWithNull",
     "AnIntEnum",
@@ -83,10 +107,16 @@
     "ModelWithAdditionalPropertiesRefed",
     "ModelWithAnyJsonProperties",
     "ModelWithAnyJsonPropertiesAdditionalPropertyType0",
+    "ModelWithCircularRefA",
+    "ModelWithCircularRefB",
+    "ModelWithCircularRefInAdditionalPropertiesA",
+    "ModelWithCircularRefInAdditionalPropertiesB",
     "ModelWithDateTimeProperty",
     "ModelWithPrimitiveAdditionalProperties",
     "ModelWithPrimitiveAdditionalPropertiesADateHolder",
     "ModelWithPropertyRef",
+    "ModelWithRecursiveRef",
+    "ModelWithRecursiveRefInAdditionalProperties",
     "ModelWithUnionProperty",
     "ModelWithUnionPropertyInlined",
     "ModelWithUnionPropertyInlinedFruitType0",
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
index 0cf302e56..889237c7b 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
@@ -1,5 +1,5 @@
 import datetime
-from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union, cast
 
 import attr
 from dateutil.parser import isoparse
@@ -7,10 +7,13 @@
 from ..models.an_all_of_enum import AnAllOfEnum
 from ..models.an_enum import AnEnum
 from ..models.different_enum import DifferentEnum
-from ..models.free_form_model import FreeFormModel
-from ..models.model_with_union_property import ModelWithUnionProperty
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.free_form_model import FreeFormModel
+    from ..models.model_with_union_property import ModelWithUnionProperty
+
+
 T = TypeVar("T", bound="AModel")
 
 
@@ -24,7 +27,7 @@ class AModel:
         a_camel_date_time (Union[datetime.date, datetime.datetime]):
         a_date (datetime.date):
         required_not_nullable (str):
-        one_of_models (Union[Any, FreeFormModel, ModelWithUnionProperty]):
+        one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', Any]):
         model (ModelWithUnionProperty):
         any_value (Union[Unset, Any]):
         an_optional_allof_enum (Union[Unset, AnAllOfEnum]):
@@ -35,9 +38,9 @@ class AModel:
         required_nullable (Optional[str]):
         not_required_nullable (Union[Unset, None, str]):
         not_required_not_nullable (Union[Unset, str]):
-        nullable_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, None]):
-        not_required_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, Unset]):
-        not_required_nullable_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]):
+        nullable_one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', None]):
+        not_required_one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', Unset]):
+        not_required_nullable_one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', None, Unset, str]):
         nullable_model (Optional[ModelWithUnionProperty]):
         not_required_model (Union[Unset, ModelWithUnionProperty]):
         not_required_nullable_model (Union[Unset, None, ModelWithUnionProperty]):
@@ -47,12 +50,12 @@ class AModel:
     a_camel_date_time: Union[datetime.date, datetime.datetime]
     a_date: datetime.date
     required_not_nullable: str
-    one_of_models: Union[Any, FreeFormModel, ModelWithUnionProperty]
-    model: ModelWithUnionProperty
+    one_of_models: Union["FreeFormModel", "ModelWithUnionProperty", Any]
+    model: "ModelWithUnionProperty"
     a_nullable_date: Optional[datetime.date]
     required_nullable: Optional[str]
-    nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None]
-    nullable_model: Optional[ModelWithUnionProperty]
+    nullable_one_of_models: Union["FreeFormModel", "ModelWithUnionProperty", None]
+    nullable_model: Optional["ModelWithUnionProperty"]
     an_allof_enum_with_overridden_default: AnAllOfEnum = AnAllOfEnum.OVERRIDDEN_DEFAULT
     any_value: Union[Unset, Any] = UNSET
     an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET
@@ -61,12 +64,15 @@ class AModel:
     attr_1_leading_digit: Union[Unset, str] = UNSET
     not_required_nullable: Union[Unset, None, str] = UNSET
     not_required_not_nullable: Union[Unset, str] = UNSET
-    not_required_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, Unset] = UNSET
-    not_required_nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str] = UNSET
-    not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET
-    not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty] = UNSET
+    not_required_one_of_models: Union["FreeFormModel", "ModelWithUnionProperty", Unset] = UNSET
+    not_required_nullable_one_of_models: Union["FreeFormModel", "ModelWithUnionProperty", None, Unset, str] = UNSET
+    not_required_model: Union[Unset, "ModelWithUnionProperty"] = UNSET
+    not_required_nullable_model: Union[Unset, None, "ModelWithUnionProperty"] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
+        from ..models.free_form_model import FreeFormModel
+        from ..models.model_with_union_property import ModelWithUnionProperty
+
         an_enum_value = self.an_enum_value.value
 
         an_allof_enum_with_overridden_default = self.an_allof_enum_with_overridden_default.value
@@ -218,6 +224,9 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.free_form_model import FreeFormModel
+        from ..models.model_with_union_property import ModelWithUnionProperty
+
         d = src_dict.copy()
         an_enum_value = AnEnum(d.pop("an_enum_value"))
 
@@ -244,7 +253,7 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date
 
         required_not_nullable = d.pop("required_not_nullable")
 
-        def _parse_one_of_models(data: object) -> Union[Any, FreeFormModel, ModelWithUnionProperty]:
+        def _parse_one_of_models(data: object) -> Union["FreeFormModel", "ModelWithUnionProperty", Any]:
             try:
                 if not isinstance(data, dict):
                     raise TypeError()
@@ -261,7 +270,7 @@ def _parse_one_of_models(data: object) -> Union[Any, FreeFormModel, ModelWithUni
                 return one_of_models_type_1
             except:  # noqa: E722
                 pass
-            return cast(Union[Any, FreeFormModel, ModelWithUnionProperty], data)
+            return cast(Union["FreeFormModel", "ModelWithUnionProperty", Any], data)
 
         one_of_models = _parse_one_of_models(d.pop("one_of_models"))
 
@@ -310,7 +319,7 @@ def _parse_one_of_models(data: object) -> Union[Any, FreeFormModel, ModelWithUni
 
         not_required_not_nullable = d.pop("not_required_not_nullable", UNSET)
 
-        def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]:
+        def _parse_nullable_one_of_models(data: object) -> Union["FreeFormModel", "ModelWithUnionProperty", None]:
             if data is None:
                 return data
             try:
@@ -329,7 +338,7 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit
 
         nullable_one_of_models = _parse_nullable_one_of_models(d.pop("nullable_one_of_models"))
 
-        def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, Unset]:
+        def _parse_not_required_one_of_models(data: object) -> Union["FreeFormModel", "ModelWithUnionProperty", Unset]:
             if isinstance(data, Unset):
                 return data
             try:
@@ -360,7 +369,7 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode
 
         def _parse_not_required_nullable_one_of_models(
             data: object,
-        ) -> Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]:
+        ) -> Union["FreeFormModel", "ModelWithUnionProperty", None, Unset, str]:
             if data is None:
                 return data
             if isinstance(data, Unset):
@@ -395,7 +404,7 @@ def _parse_not_required_nullable_one_of_models(
                 return not_required_nullable_one_of_models_type_1
             except:  # noqa: E722
                 pass
-            return cast(Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str], data)
+            return cast(Union["FreeFormModel", "ModelWithUnionProperty", None, Unset, str], data)
 
         not_required_nullable_one_of_models = _parse_not_required_nullable_one_of_models(
             d.pop("not_required_nullable_one_of_models", UNSET)
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_a_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_a_item.py
new file mode 100644
index 000000000..16fcbb88d
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_a_item.py
@@ -0,0 +1,82 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.an_array_with_a_circular_ref_in_items_object_b_item import AnArrayWithACircularRefInItemsObjectBItem
+
+
+T = TypeVar("T", bound="AnArrayWithACircularRefInItemsObjectAItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithACircularRefInItemsObjectAItem:
+    """
+    Attributes:
+        circular (Union[Unset, List['AnArrayWithACircularRefInItemsObjectBItem']]):
+    """
+
+    circular: Union[Unset, List["AnArrayWithACircularRefInItemsObjectBItem"]] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        circular: Union[Unset, List[Dict[str, Any]]] = UNSET
+        if not isinstance(self.circular, Unset):
+            circular = []
+            for componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item_data in self.circular:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item = (
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item_data.to_dict()
+                )
+
+                circular.append(componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item)
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if circular is not UNSET:
+            field_dict["circular"] = circular
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.an_array_with_a_circular_ref_in_items_object_b_item import (
+            AnArrayWithACircularRefInItemsObjectBItem,
+        )
+
+        d = src_dict.copy()
+        circular = []
+        _circular = d.pop("circular", UNSET)
+        for componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item_data in _circular or []:
+            componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item = (
+                AnArrayWithACircularRefInItemsObjectBItem.from_dict(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item_data
+                )
+            )
+
+            circular.append(componentsschemas_an_array_with_a_circular_ref_in_items_object_b_item)
+
+        an_array_with_a_circular_ref_in_items_object_a_item = cls(
+            circular=circular,
+        )
+
+        an_array_with_a_circular_ref_in_items_object_a_item.additional_properties = d
+        return an_array_with_a_circular_ref_in_items_object_a_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_a_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_a_item.py
new file mode 100644
index 000000000..f03a87604
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_a_item.py
@@ -0,0 +1,92 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar
+
+import attr
+
+if TYPE_CHECKING:
+    from ..models.an_array_with_a_circular_ref_in_items_object_additional_properties_b_item import (
+        AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem,
+    )
+
+
+T = TypeVar("T", bound="AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem:
+    """ """
+
+    additional_properties: Dict[str, List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem"]] = attr.ib(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> Dict[str, Any]:
+        pass
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = []
+            for (
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item_data
+            ) in prop:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item = (
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item_data.to_dict()
+                )
+
+                field_dict[prop_name].append(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item
+                )
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.an_array_with_a_circular_ref_in_items_object_additional_properties_b_item import (
+            AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem,
+        )
+
+        d = src_dict.copy()
+        an_array_with_a_circular_ref_in_items_object_additional_properties_a_item = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = []
+            _additional_property = prop_dict
+            for (
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item_data
+            ) in _additional_property:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item = (
+                    AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem.from_dict(
+                        componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item_data
+                    )
+                )
+
+                additional_property.append(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_b_item
+                )
+
+            additional_properties[prop_name] = additional_property
+
+        an_array_with_a_circular_ref_in_items_object_additional_properties_a_item.additional_properties = (
+            additional_properties
+        )
+        return an_array_with_a_circular_ref_in_items_object_additional_properties_a_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem"]:
+        return self.additional_properties[key]
+
+    def __setitem__(
+        self, key: str, value: List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem"]
+    ) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_b_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_b_item.py
new file mode 100644
index 000000000..cdff09b4b
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_additional_properties_b_item.py
@@ -0,0 +1,92 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar
+
+import attr
+
+if TYPE_CHECKING:
+    from ..models.an_array_with_a_circular_ref_in_items_object_additional_properties_a_item import (
+        AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem,
+    )
+
+
+T = TypeVar("T", bound="AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithACircularRefInItemsObjectAdditionalPropertiesBItem:
+    """ """
+
+    additional_properties: Dict[str, List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem"]] = attr.ib(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> Dict[str, Any]:
+        pass
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = []
+            for (
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item_data
+            ) in prop:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item = (
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item_data.to_dict()
+                )
+
+                field_dict[prop_name].append(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item
+                )
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.an_array_with_a_circular_ref_in_items_object_additional_properties_a_item import (
+            AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem,
+        )
+
+        d = src_dict.copy()
+        an_array_with_a_circular_ref_in_items_object_additional_properties_b_item = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = []
+            _additional_property = prop_dict
+            for (
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item_data
+            ) in _additional_property:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item = (
+                    AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem.from_dict(
+                        componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item_data
+                    )
+                )
+
+                additional_property.append(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_additional_properties_a_item
+                )
+
+            additional_properties[prop_name] = additional_property
+
+        an_array_with_a_circular_ref_in_items_object_additional_properties_b_item.additional_properties = (
+            additional_properties
+        )
+        return an_array_with_a_circular_ref_in_items_object_additional_properties_b_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem"]:
+        return self.additional_properties[key]
+
+    def __setitem__(
+        self, key: str, value: List["AnArrayWithACircularRefInItemsObjectAdditionalPropertiesAItem"]
+    ) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_b_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_b_item.py
new file mode 100644
index 000000000..b15bb0f7b
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_circular_ref_in_items_object_b_item.py
@@ -0,0 +1,82 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.an_array_with_a_circular_ref_in_items_object_a_item import AnArrayWithACircularRefInItemsObjectAItem
+
+
+T = TypeVar("T", bound="AnArrayWithACircularRefInItemsObjectBItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithACircularRefInItemsObjectBItem:
+    """
+    Attributes:
+        circular (Union[Unset, List['AnArrayWithACircularRefInItemsObjectAItem']]):
+    """
+
+    circular: Union[Unset, List["AnArrayWithACircularRefInItemsObjectAItem"]] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        circular: Union[Unset, List[Dict[str, Any]]] = UNSET
+        if not isinstance(self.circular, Unset):
+            circular = []
+            for componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item_data in self.circular:
+                componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item = (
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item_data.to_dict()
+                )
+
+                circular.append(componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item)
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if circular is not UNSET:
+            field_dict["circular"] = circular
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.an_array_with_a_circular_ref_in_items_object_a_item import (
+            AnArrayWithACircularRefInItemsObjectAItem,
+        )
+
+        d = src_dict.copy()
+        circular = []
+        _circular = d.pop("circular", UNSET)
+        for componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item_data in _circular or []:
+            componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item = (
+                AnArrayWithACircularRefInItemsObjectAItem.from_dict(
+                    componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item_data
+                )
+            )
+
+            circular.append(componentsschemas_an_array_with_a_circular_ref_in_items_object_a_item)
+
+        an_array_with_a_circular_ref_in_items_object_b_item = cls(
+            circular=circular,
+        )
+
+        an_array_with_a_circular_ref_in_items_object_b_item.additional_properties = d
+        return an_array_with_a_circular_ref_in_items_object_b_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_additional_properties_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_additional_properties_item.py
new file mode 100644
index 000000000..52341b8bc
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_additional_properties_item.py
@@ -0,0 +1,79 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem:
+    """ """
+
+    additional_properties: Dict[str, List["AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem"]] = attr.ib(
+        init=False, factory=dict
+    )
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = []
+            for componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item_data in prop:
+                componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item = (
+                    componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item_data.to_dict()
+                )
+
+                field_dict[prop_name].append(
+                    componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item
+                )
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        an_array_with_a_recursive_ref_in_items_object_additional_properties_item = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = []
+            _additional_property = prop_dict
+            for (
+                componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item_data
+            ) in _additional_property:
+                componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item = (
+                    AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem.from_dict(
+                        componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item_data
+                    )
+                )
+
+                additional_property.append(
+                    componentsschemas_an_array_with_a_recursive_ref_in_items_object_additional_properties_item
+                )
+
+            additional_properties[prop_name] = additional_property
+
+        an_array_with_a_recursive_ref_in_items_object_additional_properties_item.additional_properties = (
+            additional_properties
+        )
+        return an_array_with_a_recursive_ref_in_items_object_additional_properties_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> List["AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem"]:
+        return self.additional_properties[key]
+
+    def __setitem__(
+        self, key: str, value: List["AnArrayWithARecursiveRefInItemsObjectAdditionalPropertiesItem"]
+    ) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_item.py b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_item.py
new file mode 100644
index 000000000..c14ee07c2
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/an_array_with_a_recursive_ref_in_items_object_item.py
@@ -0,0 +1,74 @@
+from typing import Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="AnArrayWithARecursiveRefInItemsObjectItem")
+
+
+@attr.s(auto_attribs=True)
+class AnArrayWithARecursiveRefInItemsObjectItem:
+    """
+    Attributes:
+        recursive (Union[Unset, List['AnArrayWithARecursiveRefInItemsObjectItem']]):
+    """
+
+    recursive: Union[Unset, List["AnArrayWithARecursiveRefInItemsObjectItem"]] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        recursive: Union[Unset, List[Dict[str, Any]]] = UNSET
+        if not isinstance(self.recursive, Unset):
+            recursive = []
+            for componentsschemas_an_array_with_a_recursive_ref_in_items_object_item_data in self.recursive:
+                componentsschemas_an_array_with_a_recursive_ref_in_items_object_item = (
+                    componentsschemas_an_array_with_a_recursive_ref_in_items_object_item_data.to_dict()
+                )
+
+                recursive.append(componentsschemas_an_array_with_a_recursive_ref_in_items_object_item)
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if recursive is not UNSET:
+            field_dict["recursive"] = recursive
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        recursive = []
+        _recursive = d.pop("recursive", UNSET)
+        for componentsschemas_an_array_with_a_recursive_ref_in_items_object_item_data in _recursive or []:
+            componentsschemas_an_array_with_a_recursive_ref_in_items_object_item = (
+                AnArrayWithARecursiveRefInItemsObjectItem.from_dict(
+                    componentsschemas_an_array_with_a_recursive_ref_in_items_object_item_data
+                )
+            )
+
+            recursive.append(componentsschemas_an_array_with_a_recursive_ref_in_items_object_item)
+
+        an_array_with_a_recursive_ref_in_items_object_item = cls(
+            recursive=recursive,
+        )
+
+        an_array_with_a_recursive_ref_in_items_object_item.additional_properties = d
+        return an_array_with_a_recursive_ref_in_items_object_item
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py
index 4863298fc..d858be5b6 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py
@@ -1,24 +1,27 @@
 import datetime
 import json
 from io import BytesIO
-from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, cast
 
 import attr
 from dateutil.parser import isoparse
 
-from ..models.body_upload_file_tests_upload_post_additional_property import (
-    BodyUploadFileTestsUploadPostAdditionalProperty,
-)
-from ..models.body_upload_file_tests_upload_post_some_nullable_object import (
-    BodyUploadFileTestsUploadPostSomeNullableObject,
-)
-from ..models.body_upload_file_tests_upload_post_some_object import BodyUploadFileTestsUploadPostSomeObject
-from ..models.body_upload_file_tests_upload_post_some_optional_object import (
-    BodyUploadFileTestsUploadPostSomeOptionalObject,
-)
 from ..models.different_enum import DifferentEnum
 from ..types import UNSET, File, FileJsonType, Unset
 
+if TYPE_CHECKING:
+    from ..models.body_upload_file_tests_upload_post_additional_property import (
+        BodyUploadFileTestsUploadPostAdditionalProperty,
+    )
+    from ..models.body_upload_file_tests_upload_post_some_nullable_object import (
+        BodyUploadFileTestsUploadPostSomeNullableObject,
+    )
+    from ..models.body_upload_file_tests_upload_post_some_object import BodyUploadFileTestsUploadPostSomeObject
+    from ..models.body_upload_file_tests_upload_post_some_optional_object import (
+        BodyUploadFileTestsUploadPostSomeOptionalObject,
+    )
+
+
 T = TypeVar("T", bound="BodyUploadFileTestsUploadPost")
 
 
@@ -40,17 +43,17 @@ class BodyUploadFileTestsUploadPost:
     """
 
     some_file: File
-    some_object: BodyUploadFileTestsUploadPostSomeObject
-    some_nullable_object: Optional[BodyUploadFileTestsUploadPostSomeNullableObject]
+    some_object: "BodyUploadFileTestsUploadPostSomeObject"
+    some_nullable_object: Optional["BodyUploadFileTestsUploadPostSomeNullableObject"]
     some_optional_file: Union[Unset, File] = UNSET
     some_string: Union[Unset, str] = "some_default_string"
     a_datetime: Union[Unset, datetime.datetime] = UNSET
     a_date: Union[Unset, datetime.date] = UNSET
     some_number: Union[Unset, float] = UNSET
     some_array: Union[Unset, List[float]] = UNSET
-    some_optional_object: Union[Unset, BodyUploadFileTestsUploadPostSomeOptionalObject] = UNSET
+    some_optional_object: Union[Unset, "BodyUploadFileTestsUploadPostSomeOptionalObject"] = UNSET
     some_enum: Union[Unset, DifferentEnum] = UNSET
-    additional_properties: Dict[str, BodyUploadFileTestsUploadPostAdditionalProperty] = attr.ib(
+    additional_properties: Dict[str, "BodyUploadFileTestsUploadPostAdditionalProperty"] = attr.ib(
         init=False, factory=dict
     )
 
@@ -195,6 +198,17 @@ def to_multipart(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.body_upload_file_tests_upload_post_additional_property import (
+            BodyUploadFileTestsUploadPostAdditionalProperty,
+        )
+        from ..models.body_upload_file_tests_upload_post_some_nullable_object import (
+            BodyUploadFileTestsUploadPostSomeNullableObject,
+        )
+        from ..models.body_upload_file_tests_upload_post_some_object import BodyUploadFileTestsUploadPostSomeObject
+        from ..models.body_upload_file_tests_upload_post_some_optional_object import (
+            BodyUploadFileTestsUploadPostSomeOptionalObject,
+        )
+
         d = src_dict.copy()
         some_file = File(payload=BytesIO(d.pop("some_file")))
 
@@ -275,10 +289,10 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
     def additional_keys(self) -> List[str]:
         return list(self.additional_properties.keys())
 
-    def __getitem__(self, key: str) -> BodyUploadFileTestsUploadPostAdditionalProperty:
+    def __getitem__(self, key: str) -> "BodyUploadFileTestsUploadPostAdditionalProperty":
         return self.additional_properties[key]
 
-    def __setitem__(self, key: str, value: BodyUploadFileTestsUploadPostAdditionalProperty) -> None:
+    def __setitem__(self, key: str, value: "BodyUploadFileTestsUploadPostAdditionalProperty") -> None:
         self.additional_properties[key] = value
 
     def __delitem__(self, key: str) -> None:
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py
index 21855e7e5..4d8e71670 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py
@@ -1,10 +1,13 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
-from ..models.validation_error import ValidationError
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.validation_error import ValidationError
+
+
 T = TypeVar("T", bound="HTTPValidationError")
 
 
@@ -12,10 +15,10 @@
 class HTTPValidationError:
     """
     Attributes:
-        detail (Union[Unset, List[ValidationError]]):
+        detail (Union[Unset, List['ValidationError']]):
     """
 
-    detail: Union[Unset, List[ValidationError]] = UNSET
+    detail: Union[Unset, List["ValidationError"]] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
         detail: Union[Unset, List[Dict[str, Any]]] = UNSET
@@ -35,6 +38,8 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.validation_error import ValidationError
+
         d = src_dict.copy()
         detail = []
         _detail = d.pop("detail", UNSET)
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_inlined.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_inlined.py
index 6e3faebf4..fb8ad21a8 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_inlined.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_inlined.py
@@ -1,12 +1,15 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
-from ..models.model_with_additional_properties_inlined_additional_property import (
-    ModelWithAdditionalPropertiesInlinedAdditionalProperty,
-)
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.model_with_additional_properties_inlined_additional_property import (
+        ModelWithAdditionalPropertiesInlinedAdditionalProperty,
+    )
+
+
 T = TypeVar("T", bound="ModelWithAdditionalPropertiesInlined")
 
 
@@ -18,7 +21,7 @@ class ModelWithAdditionalPropertiesInlined:
     """
 
     a_number: Union[Unset, float] = UNSET
-    additional_properties: Dict[str, ModelWithAdditionalPropertiesInlinedAdditionalProperty] = attr.ib(
+    additional_properties: Dict[str, "ModelWithAdditionalPropertiesInlinedAdditionalProperty"] = attr.ib(
         init=False, factory=dict
     )
 
@@ -37,6 +40,10 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_additional_properties_inlined_additional_property import (
+            ModelWithAdditionalPropertiesInlinedAdditionalProperty,
+        )
+
         d = src_dict.copy()
         a_number = d.pop("a_number", UNSET)
 
@@ -57,10 +64,10 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
     def additional_keys(self) -> List[str]:
         return list(self.additional_properties.keys())
 
-    def __getitem__(self, key: str) -> ModelWithAdditionalPropertiesInlinedAdditionalProperty:
+    def __getitem__(self, key: str) -> "ModelWithAdditionalPropertiesInlinedAdditionalProperty":
         return self.additional_properties[key]
 
-    def __setitem__(self, key: str, value: ModelWithAdditionalPropertiesInlinedAdditionalProperty) -> None:
+    def __setitem__(self, key: str, value: "ModelWithAdditionalPropertiesInlinedAdditionalProperty") -> None:
         self.additional_properties[key] = value
 
     def __delitem__(self, key: str) -> None:
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py
index af82eb24f..d28dbb30e 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py
@@ -1,10 +1,12 @@
-from typing import Any, Dict, List, Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union, cast
 
 import attr
 
-from ..models.model_with_any_json_properties_additional_property_type_0 import (
-    ModelWithAnyJsonPropertiesAdditionalPropertyType0,
-)
+if TYPE_CHECKING:
+    from ..models.model_with_any_json_properties_additional_property_type_0 import (
+        ModelWithAnyJsonPropertiesAdditionalPropertyType0,
+    )
+
 
 T = TypeVar("T", bound="ModelWithAnyJsonProperties")
 
@@ -14,10 +16,13 @@ class ModelWithAnyJsonProperties:
     """ """
 
     additional_properties: Dict[
-        str, Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]
+        str, Union["ModelWithAnyJsonPropertiesAdditionalPropertyType0", List[str], bool, float, int, str]
     ] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        from ..models.model_with_any_json_properties_additional_property_type_0 import (
+            ModelWithAnyJsonPropertiesAdditionalPropertyType0,
+        )
 
         field_dict: Dict[str, Any] = {}
         for prop_name, prop in self.additional_properties.items():
@@ -37,6 +42,10 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_any_json_properties_additional_property_type_0 import (
+            ModelWithAnyJsonPropertiesAdditionalPropertyType0,
+        )
+
         d = src_dict.copy()
         model_with_any_json_properties = cls()
 
@@ -45,7 +54,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
 
             def _parse_additional_property(
                 data: object,
-            ) -> Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]:
+            ) -> Union["ModelWithAnyJsonPropertiesAdditionalPropertyType0", List[str], bool, float, int, str]:
                 try:
                     if not isinstance(data, dict):
                         raise TypeError()
@@ -63,7 +72,7 @@ def _parse_additional_property(
                 except:  # noqa: E722
                     pass
                 return cast(
-                    Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str], data
+                    Union["ModelWithAnyJsonPropertiesAdditionalPropertyType0", List[str], bool, float, int, str], data
                 )
 
             additional_property = _parse_additional_property(prop_dict)
@@ -79,13 +88,13 @@ def additional_keys(self) -> List[str]:
 
     def __getitem__(
         self, key: str
-    ) -> Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]:
+    ) -> Union["ModelWithAnyJsonPropertiesAdditionalPropertyType0", List[str], bool, float, int, str]:
         return self.additional_properties[key]
 
     def __setitem__(
         self,
         key: str,
-        value: Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str],
+        value: Union["ModelWithAnyJsonPropertiesAdditionalPropertyType0", List[str], bool, float, int, str],
     ) -> None:
         self.additional_properties[key] = value
 
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_a.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_a.py
new file mode 100644
index 000000000..11e31983d
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_a.py
@@ -0,0 +1,70 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.model_with_circular_ref_b import ModelWithCircularRefB
+
+
+T = TypeVar("T", bound="ModelWithCircularRefA")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithCircularRefA:
+    """
+    Attributes:
+        circular (Union[Unset, ModelWithCircularRefB]):
+    """
+
+    circular: Union[Unset, "ModelWithCircularRefB"] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        circular: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.circular, Unset):
+            circular = self.circular.to_dict()
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if circular is not UNSET:
+            field_dict["circular"] = circular
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_circular_ref_b import ModelWithCircularRefB
+
+        d = src_dict.copy()
+        _circular = d.pop("circular", UNSET)
+        circular: Union[Unset, ModelWithCircularRefB]
+        if isinstance(_circular, Unset):
+            circular = UNSET
+        else:
+            circular = ModelWithCircularRefB.from_dict(_circular)
+
+        model_with_circular_ref_a = cls(
+            circular=circular,
+        )
+
+        model_with_circular_ref_a.additional_properties = d
+        return model_with_circular_ref_a
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_b.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_b.py
new file mode 100644
index 000000000..5fb34f4f4
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_b.py
@@ -0,0 +1,70 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+if TYPE_CHECKING:
+    from ..models.model_with_circular_ref_a import ModelWithCircularRefA
+
+
+T = TypeVar("T", bound="ModelWithCircularRefB")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithCircularRefB:
+    """
+    Attributes:
+        circular (Union[Unset, ModelWithCircularRefA]):
+    """
+
+    circular: Union[Unset, "ModelWithCircularRefA"] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        circular: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.circular, Unset):
+            circular = self.circular.to_dict()
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if circular is not UNSET:
+            field_dict["circular"] = circular
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_circular_ref_a import ModelWithCircularRefA
+
+        d = src_dict.copy()
+        _circular = d.pop("circular", UNSET)
+        circular: Union[Unset, ModelWithCircularRefA]
+        if isinstance(_circular, Unset):
+            circular = UNSET
+        else:
+            circular = ModelWithCircularRefA.from_dict(_circular)
+
+        model_with_circular_ref_b = cls(
+            circular=circular,
+        )
+
+        model_with_circular_ref_b.additional_properties = d
+        return model_with_circular_ref_b
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_a.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_a.py
new file mode 100644
index 000000000..0d9e0155c
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_a.py
@@ -0,0 +1,61 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar
+
+import attr
+
+if TYPE_CHECKING:
+    from ..models.model_with_circular_ref_in_additional_properties_b import ModelWithCircularRefInAdditionalPropertiesB
+
+
+T = TypeVar("T", bound="ModelWithCircularRefInAdditionalPropertiesA")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithCircularRefInAdditionalPropertiesA:
+    """ """
+
+    additional_properties: Dict[str, "ModelWithCircularRefInAdditionalPropertiesB"] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        pass
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_circular_ref_in_additional_properties_b import (
+            ModelWithCircularRefInAdditionalPropertiesB,
+        )
+
+        d = src_dict.copy()
+        model_with_circular_ref_in_additional_properties_a = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ModelWithCircularRefInAdditionalPropertiesB.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        model_with_circular_ref_in_additional_properties_a.additional_properties = additional_properties
+        return model_with_circular_ref_in_additional_properties_a
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ModelWithCircularRefInAdditionalPropertiesB":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ModelWithCircularRefInAdditionalPropertiesB") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_b.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_b.py
new file mode 100644
index 000000000..0583b40f8
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_circular_ref_in_additional_properties_b.py
@@ -0,0 +1,61 @@
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar
+
+import attr
+
+if TYPE_CHECKING:
+    from ..models.model_with_circular_ref_in_additional_properties_a import ModelWithCircularRefInAdditionalPropertiesA
+
+
+T = TypeVar("T", bound="ModelWithCircularRefInAdditionalPropertiesB")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithCircularRefInAdditionalPropertiesB:
+    """ """
+
+    additional_properties: Dict[str, "ModelWithCircularRefInAdditionalPropertiesA"] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        pass
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_circular_ref_in_additional_properties_a import (
+            ModelWithCircularRefInAdditionalPropertiesA,
+        )
+
+        d = src_dict.copy()
+        model_with_circular_ref_in_additional_properties_b = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ModelWithCircularRefInAdditionalPropertiesA.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        model_with_circular_ref_in_additional_properties_b.additional_properties = additional_properties
+        return model_with_circular_ref_in_additional_properties_b
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ModelWithCircularRefInAdditionalPropertiesA":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ModelWithCircularRefInAdditionalPropertiesA") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py
index 40d384759..89144cfec 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py
@@ -1,12 +1,15 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
-from ..models.model_with_primitive_additional_properties_a_date_holder import (
-    ModelWithPrimitiveAdditionalPropertiesADateHolder,
-)
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.model_with_primitive_additional_properties_a_date_holder import (
+        ModelWithPrimitiveAdditionalPropertiesADateHolder,
+    )
+
+
 T = TypeVar("T", bound="ModelWithPrimitiveAdditionalProperties")
 
 
@@ -17,7 +20,7 @@ class ModelWithPrimitiveAdditionalProperties:
         a_date_holder (Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder]):
     """
 
-    a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] = UNSET
+    a_date_holder: Union[Unset, "ModelWithPrimitiveAdditionalPropertiesADateHolder"] = UNSET
     additional_properties: Dict[str, str] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
@@ -35,6 +38,10 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_primitive_additional_properties_a_date_holder import (
+            ModelWithPrimitiveAdditionalPropertiesADateHolder,
+        )
+
         d = src_dict.copy()
         _a_date_holder = d.pop("a_date_holder", UNSET)
         a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder]
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
index a3713efe2..d1b8b2b11 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
@@ -1,10 +1,13 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
-from ..models.model_name import ModelName
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.model_name import ModelName
+
+
 T = TypeVar("T", bound="ModelWithPropertyRef")
 
 
@@ -15,7 +18,7 @@ class ModelWithPropertyRef:
         inner (Union[Unset, ModelName]):
     """
 
-    inner: Union[Unset, ModelName] = UNSET
+    inner: Union[Unset, "ModelName"] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
@@ -33,6 +36,8 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_name import ModelName
+
         d = src_dict.copy()
         _inner = d.pop("inner", UNSET)
         inner: Union[Unset, ModelName]
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref.py
new file mode 100644
index 000000000..b60e5a100
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref.py
@@ -0,0 +1,64 @@
+from typing import Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="ModelWithRecursiveRef")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithRecursiveRef:
+    """
+    Attributes:
+        recursive (Union[Unset, ModelWithRecursiveRef]):
+    """
+
+    recursive: Union[Unset, "ModelWithRecursiveRef"] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        recursive: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.recursive, Unset):
+            recursive = self.recursive.to_dict()
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if recursive is not UNSET:
+            field_dict["recursive"] = recursive
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        _recursive = d.pop("recursive", UNSET)
+        recursive: Union[Unset, ModelWithRecursiveRef]
+        if isinstance(_recursive, Unset):
+            recursive = UNSET
+        else:
+            recursive = ModelWithRecursiveRef.from_dict(_recursive)
+
+        model_with_recursive_ref = cls(
+            recursive=recursive,
+        )
+
+        model_with_recursive_ref.additional_properties = d
+        return model_with_recursive_ref
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> Any:
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref_in_additional_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref_in_additional_properties.py
new file mode 100644
index 000000000..64d327ee6
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_recursive_ref_in_additional_properties.py
@@ -0,0 +1,52 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="ModelWithRecursiveRefInAdditionalProperties")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithRecursiveRefInAdditionalProperties:
+    """ """
+
+    additional_properties: Dict[str, "ModelWithRecursiveRefInAdditionalProperties"] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.to_dict()
+
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        model_with_recursive_ref_in_additional_properties = cls()
+
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = ModelWithRecursiveRefInAdditionalProperties.from_dict(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        model_with_recursive_ref_in_additional_properties.additional_properties = additional_properties
+        return model_with_recursive_ref_in_additional_properties
+
+    @property
+    def additional_keys(self) -> List[str]:
+        return list(self.additional_properties.keys())
+
+    def __getitem__(self, key: str) -> "ModelWithRecursiveRefInAdditionalProperties":
+        return self.additional_properties[key]
+
+    def __setitem__(self, key: str, value: "ModelWithRecursiveRefInAdditionalProperties") -> None:
+        self.additional_properties[key] = value
+
+    def __delitem__(self, key: str) -> None:
+        del self.additional_properties[key]
+
+    def __contains__(self, key: str) -> bool:
+        return key in self.additional_properties
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py
index e89861520..0c6cb6d3a 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py
@@ -1,11 +1,14 @@
-from typing import Any, Dict, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar, Union
 
 import attr
 
-from ..models.model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
-from ..models.model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
+    from ..models.model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1
+
+
 T = TypeVar("T", bound="ModelWithUnionPropertyInlined")
 
 
@@ -13,12 +16,14 @@
 class ModelWithUnionPropertyInlined:
     """
     Attributes:
-        fruit (Union[ModelWithUnionPropertyInlinedFruitType0, ModelWithUnionPropertyInlinedFruitType1, Unset]):
+        fruit (Union['ModelWithUnionPropertyInlinedFruitType0', 'ModelWithUnionPropertyInlinedFruitType1', Unset]):
     """
 
-    fruit: Union[ModelWithUnionPropertyInlinedFruitType0, ModelWithUnionPropertyInlinedFruitType1, Unset] = UNSET
+    fruit: Union["ModelWithUnionPropertyInlinedFruitType0", "ModelWithUnionPropertyInlinedFruitType1", Unset] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
+        from ..models.model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
+
         fruit: Union[Dict[str, Any], Unset]
         if isinstance(self.fruit, Unset):
             fruit = UNSET
@@ -42,11 +47,14 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
+        from ..models.model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1
+
         d = src_dict.copy()
 
         def _parse_fruit(
             data: object,
-        ) -> Union[ModelWithUnionPropertyInlinedFruitType0, ModelWithUnionPropertyInlinedFruitType1, Unset]:
+        ) -> Union["ModelWithUnionPropertyInlinedFruitType0", "ModelWithUnionPropertyInlinedFruitType1", Unset]:
             if isinstance(data, Unset):
                 return data
             try:
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_responses_unions_simple_before_complex_response_200.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_responses_unions_simple_before_complex_response_200.py
index 6ce78fcd5..579c4dbd6 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/post_responses_unions_simple_before_complex_response_200.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_responses_unions_simple_before_complex_response_200.py
@@ -1,10 +1,12 @@
-from typing import Any, Dict, List, Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union, cast
 
 import attr
 
-from ..models.post_responses_unions_simple_before_complex_response_200a_type_1 import (
-    PostResponsesUnionsSimpleBeforeComplexResponse200AType1,
-)
+if TYPE_CHECKING:
+    from ..models.post_responses_unions_simple_before_complex_response_200a_type_1 import (
+        PostResponsesUnionsSimpleBeforeComplexResponse200AType1,
+    )
+
 
 T = TypeVar("T", bound="PostResponsesUnionsSimpleBeforeComplexResponse200")
 
@@ -13,13 +15,17 @@
 class PostResponsesUnionsSimpleBeforeComplexResponse200:
     """
     Attributes:
-        a (Union[PostResponsesUnionsSimpleBeforeComplexResponse200AType1, str]):
+        a (Union['PostResponsesUnionsSimpleBeforeComplexResponse200AType1', str]):
     """
 
-    a: Union[PostResponsesUnionsSimpleBeforeComplexResponse200AType1, str]
+    a: Union["PostResponsesUnionsSimpleBeforeComplexResponse200AType1", str]
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        from ..models.post_responses_unions_simple_before_complex_response_200a_type_1 import (
+            PostResponsesUnionsSimpleBeforeComplexResponse200AType1,
+        )
+
         a: Union[Dict[str, Any], str]
 
         if isinstance(self.a, PostResponsesUnionsSimpleBeforeComplexResponse200AType1):
@@ -40,9 +46,13 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.post_responses_unions_simple_before_complex_response_200a_type_1 import (
+            PostResponsesUnionsSimpleBeforeComplexResponse200AType1,
+        )
+
         d = src_dict.copy()
 
-        def _parse_a(data: object) -> Union[PostResponsesUnionsSimpleBeforeComplexResponse200AType1, str]:
+        def _parse_a(data: object) -> Union["PostResponsesUnionsSimpleBeforeComplexResponse200AType1", str]:
             try:
                 if not isinstance(data, dict):
                     raise TypeError()
@@ -51,7 +61,7 @@ def _parse_a(data: object) -> Union[PostResponsesUnionsSimpleBeforeComplexRespon
                 return a_type_1
             except:  # noqa: E722
                 pass
-            return cast(Union[PostResponsesUnionsSimpleBeforeComplexResponse200AType1, str], data)
+            return cast(Union["PostResponsesUnionsSimpleBeforeComplexResponse200AType1", str], data)
 
         a = _parse_a(d.pop("a"))
 
diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json
index 8298670fd..7030eb412 100644
--- a/end_to_end_tests/openapi.json
+++ b/end_to_end_tests/openapi.json
@@ -2102,6 +2102,108 @@
       "model.reference.with.Periods": {
         "type": "object",
         "description": "A Model with periods in its reference"
+      },
+      "ModelWithRecursiveRef": {
+        "type": "object",
+        "properties": {
+          "recursive": {
+            "$ref": "#/components/schemas/ModelWithRecursiveRef"
+          }
+        }
+      },
+      "ModelWithCircularRefA": {
+        "type": "object",
+        "properties": {
+          "circular": {
+            "$ref": "#/components/schemas/ModelWithCircularRefB"
+          }
+        }
+      },
+      "ModelWithCircularRefB": {
+        "type": "object",
+        "properties": {
+          "circular": {
+            "$ref": "#/components/schemas/ModelWithCircularRefA"
+          }
+        }
+      },
+      "ModelWithRecursiveRefInAdditionalProperties": {
+        "type": "object",
+        "additionalProperties": {
+          "$ref": "#/components/schemas/ModelWithRecursiveRefInAdditionalProperties"
+        }
+      },
+      "ModelWithCircularRefInAdditionalPropertiesA": {
+        "type": "object",
+        "additionalProperties": {
+          "$ref": "#/components/schemas/ModelWithCircularRefInAdditionalPropertiesB"
+        }
+      },
+      "ModelWithCircularRefInAdditionalPropertiesB": {
+        "type": "object",
+        "additionalProperties": {
+          "$ref": "#/components/schemas/ModelWithCircularRefInAdditionalPropertiesA"
+        }
+      },
+      "AnArrayWithARecursiveRefInItemsObject": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "properties": {
+            "recursive": {
+              "$ref": "#/components/schemas/AnArrayWithARecursiveRefInItemsObject"
+            }
+          }
+        }
+      },
+      "AnArrayWithACircularRefInItemsObjectA": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "properties": {
+            "circular": {
+              "$ref": "#/components/schemas/AnArrayWithACircularRefInItemsObjectB"
+            }
+          }
+        }
+      },
+      "AnArrayWithACircularRefInItemsObjectB": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "properties": {
+            "circular": {
+              "$ref": "#/components/schemas/AnArrayWithACircularRefInItemsObjectA"
+            }
+          }
+        }
+      },
+      "AnArrayWithARecursiveRefInItemsObjectAdditionalProperties": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/components/schemas/AnArrayWithARecursiveRefInItemsObjectAdditionalProperties"
+          }
+        }
+      },
+      "AnArrayWithACircularRefInItemsObjectAdditionalPropertiesA": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/components/schemas/AnArrayWithACircularRefInItemsObjectAdditionalPropertiesB"
+          }
+        }
+      },
+      "AnArrayWithACircularRefInItemsObjectAdditionalPropertiesB": {
+        "type": "array",
+        "items": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/components/schemas/AnArrayWithACircularRefInItemsObjectAdditionalPropertiesA"
+          }
+        }
       }
     },
     "parameters": {
diff --git a/integration-tests/integration_tests/models/public_error.py b/integration-tests/integration_tests/models/public_error.py
index 49e928b3d..d5281d8ff 100644
--- a/integration-tests/integration_tests/models/public_error.py
+++ b/integration-tests/integration_tests/models/public_error.py
@@ -1,10 +1,13 @@
-from typing import Any, Dict, List, Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union, cast
 
 import attr
 
-from ..models.problem import Problem
 from ..types import UNSET, Unset
 
+if TYPE_CHECKING:
+    from ..models.problem import Problem
+
+
 T = TypeVar("T", bound="PublicError")
 
 
@@ -14,13 +17,13 @@ class PublicError:
     Attributes:
         errors (Union[Unset, List[str]]):
         extra_parameters (Union[Unset, List[str]]):
-        invalid_parameters (Union[Unset, List[Problem]]):
+        invalid_parameters (Union[Unset, List['Problem']]):
         missing_parameters (Union[Unset, List[str]]):
     """
 
     errors: Union[Unset, List[str]] = UNSET
     extra_parameters: Union[Unset, List[str]] = UNSET
-    invalid_parameters: Union[Unset, List[Problem]] = UNSET
+    invalid_parameters: Union[Unset, List["Problem"]] = UNSET
     missing_parameters: Union[Unset, List[str]] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
@@ -61,6 +64,8 @@ def to_dict(self) -> Dict[str, Any]:
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        from ..models.problem import Problem
+
         d = src_dict.copy()
         errors = cast(List[str], d.pop("errors", UNSET))
 
diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py
index b6c2a5411..7c63cfbaf 100644
--- a/openapi_python_client/parser/openapi.py
+++ b/openapi_python_client/parser/openapi.py
@@ -99,6 +99,9 @@ def generate_operation_id(*, path: str, method: str) -> str:
     return f"{method}_{clean_path}"
 
 
+models_relative_prefix: str = "..."
+
+
 # pylint: disable=too-many-instance-attributes
 @dataclass
 class Endpoint:
@@ -239,15 +242,19 @@ def _add_body(
                 schemas,
             )
 
+        # No reasons to use lazy imports in endpoints, so add lazy imports to relative here.
         if form_body is not None:
             endpoint.form_body = form_body
-            endpoint.relative_imports.update(endpoint.form_body.get_imports(prefix="..."))
+            endpoint.relative_imports.update(endpoint.form_body.get_imports(prefix=models_relative_prefix))
+            endpoint.relative_imports.update(endpoint.form_body.get_lazy_imports(prefix=models_relative_prefix))
         if multipart_body is not None:
             endpoint.multipart_body = multipart_body
-            endpoint.relative_imports.update(endpoint.multipart_body.get_imports(prefix="..."))
+            endpoint.relative_imports.update(endpoint.multipart_body.get_imports(prefix=models_relative_prefix))
+            endpoint.relative_imports.update(endpoint.multipart_body.get_lazy_imports(prefix=models_relative_prefix))
         if json_body is not None:
             endpoint.json_body = json_body
-            endpoint.relative_imports.update(endpoint.json_body.get_imports(prefix="..."))
+            endpoint.relative_imports.update(endpoint.json_body.get_imports(prefix=models_relative_prefix))
+            endpoint.relative_imports.update(endpoint.json_body.get_lazy_imports(prefix=models_relative_prefix))
         return endpoint, schemas
 
     @staticmethod
@@ -287,7 +294,10 @@ def _add_responses(
                     )
                 )
                 continue
-            endpoint.relative_imports |= response.prop.get_imports(prefix="...")
+
+            # No reasons to use lazy imports in endpoints, so add lazy imports to relative here.
+            endpoint.relative_imports |= response.prop.get_lazy_imports(prefix=models_relative_prefix)
+            endpoint.relative_imports |= response.prop.get_imports(prefix=models_relative_prefix)
             endpoint.responses.append(response)
         return endpoint, schemas
 
@@ -424,7 +434,9 @@ def add_parameters(
                 # There is no NULL for query params, so nullable and not required are the same.
                 prop = attr.evolve(prop, required=False, nullable=True)
 
-            endpoint.relative_imports.update(prop.get_imports(prefix="..."))
+            # No reasons to use lazy imports in endpoints, so add lazy imports to relative here.
+            endpoint.relative_imports.update(prop.get_lazy_imports(prefix=models_relative_prefix))
+            endpoint.relative_imports.update(prop.get_imports(prefix=models_relative_prefix))
             endpoint.used_python_identifiers.add(prop.python_name)
             parameters_by_location[param.param_in][prop.name] = prop
 
@@ -498,11 +510,11 @@ def from_data(
 
     def response_type(self) -> str:
         """Get the Python type of any response from this endpoint"""
-        types = sorted({response.prop.get_type_string() for response in self.responses})
+        types = sorted({response.prop.get_type_string(quoted=False) for response in self.responses})
         if len(types) == 0:
             return "Any"
         if len(types) == 1:
-            return self.responses[0].prop.get_type_string()
+            return self.responses[0].prop.get_type_string(quoted=False)
         return f"Union[{', '.join(types)}]"
 
     def iter_all_parameters(self) -> Iterator[Property]:
diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index 3eb678c62..bc22528b3 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -22,11 +22,12 @@
 from ..errors import ParameterError, ParseError, PropertyError, ValidationError
 from .converter import convert, convert_chain
 from .enum_property import EnumProperty
-from .model_property import ModelProperty, build_model_property
+from .model_property import ModelProperty, build_model_property, process_model
 from .property import Property
 from .schemas import (
     Class,
     Parameters,
+    ReferencePath,
     Schemas,
     parse_reference_path,
     update_parameters_with_data,
@@ -187,11 +188,12 @@ class ListProperty(Property, Generic[InnerProp]):
     inner_property: InnerProp
     template: ClassVar[str] = "list_property.py.jinja"
 
-    def get_base_type_string(self) -> str:
-        return f"List[{self.inner_property.get_type_string()}]"
+    # pylint: disable=unused-argument
+    def get_base_type_string(self, *, quoted: bool = False) -> str:
+        return f"List[{self.inner_property.get_type_string(quoted=not self.inner_property.is_base_type)}]"
 
-    def get_base_json_type_string(self) -> str:
-        return f"List[{self.inner_property.get_type_string(json=True)}]"
+    def get_base_json_type_string(self, *, quoted: bool = False) -> str:
+        return f"List[{self.inner_property.get_type_string(json=True, quoted=not self.inner_property.is_base_type)}]"
 
     def get_instance_type_string(self) -> str:
         """Get a string representation of runtime type that should be used for `isinstance` checks"""
@@ -210,6 +212,11 @@ def get_imports(self, *, prefix: str) -> Set[str]:
         imports.add("from typing import cast, List")
         return imports
 
+    def get_lazy_imports(self, *, prefix: str) -> Set[str]:
+        lazy_imports = super().get_lazy_imports(prefix=prefix)
+        lazy_imports.update(self.inner_property.get_lazy_imports(prefix=prefix))
+        return lazy_imports
+
 
 @attr.s(auto_attribs=True, frozen=True)
 class UnionProperty(Property):
@@ -219,7 +226,9 @@ class UnionProperty(Property):
     template: ClassVar[str] = "union_property.py.jinja"
 
     def _get_inner_type_strings(self, json: bool = False) -> Set[str]:
-        return {p.get_type_string(no_optional=True, json=json) for p in self.inner_properties}
+        return {
+            p.get_type_string(no_optional=True, json=json, quoted=not p.is_base_type) for p in self.inner_properties
+        }
 
     @staticmethod
     def _get_type_string_from_inner_type_strings(inner_types: Set[str]) -> str:
@@ -227,10 +236,11 @@ def _get_type_string_from_inner_type_strings(inner_types: Set[str]) -> str:
             return inner_types.pop()
         return f"Union[{', '.join(sorted(inner_types))}]"
 
-    def get_base_type_string(self) -> str:
+    # pylint: disable=unused-argument
+    def get_base_type_string(self, *, quoted: bool = False) -> str:
         return self._get_type_string_from_inner_type_strings(self._get_inner_type_strings(json=False))
 
-    def get_base_json_type_string(self) -> str:
+    def get_base_json_type_string(self, *, quoted: bool = False) -> str:
         return self._get_type_string_from_inner_type_strings(self._get_inner_type_strings(json=True))
 
     def get_type_strings_in_union(self, no_optional: bool = False, json: bool = False) -> Set[str]:
@@ -255,7 +265,13 @@ def get_type_strings_in_union(self, no_optional: bool = False, json: bool = Fals
             type_strings.add("Unset")
         return type_strings
 
-    def get_type_string(self, no_optional: bool = False, json: bool = False) -> str:
+    def get_type_string(
+        self,
+        no_optional: bool = False,
+        json: bool = False,
+        *,
+        quoted: bool = False,
+    ) -> str:
         """
         Get a string representation of type that should be used when declaring this property.
         This implementation differs slightly from `Property.get_type_string` in order to collapse
@@ -278,6 +294,12 @@ def get_imports(self, *, prefix: str) -> Set[str]:
         imports.add("from typing import cast, Union")
         return imports
 
+    def get_lazy_imports(self, *, prefix: str) -> Set[str]:
+        lazy_imports = super().get_lazy_imports(prefix=prefix)
+        for inner_prop in self.inner_properties:
+            lazy_imports.update(inner_prop.get_lazy_imports(prefix=prefix))
+        return lazy_imports
+
 
 def _string_based_property(
     name: str, required: bool, data: oai.Schema, config: Config
@@ -493,7 +515,15 @@ def build_union_property(
 
 
 def build_list_property(
-    *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config
+    *,
+    data: oai.Schema,
+    name: str,
+    required: bool,
+    schemas: Schemas,
+    parent_name: str,
+    config: Config,
+    process_properties: bool,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Tuple[Union[ListProperty[Any], PropertyError], Schemas]:
     """
     Build a ListProperty the right way, use this instead of the normal constructor.
@@ -513,7 +543,14 @@ def build_list_property(
     if data.items is None:
         return PropertyError(data=data, detail="type array must have items defined"), schemas
     inner_prop, schemas = property_from_data(
-        name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name=parent_name, config=config
+        name=f"{name}_item",
+        required=True,
+        data=data.items,
+        schemas=schemas,
+        parent_name=parent_name,
+        config=config,
+        process_properties=process_properties,
+        roots=roots,
     )
     if isinstance(inner_prop, PropertyError):
         inner_prop.header = f'invalid data in items of array named "{name}"'
@@ -541,6 +578,7 @@ def _property_from_ref(
     data: oai.Reference,
     schemas: Schemas,
     config: Config,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
     ref_path = parse_reference_path(data.ref)
     if isinstance(ref_path, ParseError):
@@ -563,6 +601,7 @@ def _property_from_ref(
                 return default, schemas
             prop = attr.evolve(prop, default=default)
 
+    schemas.add_dependencies(ref_path=ref_path, roots=roots)
     return prop, schemas
 
 
@@ -574,17 +613,21 @@ def _property_from_data(
     schemas: Schemas,
     parent_name: str,
     config: Config,
+    process_properties: bool,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
     """Generate a Property from the OpenAPI dictionary representation of it"""
     name = utils.remove_string_escapes(name)
     if isinstance(data, oai.Reference):
-        return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas, config=config)
+        return _property_from_ref(
+            name=name, required=required, parent=None, data=data, schemas=schemas, config=config, roots=roots
+        )
 
     sub_data: List[Union[oai.Schema, oai.Reference]] = data.allOf + data.anyOf + data.oneOf
     # A union of a single reference should just be passed through to that reference (don't create copy class)
     if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference):
         return _property_from_ref(
-            name=name, required=required, parent=data, data=sub_data[0], schemas=schemas, config=config
+            name=name, required=required, parent=data, data=sub_data[0], schemas=schemas, config=config, roots=roots
         )
 
     if data.enum:
@@ -644,11 +687,25 @@ def _property_from_data(
         )
     if data.type == oai.DataType.ARRAY:
         return build_list_property(
-            data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config
+            data=data,
+            name=name,
+            required=required,
+            schemas=schemas,
+            parent_name=parent_name,
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
     if data.type == oai.DataType.OBJECT or data.allOf:
         return build_model_property(
-            data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config
+            data=data,
+            name=name,
+            schemas=schemas,
+            required=required,
+            parent_name=parent_name,
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
     return (
         AnyProperty(
@@ -672,6 +729,8 @@ def property_from_data(
     schemas: Schemas,
     parent_name: str,
     config: Config,
+    process_properties: bool = True,
+    roots: Set[Union[ReferencePath, utils.ClassName]] = None,
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
     """
     Build a Property from an OpenAPI schema or reference. This Property represents a single input or output for a
@@ -691,23 +750,33 @@ def property_from_data(
             of duplication.
         config: Contains the parsed config that the user provided to tweak generation settings. Needed to apply class
             name overrides for generated classes.
-
+        process_properties: If the new property is a ModelProperty, determines whether it will be initialized with
+            property data
+        roots: The set of `ReferencePath`s and `ClassName`s to remove from the schemas if a child reference becomes
+            invalid
     Returns:
         A tuple containing either the parsed Property or a PropertyError (if something went wrong) and the updated
         Schemas (including any new classes that should be generated).
     """
+    roots = roots or set()
     try:
         return _property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name=parent_name, config=config
+            name=name,
+            required=required,
+            data=data,
+            schemas=schemas,
+            parent_name=parent_name,
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
     except ValidationError:
         return PropertyError(detail="Failed to validate default value", data=data), schemas
 
 
-def build_schemas(
+def _create_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] = []
@@ -739,6 +808,74 @@ def build_schemas(
     return schemas
 
 
+def _propogate_removal(*, root: Union[ReferencePath, utils.ClassName], schemas: Schemas, error: PropertyError) -> None:
+    if isinstance(root, utils.ClassName):
+        schemas.classes_by_name.pop(root, None)
+        return
+    if root in schemas.classes_by_reference:
+        error.detail = error.detail or ""
+        error.detail += f"\n{root}"
+        del schemas.classes_by_reference[root]
+        for child in schemas.dependencies.get(root, set()):
+            _propogate_removal(root=child, schemas=schemas, error=error)
+
+
+def _process_model_errors(
+    model_errors: List[Tuple[ModelProperty, PropertyError]], *, schemas: Schemas
+) -> List[PropertyError]:
+    for model, error in model_errors:
+        error.detail = error.detail or ""
+        error.detail += "\n\nFailure to process schema has resulted in the removal of:"
+        for root in model.roots:
+            _propogate_removal(root=root, schemas=schemas, error=error)
+    return [error for _, error in model_errors]
+
+
+def _process_models(*, schemas: Schemas, config: Config) -> Schemas:
+    to_process = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty))
+    still_making_progress = True
+    final_model_errors: List[Tuple[ModelProperty, PropertyError]] = []
+    latest_model_errors: List[Tuple[ModelProperty, PropertyError]] = []
+
+    # Models which refer to other models in their allOf must be processed after their referenced models
+    while still_making_progress:
+        still_making_progress = False
+        # Only accumulate errors from the last round, since we might fix some along the way
+        latest_model_errors = []
+        next_round = []
+        for model_prop in to_process:
+            schemas_or_err = process_model(model_prop, schemas=schemas, config=config)
+            if isinstance(schemas_or_err, PropertyError):
+                schemas_or_err.header = f"\nUnable to process schema {model_prop.name}:"
+                if isinstance(schemas_or_err.data, oai.Reference) and schemas_or_err.data.ref.endswith(
+                    f"/{model_prop.class_info.name}"
+                ):
+                    schemas_or_err.detail = schemas_or_err.detail or ""
+                    schemas_or_err.detail += "\n\nRecursive allOf reference found"
+                    final_model_errors.append((model_prop, schemas_or_err))
+                    continue
+                latest_model_errors.append((model_prop, schemas_or_err))
+                next_round.append(model_prop)
+                continue
+            schemas = schemas_or_err
+            still_making_progress = True
+        to_process = (prop for prop in next_round)
+
+    final_model_errors.extend(latest_model_errors)
+    errors = _process_model_errors(final_model_errors, schemas=schemas)
+    schemas.errors.extend(errors)
+    return schemas
+
+
+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"""
+    schemas = _create_schemas(components=components, schemas=schemas, config=config)
+    schemas = _process_models(schemas=schemas, config=config)
+    return schemas
+
+
 def build_parameters(
     *,
     components: Dict[str, Union[oai.Reference, oai.Parameter]],
diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py
index 39b89a2bc..26c4e2ffc 100644
--- a/openapi_python_client/parser/properties/enum_property.py
+++ b/openapi_python_client/parser/properties/enum_property.py
@@ -30,10 +30,11 @@ class EnumProperty(Property):
         oai.ParameterLocation.HEADER,
     }
 
-    def get_base_type_string(self) -> str:
+    # pylint: disable=unused-argument
+    def get_base_type_string(self, *, quoted: bool = False) -> str:
         return self.class_info.name
 
-    def get_base_json_type_string(self) -> str:
+    def get_base_json_type_string(self, *, quoted: bool = False) -> str:
         return self.value_type.__name__
 
     def get_imports(self, *, prefix: str) -> Set[str]:
diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py
index 6e68a8f8e..18e4c4c43 100644
--- a/openapi_python_client/parser/properties/model_property.py
+++ b/openapi_python_client/parser/properties/model_property.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from itertools import chain
 from typing import ClassVar, Dict, List, NamedTuple, Optional, Set, Tuple, Union
 
@@ -9,7 +11,7 @@
 from ..errors import ParseError, PropertyError
 from .enum_property import EnumProperty
 from .property import Property
-from .schemas import Class, Schemas, parse_reference_path
+from .schemas import Class, ReferencePath, Schemas, parse_reference_path
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -17,19 +19,31 @@ class ModelProperty(Property):
     """A property which refers to another Schema"""
 
     class_info: Class
-    required_properties: List[Property]
-    optional_properties: List[Property]
+    data: oai.Schema
     description: str
-    relative_imports: Set[str]
-    additional_properties: Union[bool, Property]
+    roots: Set[Union[ReferencePath, utils.ClassName]]
+    required_properties: Optional[List[Property]]
+    optional_properties: Optional[List[Property]]
+    relative_imports: Optional[Set[str]]
+    lazy_imports: Optional[Set[str]]
+    additional_properties: Optional[Union[bool, Property]]
     _json_type_string: ClassVar[str] = "Dict[str, Any]"
 
     template: ClassVar[str] = "model_property.py.jinja"
     json_is_dict: ClassVar[bool] = True
     is_multipart_body: bool = False
 
-    def get_base_type_string(self) -> str:
-        return self.class_info.name
+    def __attrs_post_init__(self) -> None:
+        if self.relative_imports:
+            self.set_relative_imports(self.relative_imports)
+
+    @property
+    def self_import(self) -> str:
+        """Constructs a self import statement from this ModelProperty's attributes"""
+        return f"models.{self.class_info.module_name} import {self.class_info.name}"
+
+    def get_base_type_string(self, *, quoted: bool = False) -> str:
+        return f'"{self.class_info.name}"' if quoted else self.class_info.name
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -42,13 +56,69 @@ def get_imports(self, *, prefix: str) -> Set[str]:
         imports = super().get_imports(prefix=prefix)
         imports.update(
             {
-                f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}",
                 "from typing import Dict",
                 "from typing import cast",
             }
         )
         return imports
 
+    def get_lazy_imports(self, *, prefix: str) -> Set[str]:
+        """Get a set of lazy import strings that should be included when this property is used somewhere
+
+        Args:
+            prefix: A prefix to put before any relative (local) module names. This should be the number of . to get
+            back to the root of the generated client.
+        """
+        return {f"from {prefix}{self.self_import}"}
+
+    def set_relative_imports(self, relative_imports: Set[str]) -> None:
+        """Set the relative imports set for this ModelProperty, filtering out self imports
+
+        Args:
+            relative_imports: The set of relative import strings
+        """
+        object.__setattr__(self, "relative_imports", {ri for ri in relative_imports if self.self_import not in ri})
+
+    def set_lazy_imports(self, lazy_imports: Set[str]) -> None:
+        """Set the lazy imports set for this ModelProperty, filtering out self imports
+
+        Args:
+            lazy_imports: The set of lazy import strings
+        """
+        object.__setattr__(self, "lazy_imports", {li for li in lazy_imports if self.self_import not in li})
+
+    def get_type_string(
+        self,
+        no_optional: bool = False,
+        json: bool = False,
+        *,
+        quoted: bool = False,
+    ) -> str:
+        """
+        Get a string representation of type that should be used when declaring this property
+
+        Args:
+            no_optional: Do not include Optional or Unset even if the value is optional (needed for isinstance checks)
+            json: True if the type refers to the property after JSON serialization
+        """
+        if json:
+            type_string = self.get_base_json_type_string()
+        else:
+            type_string = self.get_base_type_string()
+
+        if quoted:
+            if type_string == self.class_info.name:
+                type_string = f"'{type_string}'"
+
+        if no_optional or (self.required and not self.nullable):
+            return type_string
+        if self.required and self.nullable:
+            return f"Optional[{type_string}]"
+        if not self.required and self.nullable:
+            return f"Union[Unset, None, {type_string}]"
+
+        return f"Union[Unset, {type_string}]"
+
 
 def _values_are_subset(first: EnumProperty, second: EnumProperty) -> bool:
     return set(first.values.items()) <= set(second.values.items())
@@ -108,17 +178,24 @@ class _PropertyData(NamedTuple):
     optional_props: List[Property]
     required_props: List[Property]
     relative_imports: Set[str]
+    lazy_imports: Set[str]
     schemas: Schemas
 
 
-# pylint: disable=too-many-locals,too-many-branches
+# pylint: disable=too-many-locals,too-many-branches,too-many-return-statements
 def _process_properties(
-    *, data: oai.Schema, schemas: Schemas, class_name: str, config: Config
+    *,
+    data: oai.Schema,
+    schemas: Schemas,
+    class_name: utils.ClassName,
+    config: Config,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Union[_PropertyData, PropertyError]:
     from . import property_from_data
 
     properties: Dict[str, Property] = {}
     relative_imports: Set[str] = set()
+    lazy_imports: Set[str] = set()
     required_set = set(data.required or [])
 
     def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]:
@@ -145,10 +222,16 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]:
                 return PropertyError(f"Reference {sub_prop.ref} not found")
             if not isinstance(sub_model, ModelProperty):
                 return PropertyError("Cannot take allOf a non-object")
+            # Properties of allOf references first should be processed first
+            if not (
+                isinstance(sub_model.required_properties, list) and isinstance(sub_model.optional_properties, list)
+            ):
+                return PropertyError(f"Reference {sub_model.name} in allOf was not processed", data=sub_prop)
             for prop in chain(sub_model.required_properties, sub_model.optional_properties):
                 err = _add_if_no_conflict(prop)
                 if err is not None:
                     return err
+            schemas.add_dependencies(ref_path=ref_path, roots=roots)
         else:
             unprocessed_props.update(sub_prop.properties or {})
             required_set.update(sub_prop.required or [])
@@ -157,7 +240,13 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]:
         prop_required = key in required_set
         prop_or_error: Union[Property, PropertyError, None]
         prop_or_error, schemas = property_from_data(
-            name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name, config=config
+            name=key,
+            required=prop_required,
+            data=value,
+            schemas=schemas,
+            parent_name=class_name,
+            config=config,
+            roots=roots,
         )
         if isinstance(prop_or_error, Property):
             prop_or_error = _add_if_no_conflict(prop_or_error)
@@ -171,12 +260,15 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]:
             required_properties.append(prop)
         else:
             optional_properties.append(prop)
+
+        lazy_imports.update(prop.get_lazy_imports(prefix=".."))
         relative_imports.update(prop.get_imports(prefix=".."))
 
     return _PropertyData(
         optional_props=optional_properties,
         required_props=required_properties,
         relative_imports=relative_imports,
+        lazy_imports=lazy_imports,
         schemas=schemas,
     )
 
@@ -185,8 +277,9 @@ def _get_additional_properties(
     *,
     schema_additional: Union[None, bool, oai.Reference, oai.Schema],
     schemas: Schemas,
-    class_name: str,
+    class_name: utils.ClassName,
     config: Config,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Tuple[Union[bool, Property, PropertyError], Schemas]:
     from . import property_from_data
 
@@ -207,12 +300,82 @@ def _get_additional_properties(
         schemas=schemas,
         parent_name=class_name,
         config=config,
+        roots=roots,
     )
     return additional_properties, schemas
 
 
+def _process_property_data(
+    *,
+    data: oai.Schema,
+    schemas: Schemas,
+    class_info: Class,
+    config: Config,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
+) -> Tuple[Union[Tuple[_PropertyData, Union[bool, Property]], PropertyError], Schemas]:
+    property_data = _process_properties(
+        data=data, schemas=schemas, class_name=class_info.name, config=config, roots=roots
+    )
+    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,
+        roots=roots,
+    )
+    if isinstance(additional_properties, Property):
+        property_data.relative_imports.update(additional_properties.get_imports(prefix=".."))
+        property_data.lazy_imports.update(additional_properties.get_lazy_imports(prefix=".."))
+    elif isinstance(additional_properties, PropertyError):
+        return additional_properties, schemas
+
+    return (property_data, additional_properties), schemas
+
+
+def process_model(model_prop: ModelProperty, *, schemas: Schemas, config: Config) -> Union[Schemas, PropertyError]:
+    """Populate a ModelProperty instance's property data
+    Args:
+        model_prop: The ModelProperty to build property data for
+        schemas: Existing Schemas
+        config: Config data for this run of the generator, used to modifying names
+    Returns:
+        Either the updated `schemas` input or a `PropertyError` if something went wrong.
+    """
+    data_or_err, schemas = _process_property_data(
+        data=model_prop.data,
+        schemas=schemas,
+        class_info=model_prop.class_info,
+        config=config,
+        roots=model_prop.roots,
+    )
+    if isinstance(data_or_err, PropertyError):
+        return data_or_err
+
+    property_data, additional_properties = data_or_err
+
+    object.__setattr__(model_prop, "required_properties", property_data.required_props)
+    object.__setattr__(model_prop, "optional_properties", property_data.optional_props)
+    model_prop.set_relative_imports(property_data.relative_imports)
+    model_prop.set_lazy_imports(property_data.lazy_imports)
+    object.__setattr__(model_prop, "additional_properties", additional_properties)
+    return schemas
+
+
+# pylint: disable=too-many-locals
 def build_model_property(
-    *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str], config: Config
+    *,
+    data: oai.Schema,
+    name: str,
+    schemas: Schemas,
+    required: bool,
+    parent_name: Optional[str],
+    config: Config,
+    process_properties: bool,
+    roots: Set[Union[ReferencePath, utils.ClassName]],
 ) -> Tuple[Union[ModelProperty, PropertyError], Schemas]:
     """
     A single ModelProperty from its OAI data
@@ -225,36 +388,49 @@ def build_model_property(
         required: Whether or not this property is required by the parent (affects typing)
         parent_name: The name of the property that this property is inside of (affects class naming)
         config: Config data for this run of the generator, used to modifying names
+        roots: Set of strings that identify schema objects on which the new ModelProperty will depend
+        process_properties: Determines whether the new ModelProperty will be initialized with property data
     """
     class_string = data.title or name
     if parent_name:
         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
+    model_roots = {*roots, class_info.name}
+    required_properties: Optional[List[Property]] = None
+    optional_properties: Optional[List[Property]] = None
+    relative_imports: Optional[Set[str]] = None
+    lazy_imports: Optional[Set[str]] = None
+    additional_properties: Optional[Union[bool, Property]] = None
+    if process_properties:
+        data_or_err, schemas = _process_property_data(
+            data=data, schemas=schemas, class_info=class_info, config=config, roots=model_roots
+        )
+        if isinstance(data_or_err, PropertyError):
+            return data_or_err, schemas
+        property_data, additional_properties = data_or_err
+        required_properties = property_data.required_props
+        optional_properties = property_data.optional_props
+        relative_imports = property_data.relative_imports
+        lazy_imports = property_data.lazy_imports
+        for root in roots:
+            if isinstance(root, utils.ClassName):
+                continue
+            schemas.add_dependencies(root, {class_info.name})
 
     prop = ModelProperty(
         class_info=class_info,
-        required_properties=property_data.required_props,
-        optional_properties=property_data.optional_props,
-        relative_imports=property_data.relative_imports,
+        data=data,
+        roots=model_roots,
+        required_properties=required_properties,
+        optional_properties=optional_properties,
+        relative_imports=relative_imports,
+        lazy_imports=lazy_imports,
+        additional_properties=additional_properties,
         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),
         example=data.example,
     )
diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py
index bcedfc3d9..5f82df224 100644
--- a/openapi_python_client/parser/properties/property.py
+++ b/openapi_python_client/parser/properties/property.py
@@ -1,6 +1,6 @@
 __all__ = ["Property"]
 
-from typing import ClassVar, Optional, Set
+from typing import TYPE_CHECKING, ClassVar, Optional, Set
 
 import attr
 
@@ -9,6 +9,11 @@
 from ...utils import PythonIdentifier
 from ..errors import ParseError
 
+if TYPE_CHECKING:  # pragma: no cover
+    from .model_property import ModelProperty
+else:
+    ModelProperty = "ModelProperty"  # pylint: disable=invalid-name
+
 
 @attr.s(auto_attribs=True, frozen=True)
 class Property:
@@ -60,15 +65,21 @@ def set_python_name(self, new_name: str, config: Config) -> None:
         """
         object.__setattr__(self, "python_name", PythonIdentifier(value=new_name, prefix=config.field_prefix))
 
-    def get_base_type_string(self) -> str:
-        """Get the string describing the Python type of this property."""
-        return self._type_string
-
-    def get_base_json_type_string(self) -> str:
-        """Get the string describing the JSON type of this property."""
-        return self._json_type_string
-
-    def get_type_string(self, no_optional: bool = False, json: bool = False) -> str:
+    def get_base_type_string(self, *, quoted: bool = False) -> str:
+        """Get the string describing the Python type of this property. Base types no require quoting."""
+        return f'"{self._type_string}"' if not self.is_base_type and quoted else self._type_string
+
+    def get_base_json_type_string(self, *, quoted: bool = False) -> str:
+        """Get the string describing the JSON type of this property. Base types no require quoting."""
+        return f'"{self._json_type_string}"' if not self.is_base_type and quoted else self._json_type_string
+
+    def get_type_string(
+        self,
+        no_optional: bool = False,
+        json: bool = False,
+        *,
+        quoted: bool = False,
+    ) -> str:
         """
         Get a string representation of type that should be used when declaring this property
 
@@ -77,9 +88,9 @@ def get_type_string(self, no_optional: bool = False, json: bool = False) -> str:
             json: True if the type refers to the property after JSON serialization
         """
         if json:
-            type_string = self.get_base_json_type_string()
+            type_string = self.get_base_json_type_string(quoted=quoted)
         else:
-            type_string = self.get_base_type_string()
+            type_string = self.get_base_type_string(quoted=quoted)
 
         if no_optional or (self.required and not self.nullable):
             return type_string
@@ -92,7 +103,7 @@ def get_type_string(self, no_optional: bool = False, json: bool = False) -> str:
 
     def get_instance_type_string(self) -> str:
         """Get a string representation of runtime type that should be used for `isinstance` checks"""
-        return self.get_type_string(no_optional=True)
+        return self.get_type_string(no_optional=True, quoted=False)
 
     # noinspection PyUnusedLocal
     def get_imports(self, *, prefix: str) -> Set[str]:
@@ -111,6 +122,16 @@ def get_imports(self, *, prefix: str) -> Set[str]:
             imports.add(f"from {prefix}types import UNSET, Unset")
         return imports
 
+    # pylint: disable=unused-argument,no-self-use)
+    def get_lazy_imports(self, *, prefix: str) -> Set[str]:
+        """Get a set of lazy import strings that should be included when this property is used somewhere
+
+        Args:
+            prefix: A prefix to put before any relative (local) module names. This should be the number of . to get
+            back to the root of the generated client.
+        """
+        return set()
+
     def to_string(self) -> str:
         """How this should be declared in a dataclass"""
         default: Optional[str]
@@ -122,8 +143,8 @@ def to_string(self) -> str:
             default = None
 
         if default is not None:
-            return f"{self.python_name}: {self.get_type_string()} = {default}"
-        return f"{self.python_name}: {self.get_type_string()}"
+            return f"{self.python_name}: {self.get_type_string(quoted=True)} = {default}"
+        return f"{self.python_name}: {self.get_type_string(quoted=True)}"
 
     def to_docstring(self) -> str:
         """Returns property docstring"""
@@ -133,3 +154,14 @@ def to_docstring(self) -> str:
         if self.example:
             doc += f" Example: {self.example}."
         return doc
+
+    @property
+    def is_base_type(self) -> bool:
+        """Base types, represented by any other of `Property` than `ModelProperty` should not be quoted."""
+        from . import ListProperty, ModelProperty, UnionProperty
+
+        return self.__class__.__name__ not in {
+            ModelProperty.__name__,
+            ListProperty.__name__,
+            UnionProperty.__name__,
+        }
diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index a0606b8c1..fd1af5c08 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -9,7 +9,7 @@
     "parameter_from_data",
 ]
 
-from typing import TYPE_CHECKING, Dict, List, NewType, Tuple, Union, cast
+from typing import TYPE_CHECKING, Dict, List, NewType, Set, Tuple, Union, cast
 from urllib.parse import urlparse
 
 import attr
@@ -26,10 +26,10 @@
     Property = "Property"  # pylint: disable=invalid-name
 
 
-_ReferencePath = NewType("_ReferencePath", str)
+ReferencePath = NewType("ReferencePath", str)
 
 
-def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]:
+def parse_reference_path(ref_path_raw: str) -> Union[ReferencePath, ParseError]:
     """
     Takes a raw string provided in a `$ref` and turns it into a validated `_ReferencePath` or a `ParseError` if
     validation fails.
@@ -40,7 +40,7 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]
     parsed = urlparse(ref_path_raw)
     if parsed.scheme or parsed.path:
         return ParseError(detail=f"Remote references such as {ref_path_raw} are not supported yet.")
-    return cast(_ReferencePath, parsed.fragment)
+    return cast(ReferencePath, parsed.fragment)
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -73,13 +73,24 @@ def from_string(*, string: str, config: Config) -> "Class":
 class Schemas:
     """Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums)"""
 
-    classes_by_reference: Dict[_ReferencePath, Property] = attr.ib(factory=dict)
+    classes_by_reference: Dict[ReferencePath, Property] = attr.ib(factory=dict)
+    dependencies: Dict[ReferencePath, Set[Union[ReferencePath, ClassName]]] = attr.ib(factory=dict)
     classes_by_name: Dict[ClassName, Property] = attr.ib(factory=dict)
     errors: List[ParseError] = attr.ib(factory=list)
 
+    def add_dependencies(self, ref_path: ReferencePath, roots: Set[Union[ReferencePath, ClassName]]) -> None:
+        """Record new dependencies on the given ReferencePath
+
+        Args:
+            ref_path: The ReferencePath being referenced
+            roots: A set of identifiers for the objects dependent on the object corresponding to `ref_path`
+        """
+        self.dependencies.setdefault(ref_path, set())
+        self.dependencies[ref_path].update(roots)
+
 
 def update_schemas_with_data(
-    *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config
+    *, ref_path: ReferencePath, data: oai.Schema, schemas: Schemas, config: Config
 ) -> Union[Schemas, PropertyError]:
     """
     Update a `Schemas` using some new reference.
@@ -100,7 +111,15 @@ def update_schemas_with_data(
 
     prop: Union[PropertyError, Property]
     prop, schemas = property_from_data(
-        data=data, name=ref_path, schemas=schemas, required=True, parent_name="", config=config
+        data=data,
+        name=ref_path,
+        schemas=schemas,
+        required=True,
+        parent_name="",
+        config=config,
+        # Don't process ModelProperty properties because schemas are still being created
+        process_properties=False,
+        roots={ref_path},
     )
 
     if isinstance(prop, PropertyError):
@@ -108,8 +127,7 @@ def update_schemas_with_data(
         prop.header = f"Unable to parse schema {ref_path}"
         if isinstance(prop.data, oai.Reference) and prop.data.ref.endswith(ref_path):  # pragma: nocover
             prop.detail += (
-                "\n\nRecursive and circular references are not supported. "
-                "See https://github.com/openapi-generators/openapi-python-client/issues/466"
+                "\n\nRecursive and circular references are not supported directly in an array schema's 'items' section"
             )
         return prop
 
@@ -121,7 +139,7 @@ def update_schemas_with_data(
 class Parameters:
     """Structure for containing all defined, shareable, and reusable parameters"""
 
-    classes_by_reference: Dict[_ReferencePath, Parameter] = attr.ib(factory=dict)
+    classes_by_reference: Dict[ReferencePath, Parameter] = attr.ib(factory=dict)
     classes_by_name: Dict[ClassName, Parameter] = attr.ib(factory=dict)
     errors: List[ParseError] = attr.ib(factory=list)
 
@@ -154,7 +172,7 @@ def parameter_from_data(
 
 
 def update_parameters_with_data(
-    *, ref_path: _ReferencePath, data: oai.Parameter, parameters: Parameters
+    *, ref_path: ReferencePath, data: oai.Parameter, parameters: Parameters
 ) -> Union[Parameters, ParameterError]:
     """
     Update a `Parameters` using some new reference.
diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja
index dc033c41a..98f2d2682 100644
--- a/openapi_python_client/templates/model.py.jinja
+++ b/openapi_python_client/templates/model.py.jinja
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO
+from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO, TYPE_CHECKING
 
 {% if model.additional_properties %}
 from typing import List
@@ -16,9 +16,16 @@ from ..types import UNSET, Unset
 {{ relative }}
 {% endfor %}
 
+{% for lazy_import in model.lazy_imports %}
+{% if loop.first %}
+if TYPE_CHECKING:
+{% endif %}
+  {{ lazy_import }}
+{% endfor %}
+
 
 {% if model.additional_properties %}
-{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string() %}
+{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string(quoted=not model.additional_properties.is_base_type) %}
 {% endif %}
 
 {% set class_name = model.class_info.name %}
@@ -113,6 +120,9 @@ return field_dict
 {% endmacro %}
 
     def to_dict(self) -> Dict[str, Any]:
+    {% for lazy_import in model.lazy_imports %}
+        {{ lazy_import }}
+    {% endfor %}
         {{ _to_dict() | indent(8) }}
 
 {% if model.is_multipart_body %}
@@ -122,6 +132,9 @@ return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+    {% for lazy_import in model.lazy_imports %}
+        {{ lazy_import }}
+    {% endfor %}
         d = src_dict.copy()
 {% for property in model.required_properties + model.optional_properties %}
     {% if property.required %}
@@ -146,6 +159,12 @@ return field_dict
 {% if model.additional_properties %}
     {% if model.additional_properties.template %}{# Can be a bool instead of an object #}
         {% import "property_templates/" + model.additional_properties.template as prop_template %}
+
+{% if model.additional_properties.lazy_imports %}
+    {% for lazy_import in model.additional_properties.lazy_imports %}
+        {{ lazy_import }}
+    {% endfor %}
+{% endif %}
     {% else %}
         {% set prop_template = None %}
     {% endif %}
diff --git a/poetry.lock b/poetry.lock
index af8869e7d..5f692bd21 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -383,14 +383,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pydantic"
-version = "1.9.0"
-description = "Data validation and settings management using python 3.6 type hinting"
+version = "1.10.2"
+description = "Data validation and settings management using python type hints"
 category = "main"
 optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.7"
 
 [package.dependencies]
-typing-extensions = ">=3.7.4.3"
+typing-extensions = ">=4.1.0"
 
 [package.extras]
 dotenv = ["python-dotenv (>=0.10.4)"]
@@ -631,10 +631,10 @@ python-versions = ">=3.6"
 click = ">=7.1.1,<9.0.0"
 
 [package.extras]
-all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)", "rich (>=10.11.0,<13.0.0)"]
-dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
-doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)"]
-test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=22.3.0,<23.0.0)", "isort (>=5.0.6,<6.0.0)", "rich (>=10.11.0,<13.0.0)"]
+test = ["rich (>=10.11.0,<13.0.0)", "isort (>=5.0.6,<6.0.0)", "black (>=22.3.0,<23.0.0)", "mypy (==0.910)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "coverage (>=5.2,<6.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest (>=4.4.0,<5.4.0)", "shellingham (>=1.3.0,<2.0.0)"]
+doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mkdocs (>=1.1.2,<2.0.0)"]
+dev = ["pre-commit (>=2.17.0,<3.0.0)", "flake8 (>=3.8.3,<4.0.0)", "autoflake (>=1.3.1,<2.0.0)"]
+all = ["rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)", "colorama (>=0.4.3,<0.5.0)"]
 
 [[package]]
 name = "types-certifi"
@@ -662,11 +662,11 @@ python-versions = "*"
 
 [[package]]
 name = "typing-extensions"
-version = "3.10.0.0"
-description = "Backported and Experimental Type Hints for Python 3.5+"
+version = "4.3.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
 category = "main"
 optional = false
-python-versions = "*"
+python-versions = ">=3.7"
 
 [[package]]
 name = "urllib3"
@@ -1036,43 +1036,7 @@ py = [
     {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
     {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
-pydantic = [
-    {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"},
-    {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"},
-    {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"},
-    {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"},
-    {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"},
-    {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"},
-    {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"},
-    {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"},
-    {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"},
-    {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"},
-    {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"},
-    {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"},
-    {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"},
-    {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"},
-    {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"},
-    {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"},
-    {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"},
-    {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"},
-    {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"},
-    {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"},
-    {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"},
-    {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"},
-    {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"},
-    {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"},
-    {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"},
-    {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"},
-    {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"},
-    {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"},
-    {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"},
-    {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"},
-    {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"},
-    {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"},
-    {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"},
-    {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"},
-    {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"},
-]
+pydantic = []
 pyflakes = [
     {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
     {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
@@ -1207,10 +1171,7 @@ typed-ast = [
     {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
     {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
 ]
-typer = [
-    {file = "typer-0.6.1-py3-none-any.whl", hash = "sha256:54b19e5df18654070a82f8c2aa1da456a4ac16a2a83e6dcd9f170e291c56338e"},
-    {file = "typer-0.6.1.tar.gz", hash = "sha256:2d5720a5e63f73eaf31edaa15f6ab87f35f0690f8ca233017d7d23d743a91d73"},
-]
+typer = []
 types-certifi = [
     {file = "types-certifi-2020.4.0.tar.gz", hash = "sha256:787d1a0c7897a1c658f8f7958ae57141b3fff13acb866e5bcd31cfb45037546f"},
     {file = "types_certifi-2020.4.0-py3-none-any.whl", hash = "sha256:0ffdbe451d3b02f6d2cfd87bcfb2f086a4ff1fa76a35d51cfc3771e261d7a8fd"},
@@ -1223,11 +1184,7 @@ types-pyyaml = [
     {file = "types-PyYAML-6.0.3.tar.gz", hash = "sha256:6ea4eefa8579e0ce022f785a62de2bcd647fad4a81df5cf946fd67e4b059920b"},
     {file = "types_PyYAML-6.0.3-py3-none-any.whl", hash = "sha256:8b50294b55a9db89498cdc5a65b1b4545112b6cd1cf4465bd693d828b0282a17"},
 ]
-typing-extensions = [
-    {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
-    {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
-    {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
-]
+typing-extensions = []
 urllib3 = [
     {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
     {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
diff --git a/tests/conftest.py b/tests/conftest.py
index a1391ab4d..7f8442ab7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,8 +2,10 @@
 
 import pytest
 
+from openapi_python_client import schema as oai
 from openapi_python_client.parser.properties import (
     AnyProperty,
+    BooleanProperty,
     DateProperty,
     DateTimeProperty,
     EnumProperty,
@@ -34,12 +36,14 @@ def _factory(**kwargs):
         kwargs = {
             "description": "",
             "class_info": Class(name="MyClass", module_name="my_module"),
-            "required_properties": [],
-            "optional_properties": [],
-            "relative_imports": set(),
-            "additional_properties": False,
+            "data": oai.Schema.construct(),
+            "roots": set(),
+            "required_properties": None,
+            "optional_properties": None,
+            "relative_imports": None,
+            "lazy_imports": None,
+            "additional_properties": None,
             "python_name": "",
-            "description": "",
             "example": "",
             **kwargs,
         }
@@ -145,6 +149,21 @@ def _factory(**kwargs):
     return _factory
 
 
+@pytest.fixture
+def boolean_property_factory() -> Callable[..., BooleanProperty]:
+    """
+    This fixture surfaces in the test as a function which manufactures StringProperties with defaults.
+
+    You can pass the same params into this as the StringProperty constructor to override defaults.
+    """
+
+    def _factory(**kwargs):
+        kwargs = _common_kwargs(kwargs)
+        return BooleanProperty(**kwargs)
+
+    return _factory
+
+
 @pytest.fixture
 def date_time_property_factory() -> Callable[..., DateTimeProperty]:
     """
diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py
index 3c063365e..85ffe8570 100644
--- a/tests/test_parser/test_properties/test_init.py
+++ b/tests/test_parser/test_properties/test_init.py
@@ -13,6 +13,9 @@
 
 
 class TestStringProperty:
+    def test_is_base_type(self, string_property_factory):
+        assert string_property_factory().is_base_type is True
+
     @pytest.mark.parametrize(
         "required, nullable, expected",
         (
@@ -29,6 +32,9 @@ def test_get_type_string(self, string_property_factory, required, nullable, expe
 
 
 class TestDateTimeProperty:
+    def test_is_base_type(self, date_time_property_factory):
+        assert date_time_property_factory().is_base_type is True
+
     @pytest.mark.parametrize("required", (True, False))
     @pytest.mark.parametrize("nullable", (True, False))
     def test_get_imports(self, date_time_property_factory, required, nullable):
@@ -51,6 +57,9 @@ def test_get_imports(self, date_time_property_factory, required, nullable):
 
 
 class TestDateProperty:
+    def test_is_base_type(self, date_property_factory):
+        assert date_property_factory().is_base_type is True
+
     @pytest.mark.parametrize("required", (True, False))
     @pytest.mark.parametrize("nullable", (True, False))
     def test_get_imports(self, date_property_factory, required, nullable):
@@ -73,6 +82,9 @@ def test_get_imports(self, date_property_factory, required, nullable):
 
 
 class TestFileProperty:
+    def test_is_base_type(self, file_property_factory):
+        assert file_property_factory().is_base_type is True
+
     @pytest.mark.parametrize("required", (True, False))
     @pytest.mark.parametrize("nullable", (True, False))
     def test_get_imports(self, file_property_factory, required, nullable):
@@ -93,7 +105,50 @@ def test_get_imports(self, file_property_factory, required, nullable):
         assert p.get_imports(prefix="...") == expected
 
 
+class TestNoneProperty:
+    def test_is_base_type(self, none_property_factory):
+        assert none_property_factory().is_base_type is True
+
+
+class TestBooleanProperty:
+    def test_is_base_type(self, boolean_property_factory):
+        assert boolean_property_factory().is_base_type is True
+
+
+class TestAnyProperty:
+    def test_is_base_type(self, any_property_factory):
+        assert any_property_factory().is_base_type is True
+
+
+class TestIntProperty:
+    def test_is_base_type(self, int_property_factory):
+        assert int_property_factory().is_base_type is True
+
+
 class TestListProperty:
+    def test_is_base_type(self, list_property_factory):
+        assert list_property_factory().is_base_type is False
+
+    @pytest.mark.parametrize("quoted", (True, False))
+    def test_get_base_json_type_string_base_inner(self, list_property_factory, quoted):
+        p = list_property_factory()
+        assert p.get_base_json_type_string(quoted=quoted) == "List[str]"
+
+    @pytest.mark.parametrize("quoted", (True, False))
+    def test_get_base_json_type_string_model_inner(self, list_property_factory, model_property_factory, quoted):
+        m = model_property_factory()
+        p = list_property_factory(inner_property=m)
+        assert p.get_base_json_type_string(quoted=quoted) == "List[Dict[str, Any]]"
+
+    def test_get_lazy_import_base_inner(self, list_property_factory):
+        p = list_property_factory()
+        assert p.get_lazy_imports(prefix="..") == set()
+
+    def test_get_lazy_import_model_inner(self, list_property_factory, model_property_factory):
+        m = model_property_factory()
+        p = list_property_factory(inner_property=m)
+        assert p.get_lazy_imports(prefix="..") == {"from ..models.my_module import MyClass"}
+
     @pytest.mark.parametrize(
         "required, nullable, expected",
         (
@@ -103,11 +158,51 @@ class TestListProperty:
             (False, True, "Union[Unset, None, List[str]]"),
         ),
     )
-    def test_get_type_string(self, list_property_factory, required, nullable, expected):
+    def test_get_type_string_base_inner(self, list_property_factory, required, nullable, expected):
         p = list_property_factory(required=required, nullable=nullable)
 
         assert p.get_type_string() == expected
 
+    @pytest.mark.parametrize(
+        "required, nullable, expected",
+        (
+            (True, False, "List['MyClass']"),
+            (True, True, "Optional[List['MyClass']]"),
+            (False, False, "Union[Unset, List['MyClass']]"),
+            (False, True, "Union[Unset, None, List['MyClass']]"),
+        ),
+    )
+    def test_get_type_string_model_inner(
+        self, list_property_factory, model_property_factory, required, nullable, expected
+    ):
+        m = model_property_factory()
+        p = list_property_factory(required=required, nullable=nullable, inner_property=m)
+
+        assert p.get_type_string() == expected
+
+    @pytest.mark.parametrize(
+        "quoted,expected",
+        [
+            (False, "List[str]"),
+            (True, "List[str]"),
+        ],
+    )
+    def test_get_base_type_string_base_inner(self, list_property_factory, quoted, expected):
+        p = list_property_factory()
+        assert p.get_base_type_string(quoted=quoted) == expected
+
+    @pytest.mark.parametrize(
+        "quoted,expected",
+        [
+            (False, "List['MyClass']"),
+            (True, "List['MyClass']"),
+        ],
+    )
+    def test_get_base_type_string_model_inner(self, list_property_factory, model_property_factory, quoted, expected):
+        m = model_property_factory()
+        p = list_property_factory(inner_property=m)
+        assert p.get_base_type_string(quoted=quoted) == expected
+
     @pytest.mark.parametrize("required", (True, False))
     @pytest.mark.parametrize("nullable", (True, False))
     def test_get_type_imports(self, list_property_factory, date_time_property_factory, required, nullable):
@@ -131,6 +226,18 @@ def test_get_type_imports(self, list_property_factory, date_time_property_factor
 
 
 class TestUnionProperty:
+    def test_is_base_type(self, union_property_factory):
+        assert union_property_factory().is_base_type is False
+
+    def test_get_lazy_import_base_inner(self, union_property_factory):
+        p = union_property_factory()
+        assert p.get_lazy_imports(prefix="..") == set()
+
+    def test_get_lazy_import_model_inner(self, union_property_factory, model_property_factory):
+        m = model_property_factory()
+        p = union_property_factory(inner_properties=[m])
+        assert p.get_lazy_imports(prefix="..") == {"from ..models.my_module import MyClass"}
+
     @pytest.mark.parametrize(
         "nullable,required,no_optional,json,expected",
         [
@@ -173,18 +280,34 @@ def test_get_type_string(
 
         assert p.get_type_string(no_optional=no_optional, json=json) == expected
 
-    def test_get_base_type_string(self, union_property_factory, date_time_property_factory, string_property_factory):
+    def test_get_base_type_string_base_inners(
+        self, union_property_factory, date_time_property_factory, string_property_factory
+    ):
         p = union_property_factory(inner_properties=[date_time_property_factory(), string_property_factory()])
 
         assert p.get_base_type_string() == "Union[datetime.datetime, str]"
 
-    def test_get_base_type_string_one_element(self, union_property_factory, date_time_property_factory):
+    def test_get_base_type_string_one_base_inner(self, union_property_factory, date_time_property_factory):
         p = union_property_factory(
             inner_properties=[date_time_property_factory()],
         )
 
         assert p.get_base_type_string() == "datetime.datetime"
 
+    def test_get_base_type_string_one_model_inner(self, union_property_factory, model_property_factory):
+        p = union_property_factory(
+            inner_properties=[model_property_factory()],
+        )
+
+        assert p.get_base_type_string() == "'MyClass'"
+
+    def test_get_base_type_string_model_inners(
+        self, union_property_factory, date_time_property_factory, model_property_factory
+    ):
+        p = union_property_factory(inner_properties=[date_time_property_factory(), model_property_factory()])
+
+        assert p.get_base_type_string() == "Union['MyClass', datetime.datetime]"
+
     def test_get_base_json_type_string(self, union_property_factory, date_time_property_factory):
         p = union_property_factory(
             inner_properties=[date_time_property_factory()],
@@ -216,6 +339,9 @@ def test_get_type_imports(self, union_property_factory, date_time_property_facto
 
 
 class TestEnumProperty:
+    def test_is_base_type(self, enum_property_factory):
+        assert enum_property_factory().is_base_type is True
+
     @pytest.mark.parametrize(
         "required, nullable, expected",
         (
@@ -498,6 +624,29 @@ def test_property_from_data_ref_not_found(self, mocker):
         parse_reference_path.assert_called_once_with(data.ref)
         assert prop == PropertyError(data=data, detail="Could not find reference in parsed models or enums")
         assert schemas == new_schemas
+        assert schemas.dependencies == {}
+
+    @pytest.mark.parametrize("references_exist", (True, False))
+    def test_property_from_data_ref(self, property_factory, references_exist):
+        from openapi_python_client.parser.properties import Schemas, property_from_data
+
+        name = "new_name"
+        required = False
+        ref_path = "/components/schemas/RefName"
+        data = oai.Reference.construct(ref=f"#{ref_path}")
+        roots = {"new_root"}
+
+        existing_property = property_factory(name="old_name")
+        references = {ref_path: {"old_root"}} if references_exist else {}
+        schemas = Schemas(classes_by_reference={ref_path: existing_property}, dependencies=references)
+
+        prop, new_schemas = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config(), roots=roots
+        )
+
+        assert prop == property_factory(name=name, required=required)
+        assert schemas == new_schemas
+        assert schemas.dependencies == {ref_path: {*roots, *references.get(ref_path, set())}}
 
     def test_property_from_data_invalid_ref(self, mocker):
         from openapi_python_client.parser.properties import PropertyError, Schemas, property_from_data
@@ -588,14 +737,30 @@ def test_property_from_data_array(self, mocker):
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
         config = MagicMock()
+        roots = {"root"}
+        process_properties = False
 
         response = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+            name=name,
+            required=required,
+            data=data,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            roots=roots,
+            process_properties=process_properties,
         )
 
         assert response == build_list_property.return_value
         build_list_property.assert_called_once_with(
-            data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config
+            data=data,
+            name=name,
+            required=required,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
 
     def test_property_from_data_object(self, mocker):
@@ -610,14 +775,30 @@ def test_property_from_data_object(self, mocker):
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
         config = MagicMock()
+        roots = {"root"}
+        process_properties = False
 
         response = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+            name=name,
+            required=required,
+            data=data,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
 
         assert response == build_model_property.return_value
         build_model_property.assert_called_once_with(
-            data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config
+            data=data,
+            name=name,
+            required=required,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
 
     def test_property_from_data_union(self, mocker):
@@ -708,7 +889,14 @@ def test_build_list_property_no_items(self, mocker):
         schemas = properties.Schemas()
 
         p, new_schemas = properties.build_list_property(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
+            name=name,
+            required=required,
+            data=data,
+            schemas=schemas,
+            parent_name="parent",
+            config=MagicMock(),
+            process_properties=True,
+            roots={"root"},
         )
 
         assert p == PropertyError(data=data, detail="type array must have items defined")
@@ -730,9 +918,18 @@ def test_build_list_property_invalid_items(self, mocker):
             properties, "property_from_data", return_value=(properties.PropertyError(data="blah"), second_schemas)
         )
         config = MagicMock()
+        process_properties = False
+        roots = {"root"}
 
         p, new_schemas = properties.build_list_property(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+            name=name,
+            required=required,
+            data=data,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            roots=roots,
+            process_properties=process_properties,
         )
 
         assert isinstance(p, PropertyError)
@@ -741,7 +938,14 @@ def test_build_list_property_invalid_items(self, mocker):
         assert new_schemas == second_schemas
         assert schemas != new_schemas, "Schema was mutated"
         property_from_data.assert_called_once_with(
-            name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent", config=config
+            name=f"{name}_item",
+            required=True,
+            data=data.items,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            process_properties=process_properties,
+            roots=roots,
         )
 
     def test_build_list_property(self, any_property_factory):
@@ -756,7 +960,14 @@ def test_build_list_property(self, any_property_factory):
         config = Config()
 
         p, new_schemas = properties.build_list_property(
-            name=name, required=True, data=data, schemas=schemas, parent_name="parent", config=config
+            name=name,
+            required=True,
+            data=data,
+            schemas=schemas,
+            parent_name="parent",
+            config=config,
+            roots={"root"},
+            process_properties=True,
         )
 
         assert isinstance(p, properties.ListProperty)
@@ -916,17 +1127,18 @@ def test__string_based_property_unsupported_format(self, string_property_factory
         assert p == string_property_factory(name=name, required=required, nullable=nullable)
 
 
-class TestBuildSchemas:
+class TestCreateSchemas:
     def test_skips_references_and_keeps_going(self, mocker):
-        from openapi_python_client.parser.properties import Schemas, build_schemas
+        from openapi_python_client.parser.properties import Schemas, _create_schemas
         from openapi_python_client.schema import Reference, Schema
 
         components = {"a_ref": Reference.construct(), "a_schema": Schema.construct()}
         update_schemas_with_data = mocker.patch(f"{MODULE_NAME}.update_schemas_with_data")
         parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
         config = Config()
+        schemas = Schemas()
 
-        result = build_schemas(components=components, schemas=Schemas(), config=config)
+        result = _create_schemas(components=components, schemas=schemas, config=config)
         # Should not even try to parse a path for the Reference
         parse_reference_path.assert_called_once_with("#/components/schemas/a_schema")
         update_schemas_with_data.assert_called_once_with(
@@ -940,7 +1152,7 @@ def test_skips_references_and_keeps_going(self, mocker):
         assert result == update_schemas_with_data.return_value
 
     def test_records_bad_uris_and_keeps_going(self, mocker):
-        from openapi_python_client.parser.properties import Schemas, build_schemas
+        from openapi_python_client.parser.properties import Schemas, _create_schemas
         from openapi_python_client.schema import Schema
 
         components = {"first": Schema.construct(), "second": Schema.construct()}
@@ -949,8 +1161,9 @@ def test_records_bad_uris_and_keeps_going(self, mocker):
             f"{MODULE_NAME}.parse_reference_path", side_effect=[PropertyError(detail="some details"), "a_path"]
         )
         config = Config()
+        schemas = Schemas()
 
-        result = build_schemas(components=components, schemas=Schemas(), config=config)
+        result = _create_schemas(components=components, schemas=schemas, config=config)
         parse_reference_path.assert_has_calls(
             [
                 call("#/components/schemas/first"),
@@ -966,7 +1179,7 @@ def test_records_bad_uris_and_keeps_going(self, mocker):
         assert result == update_schemas_with_data.return_value
 
     def test_retries_failing_properties_while_making_progress(self, mocker):
-        from openapi_python_client.parser.properties import Schemas, build_schemas
+        from openapi_python_client.parser.properties import Schemas, _create_schemas
         from openapi_python_client.schema import Schema
 
         components = {"first": Schema.construct(), "second": Schema.construct()}
@@ -975,8 +1188,9 @@ def test_retries_failing_properties_while_making_progress(self, mocker):
         )
         parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
         config = Config()
+        schemas = Schemas()
 
-        result = build_schemas(components=components, schemas=Schemas(), config=config)
+        result = _create_schemas(components=components, schemas=schemas, config=config)
         parse_reference_path.assert_has_calls(
             [
                 call("#/components/schemas/first"),
@@ -988,6 +1202,164 @@ def test_retries_failing_properties_while_making_progress(self, mocker):
         assert result.errors == [PropertyError()]
 
 
+class TestProcessModels:
+    def test_retries_failing_models_while_making_progress(self, mocker, model_property_factory, property_factory):
+        from openapi_python_client.parser.properties import _process_models
+
+        first_model = model_property_factory()
+        schemas = Schemas(
+            classes_by_name={
+                "first": first_model,
+                "second": model_property_factory(),
+                "non-model": property_factory(),
+            }
+        )
+        process_model = mocker.patch(
+            f"{MODULE_NAME}.process_model", side_effect=[PropertyError(), Schemas(), PropertyError()]
+        )
+        process_model_errors = mocker.patch(f"{MODULE_NAME}._process_model_errors", return_value=["error"])
+        config = Config()
+
+        result = _process_models(schemas=schemas, config=config)
+
+        process_model.assert_has_calls(
+            [
+                call(first_model, schemas=schemas, config=config),
+                call(schemas.classes_by_name["second"], schemas=schemas, config=config),
+                call(first_model, schemas=result, config=config),
+            ]
+        )
+        assert process_model_errors.was_called_once_with([(first_model, PropertyError())])
+        assert all(error in result.errors for error in process_model_errors.return_value)
+
+    def test_detect_recursive_allof_reference_no_retry(self, mocker, model_property_factory):
+        from openapi_python_client.parser.properties import Class, _process_models
+        from openapi_python_client.schema import Reference
+
+        class_name = "class_name"
+        recursive_model = model_property_factory(class_info=Class(name=class_name, module_name="module_name"))
+        schemas = Schemas(
+            classes_by_name={
+                "recursive": recursive_model,
+                "second": model_property_factory(),
+            }
+        )
+        recursion_error = PropertyError(data=Reference.construct(ref=f"#/{class_name}"))
+        process_model = mocker.patch(f"{MODULE_NAME}.process_model", side_effect=[recursion_error, Schemas()])
+        process_model_errors = mocker.patch(f"{MODULE_NAME}._process_model_errors", return_value=["error"])
+        config = Config()
+
+        result = _process_models(schemas=schemas, config=config)
+
+        process_model.assert_has_calls(
+            [
+                call(recursive_model, schemas=schemas, config=config),
+                call(schemas.classes_by_name["second"], schemas=schemas, config=config),
+            ]
+        )
+        assert process_model_errors.was_called_once_with([(recursive_model, recursion_error)])
+        assert all(error in result.errors for error in process_model_errors.return_value)
+        assert "\n\nRecursive allOf reference found" in recursion_error.detail
+
+
+class TestPropogateRemoval:
+    def test_propogate_removal_class_name(self):
+        from openapi_python_client.parser.properties import ReferencePath, _propogate_removal
+        from openapi_python_client.utils import ClassName
+
+        root = ClassName("ClassName", "")
+        ref_path = ReferencePath("/reference")
+        other_class_name = ClassName("OtherClassName", "")
+        schemas = Schemas(
+            classes_by_name={root: None, other_class_name: None},
+            classes_by_reference={ref_path: None},
+            dependencies={ref_path: {other_class_name}, root: {ref_path}},
+        )
+        error = PropertyError()
+
+        _propogate_removal(root=root, schemas=schemas, error=error)
+
+        assert schemas.classes_by_name == {other_class_name: None}
+        assert schemas.classes_by_reference == {ref_path: None}
+        assert not error.detail
+
+    def test_propogate_removal_ref_path(self):
+        from openapi_python_client.parser.properties import ReferencePath, _propogate_removal
+        from openapi_python_client.utils import ClassName
+
+        root = ReferencePath("/root/reference")
+        class_name = ClassName("ClassName", "")
+        ref_path = ReferencePath("/ref/path")
+        schemas = Schemas(
+            classes_by_name={class_name: None},
+            classes_by_reference={root: None, ref_path: None},
+            dependencies={root: {ref_path, class_name}},
+        )
+        error = PropertyError()
+
+        _propogate_removal(root=root, schemas=schemas, error=error)
+
+        assert schemas.classes_by_name == {}
+        assert schemas.classes_by_reference == {}
+        assert error.detail == f"\n{root}\n{ref_path}"
+
+    def test_propogate_removal_ref_path_no_refs(self):
+        from openapi_python_client.parser.properties import ReferencePath, _propogate_removal
+        from openapi_python_client.utils import ClassName
+
+        root = ReferencePath("/root/reference")
+        class_name = ClassName("ClassName", "")
+        ref_path = ReferencePath("/ref/path")
+        schemas = Schemas(classes_by_name={class_name: None}, classes_by_reference={root: None, ref_path: None})
+        error = PropertyError()
+
+        _propogate_removal(root=root, schemas=schemas, error=error)
+
+        assert schemas.classes_by_name == {class_name: None}
+        assert schemas.classes_by_reference == {ref_path: None}
+        assert error.detail == f"\n{root}"
+
+    def test_propogate_removal_ref_path_already_removed(self):
+        from openapi_python_client.parser.properties import ReferencePath, _propogate_removal
+        from openapi_python_client.utils import ClassName
+
+        root = ReferencePath("/root/reference")
+        class_name = ClassName("ClassName", "")
+        ref_path = ReferencePath("/ref/path")
+        schemas = Schemas(
+            classes_by_name={class_name: None},
+            classes_by_reference={ref_path: None},
+            dependencies={root: {ref_path, class_name}},
+        )
+        error = PropertyError()
+
+        _propogate_removal(root=root, schemas=schemas, error=error)
+
+        assert schemas.classes_by_name == {class_name: None}
+        assert schemas.classes_by_reference == {ref_path: None}
+        assert not error.detail
+
+
+def test_process_model_errors(mocker, model_property_factory):
+    from openapi_python_client.parser.properties import _process_model_errors
+
+    propogate_removal = mocker.patch(f"{MODULE_NAME}._propogate_removal")
+    model_errors = [
+        (model_property_factory(roots={"root1", "root2"}), PropertyError(detail="existing detail")),
+        (model_property_factory(roots=set()), PropertyError()),
+        (model_property_factory(roots={"root1", "root3"}), PropertyError(detail="other existing detail")),
+    ]
+    schemas = Schemas()
+
+    result = _process_model_errors(model_errors, schemas=schemas)
+
+    propogate_removal.assert_has_calls(
+        [call(root=root, schemas=schemas, error=error) for model, error in model_errors for root in model.roots]
+    )
+    assert result == [error for _, error in model_errors]
+    assert all("\n\nFailure to process schema has resulted in the removal of:" in error.detail for error in result)
+
+
 class TestBuildParameters:
     def test_skips_references_and_keeps_going(self, mocker):
         from openapi_python_client.parser.properties import Parameters, build_parameters
@@ -1109,3 +1481,21 @@ def test_build_enum_property_bad_default():
 
     assert schemas == schemas
     assert err == PropertyError(detail="B is an invalid default for enum Existing", data=data)
+
+
+def test_build_schemas(mocker):
+    from openapi_python_client.parser.properties import Schemas, build_schemas
+    from openapi_python_client.schema import Reference, Schema
+
+    create_schemas = mocker.patch(f"{MODULE_NAME}._create_schemas")
+    process_models = mocker.patch(f"{MODULE_NAME}._process_models")
+
+    components = {"a_ref": Reference.construct(), "a_schema": Schema.construct()}
+    schemas = Schemas()
+    config = Config()
+
+    result = build_schemas(components=components, schemas=schemas, config=config)
+
+    create_schemas.assert_called_once_with(components=components, schemas=schemas, config=config)
+    process_models.assert_called_once_with(schemas=create_schemas.return_value, config=config)
+    assert result == process_models.return_value
diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py
index 7b96cb687..7f8a92270 100644
--- a/tests/test_parser/test_properties/test_model_property.py
+++ b/tests/test_parser/test_properties/test_model_property.py
@@ -7,42 +7,74 @@
 from openapi_python_client.parser.errors import PropertyError
 from openapi_python_client.parser.properties import StringProperty
 
+MODULE_NAME = "openapi_python_client.parser.properties.model_property"
 
-@pytest.mark.parametrize(
-    "no_optional,nullable,required,json,expected",
-    [
-        (False, False, False, False, "Union[Unset, MyClass]"),
-        (False, False, True, False, "MyClass"),
-        (False, True, False, False, "Union[Unset, None, MyClass]"),
-        (False, True, True, False, "Optional[MyClass]"),
-        (True, False, False, False, "MyClass"),
-        (True, False, True, False, "MyClass"),
-        (True, True, False, False, "MyClass"),
-        (True, True, True, False, "MyClass"),
-        (False, False, True, True, "Dict[str, Any]"),
-    ],
-)
-def test_get_type_string(no_optional, nullable, required, json, expected, model_property_factory):
-
-    prop = model_property_factory(
-        required=required,
-        nullable=nullable,
+
+class TestModelProperty:
+    @pytest.mark.parametrize(
+        "no_optional,nullable,required,json,quoted,expected",
+        [
+            (False, False, False, False, False, "Union[Unset, MyClass]"),
+            (False, False, True, False, False, "MyClass"),
+            (False, True, False, False, False, "Union[Unset, None, MyClass]"),
+            (False, True, True, False, False, "Optional[MyClass]"),
+            (True, False, False, False, False, "MyClass"),
+            (True, False, True, False, False, "MyClass"),
+            (True, True, False, False, False, "MyClass"),
+            (True, True, True, False, False, "MyClass"),
+            (False, False, True, True, False, "Dict[str, Any]"),
+            (False, False, False, False, True, "Union[Unset, 'MyClass']"),
+            (False, False, True, False, True, "'MyClass'"),
+            (False, True, False, False, True, "Union[Unset, None, 'MyClass']"),
+            (False, True, True, False, True, "Optional['MyClass']"),
+            (True, False, False, False, True, "'MyClass'"),
+            (True, False, True, False, True, "'MyClass'"),
+            (True, True, False, False, True, "'MyClass'"),
+            (True, True, True, False, True, "'MyClass'"),
+            (False, False, True, True, True, "Dict[str, Any]"),
+        ],
     )
+    def test_get_type_string(self, no_optional, nullable, required, json, expected, model_property_factory, quoted):
+
+        prop = model_property_factory(
+            required=required,
+            nullable=nullable,
+        )
+
+        assert prop.get_type_string(no_optional=no_optional, json=json, quoted=quoted) == expected
 
-    assert prop.get_type_string(no_optional=no_optional, json=json) == expected
+    def test_get_imports(self, model_property_factory):
+        prop = model_property_factory(required=False, nullable=True)
+
+        assert prop.get_imports(prefix="..") == {
+            "from typing import Optional",
+            "from typing import Union",
+            "from ..types import UNSET, Unset",
+            "from typing import Dict",
+            "from typing import cast",
+        }
+
+    def test_get_lazy_imports(self, model_property_factory):
+        prop = model_property_factory(required=False, nullable=True)
+
+        assert prop.get_lazy_imports(prefix="..") == {
+            "from ..models.my_module import MyClass",
+        }
 
+    def test_is_base_type(self, model_property_factory):
+        assert model_property_factory().is_base_type is False
 
-def test_get_imports(model_property_factory):
-    prop = model_property_factory(required=False, nullable=True)
+    @pytest.mark.parametrize(
+        "quoted,expected",
+        [
+            (False, "MyClass"),
+            (True, '"MyClass"'),
+        ],
+    )
+    def test_get_base_type_string(self, quoted, expected, model_property_factory):
 
-    assert prop.get_imports(prefix="..") == {
-        "from typing import Optional",
-        "from typing import Union",
-        "from ..types import UNSET, Unset",
-        "from ..models.my_module import MyClass",
-        "from typing import Dict",
-        "from typing import cast",
-    }
+        m = model_property_factory()
+        assert m.get_base_type_string(quoted=quoted) == expected
 
 
 class TestBuildModelProperty:
@@ -75,7 +107,14 @@ def test_additional_schemas(self, additional_properties_schema, expected_additio
         )
 
         model, _ = build_model_property(
-            data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config()
+            data=data,
+            name="prop",
+            schemas=Schemas(),
+            required=True,
+            parent_name="parent",
+            config=Config(),
+            roots={"root"},
+            process_properties=True,
         )
 
         assert model.additional_properties == expected_additional_properties
@@ -98,9 +137,18 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_
             nullable=nullable,
         )
         schemas = Schemas(classes_by_reference={"OtherModel": None}, classes_by_name={"OtherModel": None})
+        class_info = Class(name="ParentMyModel", module_name="parent_my_model")
+        roots = {"root"}
 
         model, new_schemas = build_model_property(
-            data=data, name=name, schemas=schemas, required=required, parent_name="parent", config=Config()
+            data=data,
+            name=name,
+            schemas=schemas,
+            required=required,
+            parent_name="parent",
+            config=Config(),
+            roots=roots,
+            process_properties=True,
         )
 
         assert new_schemas != schemas
@@ -111,11 +159,14 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_
         assert new_schemas.classes_by_reference == {
             "OtherModel": None,
         }
+        assert new_schemas.dependencies == {"root": {class_info.name}}
         assert model == model_property_factory(
             name=name,
             required=required,
             nullable=nullable,
-            class_info=Class(name="ParentMyModel", module_name="parent_my_model"),
+            roots={*roots, class_info.name},
+            data=data,
+            class_info=class_info,
             required_properties=[string_property_factory(name="req", required=True)],
             optional_properties=[date_time_property_factory(name="opt", required=False)],
             description=data.description,
@@ -126,6 +177,7 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_
                 "from ..types import UNSET, Unset",
                 "from typing import Union",
             },
+            lazy_imports=set(),
             additional_properties=True,
         )
 
@@ -136,7 +188,14 @@ def test_model_name_conflict(self):
         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()
+            data=data,
+            name="OtherModel",
+            schemas=schemas,
+            required=True,
+            parent_name=None,
+            config=Config(),
+            roots={"root"},
+            process_properties=True,
         )
 
         assert new_schemas == schemas
@@ -151,7 +210,14 @@ def test_model_bad_properties(self):
             },
         )
         result = build_model_property(
-            data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config()
+            data=data,
+            name="prop",
+            schemas=Schemas(),
+            required=True,
+            parent_name="parent",
+            config=Config(),
+            roots={"root"},
+            process_properties=True,
         )[0]
         assert isinstance(result, PropertyError)
 
@@ -166,10 +232,67 @@ def test_model_bad_additional_properties(self):
         )
         data = oai.Schema(additionalProperties=additional_properties)
         result = build_model_property(
-            data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=Config()
+            data=data,
+            name="prop",
+            schemas=Schemas(),
+            required=True,
+            parent_name="parent",
+            config=Config(),
+            roots={"root"},
+            process_properties=True,
         )[0]
         assert isinstance(result, PropertyError)
 
+    def test_process_properties_false(self, model_property_factory):
+        from openapi_python_client.parser.properties import Class, Schemas, build_model_property
+
+        name = "prop"
+        nullable = False
+        required = True
+
+        data = oai.Schema.construct(
+            required=["req"],
+            title="MyModel",
+            properties={
+                "req": oai.Schema.construct(type="string"),
+                "opt": oai.Schema(type="string", format="date-time"),
+            },
+            description="A class called MyModel",
+            nullable=nullable,
+        )
+        schemas = Schemas(classes_by_reference={"OtherModel": None}, classes_by_name={"OtherModel": None})
+        roots = {"root"}
+        class_info = Class(name="ParentMyModel", module_name="parent_my_model")
+
+        model, new_schemas = build_model_property(
+            data=data,
+            name=name,
+            schemas=schemas,
+            required=required,
+            parent_name="parent",
+            config=Config(),
+            roots=roots,
+            process_properties=False,
+        )
+
+        assert new_schemas != schemas
+        assert new_schemas.classes_by_name == {
+            "OtherModel": None,
+            "ParentMyModel": model,
+        }
+        assert new_schemas.classes_by_reference == {
+            "OtherModel": None,
+        }
+        assert model == model_property_factory(
+            name=name,
+            required=required,
+            nullable=nullable,
+            class_info=class_info,
+            data=data,
+            description=data.description,
+            roots={*roots, class_info.name},
+        )
+
 
 class TestProcessProperties:
     def test_conflicting_properties_different_types(
@@ -183,12 +306,16 @@ def test_conflicting_properties_different_types(
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[string_property_factory()]),
-                "/Second": model_property_factory(optional_properties=[date_time_property_factory()]),
+                "/First": model_property_factory(
+                    required_properties=[], optional_properties=[string_property_factory()]
+                ),
+                "/Second": model_property_factory(
+                    required_properties=[], optional_properties=[date_time_property_factory()]
+                ),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
 
         assert isinstance(result, PropertyError)
 
@@ -202,18 +329,39 @@ def test_process_properties_reference_not_exist(self):
             },
         )
 
-        result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config())
+        result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots={"root"})
+
+        assert isinstance(result, PropertyError)
+
+    def test_process_properties_all_of_reference_not_exist(self):
+        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="#/components/schema/NotExist")])
+
+        result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots={"root"})
 
         assert isinstance(result, PropertyError)
 
-    def test_invalid_reference(self, model_property_factory):
+    def test_process_properties_model_property_roots(self, model_property_factory):
+        from openapi_python_client.parser.properties import Schemas
+        from openapi_python_client.parser.properties.model_property import _process_properties
+
+        roots = {"root"}
+        data = oai.Schema(properties={"test_model_property": oai.Schema.construct(type="object")})
+
+        result = _process_properties(data=data, class_name="", schemas=Schemas(), config=Config(), roots=roots)
+
+        assert all(root in result.optional_props[0].roots for root in roots)
+
+    def test_invalid_reference(self):
         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="ThisIsNotGood")])
         schemas = Schemas()
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
 
         assert isinstance(result, PropertyError)
 
@@ -228,7 +376,22 @@ def test_non_model_reference(self, enum_property_factory):
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
+
+        assert isinstance(result, PropertyError)
+
+    def test_reference_not_processed(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="#/Unprocessed")])
+        schemas = Schemas(
+            classes_by_reference={
+                "/Unprocessed": model_property_factory(),
+            }
+        )
+
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
 
         assert isinstance(result, PropertyError)
 
@@ -241,12 +404,16 @@ def test_conflicting_properties_same_types(self, model_property_factory, string_
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[string_property_factory(default="abc")]),
-                "/Second": model_property_factory(optional_properties=[string_property_factory()]),
+                "/First": model_property_factory(
+                    required_properties=[], optional_properties=[string_property_factory(default="abc")]
+                ),
+                "/Second": model_property_factory(
+                    required_properties=[], optional_properties=[string_property_factory()]
+                ),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
 
         assert isinstance(result, PropertyError)
 
@@ -263,13 +430,14 @@ def test_allof_string_and_string_enum(self, model_property_factory, enum_propert
         schemas = Schemas(
             classes_by_reference={
                 "/First": model_property_factory(
-                    optional_properties=[string_property_factory(required=False, nullable=True)]
+                    required_properties=[],
+                    optional_properties=[string_property_factory(required=False, nullable=True)],
                 ),
-                "/Second": model_property_factory(optional_properties=[enum_property]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert result.required_props[0] == enum_property
 
     def test_allof_string_enum_and_string(self, model_property_factory, enum_property_factory, string_property_factory):
@@ -286,14 +454,15 @@ def test_allof_string_enum_and_string(self, model_property_factory, enum_propert
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[enum_property]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[enum_property]),
                 "/Second": model_property_factory(
-                    optional_properties=[string_property_factory(required=False, nullable=True)]
+                    required_properties=[],
+                    optional_properties=[string_property_factory(required=False, nullable=True)],
                 ),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert result.optional_props[0] == enum_property
 
     def test_allof_int_and_int_enum(self, model_property_factory, enum_property_factory, int_property_factory):
@@ -309,12 +478,12 @@ def test_allof_int_and_int_enum(self, model_property_factory, enum_property_fact
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[int_property_factory()]),
-                "/Second": model_property_factory(optional_properties=[enum_property]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[int_property_factory()]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert result.required_props[0] == enum_property
 
     def test_allof_enum_incompatible_type(self, model_property_factory, enum_property_factory, int_property_factory):
@@ -330,12 +499,12 @@ def test_allof_enum_incompatible_type(self, model_property_factory, enum_propert
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[int_property_factory()]),
-                "/Second": model_property_factory(optional_properties=[enum_property]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[int_property_factory()]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert isinstance(result, PropertyError)
 
     def test_allof_string_enums(self, model_property_factory, enum_property_factory):
@@ -357,12 +526,12 @@ def test_allof_string_enums(self, model_property_factory, enum_property_factory)
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[enum_property1]),
-                "/Second": model_property_factory(optional_properties=[enum_property2]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[enum_property1]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property2]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert result.required_props[0] == enum_property1
 
     def test_allof_int_enums(self, model_property_factory, enum_property_factory):
@@ -384,12 +553,12 @@ def test_allof_int_enums(self, model_property_factory, enum_property_factory):
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[enum_property1]),
-                "/Second": model_property_factory(optional_properties=[enum_property2]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[enum_property1]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property2]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert result.required_props[0] == enum_property2
 
     def test_allof_enums_are_not_subsets(self, model_property_factory, enum_property_factory):
@@ -411,12 +580,12 @@ def test_allof_enums_are_not_subsets(self, model_property_factory, enum_property
         )
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[enum_property1]),
-                "/Second": model_property_factory(optional_properties=[enum_property2]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[enum_property1]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[enum_property2]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
         assert isinstance(result, PropertyError)
 
     def test_duplicate_properties(self, model_property_factory, string_property_factory):
@@ -429,12 +598,12 @@ def test_duplicate_properties(self, model_property_factory, string_property_fact
         prop = string_property_factory(nullable=True)
         schemas = Schemas(
             classes_by_reference={
-                "/First": model_property_factory(optional_properties=[prop]),
-                "/Second": model_property_factory(optional_properties=[prop]),
+                "/First": model_property_factory(required_properties=[], optional_properties=[prop]),
+                "/Second": model_property_factory(required_properties=[], optional_properties=[prop]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config(), roots={"root"})
 
         assert result.optional_props == [prop], "There should only be one copy of duplicate properties"
 
@@ -460,15 +629,18 @@ def test_mixed_requirements(
         schemas = Schemas(
             classes_by_reference={
                 "/First": model_property_factory(
-                    optional_properties=[string_property_factory(required=first_required, nullable=first_nullable)]
+                    required_properties=[],
+                    optional_properties=[string_property_factory(required=first_required, nullable=first_nullable)],
                 ),
                 "/Second": model_property_factory(
-                    optional_properties=[string_property_factory(required=second_required, nullable=second_nullable)]
+                    required_properties=[],
+                    optional_properties=[string_property_factory(required=second_required, nullable=second_nullable)],
                 ),
             }
         )
+        roots = {"root"}
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock(), roots=roots)
 
         nullable = first_nullable and second_nullable
         required = first_required or second_required
@@ -477,6 +649,7 @@ def test_mixed_requirements(
             required=required,
         )
 
+        assert result.schemas.dependencies == {"/First": roots, "/Second": roots}
         if nullable or not required:
             assert result.optional_props == [expected_prop]
         else:
@@ -499,7 +672,64 @@ def test_direct_properties_non_ref(self, string_property_factory):
         )
         schemas = Schemas()
 
-        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock())
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock(), roots={"root"})
 
         assert result.optional_props == [string_property_factory(name="second", required=False, nullable=False)]
         assert result.required_props == [string_property_factory(name="first", required=True, nullable=False)]
+
+
+class TestProcessModel:
+    def test_process_model_error(self, mocker, model_property_factory):
+        from openapi_python_client.parser.properties import Schemas
+        from openapi_python_client.parser.properties.model_property import process_model
+
+        model_prop = model_property_factory()
+        schemas = Schemas()
+        process_property_data = mocker.patch(f"{MODULE_NAME}._process_property_data")
+        process_property_data.return_value = (PropertyError(), schemas)
+
+        result = process_model(model_prop=model_prop, schemas=schemas, config=Config())
+
+        assert result == PropertyError()
+        assert model_prop.required_properties is None
+        assert model_prop.optional_properties is None
+        assert model_prop.relative_imports is None
+        assert model_prop.additional_properties is None
+
+    def test_process_model(self, mocker, model_property_factory):
+        from openapi_python_client.parser.properties import Schemas
+        from openapi_python_client.parser.properties.model_property import _PropertyData, process_model
+
+        model_prop = model_property_factory()
+        schemas = Schemas()
+        property_data = _PropertyData(
+            required_props=["required"],
+            optional_props=["optional"],
+            relative_imports={"relative"},
+            lazy_imports={"lazy"},
+            schemas=schemas,
+        )
+        additional_properties = True
+        process_property_data = mocker.patch(f"{MODULE_NAME}._process_property_data")
+        process_property_data.return_value = ((property_data, additional_properties), schemas)
+
+        result = process_model(model_prop=model_prop, schemas=schemas, config=Config())
+
+        assert result == schemas
+        assert model_prop.required_properties == property_data.required_props
+        assert model_prop.optional_properties == property_data.optional_props
+        assert model_prop.relative_imports == property_data.relative_imports
+        assert model_prop.lazy_imports == property_data.lazy_imports
+        assert model_prop.additional_properties == additional_properties
+
+
+def test_set_relative_imports(model_property_factory):
+    from openapi_python_client.parser.properties import Class
+    from openapi_python_client.parser.properties.model_property import ModelProperty
+
+    class_info = Class("ClassName", module_name="module_name")
+    relative_imports = {"from typing import List", f"from ..models.{class_info.module_name} import {class_info.name}"}
+
+    model_property = model_property_factory(class_info=class_info, relative_imports=relative_imports)
+
+    assert model_property.relative_imports == {"from typing import List"}
diff --git a/tests/test_parser/test_properties/test_property.py b/tests/test_parser/test_properties/test_property.py
index 00da3fe46..aa1b3fb4f 100644
--- a/tests/test_parser/test_properties/test_property.py
+++ b/tests/test_parser/test_properties/test_property.py
@@ -2,34 +2,39 @@
 
 
 class TestProperty:
+    def test_is_base_type(self, property_factory):
+        assert property_factory().is_base_type is True
+
     @pytest.mark.parametrize(
-        "nullable,required,no_optional,json,expected",
+        "nullable,required,no_optional,json,quoted,expected",
         [
-            (False, False, False, False, "Union[Unset, TestType]"),
-            (False, False, True, False, "TestType"),
-            (False, True, False, False, "TestType"),
-            (False, True, True, False, "TestType"),
-            (True, False, False, False, "Union[Unset, None, TestType]"),
-            (True, False, True, False, "TestType"),
-            (True, True, False, False, "Optional[TestType]"),
-            (True, True, True, False, "TestType"),
-            (False, False, False, True, "Union[Unset, str]"),
-            (False, False, True, True, "str"),
-            (False, True, False, True, "str"),
-            (False, True, True, True, "str"),
-            (True, False, False, True, "Union[Unset, None, str]"),
-            (True, False, True, True, "str"),
-            (True, True, False, True, "Optional[str]"),
-            (True, True, True, True, "str"),
+            (False, False, False, False, False, "Union[Unset, TestType]"),
+            (False, False, True, False, False, "TestType"),
+            (False, True, False, False, False, "TestType"),
+            (False, True, True, False, False, "TestType"),
+            (True, False, False, False, False, "Union[Unset, None, TestType]"),
+            (True, False, True, False, False, "TestType"),
+            (True, True, False, False, False, "Optional[TestType]"),
+            (True, True, True, False, False, "TestType"),
+            (False, False, False, True, False, "Union[Unset, str]"),
+            (False, False, True, True, False, "str"),
+            (False, True, False, True, False, "str"),
+            (False, True, True, True, False, "str"),
+            (True, False, False, True, False, "Union[Unset, None, str]"),
+            (True, False, False, True, True, "Union[Unset, None, str]"),
+            (True, False, True, True, False, "str"),
+            (True, True, False, True, False, "Optional[str]"),
+            (True, True, True, True, False, "str"),
+            (True, True, True, True, True, "str"),
         ],
     )
-    def test_get_type_string(self, property_factory, mocker, nullable, required, no_optional, json, expected):
+    def test_get_type_string(self, property_factory, mocker, nullable, required, no_optional, json, expected, quoted):
         from openapi_python_client.parser.properties import Property
 
         mocker.patch.object(Property, "_type_string", "TestType")
         mocker.patch.object(Property, "_json_type_string", "str")
         p = property_factory(required=required, nullable=nullable)
-        assert p.get_type_string(no_optional=no_optional, json=json) == expected
+        assert p.get_type_string(no_optional=no_optional, json=json, quoted=quoted) == expected
 
     @pytest.mark.parametrize(
         "default,required,expected",
@@ -60,3 +65,31 @@ def test_get_imports(self, property_factory):
             "from typing import Optional",
             "from typing import Union",
         }
+
+    @pytest.mark.parametrize(
+        "quoted,expected",
+        [
+            (False, "TestType"),
+            (True, "TestType"),
+        ],
+    )
+    def test_get_base_type_string(self, quoted, expected, property_factory, mocker):
+        from openapi_python_client.parser.properties import Property
+
+        mocker.patch.object(Property, "_type_string", "TestType")
+        p = property_factory()
+        assert p.get_base_type_string(quoted=quoted) is expected
+
+    @pytest.mark.parametrize(
+        "quoted,expected",
+        [
+            (False, "str"),
+            (True, "str"),
+        ],
+    )
+    def test_get_base_json_type_string(self, quoted, expected, property_factory, mocker):
+        from openapi_python_client.parser.properties import Property
+
+        mocker.patch.object(Property, "_json_type_string", "str")
+        p = property_factory()
+        assert p.get_base_json_type_string(quoted=quoted) is expected