From b58fe0998af4bd398ca9a33686423b27d01dd56e Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Tue, 23 Mar 2021 18:59:52 -0600
Subject: [PATCH 01/11] refactorWIP refactor reference / class name handling to
 fix resolution errors

---
 .../api/tests/get_user_list.py                |  22 +-
 .../tests/json_body_tests_json_body_post.py   | 111 ------
 .../my_test_api_client/models/__init__.py     |   7 +-
 .../my_test_api_client/models/a_model.py      | 377 ------------------
 .../models/a_model_model.py                   |  93 -----
 .../models/a_model_not_required_model.py      |  93 -----
 .../a_model_not_required_nullable_model.py    |  93 -----
 .../models/a_model_nullable_model.py          |  93 -----
 .../my_test_api_client/models/model_name.py   |  44 ++
 .../models/model_with_property_ref.py         |  60 +++
 end_to_end_tests/openapi.json                 |  26 +-
 openapi_python_client/__init__.py             |  51 ++-
 openapi_python_client/cli.py                  |  16 +-
 openapi_python_client/config.py               |  31 +-
 openapi_python_client/parser/__init__.py      |   4 +-
 openapi_python_client/parser/errors.py        |   2 +-
 openapi_python_client/parser/openapi.py       |  57 ++-
 .../parser/properties/__init__.py             |  71 ++--
 .../parser/properties/enum_property.py        |   8 +-
 .../parser/properties/model_property.py       |  38 +-
 .../parser/properties/property.py             |   7 +-
 .../parser/properties/schemas.py              |  64 ++-
 openapi_python_client/parser/reference.py     |  27 --
 openapi_python_client/parser/responses.py     |  17 +-
 .../openapi_schema_pydantic/reference.py      |   4 +-
 openapi_python_client/utils.py                |  10 +-
 26 files changed, 348 insertions(+), 1078 deletions(-)
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
 delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/model_name.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
 delete mode 100644 openapi_python_client/parser/reference.py

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 ec2216810..fa7b39b86 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
@@ -4,7 +4,6 @@
 import httpx
 
 from ...client import Client
-from ...models.a_model import AModel
 from ...models.an_enum import AnEnum
 from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response
@@ -47,16 +46,7 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]:
-    if response.status_code == 200:
-        response_200 = []
-        _response_200 = response.json()
-        for response_200_item_data in _response_200:
-            response_200_item = AModel.from_dict(response_200_item_data)
-
-            response_200.append(response_200_item)
-
-        return response_200
+def _parse_response(*, response: httpx.Response) -> Optional[HTTPValidationError]:
     if response.status_code == 422:
         response_422 = HTTPValidationError.from_dict(response.json())
 
@@ -64,7 +54,7 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel],
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[List[AModel], HTTPValidationError]]:
+def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -78,7 +68,7 @@ def sync_detailed(
     client: Client,
     an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[List[AModel], HTTPValidationError]]:
+) -> Response[HTTPValidationError]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -97,7 +87,7 @@ def sync(
     client: Client,
     an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[List[AModel], HTTPValidationError]]:
+) -> Optional[HTTPValidationError]:
     """ Get a list of things  """
 
     return sync_detailed(
@@ -112,7 +102,7 @@ async def asyncio_detailed(
     client: Client,
     an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[List[AModel], HTTPValidationError]]:
+) -> Response[HTTPValidationError]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -130,7 +120,7 @@ async def asyncio(
     client: Client,
     an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[List[AModel], HTTPValidationError]]:
+) -> Optional[HTTPValidationError]:
     """ Get a list of things  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
deleted file mode 100644
index 074ab9d89..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from typing import Any, Dict, Optional, Union
-
-import httpx
-
-from ...client import Client
-from ...models.a_model import AModel
-from ...models.http_validation_error import HTTPValidationError
-from ...types import Response
-
-
-def _get_kwargs(
-    *,
-    client: Client,
-    json_body: AModel,
-) -> Dict[str, Any]:
-    url = "{}/tests/json_body".format(client.base_url)
-
-    headers: Dict[str, Any] = client.get_headers()
-    cookies: Dict[str, Any] = client.get_cookies()
-
-    json_json_body = json_body.to_dict()
-
-    return {
-        "url": url,
-        "headers": headers,
-        "cookies": cookies,
-        "timeout": client.get_timeout(),
-        "json": json_json_body,
-    }
-
-
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
-    if response.status_code == 200:
-        response_200 = None
-
-        return response_200
-    if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
-
-        return response_422
-    return None
-
-
-def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
-    return Response(
-        status_code=response.status_code,
-        content=response.content,
-        headers=response.headers,
-        parsed=_parse_response(response=response),
-    )
-
-
-def sync_detailed(
-    *,
-    client: Client,
-    json_body: AModel,
-) -> Response[Union[None, HTTPValidationError]]:
-    kwargs = _get_kwargs(
-        client=client,
-        json_body=json_body,
-    )
-
-    response = httpx.post(
-        **kwargs,
-    )
-
-    return _build_response(response=response)
-
-
-def sync(
-    *,
-    client: Client,
-    json_body: AModel,
-) -> Optional[Union[None, HTTPValidationError]]:
-    """ Try sending a JSON body  """
-
-    return sync_detailed(
-        client=client,
-        json_body=json_body,
-    ).parsed
-
-
-async def asyncio_detailed(
-    *,
-    client: Client,
-    json_body: AModel,
-) -> Response[Union[None, HTTPValidationError]]:
-    kwargs = _get_kwargs(
-        client=client,
-        json_body=json_body,
-    )
-
-    async with httpx.AsyncClient() as _client:
-        response = await _client.post(**kwargs)
-
-    return _build_response(response=response)
-
-
-async def asyncio(
-    *,
-    client: Client,
-    json_body: AModel,
-) -> Optional[Union[None, HTTPValidationError]]:
-    """ Try sending a JSON body  """
-
-    return (
-        await asyncio_detailed(
-            client=client,
-            json_body=json_body,
-        )
-    ).parsed
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 97515ed35..ee7e8833b 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
@@ -1,10 +1,5 @@
 """ Contains all the data models used in inputs/outputs """
 
-from .a_model import AModel
-from .a_model_model import AModelModel
-from .a_model_not_required_model import AModelNotRequiredModel
-from .a_model_not_required_nullable_model import AModelNotRequiredNullableModel
-from .a_model_nullable_model import AModelNullableModel
 from .all_of_sub_model import AllOfSubModel
 from .an_enum import AnEnum
 from .an_int_enum import AnIntEnum
@@ -14,6 +9,7 @@
 from .free_form_model import FreeFormModel
 from .http_validation_error import HTTPValidationError
 from .model_from_all_of import ModelFromAllOf
+from .model_name import ModelName
 from .model_with_additional_properties_inlined import ModelWithAdditionalPropertiesInlined
 from .model_with_additional_properties_inlined_additional_property import (
     ModelWithAdditionalPropertiesInlinedAdditionalProperty,
@@ -23,6 +19,7 @@
 from .model_with_any_json_properties_additional_property_type0 import ModelWithAnyJsonPropertiesAdditionalPropertyType0
 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_union_property import ModelWithUnionProperty
 from .model_with_union_property_inlined import ModelWithUnionPropertyInlined
 from .model_with_union_property_inlined_fruit_type0 import 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
deleted file mode 100644
index 91b62c01b..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
+++ /dev/null
@@ -1,377 +0,0 @@
-import datetime
-from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
-
-import attr
-from dateutil.parser import isoparse
-
-from ..models.a_model_model import AModelModel
-from ..models.a_model_not_required_model import AModelNotRequiredModel
-from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel
-from ..models.a_model_nullable_model import AModelNullableModel
-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
-
-T = TypeVar("T", bound="AModel")
-
-
-@attr.s(auto_attribs=True)
-class AModel:
-    """ A Model for testing all the ways custom objects can be used  """
-
-    an_enum_value: AnEnum
-    a_camel_date_time: Union[datetime.date, datetime.datetime]
-    a_date: datetime.date
-    required_not_nullable: str
-    one_of_models: Union[FreeFormModel, ModelWithUnionProperty]
-    model: AModelModel
-    a_nullable_date: Optional[datetime.date]
-    required_nullable: Optional[str]
-    nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None]
-    nullable_model: Optional[AModelNullableModel]
-    nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
-    a_not_required_date: Union[Unset, datetime.date] = UNSET
-    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, AModelNotRequiredModel] = UNSET
-    not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET
-
-    def to_dict(self) -> Dict[str, Any]:
-        an_enum_value = self.an_enum_value.value
-
-        if isinstance(self.a_camel_date_time, datetime.datetime):
-            a_camel_date_time = self.a_camel_date_time.isoformat()
-
-        else:
-            a_camel_date_time = self.a_camel_date_time.isoformat()
-
-        a_date = self.a_date.isoformat()
-        required_not_nullable = self.required_not_nullable
-        if isinstance(self.one_of_models, FreeFormModel):
-            one_of_models = self.one_of_models.to_dict()
-
-        else:
-            one_of_models = self.one_of_models.to_dict()
-
-        model = self.model.to_dict()
-
-        nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET
-        if not isinstance(self.nested_list_of_enums, Unset):
-            nested_list_of_enums = []
-            for nested_list_of_enums_item_data in self.nested_list_of_enums:
-                nested_list_of_enums_item = []
-                for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data:
-                    nested_list_of_enums_item_item = nested_list_of_enums_item_item_data.value
-
-                    nested_list_of_enums_item.append(nested_list_of_enums_item_item)
-
-                nested_list_of_enums.append(nested_list_of_enums_item)
-
-        a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None
-        a_not_required_date: Union[Unset, str] = UNSET
-        if not isinstance(self.a_not_required_date, Unset):
-            a_not_required_date = self.a_not_required_date.isoformat()
-
-        attr_1_leading_digit = self.attr_1_leading_digit
-        required_nullable = self.required_nullable
-        not_required_nullable = self.not_required_nullable
-        not_required_not_nullable = self.not_required_not_nullable
-        nullable_one_of_models: Union[Dict[str, Any], None]
-        if self.nullable_one_of_models is None:
-            nullable_one_of_models = None
-        elif isinstance(self.nullable_one_of_models, FreeFormModel):
-            nullable_one_of_models = self.nullable_one_of_models.to_dict()
-
-        else:
-            nullable_one_of_models = self.nullable_one_of_models.to_dict()
-
-        not_required_one_of_models: Union[Dict[str, Any], Unset]
-        if isinstance(self.not_required_one_of_models, Unset):
-            not_required_one_of_models = UNSET
-        elif isinstance(self.not_required_one_of_models, FreeFormModel):
-            not_required_one_of_models = UNSET
-            if not isinstance(self.not_required_one_of_models, Unset):
-                not_required_one_of_models = self.not_required_one_of_models.to_dict()
-
-        else:
-            not_required_one_of_models = UNSET
-            if not isinstance(self.not_required_one_of_models, Unset):
-                not_required_one_of_models = self.not_required_one_of_models.to_dict()
-
-        not_required_nullable_one_of_models: Union[Dict[str, Any], None, Unset, str]
-        if isinstance(self.not_required_nullable_one_of_models, Unset):
-            not_required_nullable_one_of_models = UNSET
-        elif self.not_required_nullable_one_of_models is None:
-            not_required_nullable_one_of_models = None
-        elif isinstance(self.not_required_nullable_one_of_models, FreeFormModel):
-            not_required_nullable_one_of_models = UNSET
-            if not isinstance(self.not_required_nullable_one_of_models, Unset):
-                not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict()
-
-        elif isinstance(self.not_required_nullable_one_of_models, ModelWithUnionProperty):
-            not_required_nullable_one_of_models = UNSET
-            if not isinstance(self.not_required_nullable_one_of_models, Unset):
-                not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict()
-
-        else:
-            not_required_nullable_one_of_models = self.not_required_nullable_one_of_models
-
-        nullable_model = self.nullable_model.to_dict() if self.nullable_model else None
-
-        not_required_model: Union[Unset, Dict[str, Any]] = UNSET
-        if not isinstance(self.not_required_model, Unset):
-            not_required_model = self.not_required_model.to_dict()
-
-        not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET
-        if not isinstance(self.not_required_nullable_model, Unset):
-            not_required_nullable_model = (
-                self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None
-            )
-
-        field_dict: Dict[str, Any] = {}
-        field_dict.update(
-            {
-                "an_enum_value": an_enum_value,
-                "aCamelDateTime": a_camel_date_time,
-                "a_date": a_date,
-                "required_not_nullable": required_not_nullable,
-                "one_of_models": one_of_models,
-                "model": model,
-                "a_nullable_date": a_nullable_date,
-                "required_nullable": required_nullable,
-                "nullable_one_of_models": nullable_one_of_models,
-                "nullable_model": nullable_model,
-            }
-        )
-        if nested_list_of_enums is not UNSET:
-            field_dict["nested_list_of_enums"] = nested_list_of_enums
-        if a_not_required_date is not UNSET:
-            field_dict["a_not_required_date"] = a_not_required_date
-        if attr_1_leading_digit is not UNSET:
-            field_dict["1_leading_digit"] = attr_1_leading_digit
-        if not_required_nullable is not UNSET:
-            field_dict["not_required_nullable"] = not_required_nullable
-        if not_required_not_nullable is not UNSET:
-            field_dict["not_required_not_nullable"] = not_required_not_nullable
-        if not_required_one_of_models is not UNSET:
-            field_dict["not_required_one_of_models"] = not_required_one_of_models
-        if not_required_nullable_one_of_models is not UNSET:
-            field_dict["not_required_nullable_one_of_models"] = not_required_nullable_one_of_models
-        if not_required_model is not UNSET:
-            field_dict["not_required_model"] = not_required_model
-        if not_required_nullable_model is not UNSET:
-            field_dict["not_required_nullable_model"] = not_required_nullable_model
-
-        return field_dict
-
-    @classmethod
-    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
-        d = src_dict.copy()
-        an_enum_value = AnEnum(d.pop("an_enum_value"))
-
-        def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]:
-            try:
-                a_camel_date_time_type0: datetime.datetime
-                if not isinstance(data, str):
-                    raise TypeError()
-                a_camel_date_time_type0 = isoparse(data)
-
-                return a_camel_date_time_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, str):
-                raise TypeError()
-            a_camel_date_time_type1: datetime.date
-            a_camel_date_time_type1 = isoparse(data).date()
-
-            return a_camel_date_time_type1
-
-        a_camel_date_time = _parse_a_camel_date_time(d.pop("aCamelDateTime"))
-
-        a_date = isoparse(d.pop("a_date")).date()
-
-        required_not_nullable = d.pop("required_not_nullable")
-
-        def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]:
-            try:
-                one_of_models_type0: FreeFormModel
-                if not isinstance(data, dict):
-                    raise TypeError()
-                one_of_models_type0 = FreeFormModel.from_dict(data)
-
-                return one_of_models_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, dict):
-                raise TypeError()
-            one_of_models_type1: ModelWithUnionProperty
-            one_of_models_type1 = ModelWithUnionProperty.from_dict(data)
-
-            return one_of_models_type1
-
-        one_of_models = _parse_one_of_models(d.pop("one_of_models"))
-
-        model = AModelModel.from_dict(d.pop("model"))
-
-        nested_list_of_enums = []
-        _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
-        for nested_list_of_enums_item_data in _nested_list_of_enums or []:
-            nested_list_of_enums_item = []
-            _nested_list_of_enums_item = nested_list_of_enums_item_data
-            for nested_list_of_enums_item_item_data in _nested_list_of_enums_item:
-                nested_list_of_enums_item_item = DifferentEnum(nested_list_of_enums_item_item_data)
-
-                nested_list_of_enums_item.append(nested_list_of_enums_item_item)
-
-            nested_list_of_enums.append(nested_list_of_enums_item)
-
-        a_nullable_date = None
-        _a_nullable_date = d.pop("a_nullable_date")
-        if _a_nullable_date is not None:
-            a_nullable_date = isoparse(_a_nullable_date).date()
-
-        a_not_required_date: Union[Unset, datetime.date] = UNSET
-        _a_not_required_date = d.pop("a_not_required_date", UNSET)
-        if not isinstance(_a_not_required_date, Unset):
-            a_not_required_date = isoparse(_a_not_required_date).date()
-
-        attr_1_leading_digit = d.pop("1_leading_digit", UNSET)
-
-        required_nullable = d.pop("required_nullable")
-
-        not_required_nullable = d.pop("not_required_nullable", UNSET)
-
-        not_required_not_nullable = d.pop("not_required_not_nullable", UNSET)
-
-        def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]:
-            if data is None:
-                return data
-            try:
-                nullable_one_of_models_type0: FreeFormModel
-                if not isinstance(data, dict):
-                    raise TypeError()
-                nullable_one_of_models_type0 = FreeFormModel.from_dict(data)
-
-                return nullable_one_of_models_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, dict):
-                raise TypeError()
-            nullable_one_of_models_type1: ModelWithUnionProperty
-            nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(data)
-
-            return nullable_one_of_models_type1
-
-        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]:
-            if isinstance(data, Unset):
-                return data
-            try:
-                not_required_one_of_models_type0: Union[Unset, FreeFormModel]
-                if not isinstance(data, dict):
-                    raise TypeError()
-                not_required_one_of_models_type0 = UNSET
-                _not_required_one_of_models_type0 = data
-                if not isinstance(_not_required_one_of_models_type0, Unset):
-                    not_required_one_of_models_type0 = FreeFormModel.from_dict(_not_required_one_of_models_type0)
-
-                return not_required_one_of_models_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, dict):
-                raise TypeError()
-            not_required_one_of_models_type1: Union[Unset, ModelWithUnionProperty]
-            not_required_one_of_models_type1 = UNSET
-            _not_required_one_of_models_type1 = data
-            if not isinstance(_not_required_one_of_models_type1, Unset):
-                not_required_one_of_models_type1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type1)
-
-            return not_required_one_of_models_type1
-
-        not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET))
-
-        def _parse_not_required_nullable_one_of_models(
-            data: object,
-        ) -> Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]:
-            if data is None:
-                return data
-            if isinstance(data, Unset):
-                return data
-            try:
-                not_required_nullable_one_of_models_type0: Union[Unset, FreeFormModel]
-                if not isinstance(data, dict):
-                    raise TypeError()
-                not_required_nullable_one_of_models_type0 = UNSET
-                _not_required_nullable_one_of_models_type0 = data
-                if not isinstance(_not_required_nullable_one_of_models_type0, Unset):
-                    not_required_nullable_one_of_models_type0 = FreeFormModel.from_dict(
-                        _not_required_nullable_one_of_models_type0
-                    )
-
-                return not_required_nullable_one_of_models_type0
-            except:  # noqa: E722
-                pass
-            try:
-                not_required_nullable_one_of_models_type1: Union[Unset, ModelWithUnionProperty]
-                if not isinstance(data, dict):
-                    raise TypeError()
-                not_required_nullable_one_of_models_type1 = UNSET
-                _not_required_nullable_one_of_models_type1 = data
-                if not isinstance(_not_required_nullable_one_of_models_type1, Unset):
-                    not_required_nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(
-                        _not_required_nullable_one_of_models_type1
-                    )
-
-                return not_required_nullable_one_of_models_type1
-            except:  # noqa: E722
-                pass
-            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)
-        )
-
-        nullable_model = None
-        _nullable_model = d.pop("nullable_model")
-        if _nullable_model is not None:
-            nullable_model = AModelNullableModel.from_dict(_nullable_model)
-
-        not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET
-        _not_required_model = d.pop("not_required_model", UNSET)
-        if not isinstance(_not_required_model, Unset):
-            not_required_model = AModelNotRequiredModel.from_dict(_not_required_model)
-
-        not_required_nullable_model = None
-        _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET)
-        if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset):
-            not_required_nullable_model = AModelNotRequiredNullableModel.from_dict(_not_required_nullable_model)
-
-        a_model = cls(
-            an_enum_value=an_enum_value,
-            a_camel_date_time=a_camel_date_time,
-            a_date=a_date,
-            required_not_nullable=required_not_nullable,
-            one_of_models=one_of_models,
-            model=model,
-            nested_list_of_enums=nested_list_of_enums,
-            a_nullable_date=a_nullable_date,
-            a_not_required_date=a_not_required_date,
-            attr_1_leading_digit=attr_1_leading_digit,
-            required_nullable=required_nullable,
-            not_required_nullable=not_required_nullable,
-            not_required_not_nullable=not_required_not_nullable,
-            nullable_one_of_models=nullable_one_of_models,
-            not_required_one_of_models=not_required_one_of_models,
-            not_required_nullable_one_of_models=not_required_nullable_one_of_models,
-            nullable_model=nullable_model,
-            not_required_model=not_required_model,
-            not_required_nullable_model=not_required_nullable_model,
-        )
-
-        return a_model
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
deleted file mode 100644
index 0a7a54bd6..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
-
-import attr
-
-from ..models.an_enum import AnEnum
-from ..models.an_int_enum import AnIntEnum
-from ..types import UNSET, Unset
-
-T = TypeVar("T", bound="AModelModel")
-
-
-@attr.s(auto_attribs=True)
-class AModelModel:
-    """  """
-
-    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
-    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
-
-    def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[Unset, int, str]
-        if isinstance(self.a_property, Unset):
-            a_property = UNSET
-        elif isinstance(self.a_property, AnEnum):
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        else:
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        field_dict: Dict[str, Any] = {}
-        field_dict.update(self.additional_properties)
-        field_dict.update({})
-        if a_property is not UNSET:
-            field_dict["a_property"] = a_property
-
-        return field_dict
-
-    @classmethod
-    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
-        d = src_dict.copy()
-
-        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
-            if isinstance(data, Unset):
-                return data
-            try:
-                a_property_type0: Union[Unset, AnEnum]
-                if not isinstance(data, str):
-                    raise TypeError()
-                a_property_type0 = UNSET
-                _a_property_type0 = data
-                if not isinstance(_a_property_type0, Unset):
-                    a_property_type0 = AnEnum(_a_property_type0)
-
-                return a_property_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, int):
-                raise TypeError()
-            a_property_type1: Union[Unset, AnIntEnum]
-            a_property_type1 = UNSET
-            _a_property_type1 = data
-            if not isinstance(_a_property_type1, Unset):
-                a_property_type1 = AnIntEnum(_a_property_type1)
-
-            return a_property_type1
-
-        a_property = _parse_a_property(d.pop("a_property", UNSET))
-
-        a_model_model = cls(
-            a_property=a_property,
-        )
-
-        a_model_model.additional_properties = d
-        return a_model_model
-
-    @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/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
deleted file mode 100644
index fd568db52..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
-
-import attr
-
-from ..models.an_enum import AnEnum
-from ..models.an_int_enum import AnIntEnum
-from ..types import UNSET, Unset
-
-T = TypeVar("T", bound="AModelNotRequiredModel")
-
-
-@attr.s(auto_attribs=True)
-class AModelNotRequiredModel:
-    """  """
-
-    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
-    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
-
-    def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[Unset, int, str]
-        if isinstance(self.a_property, Unset):
-            a_property = UNSET
-        elif isinstance(self.a_property, AnEnum):
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        else:
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        field_dict: Dict[str, Any] = {}
-        field_dict.update(self.additional_properties)
-        field_dict.update({})
-        if a_property is not UNSET:
-            field_dict["a_property"] = a_property
-
-        return field_dict
-
-    @classmethod
-    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
-        d = src_dict.copy()
-
-        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
-            if isinstance(data, Unset):
-                return data
-            try:
-                a_property_type0: Union[Unset, AnEnum]
-                if not isinstance(data, str):
-                    raise TypeError()
-                a_property_type0 = UNSET
-                _a_property_type0 = data
-                if not isinstance(_a_property_type0, Unset):
-                    a_property_type0 = AnEnum(_a_property_type0)
-
-                return a_property_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, int):
-                raise TypeError()
-            a_property_type1: Union[Unset, AnIntEnum]
-            a_property_type1 = UNSET
-            _a_property_type1 = data
-            if not isinstance(_a_property_type1, Unset):
-                a_property_type1 = AnIntEnum(_a_property_type1)
-
-            return a_property_type1
-
-        a_property = _parse_a_property(d.pop("a_property", UNSET))
-
-        a_model_not_required_model = cls(
-            a_property=a_property,
-        )
-
-        a_model_not_required_model.additional_properties = d
-        return a_model_not_required_model
-
-    @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/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
deleted file mode 100644
index 6413e7f9a..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
-
-import attr
-
-from ..models.an_enum import AnEnum
-from ..models.an_int_enum import AnIntEnum
-from ..types import UNSET, Unset
-
-T = TypeVar("T", bound="AModelNotRequiredNullableModel")
-
-
-@attr.s(auto_attribs=True)
-class AModelNotRequiredNullableModel:
-    """  """
-
-    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
-    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
-
-    def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[Unset, int, str]
-        if isinstance(self.a_property, Unset):
-            a_property = UNSET
-        elif isinstance(self.a_property, AnEnum):
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        else:
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        field_dict: Dict[str, Any] = {}
-        field_dict.update(self.additional_properties)
-        field_dict.update({})
-        if a_property is not UNSET:
-            field_dict["a_property"] = a_property
-
-        return field_dict
-
-    @classmethod
-    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
-        d = src_dict.copy()
-
-        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
-            if isinstance(data, Unset):
-                return data
-            try:
-                a_property_type0: Union[Unset, AnEnum]
-                if not isinstance(data, str):
-                    raise TypeError()
-                a_property_type0 = UNSET
-                _a_property_type0 = data
-                if not isinstance(_a_property_type0, Unset):
-                    a_property_type0 = AnEnum(_a_property_type0)
-
-                return a_property_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, int):
-                raise TypeError()
-            a_property_type1: Union[Unset, AnIntEnum]
-            a_property_type1 = UNSET
-            _a_property_type1 = data
-            if not isinstance(_a_property_type1, Unset):
-                a_property_type1 = AnIntEnum(_a_property_type1)
-
-            return a_property_type1
-
-        a_property = _parse_a_property(d.pop("a_property", UNSET))
-
-        a_model_not_required_nullable_model = cls(
-            a_property=a_property,
-        )
-
-        a_model_not_required_nullable_model.additional_properties = d
-        return a_model_not_required_nullable_model
-
-    @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/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
deleted file mode 100644
index cc6484d5f..000000000
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
-
-import attr
-
-from ..models.an_enum import AnEnum
-from ..models.an_int_enum import AnIntEnum
-from ..types import UNSET, Unset
-
-T = TypeVar("T", bound="AModelNullableModel")
-
-
-@attr.s(auto_attribs=True)
-class AModelNullableModel:
-    """  """
-
-    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
-    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
-
-    def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[Unset, int, str]
-        if isinstance(self.a_property, Unset):
-            a_property = UNSET
-        elif isinstance(self.a_property, AnEnum):
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        else:
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
-
-        field_dict: Dict[str, Any] = {}
-        field_dict.update(self.additional_properties)
-        field_dict.update({})
-        if a_property is not UNSET:
-            field_dict["a_property"] = a_property
-
-        return field_dict
-
-    @classmethod
-    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
-        d = src_dict.copy()
-
-        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
-            if isinstance(data, Unset):
-                return data
-            try:
-                a_property_type0: Union[Unset, AnEnum]
-                if not isinstance(data, str):
-                    raise TypeError()
-                a_property_type0 = UNSET
-                _a_property_type0 = data
-                if not isinstance(_a_property_type0, Unset):
-                    a_property_type0 = AnEnum(_a_property_type0)
-
-                return a_property_type0
-            except:  # noqa: E722
-                pass
-            if not isinstance(data, int):
-                raise TypeError()
-            a_property_type1: Union[Unset, AnIntEnum]
-            a_property_type1 = UNSET
-            _a_property_type1 = data
-            if not isinstance(_a_property_type1, Unset):
-                a_property_type1 = AnIntEnum(_a_property_type1)
-
-            return a_property_type1
-
-        a_property = _parse_a_property(d.pop("a_property", UNSET))
-
-        a_model_nullable_model = cls(
-            a_property=a_property,
-        )
-
-        a_model_nullable_model.additional_properties = d
-        return a_model_nullable_model
-
-    @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_name.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_name.py
new file mode 100644
index 000000000..75b12f284
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_name.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="ModelName")
+
+
+@attr.s(auto_attribs=True)
+class ModelName:
+    """  """
+
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        model_name = cls()
+
+        model_name.additional_properties = d
+        return model_name
+
+    @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_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
new file mode 100644
index 000000000..1553914ba
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
@@ -0,0 +1,60 @@
+from typing import Any, Dict, List, Type, TypeVar, Union
+
+import attr
+
+from ..models.model_name import ModelName
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="ModelWithPropertyRef")
+
+
+@attr.s(auto_attribs=True)
+class ModelWithPropertyRef:
+    """  """
+
+    inner: Union[Unset, ModelName] = UNSET
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+        inner: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.inner, Unset):
+            inner = self.inner.to_dict()
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+        if inner is not UNSET:
+            field_dict["inner"] = inner
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        inner: Union[Unset, ModelName] = UNSET
+        _inner = d.pop("inner", UNSET)
+        if not isinstance(_inner, Unset):
+            inner = ModelName.from_dict(_inner)
+
+        model_with_property_ref = cls(
+            inner=inner,
+        )
+
+        model_with_property_ref.additional_properties = d
+        return model_with_property_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/openapi.json b/end_to_end_tests/openapi.json
index 359419473..7ef631faf 100644
--- a/end_to_end_tests/openapi.json
+++ b/end_to_end_tests/openapi.json
@@ -819,10 +819,10 @@
           "one_of_models": {
             "oneOf": [
               {
-                "ref": "#components/schemas/FreeFormModel"
+                "ref": "#/components/schemas/FreeFormModel"
               },
               {
-                "ref": "#components/schemas/ModelWithUnionProperty"
+                "ref": "#/components/schemas/ModelWithUnionProperty"
               }
             ],
             "nullable": false
@@ -830,10 +830,10 @@
           "nullable_one_of_models": {
             "oneOf": [
               {
-                "ref": "#components/schemas/FreeFormModel"
+                "ref": "#/components/schemas/FreeFormModel"
               },
               {
-                "ref": "#components/schemas/ModelWithUnionProperty"
+                "ref": "#/components/schemas/ModelWithUnionProperty"
               }
             ],
             "nullable": true
@@ -841,10 +841,10 @@
           "not_required_one_of_models": {
             "oneOf": [
               {
-                "ref": "#components/schemas/FreeFormModel"
+                "ref": "#/components/schemas/FreeFormModel"
               },
               {
-                "ref": "#components/schemas/ModelWithUnionProperty"
+                "ref": "#/components/schemas/ModelWithUnionProperty"
               }
             ],
             "nullable": false
@@ -852,10 +852,10 @@
           "not_required_nullable_one_of_models": {
             "oneOf": [
               {
-                "ref": "#components/schemas/FreeFormModel"
+                "ref": "#/components/schemas/FreeFormModel"
               },
               {
-                "ref": "#components/schemas/ModelWithUnionProperty"
+                "ref": "#/components/schemas/ModelWithUnionProperty"
               },
               {
                 "type": "string"
@@ -1110,6 +1110,16 @@
             "type": "string"
           }
         }
+      },
+      "model_reference_doesnt_match": {
+        "title": "ModelName",
+        "type": "object"
+      },
+      "ModelWithPropertyRef": {
+        "type": "object",
+        "properties": {
+          "inner": {"$ref":  "#/components/schemas/model_reference_doesnt_match"}
+        }
       }
     }
   }
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index b5ad8afeb..46fa94a51 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -14,7 +14,8 @@
 
 from openapi_python_client import utils
 
-from .parser import GeneratorData, import_string_from_reference
+from .config import Config
+from .parser import GeneratorData, import_string_from_class
 from .parser.errors import GeneratorError
 from .utils import snake_case
 
@@ -41,15 +42,12 @@ class MetaType(str, Enum):
 
 
 class Project:
-    project_name_override: Optional[str] = None
-    package_name_override: Optional[str] = None
-    package_version_override: Optional[str] = None
-
     def __init__(
         self,
         *,
         openapi: GeneratorData,
         meta: MetaType,
+        config: Optional[Config],
         custom_template_path: Optional[Path] = None,
         file_encoding: str = "utf-8",
     ) -> None:
@@ -70,17 +68,17 @@ def __init__(
             loader = package_loader
         self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
 
-        self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
+        self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
         self.project_dir: Path = Path.cwd()
         if meta != MetaType.NONE:
             self.project_dir /= self.project_name
 
-        self.package_name: str = self.package_name_override or self.project_name.replace("-", "_")
+        self.package_name: str = config.package_name_override or self.project_name.replace("-", "_")
         self.package_dir: Path = self.project_dir / self.package_name
         self.package_description: str = utils.remove_string_escapes(
             f"A client library for accessing {self.openapi.title}"
         )
-        self.version: str = self.package_version_override or openapi.version
+        self.version: str = config.package_version_override or openapi.version
 
         self.env.filters.update(TEMPLATE_FILTERS)
 
@@ -215,21 +213,21 @@ def _build_models(self) -> None:
         imports = []
 
         model_template = self.env.get_template("model.py.jinja")
-        for model in self.openapi.models.values():
-            module_path = models_dir / f"{model.reference.module_name}.py"
+        for model in self.openapi.models:
+            module_path = models_dir / f"{model.class_info.module_name}.py"
             module_path.write_text(model_template.render(model=model), encoding=self.file_encoding)
-            imports.append(import_string_from_reference(model.reference))
+            imports.append(import_string_from_class(model.class_info))
 
         # Generate enums
         str_enum_template = self.env.get_template("str_enum.py.jinja")
         int_enum_template = self.env.get_template("int_enum.py.jinja")
-        for enum in self.openapi.enums.values():
-            module_path = models_dir / f"{enum.reference.module_name}.py"
+        for enum in self.openapi.enums:
+            module_path = models_dir / f"{enum.class_info.module_name}.py"
             if enum.value_type is int:
                 module_path.write_text(int_enum_template.render(enum=enum), encoding=self.file_encoding)
             else:
                 module_path.write_text(str_enum_template.render(enum=enum), encoding=self.file_encoding)
-            imports.append(import_string_from_reference(enum.reference))
+            imports.append(import_string_from_class(enum.class_info))
 
         models_init_template = self.env.get_template("models_init.py.jinja")
         models_init.write_text(models_init_template.render(imports=imports), encoding=self.file_encoding)
@@ -261,6 +259,7 @@ def _get_project_for_url_or_path(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
+    config: Optional[Config],
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Union[Project, GeneratorError]:
@@ -270,7 +269,13 @@ def _get_project_for_url_or_path(
     openapi = GeneratorData.from_dict(data_dict)
     if isinstance(openapi, GeneratorError):
         return openapi
-    return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding)
+    return Project(
+        openapi=openapi,
+        custom_template_path=custom_template_path,
+        meta=meta,
+        file_encoding=file_encoding,
+        config=config,
+    )
 
 
 def create_new_client(
@@ -278,6 +283,7 @@ def create_new_client(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
+    config: Optional[Config],
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Sequence[GeneratorError]:
@@ -288,7 +294,12 @@ def create_new_client(
          A list containing any errors encountered when generating.
     """
     project = _get_project_for_url_or_path(
-        url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding
+        url=url,
+        path=path,
+        custom_template_path=custom_template_path,
+        meta=meta,
+        file_encoding=file_encoding,
+        config=config,
     )
     if isinstance(project, GeneratorError):
         return [project]
@@ -300,6 +311,7 @@ def update_existing_client(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
+    config: Optional[Config],
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Sequence[GeneratorError]:
@@ -310,7 +322,12 @@ def update_existing_client(
          A list containing any errors encountered when generating.
     """
     project = _get_project_for_url_or_path(
-        url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding
+        url=url,
+        path=path,
+        custom_template_path=custom_template_path,
+        meta=meta,
+        file_encoding=file_encoding,
+        config=config,
     )
     if isinstance(project, GeneratorError):
         return [project]
diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py
index 1f94f37ea..9a60a302e 100644
--- a/openapi_python_client/cli.py
+++ b/openapi_python_client/cli.py
@@ -8,6 +8,8 @@
 from openapi_python_client import MetaType
 from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError
 
+from .config import Config
+
 app = typer.Typer()
 
 
@@ -19,14 +21,13 @@ def _version_callback(value: bool) -> None:
         raise typer.Exit()
 
 
-def _process_config(path: Optional[pathlib.Path]) -> None:
-    from .config import Config
+def _process_config(path: Optional[pathlib.Path]) -> Optional[Config]:
 
     if not path:
-        return
+        return None
 
     try:
-        Config.load_from_path(path=path)
+        return Config.load_from_path(path=path)
     except:  # noqa
         raise typer.BadParameter("Unable to parse config")
 
@@ -35,9 +36,6 @@ def _process_config(path: Optional[pathlib.Path]) -> None:
 @app.callback(name="openapi-python-client")
 def cli(
     version: bool = typer.Option(False, "--version", callback=_version_callback, help="Print the version and exit"),
-    config: Optional[pathlib.Path] = typer.Option(
-        None, callback=_process_config, help="Path to the config file to use"
-    ),
 ) -> None:
     """ Generate a Python client from an OpenAPI JSON document """
     pass
@@ -117,8 +115,9 @@ def generate(
     url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
     path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"),
     custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options),  # type: ignore
-    file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
     meta: MetaType = _meta_option,
+    file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
+    config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"),
 ) -> None:
     """ Generate a new OpenAPI Client library """
     from . import create_new_client
@@ -149,6 +148,7 @@ def update(
     custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options),  # type: ignore
     meta: MetaType = _meta_option,
     file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
+    config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"),
 ) -> None:
     """ Update an existing OpenAPI Client library """
     from . import update_existing_client
diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py
index b4fa787a9..947d0a405 100644
--- a/openapi_python_client/config.py
+++ b/openapi_python_client/config.py
@@ -4,39 +4,18 @@
 import yaml
 from pydantic import BaseModel
 
-
-class ClassOverride(BaseModel):
-    class_name: str
-    module_name: str
+from openapi_python_client.parser.properties import Class
 
 
 class Config(BaseModel):
-    class_overrides: Optional[Dict[str, ClassOverride]]
+    class_overrides: Dict[str, Class] = {}
     project_name_override: Optional[str]
     package_name_override: Optional[str]
     package_version_override: Optional[str]
-    field_prefix: Optional[str]
-
-    def load_config(self) -> None:
-        """ Sets globals based on Config """
-        from openapi_python_client import Project
-
-        from . import utils
-        from .parser import reference
-
-        if self.class_overrides is not None:
-            for class_name, class_data in self.class_overrides.items():
-                reference.class_overrides[class_name] = reference.Reference(**dict(class_data))
-
-        Project.project_name_override = self.project_name_override
-        Project.package_name_override = self.package_name_override
-        Project.package_version_override = self.package_version_override
-
-        if self.field_prefix is not None:
-            utils.FIELD_PREFIX = self.field_prefix
+    field_prefix: str = "field_"
 
     @staticmethod
-    def load_from_path(path: Path) -> None:
+    def load_from_path(path: Path) -> "Config":
         """ Creates a Config from provided JSON or YAML file and sets a bunch of globals from it """
         config_data = yaml.safe_load(path.read_text())
-        Config(**config_data).load_config()
+        return Config(**config_data)
diff --git a/openapi_python_client/parser/__init__.py b/openapi_python_client/parser/__init__.py
index 65fd27df9..6c20f52d1 100644
--- a/openapi_python_client/parser/__init__.py
+++ b/openapi_python_client/parser/__init__.py
@@ -1,5 +1,5 @@
 """ Classes representing the data in the OpenAPI schema """
 
-__all__ = ["GeneratorData", "import_string_from_reference"]
+__all__ = ["GeneratorData", "import_string_from_class"]
 
-from .openapi import GeneratorData, import_string_from_reference
+from .openapi import GeneratorData, import_string_from_class
diff --git a/openapi_python_client/parser/errors.py b/openapi_python_client/parser/errors.py
index 5c49c7c78..9a5ced0f5 100644
--- a/openapi_python_client/parser/errors.py
+++ b/openapi_python_client/parser/errors.py
@@ -2,7 +2,7 @@
 from enum import Enum
 from typing import Optional
 
-__all__ = ["GeneratorError", "ParseError", "PropertyError", "ValidationError"]
+__all__ = ["ErrorLevel", "GeneratorError", "ParseError", "PropertyError", "ValidationError"]
 
 from pydantic import BaseModel
 
diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py
index 2361c3c66..53a0c70a4 100644
--- a/openapi_python_client/parser/openapi.py
+++ b/openapi_python_client/parser/openapi.py
@@ -1,15 +1,15 @@
 from copy import deepcopy
 from dataclasses import dataclass, field
 from enum import Enum
-from typing import Any, Dict, List, Optional, Set, Tuple, Union
+from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
 
 from pydantic import ValidationError
 
+from .. import Config
 from .. import schema as oai
 from .. import utils
 from .errors import GeneratorError, ParseError, PropertyError
-from .properties import EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data
-from .reference import Reference
+from .properties import Class, EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data
 from .responses import Response, response_from_data
 
 
@@ -22,9 +22,9 @@ class ParameterLocation(str, Enum):
     COOKIE = "cookie"
 
 
-def import_string_from_reference(reference: Reference, prefix: str = "") -> str:
+def import_string_from_class(class_: Class, prefix: str = "") -> str:
     """ Create a string which is used to import a reference """
-    return f"from {prefix}.{reference.module_name} import {reference.class_name}"
+    return f"from {prefix}.{class_.module_name} import {class_.name}"
 
 
 @dataclass
@@ -96,27 +96,27 @@ class Endpoint:
     header_parameters: List[Property] = field(default_factory=list)
     cookie_parameters: List[Property] = field(default_factory=list)
     responses: List[Response] = field(default_factory=list)
-    form_body_reference: Optional[Reference] = None
+    form_body_class: Optional[Class] = None
     json_body: Optional[Property] = None
-    multipart_body_reference: Optional[Reference] = None
+    multipart_body_class: Optional[Class] = None
     errors: List[ParseError] = field(default_factory=list)
 
     @staticmethod
-    def parse_request_form_body(body: oai.RequestBody) -> Optional[Reference]:
+    def parse_request_form_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]:
         """ Return form_body_reference """
         body_content = body.content
         form_body = body_content.get("application/x-www-form-urlencoded")
         if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference):
-            return Reference.from_ref(form_body.media_type_schema.ref)
+            return Class.from_string(string=form_body.media_type_schema.ref, schemas=schemas)
         return None
 
     @staticmethod
-    def parse_multipart_body(body: oai.RequestBody) -> Optional[Reference]:
+    def parse_multipart_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]:
         """ Return form_body_reference """
         body_content = body.content
         json_body = body_content.get("multipart/form-data")
         if json_body is not None and isinstance(json_body.media_type_schema, oai.Reference):
-            return Reference.from_ref(json_body.media_type_schema.ref)
+            return Class.from_string(string=json_body.media_type_schema.ref, schemas=schemas)
         return None
 
     @staticmethod
@@ -145,23 +145,19 @@ def _add_body(
         if data.requestBody is None or isinstance(data.requestBody, oai.Reference):
             return endpoint, schemas
 
-        endpoint.form_body_reference = Endpoint.parse_request_form_body(data.requestBody)
+        endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, schemas=schemas)
         json_body, schemas = Endpoint.parse_request_json_body(
             body=data.requestBody, schemas=schemas, parent_name=endpoint.name
         )
         if isinstance(json_body, ParseError):
             return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas
 
-        endpoint.multipart_body_reference = Endpoint.parse_multipart_body(data.requestBody)
+        endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, schemas=schemas)
 
-        if endpoint.form_body_reference:
-            endpoint.relative_imports.add(
-                import_string_from_reference(endpoint.form_body_reference, prefix="...models")
-            )
-        if endpoint.multipart_body_reference:
-            endpoint.relative_imports.add(
-                import_string_from_reference(endpoint.multipart_body_reference, prefix="...models")
-            )
+        if endpoint.form_body_class:
+            endpoint.relative_imports.add(import_string_from_class(endpoint.form_body_class, prefix="...models"))
+        if endpoint.multipart_body_class:
+            endpoint.relative_imports.add(import_string_from_class(endpoint.multipart_body_class, prefix="...models"))
         if json_body is not None:
             endpoint.json_body = json_body
             endpoint.relative_imports.update(endpoint.json_body.get_imports(prefix="..."))
@@ -273,13 +269,13 @@ class GeneratorData:
     title: str
     description: Optional[str]
     version: str
-    models: Dict[str, ModelProperty]
+    models: Iterator[ModelProperty]
     errors: List[ParseError]
     endpoint_collections_by_tag: Dict[str, EndpointCollection]
-    enums: Dict[str, EnumProperty]
+    enums: Iterator[EnumProperty]
 
     @staticmethod
-    def from_dict(d: Dict[str, Any]) -> Union["GeneratorData", GeneratorError]:
+    def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", GeneratorError]:
         """ Create an OpenAPI from dict """
         try:
             openapi = oai.OpenAPI.parse_obj(d)
@@ -295,19 +291,20 @@ def from_dict(d: Dict[str, Any]) -> Union["GeneratorData", GeneratorError]:
                 header="openapi-python-client only supports OpenAPI 3.x",
                 detail=f"The version of the provided document was {openapi.openapi}",
             )
-        if openapi.components is None or openapi.components.schemas is None:
-            schemas = Schemas()
-        else:
-            schemas = build_schemas(components=openapi.components.schemas)
+        schemas = Schemas(class_overrides=config.class_overrides)
+        if openapi.components and openapi.components.schemas:
+            schemas = build_schemas(components=openapi.components.schemas, schemas=schemas)
         endpoint_collections_by_tag, schemas = EndpointCollection.from_data(data=openapi.paths, schemas=schemas)
-        enums = schemas.enums
+
+        enums = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, EnumProperty))
+        models = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty))
 
         return GeneratorData(
             title=openapi.info.title,
             description=openapi.info.description,
             version=openapi.info.version,
             endpoint_collections_by_tag=endpoint_collections_by_tag,
-            models=schemas.models,
+            models=models,
             errors=schemas.errors,
             enums=enums,
         )
diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index dfa832468..58c65139b 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -1,3 +1,13 @@
+__all__ = [
+    "Class",
+    "EnumProperty",
+    "ModelProperty",
+    "Property",
+    "Schemas",
+    "build_schemas",
+    "property_from_data",
+]
+
 from itertools import chain
 from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union
 
@@ -5,13 +15,12 @@
 
 from ... import schema as oai
 from ... import utils
-from ..errors import PropertyError, ValidationError
-from ..reference import Reference
+from ..errors import ParseError, PropertyError, ValidationError
 from .converter import convert, convert_chain
 from .enum_property import EnumProperty
 from .model_property import ModelProperty, build_model_property
 from .property import Property
-from .schemas import Schemas
+from .schemas import Class, Schemas, parse_reference_path, update_schemas_with_data
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -286,15 +295,15 @@ def build_enum_property(
     class_name = data.title or name
     if parent_name:
         class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
-    reference = Reference.from_ref(class_name)
+    class_info = Class.from_string(string=class_name, schemas=schemas)
     values = EnumProperty.values_from_list(enum)
 
-    if reference.class_name in schemas.enums:
-        existing = schemas.enums[reference.class_name]
-        if values != existing.values:
+    if class_info.name in schemas.classes_by_name:
+        existing = schemas.classes_by_name[class_info.name]
+        if not isinstance(existing, EnumProperty) or values != existing.values:
             return (
                 PropertyError(
-                    detail=f"Found conflicting enums named {reference.class_name} with incompatible values.", data=data
+                    detail=f"Found conflicting enums named {class_info.name} with incompatible values.", data=data
                 ),
                 schemas,
             )
@@ -309,12 +318,10 @@ def build_enum_property(
     if data.default is not None:
         inverse_values = {v: k for k, v in values.items()}
         try:
-            default = f"{reference.class_name}.{inverse_values[data.default]}"
+            default = f"{class_info.name}.{inverse_values[data.default]}"
         except KeyError:
             return (
-                PropertyError(
-                    detail=f"{data.default} is an invalid default for enum {reference.class_name}", data=data
-                ),
+                PropertyError(detail=f"{data.default} is an invalid default for enum {class_info.name}", data=data),
                 schemas,
             )
 
@@ -323,11 +330,11 @@ def build_enum_property(
         required=required,
         default=default,
         nullable=data.nullable,
-        reference=reference,
+        class_info=class_info,
         values=values,
         value_type=value_type,
     )
-    schemas = attr.evolve(schemas, enums={**schemas.enums, prop.reference.class_name: prop})
+    schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop})
     return prop, schemas
 
 
@@ -388,8 +395,10 @@ def _property_from_data(
     """ Generate a Property from the OpenAPI dictionary representation of it """
     name = utils.remove_string_escapes(name)
     if isinstance(data, oai.Reference):
-        reference = Reference.from_ref(data.ref)
-        existing = schemas.enums.get(reference.class_name) or schemas.models.get(reference.class_name)
+        ref_path = parse_reference_path(data.ref)
+        if isinstance(ref_path, ParseError):
+            return PropertyError(data=data, detail=ref_path.detail), schemas
+        existing = schemas.classes_by_reference.get(ref_path)
         if existing:
             return (
                 attr.evolve(existing, required=required, name=name),
@@ -457,23 +466,8 @@ def property_from_data(
         return PropertyError(detail="Failed to validate default value", data=data), schemas
 
 
-def update_schemas_with_data(name: str, data: oai.Schema, schemas: Schemas) -> Union[Schemas, PropertyError]:
-    prop: Union[PropertyError, ModelProperty, EnumProperty]
-    if data.enum is not None:
-        prop, schemas = build_enum_property(
-            data=data, name=name, required=True, schemas=schemas, enum=data.enum, parent_name=None
-        )
-    else:
-        prop, schemas = build_model_property(data=data, name=name, schemas=schemas, required=True, parent_name=None)
-    if isinstance(prop, PropertyError):
-        return prop
-    else:
-        return schemas
-
-
-def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) -> Schemas:
+def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas) -> Schemas:
     """ Get a list of Schemas from an OpenAPI dict """
-    schemas = Schemas()
     to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items()
     processing = True
     errors: List[PropertyError] = []
@@ -488,13 +482,18 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
             if isinstance(data, oai.Reference):
                 schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported."))
                 continue
-            schemas_or_err = update_schemas_with_data(name, data, schemas)
+            ref_path = parse_reference_path(f"#/components/schemas/{name}")
+            if isinstance(ref_path, ParseError):
+                next_round.append((name, data))
+                errors.append(PropertyError(detail=ref_path.detail, data=data))
+                continue
+            schemas_or_err = update_schemas_with_data(ref_path, data, schemas)
             if isinstance(schemas_or_err, PropertyError):
                 next_round.append((name, data))
                 errors.append(schemas_or_err)
-            else:
-                schemas = schemas_or_err
-                processing = True  # We made some progress this round, do another after it's done
+                continue
+            schemas = schemas_or_err
+            processing = True  # We made some progress this round, do another after it's done
         to_process = next_round
 
     schemas.errors.extend(errors)
diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py
index fca4fc881..9e62643f1 100644
--- a/openapi_python_client/parser/properties/enum_property.py
+++ b/openapi_python_client/parser/properties/enum_property.py
@@ -5,8 +5,8 @@
 import attr
 
 from ... import utils
-from ..reference import Reference
 from .property import Property
+from .schemas import Class
 
 ValueType = Union[str, int]
 
@@ -16,14 +16,14 @@ class EnumProperty(Property):
     """ A property that should use an enum """
 
     values: Dict[str, ValueType]
-    reference: Reference
+    class_info: Class
     value_type: Type[ValueType]
     default: Optional[Any] = attr.ib()
 
     template: ClassVar[str] = "enum_property.py.jinja"
 
     def get_base_type_string(self, json: bool = False) -> str:
-        return self.reference.class_name
+        return self.class_info.name
 
     def get_base_json_type_string(self, json: bool = False) -> str:
         return self.value_type.__name__
@@ -37,7 +37,7 @@ def get_imports(self, *, prefix: str) -> Set[str]:
             back to the root of the generated client.
         """
         imports = super().get_imports(prefix=prefix)
-        imports.add(f"from {prefix}models.{self.reference.module_name} import {self.reference.class_name}")
+        imports.add(f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}")
         return imports
 
     @staticmethod
diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py
index 35717e8ba..df848ddae 100644
--- a/openapi_python_client/parser/properties/model_property.py
+++ b/openapi_python_client/parser/properties/model_property.py
@@ -3,19 +3,21 @@
 
 import attr
 
+from ... import Config
 from ... import schema as oai
 from ... import utils
-from ..errors import PropertyError
-from ..reference import Reference
+from ...utils import to_valid_python_identifier
+from ..errors import ParseError, PropertyError
+from . import parse_reference_path
 from .property import Property
-from .schemas import Schemas
+from .schemas import Class, Schemas
 
 
 @attr.s(auto_attribs=True, frozen=True)
 class ModelProperty(Property):
     """ A property which refers to another Schema """
 
-    reference: Reference
+    class_info: Class
     required_properties: List[Property]
     optional_properties: List[Property]
     description: str
@@ -27,7 +29,7 @@ class ModelProperty(Property):
     json_is_dict: ClassVar[bool] = True
 
     def get_base_type_string(self, json: bool = False) -> str:
-        return self.reference.class_name
+        return self.class_info.name
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -40,7 +42,7 @@ def get_imports(self, *, prefix: str) -> Set[str]:
         imports = super().get_imports(prefix=prefix)
         imports.update(
             {
-                f"from {prefix}models.{self.reference.module_name} import {self.reference.class_name}",
+                f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}",
                 "from typing import Dict",
                 "from typing import cast",
             }
@@ -88,10 +90,14 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
     unprocessed_props = data.properties or {}
     for sub_prop in data.allOf or []:
         if isinstance(sub_prop, oai.Reference):
-            source_name = Reference.from_ref(sub_prop.ref).class_name
-            sub_model = schemas.models.get(source_name)
+            ref_path = parse_reference_path(sub_prop.ref)
+            if isinstance(ref_path, ParseError):
+                return PropertyError(detail=ref_path.detail, data=sub_prop)
+            sub_model = schemas.classes_by_reference.get(ref_path)
             if sub_model is None:
                 return PropertyError(f"Reference {sub_prop.ref} not found")
+            if not isinstance(sub_model, ModelProperty):
+                return PropertyError("Cannot take allOf a non-object")
             for prop in chain(sub_model.required_properties, sub_model.optional_properties):
                 prop_or_error = _check_existing(prop)
                 if isinstance(prop_or_error, PropertyError):
@@ -155,7 +161,7 @@ def _get_additional_properties(
 
 
 def build_model_property(
-    *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str]
+    *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str], config: Config
 ) -> Tuple[Union[ModelProperty, PropertyError], Schemas]:
     """
     A single ModelProperty from its OAI data
@@ -167,11 +173,12 @@ def build_model_property(
         schemas: Existing Schemas which have already been processed (to check name conflicts)
         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
     """
     class_name = data.title or name
     if parent_name:
         class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
-    ref = Reference.from_ref(class_name)
+    class_info = Class.from_string(string=class_name, schemas=schemas)
 
     property_data = _process_properties(data=data, schemas=schemas, class_name=class_name)
     if isinstance(property_data, PropertyError):
@@ -187,7 +194,7 @@ def build_model_property(
         return additional_properties, schemas
 
     prop = ModelProperty(
-        reference=ref,
+        class_info=class_info,
         required_properties=property_data.required_props,
         optional_properties=property_data.optional_props,
         relative_imports=property_data.relative_imports,
@@ -197,12 +204,11 @@ def build_model_property(
         required=required,
         name=name,
         additional_properties=additional_properties,
+        python_name=to_valid_python_identifier(value=name, field_prefix=config.field_prefix),
     )
-    if prop.reference.class_name in schemas.models:
-        error = PropertyError(
-            data=data, detail=f'Attempted to generate duplicate models with name "{prop.reference.class_name}"'
-        )
+    if class_info.name in schemas.classes_by_name:
+        error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"')
         return error, schemas
 
-    schemas = attr.evolve(schemas, models={**schemas.models, prop.reference.class_name: prop})
+    schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop})
     return prop, schemas
diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py
index 7eb5161f9..3b70e67b6 100644
--- a/openapi_python_client/parser/properties/property.py
+++ b/openapi_python_client/parser/properties/property.py
@@ -2,7 +2,7 @@
 
 import attr
 
-from ... import utils
+from ...utils import PythonIdentifier
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -26,14 +26,11 @@ class Property:
     _type_string: ClassVar[str] = ""
     _json_type_string: ClassVar[str] = ""  # Type of the property after JSON serialization
     default: Optional[str] = attr.ib()
-    python_name: str = attr.ib(init=False)
+    python_name: PythonIdentifier
 
     template: ClassVar[Optional[str]] = None
     json_is_dict: ClassVar[bool] = False
 
-    def __attrs_post_init__(self) -> None:
-        object.__setattr__(self, "python_name", utils.to_valid_python_identifier(utils.snake_case(self.name)))
-
     def get_base_type_string(self) -> str:
         return self._type_string
 
diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index c30f6a059..c3cbd2466 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -1,10 +1,15 @@
-__all__ = ["Schemas"]
+__all__ = ["Class", "Schemas", "parse_reference_path"]
 
-from typing import TYPE_CHECKING, Dict, List
+from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast
+from urllib.parse import urlparse
 
 import attr
+from pydantic import BaseModel
 
-from ..errors import ParseError
+from ... import Config
+from ... import schema as oai
+from ... import utils
+from ..errors import ParseError, PropertyError
 
 if TYPE_CHECKING:  # pragma: no cover
     from .enum_property import EnumProperty
@@ -14,10 +19,57 @@
     ModelProperty = "ModelProperty"
 
 
+_ReferencePath = NewType("_ReferencePath", str)
+_ClassName = NewType("_ClassName", str)
+
+
+def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]:
+    parsed = urlparse(ref_path_raw)
+    if parsed.scheme is not None or parsed.path is not None:
+        return ParseError(detail="Remote references are not supported yet.")
+    return cast(_ReferencePath, parsed.fragment)
+
+
+class Class(BaseModel):
+    """ Info about a generated class which will be in models """
+
+    name: _ClassName
+    module_name: str
+
+    @staticmethod
+    def from_string(*, string: str, config: Config) -> "Class":
+        """ Get a Class from an arbitrary string """
+        class_name = string.split("/")[-1]  # Get rid of ref path stuff
+        class_name = utils.pascal_case(class_name)
+
+        if class_name in config.class_overrides:
+            return config.class_overrides[class_name]
+
+        return Class(name=cast(_ClassName, class_name), module_name=utils.snake_case(class_name))
+
+
 @attr.s(auto_attribs=True, frozen=True)
 class Schemas:
-    """ Structure for containing all defined, shareable, and resuabled schemas (attr classes and Enums) """
+    """ Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums) """
 
-    enums: Dict[str, EnumProperty] = attr.ib(factory=dict)
-    models: Dict[str, ModelProperty] = attr.ib(factory=dict)
+    classes_by_reference: Dict[_ReferencePath, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict)
+    classes_by_name: Dict[_ClassName, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict)
     errors: List[ParseError] = attr.ib(factory=list)
+
+
+def update_schemas_with_data(
+    ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas
+) -> Union[Schemas, PropertyError]:
+    from . import build_enum_property, build_model_property
+
+    prop: Union[PropertyError, ModelProperty, EnumProperty]
+    if data.enum is not None:
+        prop, schemas = build_enum_property(
+            data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None
+        )
+    else:
+        prop, schemas = build_model_property(data=data, name=ref_path, schemas=schemas, required=True, parent_name=None)
+    if isinstance(prop, PropertyError):
+        return prop
+    schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference})
+    return schemas
diff --git a/openapi_python_client/parser/reference.py b/openapi_python_client/parser/reference.py
deleted file mode 100644
index 809d24441..000000000
--- a/openapi_python_client/parser/reference.py
+++ /dev/null
@@ -1,27 +0,0 @@
-""" A Reference is ultimately a Class which will be in models, usually defined in a body input or return type """
-
-from dataclasses import dataclass
-from typing import Dict
-
-from .. import utils
-
-class_overrides: Dict[str, "Reference"] = {}
-
-
-@dataclass
-class Reference:
-    """ A reference to a class which will be in models """
-
-    class_name: str
-    module_name: str
-
-    @staticmethod
-    def from_ref(ref: str) -> "Reference":
-        """ Get a Reference from the openapi #/schemas/blahblah string """
-        ref_value = ref.split("/")[-1]
-        class_name = utils.pascal_case(ref_value)
-
-        if class_name in class_overrides:
-            return class_overrides[class_name]
-
-        return Reference(class_name=class_name, module_name=utils.snake_case(class_name))
diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py
index 3d01a0eab..75ccfd418 100644
--- a/openapi_python_client/parser/responses.py
+++ b/openapi_python_client/parser/responses.py
@@ -4,7 +4,9 @@
 
 import attr
 
+from .. import Config
 from .. import schema as oai
+from ..utils import to_valid_python_identifier
 from .errors import ParseError, PropertyError
 from .properties import NoneProperty, Property, Schemas, property_from_data
 
@@ -26,23 +28,30 @@ class Response:
 }
 
 
-def empty_response(status_code: int, response_name: str) -> Response:
+def empty_response(*, status_code: int, response_name: str, config: Config) -> Response:
+    """ Return an empty response, for when no response type is defined """
     return Response(
         status_code=status_code,
-        prop=NoneProperty(name=response_name, default=None, nullable=False, required=True),
+        prop=NoneProperty(
+            name=response_name,
+            default=None,
+            nullable=False,
+            required=True,
+            python_name=to_valid_python_identifier(value=response_name, field_prefix=config.field_prefix),
+        ),
         source="None",
     )
 
 
 def response_from_data(
-    *, status_code: int, data: Union[oai.Response, oai.Reference], schemas: Schemas, parent_name: str
+    *, status_code: int, data: Union[oai.Response, oai.Reference], schemas: Schemas, parent_name: str, config: Config
 ) -> Tuple[Union[Response, ParseError], Schemas]:
     """ Generate a Response from the OpenAPI dictionary representation of it """
 
     response_name = f"response_{status_code}"
     if isinstance(data, oai.Reference) or data.content is None:
         return (
-            empty_response(status_code, response_name),
+            empty_response(status_code=status_code, response_name=response_name, config=config),
             schemas,
         )
 
diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py
index 7803b3a54..5a0bfc644 100644
--- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py
+++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py
@@ -1,4 +1,4 @@
-from pydantic import BaseModel, Field
+from pydantic import AnyUrl, BaseModel, Field
 
 
 class Reference(BaseModel):
@@ -12,7 +12,7 @@ class Reference(BaseModel):
     and not by the JSON Schema specification.
     """
 
-    ref: str = Field(alias="$ref")
+    ref: AnyUrl = Field(alias="$ref")
     """**REQUIRED**. The reference string."""
 
     class Config:
diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py
index 6cdcb2119..9bad71450 100644
--- a/openapi_python_client/utils.py
+++ b/openapi_python_client/utils.py
@@ -1,6 +1,7 @@
 import builtins
 import re
 from keyword import iskeyword
+from typing import NewType, cast
 
 import stringcase
 
@@ -47,11 +48,10 @@ def remove_string_escapes(value: str) -> str:
     return value.replace('"', r"\"")
 
 
-# This can be changed by config.Config.load_config
-FIELD_PREFIX = "field_"
+PythonIdentifier = NewType("PythonIdentifier", str)
 
 
-def to_valid_python_identifier(value: str) -> str:
+def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdentifier:
     """
     Given a string, attempt to coerce it into a valid Python identifier by stripping out invalid characters and, if
     necessary, prepending a prefix.
@@ -62,6 +62,6 @@ def to_valid_python_identifier(value: str) -> str:
     new_value = fix_reserved_words(fix_keywords(sanitize(value)))
 
     if new_value.isidentifier():
-        return new_value
+        return cast(PythonIdentifier, new_value)
 
-    return f"{FIELD_PREFIX}{new_value}"
+    return cast(PythonIdentifier, f"{field_prefix}{new_value}")

From da8a09e1c51bd053790afaac593fb37992461639 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sat, 27 Mar 2021 10:58:32 -0600
Subject: [PATCH 02/11] refactorRevert field_prefix refactor, continue class
 name refactor [WIP]

---
 openapi_python_client/config.py               |  6 +++-
 openapi_python_client/parser/openapi.py       | 36 ++++++++++---------
 .../parser/properties/__init__.py             |  1 +
 .../parser/properties/model_property.py       |  4 +--
 .../parser/properties/property.py             |  7 ++--
 .../parser/properties/schemas.py              |  6 ++--
 openapi_python_client/parser/responses.py     |  6 ++--
 openapi_python_client/utils.py                | 10 +++---
 8 files changed, 43 insertions(+), 33 deletions(-)

diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py
index 947d0a405..3deb79e71 100644
--- a/openapi_python_client/config.py
+++ b/openapi_python_client/config.py
@@ -17,5 +17,9 @@ class Config(BaseModel):
     @staticmethod
     def load_from_path(path: Path) -> "Config":
         """ Creates a Config from provided JSON or YAML file and sets a bunch of globals from it """
+        from . import utils
+
         config_data = yaml.safe_load(path.read_text())
-        return Config(**config_data)
+        config = Config(**config_data)
+        utils.FIELD_PREFIX = config.field_prefix
+        return config
diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py
index 53a0c70a4..d40923ec1 100644
--- a/openapi_python_client/parser/openapi.py
+++ b/openapi_python_client/parser/openapi.py
@@ -37,7 +37,7 @@ class EndpointCollection:
 
     @staticmethod
     def from_data(
-        *, data: Dict[str, oai.PathItem], schemas: Schemas
+        *, data: Dict[str, oai.PathItem], schemas: Schemas, config: Config
     ) -> Tuple[Dict[str, "EndpointCollection"], Schemas]:
         """ Parse the openapi paths data to get EndpointCollections by tag """
         endpoints_by_tag: Dict[str, EndpointCollection] = {}
@@ -52,7 +52,7 @@ def from_data(
                 tag = utils.snake_case((operation.tags or ["default"])[0])
                 collection = endpoints_by_tag.setdefault(tag, EndpointCollection(tag=tag))
                 endpoint, schemas = Endpoint.from_data(
-                    data=operation, path=path, method=method, tag=tag, schemas=schemas
+                    data=operation, path=path, method=method, tag=tag, schemas=schemas, config=config
                 )
                 if isinstance(endpoint, ParseError):
                     endpoint.header = (
@@ -102,21 +102,21 @@ class Endpoint:
     errors: List[ParseError] = field(default_factory=list)
 
     @staticmethod
-    def parse_request_form_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]:
+    def parse_request_form_body(*, body: oai.RequestBody, config: Config) -> Optional[Class]:
         """ Return form_body_reference """
         body_content = body.content
         form_body = body_content.get("application/x-www-form-urlencoded")
         if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference):
-            return Class.from_string(string=form_body.media_type_schema.ref, schemas=schemas)
+            return Class.from_string(string=form_body.media_type_schema.ref, config=config)
         return None
 
     @staticmethod
-    def parse_multipart_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]:
+    def parse_multipart_body(*, body: oai.RequestBody, config: Config) -> Optional[Class]:
         """ Return form_body_reference """
         body_content = body.content
         json_body = body_content.get("multipart/form-data")
         if json_body is not None and isinstance(json_body.media_type_schema, oai.Reference):
-            return Class.from_string(string=json_body.media_type_schema.ref, schemas=schemas)
+            return Class.from_string(string=json_body.media_type_schema.ref, config=config)
         return None
 
     @staticmethod
@@ -138,21 +138,21 @@ def parse_request_json_body(
 
     @staticmethod
     def _add_body(
-        *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas
+        *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config
     ) -> Tuple[Union[ParseError, "Endpoint"], Schemas]:
         """ Adds form or JSON body to Endpoint if included in data """
         endpoint = deepcopy(endpoint)
         if data.requestBody is None or isinstance(data.requestBody, oai.Reference):
             return endpoint, schemas
 
-        endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, schemas=schemas)
+        endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, config=config)
         json_body, schemas = Endpoint.parse_request_json_body(
             body=data.requestBody, schemas=schemas, parent_name=endpoint.name
         )
         if isinstance(json_body, ParseError):
             return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas
 
-        endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, schemas=schemas)
+        endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, config=config)
 
         if endpoint.form_body_class:
             endpoint.relative_imports.add(import_string_from_class(endpoint.form_body_class, prefix="...models"))
@@ -164,7 +164,9 @@ def _add_body(
         return endpoint, schemas
 
     @staticmethod
-    def _add_responses(*, endpoint: "Endpoint", data: oai.Responses, schemas: Schemas) -> Tuple["Endpoint", Schemas]:
+    def _add_responses(
+        *, endpoint: "Endpoint", data: oai.Responses, schemas: Schemas, config: Config
+    ) -> Tuple["Endpoint", Schemas]:
         endpoint = deepcopy(endpoint)
         for code, response_data in data.items():
 
@@ -183,7 +185,7 @@ def _add_responses(*, endpoint: "Endpoint", data: oai.Responses, schemas: Schema
                 continue
 
             response, schemas = response_from_data(
-                status_code=status_code, data=response_data, schemas=schemas, parent_name=endpoint.name
+                status_code=status_code, data=response_data, schemas=schemas, parent_name=endpoint.name, config=config
             )
             if isinstance(response, ParseError):
                 endpoint.errors.append(
@@ -235,7 +237,7 @@ def _add_parameters(
 
     @staticmethod
     def from_data(
-        *, data: oai.Operation, path: str, method: str, tag: str, schemas: Schemas
+        *, data: oai.Operation, path: str, method: str, tag: str, schemas: Schemas, config: Config
     ) -> Tuple[Union["Endpoint", ParseError], Schemas]:
         """ Construct an endpoint from the OpenAPI data """
 
@@ -256,8 +258,8 @@ def from_data(
         result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas)
         if isinstance(result, ParseError):
             return result, schemas
-        result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas)
-        result, schemas = Endpoint._add_body(endpoint=result, data=data, schemas=schemas)
+        result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config)
+        result, schemas = Endpoint._add_body(endpoint=result, data=data, schemas=schemas, config=config)
 
         return result, schemas
 
@@ -291,10 +293,12 @@ def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", Ge
                 header="openapi-python-client only supports OpenAPI 3.x",
                 detail=f"The version of the provided document was {openapi.openapi}",
             )
-        schemas = Schemas(class_overrides=config.class_overrides)
+        schemas = Schemas()
         if openapi.components and openapi.components.schemas:
             schemas = build_schemas(components=openapi.components.schemas, schemas=schemas)
-        endpoint_collections_by_tag, schemas = EndpointCollection.from_data(data=openapi.paths, schemas=schemas)
+        endpoint_collections_by_tag, schemas = EndpointCollection.from_data(
+            data=openapi.paths, schemas=schemas, config=config
+        )
 
         enums = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, EnumProperty))
         models = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty))
diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index 58c65139b..0cd100618 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -2,6 +2,7 @@
     "Class",
     "EnumProperty",
     "ModelProperty",
+    "NoneProperty",
     "Property",
     "Schemas",
     "build_schemas",
diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py
index df848ddae..2e400cb6d 100644
--- a/openapi_python_client/parser/properties/model_property.py
+++ b/openapi_python_client/parser/properties/model_property.py
@@ -6,7 +6,6 @@
 from ... import Config
 from ... import schema as oai
 from ... import utils
-from ...utils import to_valid_python_identifier
 from ..errors import ParseError, PropertyError
 from . import parse_reference_path
 from .property import Property
@@ -178,7 +177,7 @@ def build_model_property(
     class_name = data.title or name
     if parent_name:
         class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
-    class_info = Class.from_string(string=class_name, schemas=schemas)
+    class_info = Class.from_string(string=class_name, config=config)
 
     property_data = _process_properties(data=data, schemas=schemas, class_name=class_name)
     if isinstance(property_data, PropertyError):
@@ -204,7 +203,6 @@ def build_model_property(
         required=required,
         name=name,
         additional_properties=additional_properties,
-        python_name=to_valid_python_identifier(value=name, field_prefix=config.field_prefix),
     )
     if class_info.name in schemas.classes_by_name:
         error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"')
diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py
index 3b70e67b6..7eb5161f9 100644
--- a/openapi_python_client/parser/properties/property.py
+++ b/openapi_python_client/parser/properties/property.py
@@ -2,7 +2,7 @@
 
 import attr
 
-from ...utils import PythonIdentifier
+from ... import utils
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -26,11 +26,14 @@ class Property:
     _type_string: ClassVar[str] = ""
     _json_type_string: ClassVar[str] = ""  # Type of the property after JSON serialization
     default: Optional[str] = attr.ib()
-    python_name: PythonIdentifier
+    python_name: str = attr.ib(init=False)
 
     template: ClassVar[Optional[str]] = None
     json_is_dict: ClassVar[bool] = False
 
+    def __attrs_post_init__(self) -> None:
+        object.__setattr__(self, "python_name", utils.to_valid_python_identifier(utils.snake_case(self.name)))
+
     def get_base_type_string(self) -> str:
         return self._type_string
 
diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index c3cbd2466..19a794d8d 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -58,7 +58,7 @@ class Schemas:
 
 
 def update_schemas_with_data(
-    ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas
+    ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config
 ) -> Union[Schemas, PropertyError]:
     from . import build_enum_property, build_model_property
 
@@ -68,7 +68,9 @@ def update_schemas_with_data(
             data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None
         )
     else:
-        prop, schemas = build_model_property(data=data, name=ref_path, schemas=schemas, required=True, parent_name=None)
+        prop, schemas = build_model_property(
+            data=data, name=ref_path, schemas=schemas, required=True, parent_name=None, config=config
+        )
     if isinstance(prop, PropertyError):
         return prop
     schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference})
diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py
index 75ccfd418..c99a453d1 100644
--- a/openapi_python_client/parser/responses.py
+++ b/openapi_python_client/parser/responses.py
@@ -6,7 +6,6 @@
 
 from .. import Config
 from .. import schema as oai
-from ..utils import to_valid_python_identifier
 from .errors import ParseError, PropertyError
 from .properties import NoneProperty, Property, Schemas, property_from_data
 
@@ -28,7 +27,7 @@ class Response:
 }
 
 
-def empty_response(*, status_code: int, response_name: str, config: Config) -> Response:
+def empty_response(status_code: int, response_name: str) -> Response:
     """ Return an empty response, for when no response type is defined """
     return Response(
         status_code=status_code,
@@ -37,7 +36,6 @@ def empty_response(*, status_code: int, response_name: str, config: Config) -> R
             default=None,
             nullable=False,
             required=True,
-            python_name=to_valid_python_identifier(value=response_name, field_prefix=config.field_prefix),
         ),
         source="None",
     )
@@ -51,7 +49,7 @@ def response_from_data(
     response_name = f"response_{status_code}"
     if isinstance(data, oai.Reference) or data.content is None:
         return (
-            empty_response(status_code=status_code, response_name=response_name, config=config),
+            empty_response(status_code=status_code, response_name=response_name),
             schemas,
         )
 
diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py
index 9bad71450..6cdcb2119 100644
--- a/openapi_python_client/utils.py
+++ b/openapi_python_client/utils.py
@@ -1,7 +1,6 @@
 import builtins
 import re
 from keyword import iskeyword
-from typing import NewType, cast
 
 import stringcase
 
@@ -48,10 +47,11 @@ def remove_string_escapes(value: str) -> str:
     return value.replace('"', r"\"")
 
 
-PythonIdentifier = NewType("PythonIdentifier", str)
+# This can be changed by config.Config.load_config
+FIELD_PREFIX = "field_"
 
 
-def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdentifier:
+def to_valid_python_identifier(value: str) -> str:
     """
     Given a string, attempt to coerce it into a valid Python identifier by stripping out invalid characters and, if
     necessary, prepending a prefix.
@@ -62,6 +62,6 @@ def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdenti
     new_value = fix_reserved_words(fix_keywords(sanitize(value)))
 
     if new_value.isidentifier():
-        return cast(PythonIdentifier, new_value)
+        return new_value
 
-    return cast(PythonIdentifier, f"{field_prefix}{new_value}")
+    return f"{FIELD_PREFIX}{new_value}"

From ff8bdb919d0aa54cace8ff9ed964c133ca4bbd06 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sat, 27 Mar 2021 12:04:00 -0600
Subject: [PATCH 03/11] test: Fix all the type errors and start fixing unit
 tests

---
 .../api/tests/defaults_tests_defaults_post.py | 124 +++----
 .../api/tests/get_user_list.py                |  37 +-
 .../api/tests/int_enum_tests_int_enum_post.py |  28 +-
 .../tests/json_body_tests_json_body_post.py   | 109 ++++++
 ...tional_value_tests_optional_query_param.py |  15 +-
 .../tests/upload_file_tests_upload_post.py    |  37 +-
 .../my_test_api_client/models/__init__.py     |   5 +
 .../my_test_api_client/models/a_model.py      | 351 ++++++++++++++++++
 .../models/a_model_model.py                   |  44 +++
 .../models/a_model_not_required_model.py      |  44 +++
 .../a_model_not_required_nullable_model.py    |  44 +++
 .../models/a_model_nullable_model.py          |  44 +++
 .../models/http_validation_error.py           |   9 +-
 .../models/model_from_all_of.py               |  21 +-
 .../model_with_additional_properties_refed.py |  20 +-
 .../models/model_with_property_ref.py         |  12 +-
 .../models/model_with_union_property.py       |  34 +-
 end_to_end_tests/regen_golden_record.py       |   2 +-
 end_to_end_tests/test_end_to_end.py           |   2 +-
 openapi_python_client/__init__.py             |  10 +-
 openapi_python_client/cli.py                  |  22 +-
 openapi_python_client/config.py               |   7 +-
 openapi_python_client/parser/openapi.py       |  20 +-
 .../parser/properties/__init__.py             |  50 ++-
 .../parser/properties/model_property.py       |  20 +-
 .../parser/properties/schemas.py              |  20 +-
 openapi_python_client/parser/responses.py     |   1 +
 .../templates/endpoint_macros.py.jinja        |  12 +-
 .../templates/endpoint_module.py.jinja        |   8 +-
 .../templates/int_enum.py.jinja               |   2 +-
 .../templates/model.py.jinja                  |  15 +-
 .../property_templates/enum_property.py.jinja |   2 +-
 .../model_property.py.jinja                   |   2 +-
 .../templates/str_enum.py.jinja               |   2 +-
 tests/test___init__.py                        | 112 +++---
 .../test_properties/test_model_property.py    |  23 +-
 36 files changed, 967 insertions(+), 343 deletions(-)
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
 create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py

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 b6aaaea6e..fc924f5f9 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
@@ -5,9 +5,6 @@
 from dateutil.parser import isoparse
 
 from ...client import Client
-from ...models.an_enum import AnEnum
-from ...models.http_validation_error import HTTPValidationError
-from ...models.model_with_union_property import ModelWithUnionProperty
 from ...types import UNSET, Response, Unset
 
 
@@ -23,14 +20,14 @@ def _get_kwargs(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[AnEnum]] = UNSET,
+    list_prop: Union[Unset, List[None]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
-    enum_prop: Union[Unset, AnEnum] = UNSET,
-    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
-    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
+    union_prop_with_ref: Union[None, Unset, float] = 0.6,
+    enum_prop: Union[Unset, None] = UNSET,
+    model_prop: Union[Unset, None] = UNSET,
+    required_model_prop: None,
+    nullable_model_prop: Union[None, Unset] = UNSET,
+    nullable_required_model_prop: None,
 ) -> Dict[str, Any]:
     url = "{}/tests/defaults".format(client.base_url)
 
@@ -57,11 +54,11 @@ def _get_kwargs(
     if not isinstance(date_prop, Unset):
         json_date_prop = date_prop.isoformat()
 
-    json_list_prop: Union[Unset, List[str]] = UNSET
+    json_list_prop: Union[Unset, List[None]] = UNSET
     if not isinstance(list_prop, Unset):
         json_list_prop = []
         for list_prop_item_data in list_prop:
-            list_prop_item = list_prop_item_data.value
+            list_prop_item = None
 
             json_list_prop.append(list_prop_item)
 
@@ -71,42 +68,34 @@ def _get_kwargs(
     else:
         json_union_prop = union_prop
 
-    json_union_prop_with_ref: Union[Unset, float, str]
+    json_union_prop_with_ref: Union[None, Unset, float]
     if isinstance(union_prop_with_ref, Unset):
         json_union_prop_with_ref = UNSET
-    elif isinstance(union_prop_with_ref, AnEnum):
-        json_union_prop_with_ref = UNSET
-        if not isinstance(union_prop_with_ref, Unset):
-            json_union_prop_with_ref = union_prop_with_ref.value
+    elif isinstance(union_prop_with_ref, None):
+        json_union_prop_with_ref = None
 
     else:
         json_union_prop_with_ref = union_prop_with_ref
 
-    json_enum_prop: Union[Unset, str] = UNSET
-    if not isinstance(enum_prop, Unset):
-        json_enum_prop = enum_prop.value
+    json_enum_prop = None
 
-    json_model_prop: Union[Unset, Dict[str, Any]] = UNSET
-    if not isinstance(model_prop, Unset):
-        json_model_prop = model_prop.to_dict()
+    json_model_prop = None
 
-    json_required_model_prop = required_model_prop.to_dict()
+    json_required_model_prop = None
 
-    json_nullable_model_prop: Union[Dict[str, Any], None, Unset]
+    json_nullable_model_prop: Union[None, Unset]
     if isinstance(nullable_model_prop, Unset):
         json_nullable_model_prop = UNSET
     elif nullable_model_prop is None:
         json_nullable_model_prop = None
     else:
-        json_nullable_model_prop = UNSET
-        if not isinstance(nullable_model_prop, Unset):
-            json_nullable_model_prop = nullable_model_prop.to_dict()
+        json_nullable_model_prop = None
 
-    json_nullable_required_model_prop: Union[Dict[str, Any], None]
+    json_nullable_required_model_prop: None
     if nullable_required_model_prop is None:
         json_nullable_required_model_prop = None
     else:
-        json_nullable_required_model_prop = nullable_required_model_prop.to_dict()
+        json_nullable_required_model_prop = None
 
     params: Dict[str, Any] = {
         "string_prop": string_prop,
@@ -122,12 +111,11 @@ def _get_kwargs(
         "union_prop": json_union_prop,
         "union_prop_with_ref": json_union_prop_with_ref,
         "enum_prop": json_enum_prop,
+        "model_prop": json_model_prop,
+        "required_model_prop": json_required_model_prop,
         "nullable_model_prop": json_nullable_model_prop,
         "nullable_required_model_prop": json_nullable_required_model_prop,
     }
-    if not isinstance(json_model_prop, Unset):
-        params.update(json_model_prop)
-    params.update(json_required_model_prop)
     params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
 
     return {
@@ -139,19 +127,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
+        response_422 = None
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -172,15 +160,15 @@ def sync_detailed(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[AnEnum]] = UNSET,
+    list_prop: Union[Unset, List[None]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
-    enum_prop: Union[Unset, AnEnum] = UNSET,
-    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
-    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
-) -> Response[Union[None, HTTPValidationError]]:
+    union_prop_with_ref: Union[None, Unset, float] = 0.6,
+    enum_prop: Union[Unset, None] = UNSET,
+    model_prop: Union[Unset, None] = UNSET,
+    required_model_prop: None,
+    nullable_model_prop: Union[None, Unset] = UNSET,
+    nullable_required_model_prop: None,
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         string_prop=string_prop,
@@ -221,15 +209,15 @@ def sync(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[AnEnum]] = UNSET,
+    list_prop: Union[Unset, List[None]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
-    enum_prop: Union[Unset, AnEnum] = UNSET,
-    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
-    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
-) -> Optional[Union[None, HTTPValidationError]]:
+    union_prop_with_ref: Union[None, Unset, float] = 0.6,
+    enum_prop: Union[Unset, None] = UNSET,
+    model_prop: Union[Unset, None] = UNSET,
+    required_model_prop: None,
+    nullable_model_prop: Union[None, Unset] = UNSET,
+    nullable_required_model_prop: None,
+) -> Optional[Union[None, None]]:
     """  """
 
     return sync_detailed(
@@ -266,15 +254,15 @@ async def asyncio_detailed(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[AnEnum]] = UNSET,
+    list_prop: Union[Unset, List[None]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
-    enum_prop: Union[Unset, AnEnum] = UNSET,
-    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
-    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
-) -> Response[Union[None, HTTPValidationError]]:
+    union_prop_with_ref: Union[None, Unset, float] = 0.6,
+    enum_prop: Union[Unset, None] = UNSET,
+    model_prop: Union[Unset, None] = UNSET,
+    required_model_prop: None,
+    nullable_model_prop: Union[None, Unset] = UNSET,
+    nullable_required_model_prop: None,
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         string_prop=string_prop,
@@ -314,15 +302,15 @@ async def asyncio(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[AnEnum]] = UNSET,
+    list_prop: Union[Unset, List[None]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
-    enum_prop: Union[Unset, AnEnum] = UNSET,
-    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
-    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
-) -> Optional[Union[None, HTTPValidationError]]:
+    union_prop_with_ref: Union[None, Unset, float] = 0.6,
+    enum_prop: Union[Unset, None] = UNSET,
+    model_prop: Union[Unset, None] = UNSET,
+    required_model_prop: None,
+    nullable_model_prop: Union[None, Unset] = UNSET,
+    nullable_required_model_prop: None,
+) -> Optional[Union[None, None]]:
     """  """
 
     return (
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 fa7b39b86..5cc5cde5c 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
@@ -4,15 +4,13 @@
 import httpx
 
 from ...client import Client
-from ...models.an_enum import AnEnum
-from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response
 
 
 def _get_kwargs(
     *,
     client: Client,
-    an_enum_value: List[AnEnum],
+    an_enum_value: List[None],
     some_date: Union[datetime.date, datetime.datetime],
 ) -> Dict[str, Any]:
     url = "{}/tests/".format(client.base_url)
@@ -22,7 +20,7 @@ def _get_kwargs(
 
     json_an_enum_value = []
     for an_enum_value_item_data in an_enum_value:
-        an_enum_value_item = an_enum_value_item_data.value
+        an_enum_value_item = None
 
         json_an_enum_value.append(an_enum_value_item)
 
@@ -46,15 +44,24 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[HTTPValidationError]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[List[None], None]]:
+    if response.status_code == 200:
+        response_200 = []
+        _response_200 = response.json()
+        for response_200_item_data in _response_200:
+            response_200_item = None
+
+            response_200.append(response_200_item)
+
+        return response_200
     if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
+        response_422 = None
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError]:
+def _build_response(*, response: httpx.Response) -> Response[Union[List[None], None]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -66,9 +73,9 @@ def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError
 def sync_detailed(
     *,
     client: Client,
-    an_enum_value: List[AnEnum],
+    an_enum_value: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[HTTPValidationError]:
+) -> Response[Union[List[None], None]]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -85,9 +92,9 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    an_enum_value: List[AnEnum],
+    an_enum_value: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[HTTPValidationError]:
+) -> Optional[Union[List[None], None]]:
     """ Get a list of things  """
 
     return sync_detailed(
@@ -100,9 +107,9 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    an_enum_value: List[AnEnum],
+    an_enum_value: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[HTTPValidationError]:
+) -> Response[Union[List[None], None]]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -118,9 +125,9 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    an_enum_value: List[AnEnum],
+    an_enum_value: List[None],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[HTTPValidationError]:
+) -> Optional[Union[List[None], None]]:
     """ Get a list of things  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
index 7d14632c4..96aed7f17 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
@@ -3,22 +3,20 @@
 import httpx
 
 from ...client import Client
-from ...models.an_int_enum import AnIntEnum
-from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response
 
 
 def _get_kwargs(
     *,
     client: Client,
-    int_enum: AnIntEnum,
+    int_enum: None,
 ) -> Dict[str, Any]:
     url = "{}/tests/int_enum".format(client.base_url)
 
     headers: Dict[str, Any] = client.get_headers()
     cookies: Dict[str, Any] = client.get_cookies()
 
-    json_int_enum = int_enum.value
+    json_int_enum = None
 
     params: Dict[str, Any] = {
         "int_enum": json_int_enum,
@@ -34,19 +32,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
+        response_422 = None
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -58,8 +56,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPVal
 def sync_detailed(
     *,
     client: Client,
-    int_enum: AnIntEnum,
-) -> Response[Union[None, HTTPValidationError]]:
+    int_enum: None,
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         int_enum=int_enum,
@@ -75,8 +73,8 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    int_enum: AnIntEnum,
-) -> Optional[Union[None, HTTPValidationError]]:
+    int_enum: None,
+) -> Optional[Union[None, None]]:
     """  """
 
     return sync_detailed(
@@ -88,8 +86,8 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    int_enum: AnIntEnum,
-) -> Response[Union[None, HTTPValidationError]]:
+    int_enum: None,
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         int_enum=int_enum,
@@ -104,8 +102,8 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    int_enum: AnIntEnum,
-) -> Optional[Union[None, HTTPValidationError]]:
+    int_enum: None,
+) -> Optional[Union[None, None]]:
     """  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
new file mode 100644
index 000000000..a9a89a47c
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
@@ -0,0 +1,109 @@
+from typing import Any, Dict, Optional, Union
+
+import httpx
+
+from ...client import Client
+from ...types import Response
+
+
+def _get_kwargs(
+    *,
+    client: Client,
+    json_body: None,
+) -> Dict[str, Any]:
+    url = "{}/tests/json_body".format(client.base_url)
+
+    headers: Dict[str, Any] = client.get_headers()
+    cookies: Dict[str, Any] = client.get_cookies()
+
+    json_json_body = None
+
+    return {
+        "url": url,
+        "headers": headers,
+        "cookies": cookies,
+        "timeout": client.get_timeout(),
+        "json": json_json_body,
+    }
+
+
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+    if response.status_code == 200:
+        response_200 = None
+
+        return response_200
+    if response.status_code == 422:
+        response_422 = None
+
+        return response_422
+    return None
+
+
+def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+    return Response(
+        status_code=response.status_code,
+        content=response.content,
+        headers=response.headers,
+        parsed=_parse_response(response=response),
+    )
+
+
+def sync_detailed(
+    *,
+    client: Client,
+    json_body: None,
+) -> Response[Union[None, None]]:
+    kwargs = _get_kwargs(
+        client=client,
+        json_body=json_body,
+    )
+
+    response = httpx.post(
+        **kwargs,
+    )
+
+    return _build_response(response=response)
+
+
+def sync(
+    *,
+    client: Client,
+    json_body: None,
+) -> Optional[Union[None, None]]:
+    """ Try sending a JSON body  """
+
+    return sync_detailed(
+        client=client,
+        json_body=json_body,
+    ).parsed
+
+
+async def asyncio_detailed(
+    *,
+    client: Client,
+    json_body: None,
+) -> Response[Union[None, None]]:
+    kwargs = _get_kwargs(
+        client=client,
+        json_body=json_body,
+    )
+
+    async with httpx.AsyncClient() as _client:
+        response = await _client.post(**kwargs)
+
+    return _build_response(response=response)
+
+
+async def asyncio(
+    *,
+    client: Client,
+    json_body: None,
+) -> Optional[Union[None, None]]:
+    """ Try sending a JSON body  """
+
+    return (
+        await asyncio_detailed(
+            client=client,
+            json_body=json_body,
+        )
+    ).parsed
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
index 64431ba2f..2f1f1d48b 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
@@ -3,7 +3,6 @@
 import httpx
 
 from ...client import Client
-from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response, Unset
 
 
@@ -35,19 +34,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
+        response_422 = None
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -60,7 +59,7 @@ def sync_detailed(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Response[Union[None, HTTPValidationError]]:
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         query_param=query_param,
@@ -77,7 +76,7 @@ def sync(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Optional[Union[None, HTTPValidationError]]:
+) -> Optional[Union[None, None]]:
     """ Test optional query parameters """
 
     return sync_detailed(
@@ -90,7 +89,7 @@ async def asyncio_detailed(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Response[Union[None, HTTPValidationError]]:
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
         query_param=query_param,
@@ -106,7 +105,7 @@ async def asyncio(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Optional[Union[None, HTTPValidationError]]:
+) -> Optional[Union[None, None]]:
     """ Test optional query parameters """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
index 705443d95..c06943593 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
@@ -3,15 +3,12 @@
 import httpx
 
 from ...client import Client
-from ...models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost
-from ...models.http_validation_error import HTTPValidationError
-from ...types import UNSET, File, Response, Unset
+from ...types import UNSET, Response, Unset
 
 
 def _get_kwargs(
     *,
     client: Client,
-    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
 ) -> Dict[str, Any]:
     url = "{}/tests/upload".format(client.base_url)
@@ -22,37 +19,27 @@ def _get_kwargs(
     if keep_alive is not UNSET:
         headers["keep-alive"] = keep_alive
 
-    files = {}
-    data = {}
-    for key, value in multipart_data.to_dict().items():
-        if isinstance(value, File):
-            files[key] = value
-        else:
-            data[key] = value
-
     return {
         "url": url,
         "headers": headers,
         "cookies": cookies,
         "timeout": client.get_timeout(),
-        "files": files,
-        "data": data,
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = HTTPValidationError.from_dict(response.json())
+        response_422 = None
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -64,12 +51,10 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPVal
 def sync_detailed(
     *,
     client: Client,
-    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Response[Union[None, HTTPValidationError]]:
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
-        multipart_data=multipart_data,
         keep_alive=keep_alive,
     )
 
@@ -83,14 +68,12 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Optional[Union[None, HTTPValidationError]]:
+) -> Optional[Union[None, None]]:
     """ Upload a file  """
 
     return sync_detailed(
         client=client,
-        multipart_data=multipart_data,
         keep_alive=keep_alive,
     ).parsed
 
@@ -98,12 +81,10 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Response[Union[None, HTTPValidationError]]:
+) -> Response[Union[None, None]]:
     kwargs = _get_kwargs(
         client=client,
-        multipart_data=multipart_data,
         keep_alive=keep_alive,
     )
 
@@ -116,15 +97,13 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Optional[Union[None, HTTPValidationError]]:
+) -> Optional[Union[None, None]]:
     """ Upload a file  """
 
     return (
         await asyncio_detailed(
             client=client,
-            multipart_data=multipart_data,
             keep_alive=keep_alive,
         )
     ).parsed
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 ee7e8833b..bd49638c2 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
@@ -1,5 +1,10 @@
 """ Contains all the data models used in inputs/outputs """
 
+from .a_model import AModel
+from .a_model_model import AModelModel
+from .a_model_not_required_model import AModelNotRequiredModel
+from .a_model_not_required_nullable_model import AModelNotRequiredNullableModel
+from .a_model_nullable_model import AModelNullableModel
 from .all_of_sub_model import AllOfSubModel
 from .an_enum import AnEnum
 from .an_int_enum import AnIntEnum
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
new file mode 100644
index 000000000..92a9738d9
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py
@@ -0,0 +1,351 @@
+import datetime
+from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
+
+import attr
+from dateutil.parser import isoparse
+
+from ..models.a_model_model import AModelModel
+from ..models.a_model_not_required_model import AModelNotRequiredModel
+from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel
+from ..models.a_model_nullable_model import AModelNullableModel
+from ..types import UNSET, Unset
+
+T = TypeVar("T", bound="AModel")
+
+
+@attr.s(auto_attribs=True)
+class AModel:
+    """ A Model for testing all the ways custom objects can be used  """
+
+    an_enum_value: None
+    a_camel_date_time: Union[datetime.date, datetime.datetime]
+    a_date: datetime.date
+    required_not_nullable: str
+    one_of_models: None
+    model: AModelModel
+    a_nullable_date: Optional[datetime.date]
+    required_nullable: Optional[str]
+    nullable_one_of_models: None
+    nullable_model: Optional[AModelNullableModel]
+    nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET
+    a_not_required_date: Union[Unset, datetime.date] = UNSET
+    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[None, Unset] = UNSET
+    not_required_nullable_one_of_models: Union[None, Unset, str] = UNSET
+    not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET
+    not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET
+
+    def to_dict(self) -> Dict[str, Any]:
+        an_enum_value = None
+
+        if isinstance(self.a_camel_date_time, datetime.datetime):
+            a_camel_date_time = self.a_camel_date_time.isoformat()
+
+        else:
+            a_camel_date_time = self.a_camel_date_time.isoformat()
+
+        a_date = self.a_date.isoformat()
+        required_not_nullable = self.required_not_nullable
+        if isinstance(self.one_of_models, None):
+            one_of_models = None
+
+        else:
+            one_of_models = None
+
+        model = self.model.to_dict()
+
+        nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET
+        if not isinstance(self.nested_list_of_enums, Unset):
+            nested_list_of_enums = []
+            for nested_list_of_enums_item_data in self.nested_list_of_enums:
+                nested_list_of_enums_item = []
+                for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data:
+                    nested_list_of_enums_item_item = None
+
+                    nested_list_of_enums_item.append(nested_list_of_enums_item_item)
+
+                nested_list_of_enums.append(nested_list_of_enums_item)
+
+        a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None
+        a_not_required_date: Union[Unset, str] = UNSET
+        if not isinstance(self.a_not_required_date, Unset):
+            a_not_required_date = self.a_not_required_date.isoformat()
+
+        attr_1_leading_digit = self.attr_1_leading_digit
+        required_nullable = self.required_nullable
+        not_required_nullable = self.not_required_nullable
+        not_required_not_nullable = self.not_required_not_nullable
+        nullable_one_of_models: None
+        if self.nullable_one_of_models is None:
+            nullable_one_of_models = None
+        elif isinstance(self.nullable_one_of_models, None):
+            nullable_one_of_models = None
+
+        else:
+            nullable_one_of_models = None
+
+        not_required_one_of_models: Union[None, Unset]
+        if isinstance(self.not_required_one_of_models, Unset):
+            not_required_one_of_models = UNSET
+        elif isinstance(self.not_required_one_of_models, None):
+            not_required_one_of_models = None
+
+        else:
+            not_required_one_of_models = None
+
+        not_required_nullable_one_of_models: Union[None, Unset, str]
+        if isinstance(self.not_required_nullable_one_of_models, Unset):
+            not_required_nullable_one_of_models = UNSET
+        elif self.not_required_nullable_one_of_models is None:
+            not_required_nullable_one_of_models = None
+        elif isinstance(self.not_required_nullable_one_of_models, None):
+            not_required_nullable_one_of_models = None
+
+        elif isinstance(self.not_required_nullable_one_of_models, None):
+            not_required_nullable_one_of_models = None
+
+        else:
+            not_required_nullable_one_of_models = self.not_required_nullable_one_of_models
+
+        nullable_model = self.nullable_model.to_dict() if self.nullable_model else None
+
+        not_required_model: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.not_required_model, Unset):
+            not_required_model = self.not_required_model.to_dict()
+
+        not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET
+        if not isinstance(self.not_required_nullable_model, Unset):
+            not_required_nullable_model = (
+                self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None
+            )
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(
+            {
+                "an_enum_value": an_enum_value,
+                "aCamelDateTime": a_camel_date_time,
+                "a_date": a_date,
+                "required_not_nullable": required_not_nullable,
+                "one_of_models": one_of_models,
+                "model": model,
+                "a_nullable_date": a_nullable_date,
+                "required_nullable": required_nullable,
+                "nullable_one_of_models": nullable_one_of_models,
+                "nullable_model": nullable_model,
+            }
+        )
+        if nested_list_of_enums is not UNSET:
+            field_dict["nested_list_of_enums"] = nested_list_of_enums
+        if a_not_required_date is not UNSET:
+            field_dict["a_not_required_date"] = a_not_required_date
+        if attr_1_leading_digit is not UNSET:
+            field_dict["1_leading_digit"] = attr_1_leading_digit
+        if not_required_nullable is not UNSET:
+            field_dict["not_required_nullable"] = not_required_nullable
+        if not_required_not_nullable is not UNSET:
+            field_dict["not_required_not_nullable"] = not_required_not_nullable
+        if not_required_one_of_models is not UNSET:
+            field_dict["not_required_one_of_models"] = not_required_one_of_models
+        if not_required_nullable_one_of_models is not UNSET:
+            field_dict["not_required_nullable_one_of_models"] = not_required_nullable_one_of_models
+        if not_required_model is not UNSET:
+            field_dict["not_required_model"] = not_required_model
+        if not_required_nullable_model is not UNSET:
+            field_dict["not_required_nullable_model"] = not_required_nullable_model
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        an_enum_value = None
+
+        def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]:
+            try:
+                a_camel_date_time_type0: datetime.datetime
+                if not isinstance(data, str):
+                    raise TypeError()
+                a_camel_date_time_type0 = isoparse(data)
+
+                return a_camel_date_time_type0
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, str):
+                raise TypeError()
+            a_camel_date_time_type1: datetime.date
+            a_camel_date_time_type1 = isoparse(data).date()
+
+            return a_camel_date_time_type1
+
+        a_camel_date_time = _parse_a_camel_date_time(d.pop("aCamelDateTime"))
+
+        a_date = isoparse(d.pop("a_date")).date()
+
+        required_not_nullable = d.pop("required_not_nullable")
+
+        def _parse_one_of_models(data: object) -> None:
+            if data is None:
+                return data
+            try:
+                one_of_models_type0: None
+                if not data is None:
+                    raise TypeError()
+                one_of_models_type0 = UNSET
+
+                return one_of_models_type0
+            except:  # noqa: E722
+                pass
+            if not data is None:
+                raise TypeError()
+            one_of_models_type1: None
+            one_of_models_type1 = UNSET
+
+            return one_of_models_type1
+
+        one_of_models = _parse_one_of_models(d.pop("one_of_models"))
+
+        model = AModelModel.from_dict(d.pop("model"))
+
+        nested_list_of_enums = []
+        _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
+        for nested_list_of_enums_item_data in _nested_list_of_enums or []:
+            nested_list_of_enums_item = []
+            _nested_list_of_enums_item = nested_list_of_enums_item_data
+            for nested_list_of_enums_item_item_data in _nested_list_of_enums_item:
+                nested_list_of_enums_item_item = None
+
+                nested_list_of_enums_item.append(nested_list_of_enums_item_item)
+
+            nested_list_of_enums.append(nested_list_of_enums_item)
+
+        a_nullable_date = None
+        _a_nullable_date = d.pop("a_nullable_date")
+        if _a_nullable_date is not None:
+            a_nullable_date = isoparse(_a_nullable_date).date()
+
+        a_not_required_date: Union[Unset, datetime.date] = UNSET
+        _a_not_required_date = d.pop("a_not_required_date", UNSET)
+        if not isinstance(_a_not_required_date, Unset):
+            a_not_required_date = isoparse(_a_not_required_date).date()
+
+        attr_1_leading_digit = d.pop("1_leading_digit", UNSET)
+
+        required_nullable = d.pop("required_nullable")
+
+        not_required_nullable = d.pop("not_required_nullable", UNSET)
+
+        not_required_not_nullable = d.pop("not_required_not_nullable", UNSET)
+
+        def _parse_nullable_one_of_models(data: object) -> None:
+            if data is None:
+                return data
+            try:
+                nullable_one_of_models_type0: None
+                if not data is None:
+                    raise TypeError()
+                nullable_one_of_models_type0 = UNSET
+
+                return nullable_one_of_models_type0
+            except:  # noqa: E722
+                pass
+            if not data is None:
+                raise TypeError()
+            nullable_one_of_models_type1: None
+            nullable_one_of_models_type1 = UNSET
+
+            return nullable_one_of_models_type1
+
+        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[None, Unset]:
+            if data is None:
+                return data
+            if isinstance(data, Unset):
+                return data
+            try:
+                not_required_one_of_models_type0: Union[Unset, None]
+                if not data is None:
+                    raise TypeError()
+                not_required_one_of_models_type0 = UNSET
+
+                return not_required_one_of_models_type0
+            except:  # noqa: E722
+                pass
+            if not data is None:
+                raise TypeError()
+            not_required_one_of_models_type1: Union[Unset, None]
+            not_required_one_of_models_type1 = UNSET
+
+            return not_required_one_of_models_type1
+
+        not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET))
+
+        def _parse_not_required_nullable_one_of_models(data: object) -> Union[None, Unset, str]:
+            if data is None:
+                return data
+            if isinstance(data, Unset):
+                return data
+            try:
+                not_required_nullable_one_of_models_type0: Union[Unset, None]
+                if not data is None:
+                    raise TypeError()
+                not_required_nullable_one_of_models_type0 = UNSET
+
+                return not_required_nullable_one_of_models_type0
+            except:  # noqa: E722
+                pass
+            try:
+                not_required_nullable_one_of_models_type1: Union[Unset, None]
+                if not data is None:
+                    raise TypeError()
+                not_required_nullable_one_of_models_type1 = UNSET
+
+                return not_required_nullable_one_of_models_type1
+            except:  # noqa: E722
+                pass
+            return cast(Union[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)
+        )
+
+        nullable_model = None
+        _nullable_model = d.pop("nullable_model")
+        if _nullable_model is not None:
+            nullable_model = AModelNullableModel.from_dict(_nullable_model)
+
+        not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET
+        _not_required_model = d.pop("not_required_model", UNSET)
+        if not isinstance(_not_required_model, Unset):
+            not_required_model = AModelNotRequiredModel.from_dict(_not_required_model)
+
+        not_required_nullable_model = None
+        _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET)
+        if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset):
+            not_required_nullable_model = AModelNotRequiredNullableModel.from_dict(_not_required_nullable_model)
+
+        a_model = cls(
+            an_enum_value=an_enum_value,
+            a_camel_date_time=a_camel_date_time,
+            a_date=a_date,
+            required_not_nullable=required_not_nullable,
+            one_of_models=one_of_models,
+            model=model,
+            nested_list_of_enums=nested_list_of_enums,
+            a_nullable_date=a_nullable_date,
+            a_not_required_date=a_not_required_date,
+            attr_1_leading_digit=attr_1_leading_digit,
+            required_nullable=required_nullable,
+            not_required_nullable=not_required_nullable,
+            not_required_not_nullable=not_required_not_nullable,
+            nullable_one_of_models=nullable_one_of_models,
+            not_required_one_of_models=not_required_one_of_models,
+            not_required_nullable_one_of_models=not_required_nullable_one_of_models,
+            nullable_model=nullable_model,
+            not_required_model=not_required_model,
+            not_required_nullable_model=not_required_nullable_model,
+        )
+
+        return a_model
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
new file mode 100644
index 000000000..55ff303f9
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="AModelModel")
+
+
+@attr.s(auto_attribs=True)
+class AModelModel:
+    """  """
+
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        a_model_model = cls()
+
+        a_model_model.additional_properties = d
+        return a_model_model
+
+    @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/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
new file mode 100644
index 000000000..4c86e1019
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="AModelNotRequiredModel")
+
+
+@attr.s(auto_attribs=True)
+class AModelNotRequiredModel:
+    """  """
+
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        a_model_not_required_model = cls()
+
+        a_model_not_required_model.additional_properties = d
+        return a_model_not_required_model
+
+    @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/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
new file mode 100644
index 000000000..c0bf0fafb
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="AModelNotRequiredNullableModel")
+
+
+@attr.s(auto_attribs=True)
+class AModelNotRequiredNullableModel:
+    """  """
+
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        a_model_not_required_nullable_model = cls()
+
+        a_model_not_required_nullable_model.additional_properties = d
+        return a_model_not_required_nullable_model
+
+    @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/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
new file mode 100644
index 000000000..fe66227fb
--- /dev/null
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict, List, Type, TypeVar
+
+import attr
+
+T = TypeVar("T", bound="AModelNullableModel")
+
+
+@attr.s(auto_attribs=True)
+class AModelNullableModel:
+    """  """
+
+    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+
+    def to_dict(self) -> Dict[str, Any]:
+
+        field_dict: Dict[str, Any] = {}
+        field_dict.update(self.additional_properties)
+        field_dict.update({})
+
+        return field_dict
+
+    @classmethod
+    def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
+        d = src_dict.copy()
+        a_model_nullable_model = cls()
+
+        a_model_nullable_model.additional_properties = d
+        return a_model_nullable_model
+
+    @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/http_validation_error.py b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py
index feb2cdd6b..92ad83e50 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
@@ -2,7 +2,6 @@
 
 import attr
 
-from ..models.validation_error import ValidationError
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="HTTPValidationError")
@@ -12,14 +11,14 @@
 class HTTPValidationError:
     """  """
 
-    detail: Union[Unset, List[ValidationError]] = UNSET
+    detail: Union[Unset, List[None]] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
-        detail: Union[Unset, List[Dict[str, Any]]] = UNSET
+        detail: Union[Unset, List[None]] = UNSET
         if not isinstance(self.detail, Unset):
             detail = []
             for detail_item_data in self.detail:
-                detail_item = detail_item_data.to_dict()
+                detail_item = None
 
                 detail.append(detail_item)
 
@@ -36,7 +35,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         detail = []
         _detail = d.pop("detail", UNSET)
         for detail_item_data in _detail or []:
-            detail_item = ValidationError.from_dict(detail_item_data)
+            detail_item = None
 
             detail.append(detail_item)
 
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
index ce26a3bbb..7f4ef8bbd 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
@@ -1,9 +1,7 @@
-from typing import Any, Dict, List, Type, TypeVar, Union
+from typing import Any, Dict, List, Type, TypeVar
 
 import attr
 
-from ..types import UNSET, Unset
-
 T = TypeVar("T", bound="ModelFromAllOf")
 
 
@@ -11,35 +9,20 @@
 class ModelFromAllOf:
     """  """
 
-    a_sub_property: Union[Unset, str] = UNSET
-    another_sub_property: Union[Unset, str] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
-        a_sub_property = self.a_sub_property
-        another_sub_property = self.another_sub_property
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
-        if a_sub_property is not UNSET:
-            field_dict["a_sub_property"] = a_sub_property
-        if another_sub_property is not UNSET:
-            field_dict["another_sub_property"] = another_sub_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        a_sub_property = d.pop("a_sub_property", UNSET)
-
-        another_sub_property = d.pop("another_sub_property", UNSET)
-
-        model_from_all_of = cls(
-            a_sub_property=a_sub_property,
-            another_sub_property=another_sub_property,
-        )
+        model_from_all_of = cls()
 
         model_from_all_of.additional_properties = d
         return model_from_all_of
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
index b265db582..1cfd6fdc7 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
@@ -2,8 +2,6 @@
 
 import attr
 
-from ..models.an_enum import AnEnum
-
 T = TypeVar("T", bound="ModelWithAdditionalPropertiesRefed")
 
 
@@ -11,14 +9,12 @@
 class ModelWithAdditionalPropertiesRefed:
     """  """
 
-    additional_properties: Dict[str, AnEnum] = attr.ib(init=False, factory=dict)
+    additional_properties: Dict[str, Any] = 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.value
-
+        field_dict.update(self.additional_properties)
         field_dict.update({})
 
         return field_dict
@@ -28,23 +24,17 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
         model_with_additional_properties_refed = cls()
 
-        additional_properties = {}
-        for prop_name, prop_dict in d.items():
-            additional_property = AnEnum(prop_dict)
-
-            additional_properties[prop_name] = additional_property
-
-        model_with_additional_properties_refed.additional_properties = additional_properties
+        model_with_additional_properties_refed.additional_properties = d
         return model_with_additional_properties_refed
 
     @property
     def additional_keys(self) -> List[str]:
         return list(self.additional_properties.keys())
 
-    def __getitem__(self, key: str) -> AnEnum:
+    def __getitem__(self, key: str) -> Any:
         return self.additional_properties[key]
 
-    def __setitem__(self, key: str, value: AnEnum) -> None:
+    def __setitem__(self, key: str, value: Any) -> 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_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
index 1553914ba..cfb47ded3 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
@@ -2,7 +2,6 @@
 
 import attr
 
-from ..models.model_name import ModelName
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="ModelWithPropertyRef")
@@ -12,13 +11,11 @@
 class ModelWithPropertyRef:
     """  """
 
-    inner: Union[Unset, ModelName] = UNSET
+    inner: Union[Unset, None] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
-        inner: Union[Unset, Dict[str, Any]] = UNSET
-        if not isinstance(self.inner, Unset):
-            inner = self.inner.to_dict()
+        inner = None
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
@@ -31,10 +28,7 @@ def to_dict(self) -> Dict[str, Any]:
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        inner: Union[Unset, ModelName] = UNSET
-        _inner = d.pop("inner", UNSET)
-        if not isinstance(_inner, Unset):
-            inner = ModelName.from_dict(_inner)
+        inner = None
 
         model_with_property_ref = cls(
             inner=inner,
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
index a3f049533..c7a849cbf 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
@@ -2,8 +2,6 @@
 
 import attr
 
-from ..models.an_enum import AnEnum
-from ..models.an_int_enum import AnIntEnum
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="ModelWithUnionProperty")
@@ -13,21 +11,17 @@
 class ModelWithUnionProperty:
     """  """
 
-    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
+    a_property: Union[None, Unset] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[Unset, int, str]
+        a_property: Union[None, Unset]
         if isinstance(self.a_property, Unset):
             a_property = UNSET
-        elif isinstance(self.a_property, AnEnum):
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
+        elif isinstance(self.a_property, None):
+            a_property = None
 
         else:
-            a_property = UNSET
-            if not isinstance(self.a_property, Unset):
-                a_property = self.a_property.value
+            a_property = None
 
         field_dict: Dict[str, Any] = {}
         field_dict.update({})
@@ -40,28 +34,24 @@ def to_dict(self) -> Dict[str, Any]:
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
 
-        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
+        def _parse_a_property(data: object) -> Union[None, Unset]:
+            if data is None:
+                return data
             if isinstance(data, Unset):
                 return data
             try:
-                a_property_type0: Union[Unset, AnEnum]
-                if not isinstance(data, str):
+                a_property_type0: Union[Unset, None]
+                if not data is None:
                     raise TypeError()
                 a_property_type0 = UNSET
-                _a_property_type0 = data
-                if not isinstance(_a_property_type0, Unset):
-                    a_property_type0 = AnEnum(_a_property_type0)
 
                 return a_property_type0
             except:  # noqa: E722
                 pass
-            if not isinstance(data, int):
+            if not data is None:
                 raise TypeError()
-            a_property_type1: Union[Unset, AnIntEnum]
+            a_property_type1: Union[Unset, None]
             a_property_type1 = UNSET
-            _a_property_type1 = data
-            if not isinstance(_a_property_type1, Unset):
-                a_property_type1 = AnIntEnum(_a_property_type1)
 
             return a_property_type1
 
diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py
index 5269f522d..350124c7e 100644
--- a/end_to_end_tests/regen_golden_record.py
+++ b/end_to_end_tests/regen_golden_record.py
@@ -18,7 +18,7 @@
     shutil.rmtree(gr_path, ignore_errors=True)
     shutil.rmtree(output_path, ignore_errors=True)
 
-    result = runner.invoke(app, [f"--config={config_path}", "generate", f"--path={openapi_path}"])
+    result = runner.invoke(app, ["generate", f"--config={config_path}", f"--path={openapi_path}"])
 
     if result.stdout:
         print(result.stdout)
diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py
index 9201d9746..fa4d21598 100644
--- a/end_to_end_tests/test_end_to_end.py
+++ b/end_to_end_tests/test_end_to_end.py
@@ -53,7 +53,7 @@ def run_e2e_test(extra_args=None, expected_differences=None):
     output_path = Path.cwd() / "my-test-api-client"
     shutil.rmtree(output_path, ignore_errors=True)
 
-    args = [f"--config={config_path}", "generate", f"--path={openapi_path}"]
+    args = ["generate", f"--config={config_path}", f"--path={openapi_path}"]
     if extra_args:
         args.extend(extra_args)
     result = runner.invoke(app, args)
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 46fa94a51..16cc07000 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -47,7 +47,7 @@ def __init__(
         *,
         openapi: GeneratorData,
         meta: MetaType,
-        config: Optional[Config],
+        config: Config,
         custom_template_path: Optional[Path] = None,
         file_encoding: str = "utf-8",
     ) -> None:
@@ -259,14 +259,14 @@ def _get_project_for_url_or_path(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
-    config: Optional[Config],
+    config: Config,
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Union[Project, GeneratorError]:
     data_dict = _get_document(url=url, path=path)
     if isinstance(data_dict, GeneratorError):
         return data_dict
-    openapi = GeneratorData.from_dict(data_dict)
+    openapi = GeneratorData.from_dict(data_dict, config=config)
     if isinstance(openapi, GeneratorError):
         return openapi
     return Project(
@@ -283,7 +283,7 @@ def create_new_client(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
-    config: Optional[Config],
+    config: Config,
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Sequence[GeneratorError]:
@@ -311,7 +311,7 @@ def update_existing_client(
     url: Optional[str],
     path: Optional[Path],
     meta: MetaType,
-    config: Optional[Config],
+    config: Config,
     custom_template_path: Optional[Path] = None,
     file_encoding: str = "utf-8",
 ) -> Sequence[GeneratorError]:
diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py
index 9a60a302e..fea7cc34f 100644
--- a/openapi_python_client/cli.py
+++ b/openapi_python_client/cli.py
@@ -109,6 +109,8 @@ def handle_errors(errors: Sequence[GeneratorError]) -> None:
     help="The type of metadata you want to generate.",
 )
 
+CONFIG_OPTION = typer.Option(None, "--config", help="Path to the config file to use")
+
 
 @app.command()
 def generate(
@@ -117,7 +119,7 @@ def generate(
     custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options),  # type: ignore
     meta: MetaType = _meta_option,
     file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
-    config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"),
+    config_path: Optional[pathlib.Path] = CONFIG_OPTION,
 ) -> None:
     """ Generate a new OpenAPI Client library """
     from . import create_new_client
@@ -135,8 +137,14 @@ def generate(
         typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
         raise typer.Exit(code=1)
 
+    config = Config.load_from_path(config_path) if config_path is not None else Config()
     errors = create_new_client(
-        url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding
+        url=url,
+        path=path,
+        meta=meta,
+        custom_template_path=custom_template_path,
+        file_encoding=file_encoding,
+        config=config,
     )
     handle_errors(errors)
 
@@ -148,7 +156,7 @@ def update(
     custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options),  # type: ignore
     meta: MetaType = _meta_option,
     file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
-    config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"),
+    config_path: Optional[pathlib.Path] = CONFIG_OPTION,
 ) -> None:
     """ Update an existing OpenAPI Client library """
     from . import update_existing_client
@@ -166,7 +174,13 @@ def update(
         typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
         raise typer.Exit(code=1)
 
+    config = Config.load_from_path(config_path) if config_path is not None else Config()
     errors = update_existing_client(
-        url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding
+        url=url,
+        path=path,
+        meta=meta,
+        custom_template_path=custom_template_path,
+        file_encoding=file_encoding,
+        config=config,
     )
     handle_errors(errors)
diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py
index 3deb79e71..273848e57 100644
--- a/openapi_python_client/config.py
+++ b/openapi_python_client/config.py
@@ -4,11 +4,14 @@
 import yaml
 from pydantic import BaseModel
 
-from openapi_python_client.parser.properties import Class
+
+class ClassOverride(BaseModel):
+    class_name: Optional[str] = None
+    module_name: Optional[str] = None
 
 
 class Config(BaseModel):
-    class_overrides: Dict[str, Class] = {}
+    class_overrides: Dict[str, ClassOverride] = {}
     project_name_override: Optional[str]
     package_name_override: Optional[str]
     package_version_override: Optional[str]
diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py
index d40923ec1..9dd2106bc 100644
--- a/openapi_python_client/parser/openapi.py
+++ b/openapi_python_client/parser/openapi.py
@@ -5,9 +5,9 @@
 
 from pydantic import ValidationError
 
-from .. import Config
 from .. import schema as oai
 from .. import utils
+from ..config import Config
 from .errors import GeneratorError, ParseError, PropertyError
 from .properties import Class, EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data
 from .responses import Response, response_from_data
@@ -121,7 +121,7 @@ def parse_multipart_body(*, body: oai.RequestBody, config: Config) -> Optional[C
 
     @staticmethod
     def parse_request_json_body(
-        *, body: oai.RequestBody, schemas: Schemas, parent_name: str
+        *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config
     ) -> Tuple[Union[Property, PropertyError, None], Schemas]:
         """ Return json_body """
         body_content = body.content
@@ -133,12 +133,17 @@ def parse_request_json_body(
                 data=json_body.media_type_schema,
                 schemas=schemas,
                 parent_name=parent_name,
+                config=config,
             )
         return None, schemas
 
     @staticmethod
     def _add_body(
-        *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config
+        *,
+        endpoint: "Endpoint",
+        data: oai.Operation,
+        schemas: Schemas,
+        config: Config,
     ) -> Tuple[Union[ParseError, "Endpoint"], Schemas]:
         """ Adds form or JSON body to Endpoint if included in data """
         endpoint = deepcopy(endpoint)
@@ -147,7 +152,7 @@ def _add_body(
 
         endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, config=config)
         json_body, schemas = Endpoint.parse_request_json_body(
-            body=data.requestBody, schemas=schemas, parent_name=endpoint.name
+            body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config
         )
         if isinstance(json_body, ParseError):
             return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas
@@ -204,7 +209,7 @@ def _add_responses(
 
     @staticmethod
     def _add_parameters(
-        *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas
+        *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config
     ) -> Tuple[Union["Endpoint", ParseError], Schemas]:
         endpoint = deepcopy(endpoint)
         if data.parameters is None:
@@ -218,6 +223,7 @@ def _add_parameters(
                 data=param.param_schema,
                 schemas=schemas,
                 parent_name=endpoint.name,
+                config=config,
             )
             if isinstance(prop, ParseError):
                 return ParseError(detail=f"cannot parse parameter of endpoint {endpoint.name}", data=prop.data), schemas
@@ -255,7 +261,7 @@ def from_data(
             tag=tag,
         )
 
-        result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas)
+        result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas, config=config)
         if isinstance(result, ParseError):
             return result, schemas
         result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config)
@@ -295,7 +301,7 @@ def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", Ge
             )
         schemas = Schemas()
         if openapi.components and openapi.components.schemas:
-            schemas = build_schemas(components=openapi.components.schemas, schemas=schemas)
+            schemas = build_schemas(components=openapi.components.schemas, schemas=schemas, config=config)
         endpoint_collections_by_tag, schemas = EndpointCollection.from_data(
             data=openapi.paths, schemas=schemas, config=config
         )
diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index 0cd100618..d9a6ddea0 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -14,6 +14,7 @@
 
 import attr
 
+from ... import Config
 from ... import schema as oai
 from ... import utils
 from ..errors import ParseError, PropertyError, ValidationError
@@ -277,6 +278,7 @@ def build_enum_property(
     schemas: Schemas,
     enum: List[Union[str, int]],
     parent_name: Optional[str],
+    config: Config,
 ) -> Tuple[Union[EnumProperty, PropertyError], Schemas]:
     """
     Create an EnumProperty from schema data.
@@ -288,6 +290,7 @@ def build_enum_property(
         schemas: The Schemas which have been defined so far (used to prevent naming collisions)
         enum: The enum from the provided data. Required separately here to prevent extra type checking.
         parent_name: The context in which this EnumProperty is defined, used to create more specific class names.
+        config: The global config for this run of the generator
 
     Returns:
         A tuple containing either the created property or a PropertyError describing what went wrong AND update schemas.
@@ -296,7 +299,7 @@ def build_enum_property(
     class_name = data.title or name
     if parent_name:
         class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
-    class_info = Class.from_string(string=class_name, schemas=schemas)
+    class_info = Class.from_string(string=class_name, config=config)
     values = EnumProperty.values_from_list(enum)
 
     if class_info.name in schemas.classes_by_name:
@@ -340,12 +343,17 @@ def build_enum_property(
 
 
 def build_union_property(
-    *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str
+    *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config
 ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]:
     sub_properties: List[Property] = []
     for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)):
         sub_prop, schemas = property_from_data(
-            name=f"{name}_type{i}", required=required, data=sub_prop_data, schemas=schemas, parent_name=parent_name
+            name=f"{name}_type{i}",
+            required=required,
+            data=sub_prop_data,
+            schemas=schemas,
+            parent_name=parent_name,
+            config=config,
         )
         if isinstance(sub_prop, PropertyError):
             return PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data), schemas
@@ -365,12 +373,12 @@ def build_union_property(
 
 
 def build_list_property(
-    *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str
+    *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config
 ) -> Tuple[Union[ListProperty[Any], PropertyError], Schemas]:
     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
+        name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name=parent_name, config=config
     )
     if isinstance(inner_prop, PropertyError):
         return PropertyError(data=inner_prop.data, detail=f"invalid data in items of array {name}"), schemas
@@ -392,6 +400,7 @@ def _property_from_data(
     data: Union[oai.Reference, oai.Schema],
     schemas: Schemas,
     parent_name: str,
+    config: Config,
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
     """ Generate a Property from the OpenAPI dictionary representation of it """
     name = utils.remove_string_escapes(name)
@@ -408,10 +417,18 @@ def _property_from_data(
         return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas
     if data.enum:
         return build_enum_property(
-            data=data, name=name, required=required, schemas=schemas, enum=data.enum, parent_name=parent_name
+            data=data,
+            name=name,
+            required=required,
+            schemas=schemas,
+            enum=data.enum,
+            parent_name=parent_name,
+            config=config,
         )
     if data.anyOf or data.oneOf:
-        return build_union_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name)
+        return build_union_property(
+            data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config
+        )
     if data.type == "string":
         return _string_based_property(name=name, required=required, data=data), schemas
     elif data.type == "number":
@@ -445,9 +462,13 @@ def _property_from_data(
             schemas,
         )
     elif data.type == "array":
-        return build_list_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name)
+        return build_list_property(
+            data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config
+        )
     elif data.type == "object" or data.allOf:
-        return build_model_property(data=data, name=name, schemas=schemas, required=required, parent_name=parent_name)
+        return build_model_property(
+            data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config
+        )
     elif not data.type:
         return NoneProperty(name=name, required=required, nullable=False, default=None), schemas
     return PropertyError(data=data, detail=f"unknown type {data.type}"), schemas
@@ -460,14 +481,19 @@ def property_from_data(
     data: Union[oai.Reference, oai.Schema],
     schemas: Schemas,
     parent_name: str,
+    config: Config,
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
     try:
-        return _property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name=parent_name)
+        return _property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name=parent_name, config=config
+        )
     except ValidationError:
         return PropertyError(detail="Failed to validate default value", data=data), schemas
 
 
-def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas) -> 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 """
     to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items()
     processing = True
@@ -488,7 +514,7 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], sc
                 next_round.append((name, data))
                 errors.append(PropertyError(detail=ref_path.detail, data=data))
                 continue
-            schemas_or_err = update_schemas_with_data(ref_path, data, schemas)
+            schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config)
             if isinstance(schemas_or_err, PropertyError):
                 next_round.append((name, data))
                 errors.append(schemas_or_err)
diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py
index 2e400cb6d..83d824d8a 100644
--- a/openapi_python_client/parser/properties/model_property.py
+++ b/openapi_python_client/parser/properties/model_property.py
@@ -7,9 +7,8 @@
 from ... import schema as oai
 from ... import utils
 from ..errors import ParseError, PropertyError
-from . import parse_reference_path
 from .property import Property
-from .schemas import Class, Schemas
+from .schemas import Class, Schemas, parse_reference_path
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -68,7 +67,9 @@ class _PropertyData(NamedTuple):
     schemas: Schemas
 
 
-def _process_properties(*, data: oai.Schema, schemas: Schemas, class_name: str) -> Union[_PropertyData, PropertyError]:
+def _process_properties(
+    *, data: oai.Schema, schemas: Schemas, class_name: str, config: Config
+) -> Union[_PropertyData, PropertyError]:
     from . import property_from_data
 
     properties: Dict[str, Property] = {}
@@ -108,7 +109,7 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
     for key, value in unprocessed_props.items():
         prop_required = key in required_set
         prop_or_error, schemas = property_from_data(
-            name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name
+            name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name, config=config
         )
         if isinstance(prop_or_error, Property):
             prop_or_error = _check_existing(prop_or_error)
@@ -135,7 +136,11 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
 
 
 def _get_additional_properties(
-    *, schema_additional: Union[None, bool, oai.Reference, oai.Schema], schemas: Schemas, class_name: str
+    *,
+    schema_additional: Union[None, bool, oai.Reference, oai.Schema],
+    schemas: Schemas,
+    class_name: str,
+    config: Config,
 ) -> Tuple[Union[bool, Property, PropertyError], Schemas]:
     from . import property_from_data
 
@@ -155,6 +160,7 @@ def _get_additional_properties(
         data=schema_additional,
         schemas=schemas,
         parent_name=class_name,
+        config=config,
     )
     return additional_properties, schemas
 
@@ -179,13 +185,13 @@ def build_model_property(
         class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
     class_info = Class.from_string(string=class_name, config=config)
 
-    property_data = _process_properties(data=data, schemas=schemas, class_name=class_name)
+    property_data = _process_properties(data=data, schemas=schemas, class_name=class_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_name
+        schema_additional=data.additionalProperties, schemas=schemas, class_name=class_name, config=config
     )
     if isinstance(additional_properties, Property):
         property_data.relative_imports.update(additional_properties.get_imports(prefix=".."))
diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index 19a794d8d..73adb6b57 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -1,4 +1,4 @@
-__all__ = ["Class", "Schemas", "parse_reference_path"]
+__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with_data"]
 
 from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast
 from urllib.parse import urlparse
@@ -25,8 +25,8 @@
 
 def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]:
     parsed = urlparse(ref_path_raw)
-    if parsed.scheme is not None or parsed.path is not None:
-        return ParseError(detail="Remote references are not supported yet.")
+    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)
 
 
@@ -41,11 +41,13 @@ def from_string(*, string: str, config: Config) -> "Class":
         """ Get a Class from an arbitrary string """
         class_name = string.split("/")[-1]  # Get rid of ref path stuff
         class_name = utils.pascal_case(class_name)
+        module_name = utils.snake_case(class_name)
+        override = config.class_overrides.get(class_name)
+        if override is not None:
+            class_name = override.class_name or class_name
+            module_name = override.module_name or module_name
 
-        if class_name in config.class_overrides:
-            return config.class_overrides[class_name]
-
-        return Class(name=cast(_ClassName, class_name), module_name=utils.snake_case(class_name))
+        return Class(name=cast(_ClassName, class_name), module_name=module_name)
 
 
 @attr.s(auto_attribs=True, frozen=True)
@@ -58,14 +60,14 @@ class Schemas:
 
 
 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]:
     from . import build_enum_property, build_model_property
 
     prop: Union[PropertyError, ModelProperty, EnumProperty]
     if data.enum is not None:
         prop, schemas = build_enum_property(
-            data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None
+            data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None, config=config
         )
     else:
         prop, schemas = build_model_property(
diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py
index c99a453d1..a7d5e89d7 100644
--- a/openapi_python_client/parser/responses.py
+++ b/openapi_python_client/parser/responses.py
@@ -74,6 +74,7 @@ def response_from_data(
         data=schema_data,
         schemas=schemas,
         parent_name=parent_name,
+        config=config,
     )
 
     if isinstance(prop, PropertyError):
diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja
index 705985aab..45fe6d7c3 100644
--- a/openapi_python_client/templates/endpoint_macros.py.jinja
+++ b/openapi_python_client/templates/endpoint_macros.py.jinja
@@ -99,12 +99,12 @@ client: Client,
 {{ parameter.to_string() }},
 {% endfor %}
 {# Form data if any #}
-{% if endpoint.form_body_reference %}
-form_data: {{ endpoint.form_body_reference.class_name }},
+{% if endpoint.form_body_class %}
+form_data: {{ endpoint.form_body_class.name }},
 {% endif %}
 {# Multipart data if any #}
-{% if endpoint.multipart_body_reference %}
-multipart_data: {{ endpoint.multipart_body_reference.class_name }},
+{% if endpoint.multipart_body_class %}
+multipart_data: {{ endpoint.multipart_body_class.class_name }},
 {% endif %}
 {# JSON body if any #}
 {% if endpoint.json_body %}
@@ -129,10 +129,10 @@ client=client,
 {% for parameter in endpoint.path_parameters %}
 {{ parameter.python_name }}={{ parameter.python_name }},
 {% endfor %}
-{% if endpoint.form_body_reference %}
+{% if endpoint.form_body_class %}
 form_data=form_data,
 {% endif %}
-{% if endpoint.multipart_body_reference %}
+{% if endpoint.multipart_body_class %}
 multipart_data=multipart_data,
 {% endif %}
 {% if endpoint.json_body %}
diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja
index 5b1c434bc..a4e7d346b 100644
--- a/openapi_python_client/templates/endpoint_module.py.jinja
+++ b/openapi_python_client/templates/endpoint_module.py.jinja
@@ -4,7 +4,7 @@ import httpx
 from attr import asdict
 
 from ...client import AuthenticatedClient, Client
-from ...types import Response, UNSET{% if endpoint.multipart_body_reference %}, File {% endif %}
+from ...types import Response, UNSET{% if endpoint.multipart_body_class %}, File {% endif %}
 
 {% for relative in endpoint.relative_imports %}
 {{ relative }}
@@ -36,7 +36,7 @@ def _get_kwargs(
 
     {{ json_body(endpoint) | indent(4) }}
 
-    {% if endpoint.multipart_body_reference %}
+    {% if endpoint.multipart_body_class %}
     files = {}
     data = {}
     for key, value in multipart_data.to_dict().items():
@@ -51,9 +51,9 @@ def _get_kwargs(
         "headers": headers,
         "cookies": cookies,
         "timeout": client.get_timeout(),
-        {% if endpoint.form_body_reference %}
+        {% if endpoint.form_body_class %}
         "data": asdict(form_data),
-        {% elif endpoint.multipart_body_reference %}
+        {% elif endpoint.multipart_body_class %}
         "files": files,
         "data": data,
         {% elif endpoint.json_body %}
diff --git a/openapi_python_client/templates/int_enum.py.jinja b/openapi_python_client/templates/int_enum.py.jinja
index 18d6066ae..a508f1c8e 100644
--- a/openapi_python_client/templates/int_enum.py.jinja
+++ b/openapi_python_client/templates/int_enum.py.jinja
@@ -1,6 +1,6 @@
 from enum import IntEnum
 
-class {{ enum.reference.class_name }}(IntEnum):
+class {{ enum.class_info.name }}(IntEnum):
     {% for key, value in enum.values.items() %}
     {{ key }} = {{ value }}
     {% endfor %}
diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja
index c286489e3..8541db32d 100644
--- a/openapi_python_client/templates/model.py.jinja
+++ b/openapi_python_client/templates/model.py.jinja
@@ -18,10 +18,13 @@ from ..types import UNSET, Unset
 {% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string() %}
 {% endif %}
 
-T = TypeVar("T", bound="{{ model.reference.class_name }}")
+{% set class_name = model.class_info.name %}
+{% set module_name = model.class_info.module_name %}
+
+T = TypeVar("T", bound="{{ class_name }}")
 
 @attr.s(auto_attribs=True)
-class {{ model.reference.class_name }}:
+class {{ class_name }}:
     """ {{ model.description }} """
     {% for property in model.required_properties + model.optional_properties %}
     {% if property.default is none and property.required %}
@@ -91,7 +94,7 @@ class {{ model.reference.class_name }}:
     {% endif %}
 
 {% endfor %}
-        {{model.reference.module_name}} = cls(
+        {{ module_name }} = cls(
 {% for property in model.required_properties + model.optional_properties %}
             {{ property.python_name }}={{ property.python_name }},
 {% endfor %}
@@ -105,12 +108,12 @@ class {{ model.reference.class_name }}:
             {{ construct(model.additional_properties, "prop_dict") | indent(12) }}
             additional_properties[prop_name] = {{ model.additional_properties.python_name }}
 
-        {{model.reference.module_name}}.additional_properties = additional_properties
+        {{ module_name }}.additional_properties = additional_properties
     {% else %}
-        {{model.reference.module_name}}.additional_properties = d
+        {{ module_name }}.additional_properties = d
     {% endif %}
 {% endif %}
-        return {{model.reference.module_name}}
+        return {{ module_name }}
 
     {% if model.additional_properties %}
     @property
diff --git a/openapi_python_client/templates/property_templates/enum_property.py.jinja b/openapi_python_client/templates/property_templates/enum_property.py.jinja
index 4916927bd..9dd051b38 100644
--- a/openapi_python_client/templates/property_templates/enum_property.py.jinja
+++ b/openapi_python_client/templates/property_templates/enum_property.py.jinja
@@ -1,5 +1,5 @@
 {% macro construct_function(property, source) %}
-{{ property.reference.class_name }}({{ source }})
+{{ property.class_info.name }}({{ source }})
 {% endmacro %}
 
 {% from "property_templates/property_macros.py.jinja" import construct_template %}
diff --git a/openapi_python_client/templates/property_templates/model_property.py.jinja b/openapi_python_client/templates/property_templates/model_property.py.jinja
index 4e394cc34..2772918cf 100644
--- a/openapi_python_client/templates/property_templates/model_property.py.jinja
+++ b/openapi_python_client/templates/property_templates/model_property.py.jinja
@@ -1,5 +1,5 @@
 {% macro construct_function(property, source) %}
-{{ property.reference.class_name }}.from_dict({{ source }})
+{{ property.class_info.name }}.from_dict({{ source }})
 {% endmacro %}
 
 {% from "property_templates/property_macros.py.jinja" import construct_template %}
diff --git a/openapi_python_client/templates/str_enum.py.jinja b/openapi_python_client/templates/str_enum.py.jinja
index 74dcd1de2..4a9ab384a 100644
--- a/openapi_python_client/templates/str_enum.py.jinja
+++ b/openapi_python_client/templates/str_enum.py.jinja
@@ -1,6 +1,6 @@
 from enum import Enum
 
-class {{ enum.reference.class_name }}(str, Enum):
+class {{ enum.class_info.name }}(str, Enum):
     {% for key, value in enum.values.items() %}
     {{ key }} = "{{ value }}"
     {% endfor %}
diff --git a/tests/test___init__.py b/tests/test___init__.py
index 3d2547d89..342928b94 100644
--- a/tests/test___init__.py
+++ b/tests/test___init__.py
@@ -5,7 +5,7 @@
 import pytest
 import yaml
 
-from openapi_python_client import GeneratorError
+from openapi_python_client import Config, GeneratorError
 
 
 def test__get_project_for_url_or_path(mocker):
@@ -16,15 +16,16 @@ def test__get_project_for_url_or_path(mocker):
     _Project = mocker.patch("openapi_python_client.Project")
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, _get_project_for_url_or_path
 
-    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY)
+    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_document.assert_called_once_with(url=url, path=path)
-    from_dict.assert_called_once_with(data_dict)
+    from_dict.assert_called_once_with(data_dict, config=config)
     _Project.assert_called_once_with(
-        openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config
     )
     assert project == _Project.return_value
 
@@ -37,13 +38,14 @@ def test__get_project_for_url_or_path_generator_error(mocker):
     _Project = mocker.patch("openapi_python_client.Project")
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, _get_project_for_url_or_path
 
-    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY)
+    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_document.assert_called_once_with(url=url, path=path)
-    from_dict.assert_called_once_with(data_dict)
+    from_dict.assert_called_once_with(data_dict, config=config)
     _Project.assert_not_called()
     assert project == error
 
@@ -58,7 +60,7 @@ def test__get_project_for_url_or_path_document_error(mocker):
 
     from openapi_python_client import MetaType, _get_project_for_url_or_path
 
-    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY)
+    project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=Config())
 
     _get_document.assert_called_once_with(url=url, path=path)
     from_dict.assert_not_called()
@@ -72,13 +74,14 @@ def test_create_new_client(mocker):
     )
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, create_new_client
 
-    result = create_new_client(url=url, path=path, meta=MetaType.POETRY)
+    result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_project_for_url_or_path.assert_called_once_with(
-        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config
     )
     project.build.assert_called_once()
     assert result == project.build.return_value
@@ -91,13 +94,14 @@ def test_create_new_client_project_error(mocker):
     )
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, create_new_client
 
-    result = create_new_client(url=url, path=path, meta=MetaType.POETRY)
+    result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_project_for_url_or_path.assert_called_once_with(
-        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config
     )
     assert result == [error]
 
@@ -112,7 +116,7 @@ def test_update_existing_client(mocker):
 
     from openapi_python_client import MetaType, update_existing_client
 
-    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY)
+    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config())
 
     _get_project_for_url_or_path.assert_called_once_with(
         url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
@@ -131,7 +135,7 @@ def test_update_existing_client_project_error(mocker):
 
     from openapi_python_client import MetaType, update_existing_client
 
-    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY)
+    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config())
 
     _get_project_for_url_or_path.assert_called_once_with(
         url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
@@ -225,13 +229,21 @@ def test__get_document_bad_yaml(self, mocker):
         assert result == GeneratorError(header="Invalid YAML from provided source")
 
 
+def make_project():
+    from unittest.mock import MagicMock
+
+    from openapi_python_client import MetaType, Project
+
+    return Project(openapi=MagicMock(title="My Test API"), meta=MetaType.POETRY, config=Config())
+
+
 class TestProject:
     def test___init__(self, mocker):
         openapi = mocker.MagicMock(title="My Test API")
 
         from openapi_python_client import MetaType, Project
 
-        project = Project(openapi=openapi, meta=MetaType.POETRY)
+        project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config())
 
         assert project.openapi == openapi
         assert project.project_name == "my-test-api-client"
@@ -246,7 +258,7 @@ def test___init___no_meta(self, mocker):
 
         from openapi_python_client import MetaType, Project
 
-        project = Project(openapi=openapi, meta=MetaType.NONE)
+        project = Project(openapi=openapi, meta=MetaType.NONE, config=Config())
 
         assert project.openapi == openapi
         assert project.project_name == "my-test-api-client"
@@ -261,22 +273,22 @@ def test_project_and_package_name_overrides(self, mocker):
 
         from openapi_python_client import MetaType, Project
 
-        Project.project_name_override = "my-special-project-name"
-        project = Project(openapi=openapi, meta=MetaType.POETRY)
+        project = Project(
+            openapi=openapi, meta=MetaType.POETRY, config=Config(project_name_override="my-special-project-name")
+        )
 
         assert project.project_name == "my-special-project-name"
         assert project.package_name == "my_special_project_name"
 
-        Project.package_name_override = "my_special_package_name"
-        project = Project(openapi=openapi, meta=MetaType.POETRY)
+        project = Project(
+            openapi=openapi, meta=MetaType.POETRY, config=Config(package_name_override="my_special_package_name")
+        )
 
         assert project.project_name == "my-special-project-name"
         assert project.package_name == "my_special_package_name"
 
     def test_build(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project.project_dir = mocker.MagicMock()
         project.package_dir = mocker.MagicMock()
         project._build_metadata = mocker.MagicMock()
@@ -298,9 +310,7 @@ def test_build(self, mocker):
         assert result == project._get_errors.return_value
 
     def test_build_no_meta(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE)
+        project = make_project()
         project.project_dir = mocker.MagicMock()
         project.package_dir = mocker.MagicMock()
         project._build_metadata = mocker.MagicMock()
@@ -315,9 +325,7 @@ def test_build_no_meta(self, mocker):
         project.project_dir.mkdir.assert_not_called()
 
     def test_build_file_exists(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project.project_dir = mocker.MagicMock()
         project.project_dir.mkdir.side_effect = FileExistsError
         result = project.build()
@@ -327,10 +335,10 @@ def test_build_file_exists(self, mocker):
         assert result == [GeneratorError(detail="Directory already exists. Delete it or use the update command.")]
 
     def test_update(self, mocker):
-        from openapi_python_client import MetaType, Project, shutil
+        from openapi_python_client import shutil
 
         rmtree = mocker.patch.object(shutil, "rmtree")
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project.package_dir = mocker.MagicMock()
         project._build_metadata = mocker.MagicMock()
         project._build_models = mocker.MagicMock()
@@ -350,9 +358,7 @@ def test_update(self, mocker):
         assert result == project._get_errors.return_value
 
     def test_update_missing_dir(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project.package_dir = mocker.MagicMock()
         project.package_dir.is_dir.return_value = False
         project._build_models = mocker.MagicMock()
@@ -364,9 +370,7 @@ def test_update_missing_dir(self, mocker):
         project._build_models.assert_not_called()
 
     def test__build_metadata_poetry(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project._build_pyproject_toml = mocker.MagicMock()
         project.project_dir = mocker.MagicMock()
         readme_path = mocker.MagicMock(autospec=pathlib.Path)
@@ -400,9 +404,7 @@ def test__build_metadata_poetry(self, mocker):
         project._build_pyproject_toml.assert_called_once_with(use_poetry=True)
 
     def test__build_metadata_setup(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP)
+        project = make_project()
         project._build_pyproject_toml = mocker.MagicMock()
         project._build_setup_py = mocker.MagicMock()
         project.project_dir = mocker.MagicMock()
@@ -438,9 +440,7 @@ def test__build_metadata_setup(self, mocker):
         project._build_setup_py.assert_called_once()
 
     def test__build_metadata_none(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE)
+        project = make_project()
         project._build_pyproject_toml = mocker.MagicMock()
 
         project._build_metadata()
@@ -449,9 +449,7 @@ def test__build_metadata_none(self, mocker):
 
     @pytest.mark.parametrize("use_poetry", [(True,), (False,)])
     def test__build_pyproject_toml(self, mocker, use_poetry):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY)
+        project = make_project()
         project.project_dir = mocker.MagicMock()
         pyproject_path = mocker.MagicMock(autospec=pathlib.Path)
         paths = {
@@ -480,9 +478,7 @@ def test__build_pyproject_toml(self, mocker, use_poetry):
         pyproject_path.write_text.assert_called_once_with(pyproject_template.render(), encoding="utf-8")
 
     def test__build_setup_py(self, mocker):
-        from openapi_python_client import MetaType, Project
-
-        project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP)
+        project = make_project()
         project.project_dir = mocker.MagicMock()
         setup_path = mocker.MagicMock(autospec=pathlib.Path)
         paths = {
@@ -513,11 +509,8 @@ def test__build_setup_py(self, mocker):
 def test__reformat(mocker):
     import subprocess
 
-    from openapi_python_client import GeneratorData, MetaType, Project
-
     sub_run = mocker.patch("subprocess.run")
-    openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API")
-    project = Project(openapi=openapi, meta=MetaType.POETRY)
+    project = make_project()
     project.project_dir = mocker.MagicMock(autospec=pathlib.Path)
 
     project._reformat()
@@ -556,30 +549,27 @@ def test__get_errors(mocker):
         },
         errors=[3],
     )
-    project = Project(openapi=openapi, meta=MetaType.POETRY)
+    project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config())
 
     assert project._get_errors() == [1, 2, 3]
 
 
 def test__custom_templates(mocker):
     from openapi_python_client import GeneratorData, MetaType, Project
-    from openapi_python_client.parser.openapi import EndpointCollection, Schemas
 
     openapi = mocker.MagicMock(
         autospec=GeneratorData,
         title="My Test API",
-        endpoint_collections_by_tag={
-            "default": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[1]),
-            "other": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[2]),
-        },
-        schemas=mocker.MagicMock(autospec=Schemas, errors=[3]),
     )
 
-    project = Project(openapi=openapi, meta=MetaType.POETRY)
+    project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config())
     assert isinstance(project.env.loader, jinja2.PackageLoader)
 
     project = Project(
-        openapi=openapi, custom_template_path="../end_to_end_tests/test_custom_templates", meta=MetaType.POETRY
+        openapi=openapi,
+        custom_template_path="../end_to_end_tests/test_custom_templates",
+        meta=MetaType.POETRY,
+        config=Config(),
     )
     assert isinstance(project.env.loader, jinja2.ChoiceLoader)
     assert len(project.env.loader.loaders) == 2
diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py
index 9a856c190..ab35e514d 100644
--- a/tests/test_parser/test_properties/test_model_property.py
+++ b/tests/test_parser/test_properties/test_model_property.py
@@ -5,7 +5,12 @@
 import openapi_python_client.schema as oai
 from openapi_python_client.parser.errors import PropertyError
 from openapi_python_client.parser.properties import DateTimeProperty, ModelProperty, StringProperty
-from openapi_python_client.parser.reference import Reference
+
+
+def get_class():
+    from openapi_python_client.parser.properties import Class
+
+    return Class(name="MyClass", module_name="my_module")
 
 
 @pytest.mark.parametrize(
@@ -23,14 +28,14 @@
     ],
 )
 def test_get_type_string(no_optional, nullable, required, json, expected):
-    from openapi_python_client.parser.properties import ModelProperty, Reference
+    from openapi_python_client.parser.properties import ModelProperty
 
     prop = ModelProperty(
         name="prop",
         required=required,
         nullable=nullable,
         default=None,
-        reference=Reference(class_name="MyClass", module_name="my_module"),
+        class_info=get_class(),
         description="",
         optional_properties=[],
         required_properties=[],
@@ -42,14 +47,14 @@ def test_get_type_string(no_optional, nullable, required, json, expected):
 
 
 def test_get_imports():
-    from openapi_python_client.parser.properties import ModelProperty, Reference
+    from openapi_python_client.parser.properties import ModelProperty
 
     prop = ModelProperty(
         name="prop",
         required=False,
         nullable=True,
         default=None,
-        reference=Reference(class_name="MyClass", module_name="my_module"),
+        class_info=get_class(),
         description="",
         optional_properties=[],
         required_properties=[],
@@ -99,7 +104,7 @@ def test_additional_schemas(self, additional_properties_schema, expected_additio
         assert model.additional_properties == expected_additional_properties
 
     def test_happy_path(self):
-        from openapi_python_client.parser.properties import Schemas, build_model_property
+        from openapi_python_client.parser.properties import Class, Schemas, build_model_property
 
         data = oai.Schema.construct(
             required=["req"],
@@ -131,7 +136,7 @@ def test_happy_path(self):
             required=True,
             nullable=False,
             default=None,
-            reference=Reference(class_name="ParentMyModel", module_name="parent_my_model"),
+            class_info=Class(name="ParentMyModel", module_name="parent_my_model"),
             required_properties=[StringProperty(name="req", required=True, nullable=False, default=None)],
             optional_properties=[DateTimeProperty(name="opt", required=False, nullable=False, default=None)],
             description=data.description,
@@ -209,7 +214,7 @@ def test_bad_additional_props_return_error(self):
 
 @pytest.fixture
 def model_property() -> Callable[..., ModelProperty]:
-    from openapi_python_client.parser.reference import Reference
+    from openapi_python_client.parser.properties import Class
 
     def _factory(**kwargs):
         kwargs = {
@@ -218,7 +223,7 @@ def _factory(**kwargs):
             "required": True,
             "nullable": True,
             "default": None,
-            "reference": Reference(class_name="", module_name=""),
+            "reference": Class(name="", module_name=""),
             "required_properties": [],
             "optional_properties": [],
             "relative_imports": set(),

From 2bbbed642f06923cd7befc7cdbe262e3cfa3e68d Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sat, 27 Mar 2021 17:04:36 -0600
Subject: [PATCH 04/11] test: Fix more broken tests

---
 .../parser/properties/schemas.py              |  12 +-
 tests/conftest.py                             |  58 ++++
 tests/test___init__.py                        |  58 ++--
 tests/test_cli.py                             |  61 ++--
 tests/test_config.py                          |  63 ++--
 tests/test_parser/test_openapi.py             | 300 ++++++++++++------
 .../test_properties/test_model_property.py    |  49 +--
 .../test_properties/test_schemas.py           |  34 ++
 tests/test_parser/test_reference.py           |  16 -
 tests/test_parser/test_responses.py           |  16 +-
 10 files changed, 425 insertions(+), 242 deletions(-)
 create mode 100644 tests/conftest.py
 create mode 100644 tests/test_parser/test_properties/test_schemas.py
 delete mode 100644 tests/test_parser/test_reference.py

diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index 73adb6b57..8f4b43129 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -41,11 +41,15 @@ def from_string(*, string: str, config: Config) -> "Class":
         """ Get a Class from an arbitrary string """
         class_name = string.split("/")[-1]  # Get rid of ref path stuff
         class_name = utils.pascal_case(class_name)
-        module_name = utils.snake_case(class_name)
         override = config.class_overrides.get(class_name)
-        if override is not None:
-            class_name = override.class_name or class_name
-            module_name = override.module_name or module_name
+
+        if override is not None and override.class_name is not None:
+            class_name = override.class_name
+
+        if override is not None and override.module_name is not None:
+            module_name = override.module_name
+        else:
+            module_name = utils.snake_case(class_name)
 
         return Class(name=cast(_ClassName, class_name), module_name=module_name)
 
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 000000000..d432982ae
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,58 @@
+from typing import Callable
+
+import pytest
+
+from openapi_python_client.parser.properties import EnumProperty, ModelProperty
+
+
+@pytest.fixture
+def model_property_factory() -> Callable[..., ModelProperty]:
+    """
+    This fixture surfaces in the test as a function which manufactures ModelProperties with defaults.
+
+    You can pass the same params into this as the ModelProperty constructor to override defaults.
+    """
+    from openapi_python_client.parser.properties import Class
+
+    def _factory(**kwargs):
+        kwargs = {
+            "name": "",
+            "description": "",
+            "required": True,
+            "nullable": True,
+            "default": None,
+            "class_info": Class(name="", module_name=""),
+            "required_properties": [],
+            "optional_properties": [],
+            "relative_imports": set(),
+            "additional_properties": False,
+            **kwargs,
+        }
+        return ModelProperty(**kwargs)
+
+    return _factory
+
+
+@pytest.fixture
+def enum_property_factory() -> Callable[..., EnumProperty]:
+    """
+    This fixture surfaces in the test as a function which manufactures EnumProperties with defaults.
+
+    You can pass the same params into this as the EnumProerty constructor to override defaults.
+    """
+    from openapi_python_client.parser.properties import Class
+
+    def _factory(**kwargs):
+        kwargs = {
+            "name": "test",
+            "required": True,
+            "nullable": True,
+            "default": None,
+            "class_info": Class(name="", module_name=""),
+            "values": {},
+            "value_type": str,
+            **kwargs,
+        }
+        return EnumProperty(**kwargs)
+
+    return _factory
diff --git a/tests/test___init__.py b/tests/test___init__.py
index 342928b94..0579e83f0 100644
--- a/tests/test___init__.py
+++ b/tests/test___init__.py
@@ -113,13 +113,14 @@ def test_update_existing_client(mocker):
     )
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, update_existing_client
 
-    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config())
+    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_project_for_url_or_path.assert_called_once_with(
-        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config
     )
     project.update.assert_called_once()
     assert result == project.update.return_value
@@ -132,13 +133,14 @@ def test_update_existing_client_project_error(mocker):
     )
     url = mocker.MagicMock()
     path = mocker.MagicMock()
+    config = mocker.MagicMock()
 
     from openapi_python_client import MetaType, update_existing_client
 
-    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config())
+    result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config)
 
     _get_project_for_url_or_path.assert_called_once_with(
-        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config
     )
     assert result == [error]
 
@@ -229,12 +231,14 @@ def test__get_document_bad_yaml(self, mocker):
         assert result == GeneratorError(header="Invalid YAML from provided source")
 
 
-def make_project():
+def make_project(**kwargs):
     from unittest.mock import MagicMock
 
     from openapi_python_client import MetaType, Project
 
-    return Project(openapi=MagicMock(title="My Test API"), meta=MetaType.POETRY, config=Config())
+    kwargs = {"openapi": MagicMock(title="My Test API"), "meta": MetaType.POETRY, "config": Config(), **kwargs}
+
+    return Project(**kwargs)
 
 
 class TestProject:
@@ -261,31 +265,35 @@ def test___init___no_meta(self, mocker):
         project = Project(openapi=openapi, meta=MetaType.NONE, config=Config())
 
         assert project.openapi == openapi
-        assert project.project_name == "my-test-api-client"
-        assert project.package_name == "my_test_api_client"
         assert project.package_description == "A client library for accessing My Test API"
         assert project.meta == MetaType.NONE
         assert project.project_dir == pathlib.Path.cwd()
         assert project.package_dir == pathlib.Path.cwd() / project.package_name
 
-    def test_project_and_package_name_overrides(self, mocker):
+    @pytest.mark.parametrize(
+        "project_override, package_override, expected_project_name, expected_package_name",
+        (
+            (None, None, "my-test-api-client", "my_test_api_client"),
+            ("custom-project", None, "custom-project", "custom_project"),
+            ("custom-project", "custom_package", "custom-project", "custom_package"),
+            (None, "custom_package", "my-test-api-client", "custom_package"),
+        ),
+    )
+    def test_project_and_package_names(
+        self, mocker, project_override, package_override, expected_project_name, expected_package_name
+    ):
         openapi = mocker.MagicMock(title="My Test API")
 
         from openapi_python_client import MetaType, Project
 
         project = Project(
-            openapi=openapi, meta=MetaType.POETRY, config=Config(project_name_override="my-special-project-name")
-        )
-
-        assert project.project_name == "my-special-project-name"
-        assert project.package_name == "my_special_project_name"
-
-        project = Project(
-            openapi=openapi, meta=MetaType.POETRY, config=Config(package_name_override="my_special_package_name")
+            openapi=openapi,
+            meta=MetaType.POETRY,
+            config=Config(project_name_override=project_override, package_name_override=package_override),
         )
 
-        assert project.project_name == "my-special-project-name"
-        assert project.package_name == "my_special_package_name"
+        assert project.project_name == expected_project_name
+        assert project.package_name == expected_package_name
 
     def test_build(self, mocker):
         project = make_project()
@@ -310,7 +318,9 @@ def test_build(self, mocker):
         assert result == project._get_errors.return_value
 
     def test_build_no_meta(self, mocker):
-        project = make_project()
+        from openapi_python_client import MetaType
+
+        project = make_project(meta=MetaType.NONE)
         project.project_dir = mocker.MagicMock()
         project.package_dir = mocker.MagicMock()
         project._build_metadata = mocker.MagicMock()
@@ -404,7 +414,9 @@ def test__build_metadata_poetry(self, mocker):
         project._build_pyproject_toml.assert_called_once_with(use_poetry=True)
 
     def test__build_metadata_setup(self, mocker):
-        project = make_project()
+        from openapi_python_client import MetaType
+
+        project = make_project(meta=MetaType.SETUP)
         project._build_pyproject_toml = mocker.MagicMock()
         project._build_setup_py = mocker.MagicMock()
         project.project_dir = mocker.MagicMock()
@@ -440,7 +452,9 @@ def test__build_metadata_setup(self, mocker):
         project._build_setup_py.assert_called_once()
 
     def test__build_metadata_none(self, mocker):
-        project = make_project()
+        from openapi_python_client import MetaType
+
+        project = make_project(meta=MetaType.NONE)
         project._build_pyproject_toml = mocker.MagicMock()
 
         project._build_metadata()
diff --git a/tests/test_cli.py b/tests/test_cli.py
index d5bc2c69e..4954aea25 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -35,12 +35,12 @@ def test_config_arg(mocker, _create_new_client):
 
     result = runner.invoke(
         app,
-        [f"--config={config_path}", "generate", f"--path={path}", f"--file-encoding={file_encoding}"],
+        ["generate", f"--config={config_path}", f"--path={path}", f"--file-encoding={file_encoding}"],
         catch_exceptions=False,
     )
 
     assert result.exit_code == 0
-    load_config.assert_called_once_with(path=Path(config_path))
+    load_config.assert_called_once_with(Path(config_path))
     _create_new_client.assert_called_once_with(
         url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
     )
@@ -55,11 +55,11 @@ def test_bad_config(mocker, _create_new_client):
     config_path = "config/path"
     path = "cool/path"
 
-    result = runner.invoke(app, [f"--config={config_path}", "generate", f"--path={path}"])
+    result = runner.invoke(app, ["generate", f"--config={config_path}", f"--path={path}"])
 
     assert result.exit_code == 2
     assert "Unable to parse config" in result.stdout
-    load_config.assert_called_once_with(path=Path(config_path))
+    load_config.assert_called_once_with(Path(config_path))
     _create_new_client.assert_not_called()
 
 
@@ -82,47 +82,62 @@ def test_generate_url_and_path(self, _create_new_client):
 
     def test_generate_url(self, _create_new_client):
         url = "cool.url"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["generate", f"--url={url}"])
 
         assert result.exit_code == 0
         _create_new_client.assert_called_once_with(
-            url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config()
         )
 
     def test_generate_path(self, _create_new_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["generate", f"--path={path}"])
 
         assert result.exit_code == 0
         _create_new_client.assert_called_once_with(
-            url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=None,
+            path=Path(path),
+            custom_template_path=None,
+            meta=MetaType.POETRY,
+            file_encoding="utf-8",
+            config=Config(),
         )
 
     def test_generate_meta(self, _create_new_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"])
 
         assert result.exit_code == 0
         _create_new_client.assert_called_once_with(
-            url=None, path=Path(path), custom_template_path=None, meta=MetaType.NONE, file_encoding="utf-8"
+            url=None,
+            path=Path(path),
+            custom_template_path=None,
+            meta=MetaType.NONE,
+            file_encoding="utf-8",
+            config=Config(),
         )
 
     def test_generate_encoding(self, _create_new_client):
         path = "cool/path"
         file_encoding = "utf-8"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["generate", f"--path={path}", f"--file-encoding={file_encoding}"])
 
         assert result.exit_code == 0
         _create_new_client.assert_called_once_with(
-            url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=None,
+            path=Path(path),
+            custom_template_path=None,
+            meta=MetaType.POETRY,
+            file_encoding="utf-8",
+            config=Config(),
         )
 
     def test_generate_encoding_errors(self, _create_new_client):
@@ -198,36 +213,46 @@ def test_update_url_and_path(self, _update_existing_client):
 
     def test_update_url(self, _update_existing_client):
         url = "cool.url"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["update", f"--url={url}"])
 
         assert result.exit_code == 0
         _update_existing_client.assert_called_once_with(
-            url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config()
         )
 
     def test_update_path(self, _update_existing_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["update", f"--path={path}"])
 
         assert result.exit_code == 0
         _update_existing_client.assert_called_once_with(
-            url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=None,
+            path=Path(path),
+            custom_template_path=None,
+            meta=MetaType.POETRY,
+            file_encoding="utf-8",
+            config=Config(),
         )
 
     def test_update_encoding(self, _update_existing_client):
         path = "cool/path"
         file_encoding = "utf-8"
-        from openapi_python_client.cli import MetaType, app
+        from openapi_python_client.cli import MetaType, app, Config
 
         result = runner.invoke(app, ["update", f"--path={path}", f"--file-encoding={file_encoding}"])
 
         assert result.exit_code == 0
         _update_existing_client.assert_called_once_with(
-            url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+            url=None,
+            path=Path(path),
+            custom_template_path=None,
+            meta=MetaType.POETRY,
+            file_encoding="utf-8",
+            config=Config(),
         )
 
     def test_update_encoding_errors(self, _update_existing_client):
diff --git a/tests/test_config.py b/tests/test_config.py
index 0ed3ace75..d0cdbcd2b 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -4,46 +4,29 @@
 
 
 def test_load_from_path(mocker):
-    safe_load = mocker.patch("yaml.safe_load", return_value={})
+    from openapi_python_client import utils
+
+    override1 = {"class_name": "ExampleClass", "module_name": "example_module"}
+    override2 = {"class_name": "DifferentClass", "module_name": "different_module"}
+    safe_load = mocker.patch(
+        "yaml.safe_load",
+        return_value={
+            "field_prefix": "blah",
+            "class_overrides": {"Class1": override1, "Class2": override2},
+            "project_name_override": "project-name",
+            "package_name_override": "package_name",
+            "package_version_override": "package_version",
+        },
+    )
     fake_path = mocker.MagicMock(autospec=pathlib.Path)
-    load_config = mocker.patch("openapi_python_client.config.Config.load_config")
 
-    Config.load_from_path(fake_path)
+    config = Config.load_from_path(fake_path)
     safe_load.assert_called()
-    load_config.assert_called()
-
-
-class TestLoadConfig:
-    def test_class_overrides(self):
-        from openapi_python_client.parser import reference
-
-        override1 = {"class_name": "ExampleClass", "module_name": "example_module"}
-        override2 = {"class_name": "DifferentClass", "module_name": "different_module"}
-        config = Config(class_overrides={"Class1": override1, "Class2": override2})
-        config.load_config()
-
-        assert reference.class_overrides["Class1"] == reference.Reference(**override1)
-        assert reference.class_overrides["Class2"] == reference.Reference(**override2)
-
-    def test_project_and_package_name_and_package_version_overrides(self):
-        config = Config(
-            project_name_override="project-name",
-            package_name_override="package_name",
-            package_version_override="package_version",
-        )
-        config.load_config()
-
-        from openapi_python_client import Project
-
-        assert Project.project_name_override == "project-name"
-        assert Project.package_name_override == "package_name"
-        assert Project.package_version_override == "package_version"
-
-    def test_field_prefix(self):
-        Config(field_prefix="blah").load_config()
-
-        from openapi_python_client import utils
-
-        assert utils.FIELD_PREFIX == "blah"
-
-        utils.FIELD_PREFIX = "field_"
+    assert utils.FIELD_PREFIX == "blah"
+    assert config.class_overrides["Class1"] == override1
+    assert config.class_overrides["Class2"] == override2
+    assert config.project_name_override == "project-name"
+    assert config.package_name_override == "package_name"
+    assert config.package_version_override == "package_version"
+
+    utils.FIELD_PREFIX = "field_"
diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py
index 1bbaa3e6e..b9124914e 100644
--- a/tests/test_parser/test_openapi.py
+++ b/tests/test_parser/test_openapi.py
@@ -1,3 +1,5 @@
+from unittest.mock import MagicMock
+
 import openapi_python_client.schema as oai
 from openapi_python_client import GeneratorError
 from openapi_python_client.parser.errors import ParseError
@@ -6,51 +8,58 @@
 
 
 class TestGeneratorData:
-    def test_from_dict(self, mocker):
+    def test_from_dict(self, mocker, model_property_factory, enum_property_factory):
+        from openapi_python_client.parser.properties import Schemas
+
         build_schemas = mocker.patch(f"{MODULE_NAME}.build_schemas")
         EndpointCollection = mocker.patch(f"{MODULE_NAME}.EndpointCollection")
         schemas = mocker.MagicMock()
+        schemas.classes_by_name = {
+            "Model": model_property_factory(),
+            "Enum": enum_property_factory(),
+        }
         endpoints_collections_by_tag = mocker.MagicMock()
         EndpointCollection.from_data.return_value = (endpoints_collections_by_tag, schemas)
         OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI")
         openapi = OpenAPI.parse_obj.return_value
         openapi.openapi = mocker.MagicMock(major=3)
-
+        config = mocker.MagicMock()
         in_dict = mocker.MagicMock()
 
         from openapi_python_client.parser.openapi import GeneratorData
 
-        generator_data = GeneratorData.from_dict(in_dict)
+        generator_data = GeneratorData.from_dict(in_dict, config=config)
 
         OpenAPI.parse_obj.assert_called_once_with(in_dict)
-        build_schemas.assert_called_once_with(components=openapi.components.schemas)
-        EndpointCollection.from_data.assert_called_once_with(data=openapi.paths, schemas=build_schemas.return_value)
-        assert generator_data == GeneratorData(
-            title=openapi.info.title,
-            description=openapi.info.description,
-            version=openapi.info.version,
-            endpoint_collections_by_tag=endpoints_collections_by_tag,
-            errors=schemas.errors,
-            models=schemas.models,
-            enums=schemas.enums,
-        )
+        build_schemas.assert_called_once_with(components=openapi.components.schemas, config=config, schemas=Schemas())
+        EndpointCollection.from_data.assert_called_once_with(
+            data=openapi.paths, schemas=build_schemas.return_value, config=config
+        )
+        assert generator_data.title == openapi.info.title
+        assert generator_data.description == openapi.info.description
+        assert generator_data.version == openapi.info.version
+        assert generator_data.endpoint_collections_by_tag == endpoints_collections_by_tag
+        assert generator_data.errors == schemas.errors
+        assert list(generator_data.models) == [schemas.classes_by_name["Model"]]
+        assert list(generator_data.enums) == [schemas.classes_by_name["Enum"]]
 
         # Test no components
         openapi.components = None
         build_schemas.reset_mock()
 
-        GeneratorData.from_dict(in_dict)
+        GeneratorData.from_dict(in_dict, config=config)
 
         build_schemas.assert_not_called()
 
     def test_from_dict_invalid_schema(self, mocker):
         Schemas = mocker.patch(f"{MODULE_NAME}.Schemas")
+        config = mocker.MagicMock()
 
         in_dict = {}
 
         from openapi_python_client.parser.openapi import GeneratorData
 
-        generator_data = GeneratorData.from_dict(in_dict)
+        generator_data = GeneratorData.from_dict(in_dict, config=config)
 
         assert generator_data == GeneratorError(
             header="Failed to parse OpenAPI document",
@@ -69,12 +78,13 @@ def test_from_dict_invalid_schema(self, mocker):
 
     def test_swagger_document_invalid_schema(self, mocker):
         Schemas = mocker.patch(f"{MODULE_NAME}.Schemas")
+        config = mocker.MagicMock()
 
         in_dict = {"swagger": "2.0"}
 
         from openapi_python_client.parser.openapi import GeneratorData
 
-        generator_data = GeneratorData.from_dict(in_dict)
+        generator_data = GeneratorData.from_dict(in_dict, config=config)
 
         assert generator_data == GeneratorError(
             header="Failed to parse OpenAPI document",
@@ -94,16 +104,15 @@ def test_swagger_document_invalid_schema(self, mocker):
 
     def test_from_dict_invalid_version(self, mocker):
         Schemas = mocker.patch(f"{MODULE_NAME}.Schemas")
-
         OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI")
         openapi = OpenAPI.parse_obj.return_value
         openapi.openapi = oai.SemVer("2.1.3")
-
         in_dict = mocker.MagicMock()
+        config = mocker.MagicMock()
 
         from openapi_python_client.parser.openapi import GeneratorData
 
-        generator_data = GeneratorData.from_dict(in_dict)
+        generator_data = GeneratorData.from_dict(in_dict, config=config)
 
         assert generator_data == GeneratorError(
             header="openapi-python-client only supports OpenAPI 3.x",
@@ -123,21 +132,23 @@ def test_parse_request_form_body(self, mocker):
                 )
             }
         )
-        from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
+        from_string = mocker.patch(f"{MODULE_NAME}.Class.from_string")
+        config = mocker.MagicMock()
 
         from openapi_python_client.parser.openapi import Endpoint
 
-        result = Endpoint.parse_request_form_body(body)
+        result = Endpoint.parse_request_form_body(body=body, config=config)
 
-        from_ref.assert_called_once_with(ref)
-        assert result == from_ref()
+        from_string.assert_called_once_with(string=ref, config=config)
+        assert result == from_string.return_value
 
     def test_parse_request_form_body_no_data(self):
         body = oai.RequestBody.construct(content={})
+        config = MagicMock()
 
         from openapi_python_client.parser.openapi import Endpoint
 
-        result = Endpoint.parse_request_form_body(body)
+        result = Endpoint.parse_request_form_body(body=body, config=config)
 
         assert result is None
 
@@ -146,21 +157,22 @@ def test_parse_multipart_body(self, mocker):
         body = oai.RequestBody.construct(
             content={"multipart/form-data": oai.MediaType.construct(media_type_schema=oai.Reference.construct(ref=ref))}
         )
-        from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
+        from_string = mocker.patch(f"{MODULE_NAME}.Class.from_string")
+        config = MagicMock()
 
         from openapi_python_client.parser.openapi import Endpoint
 
-        result = Endpoint.parse_multipart_body(body)
+        result = Endpoint.parse_multipart_body(body=body, config=config)
 
-        from_ref.assert_called_once_with(ref)
-        assert result == from_ref()
+        from_string.assert_called_once_with(string=ref, config=config)
+        assert result == from_string.return_value
 
     def test_parse_multipart_body_no_data(self):
         body = oai.RequestBody.construct(content={})
 
         from openapi_python_client.parser.openapi import Endpoint
 
-        result = Endpoint.parse_multipart_body(body)
+        result = Endpoint.parse_multipart_body(body=body, config=MagicMock())
 
         assert result is None
 
@@ -173,11 +185,12 @@ def test_parse_request_json_body(self, mocker):
         )
         property_from_data = mocker.patch(f"{MODULE_NAME}.property_from_data")
         schemas = Schemas()
+        config = MagicMock()
 
-        result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent")
+        result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=config)
 
         property_from_data.assert_called_once_with(
-            name="json_body", required=True, data=schema, schemas=schemas, parent_name="parent"
+            name="json_body", required=True, data=schema, schemas=schemas, parent_name="parent", config=config
         )
         assert result == property_from_data.return_value
 
@@ -187,7 +200,7 @@ def test_parse_request_json_body_no_data(self):
         body = oai.RequestBody.construct(content={})
         schemas = Schemas()
 
-        result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent")
+        result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=MagicMock())
 
         assert result == (None, schemas)
 
@@ -206,7 +219,7 @@ def test_add_body_no_data(self, mocker):
         )
         schemas = Schemas()
 
-        Endpoint._add_body(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas)
+        Endpoint._add_body(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas, config=MagicMock())
 
         parse_request_form_body.assert_not_called()
 
@@ -230,7 +243,10 @@ def test_add_body_bad_data(self, mocker):
         schemas = Schemas()
 
         result = Endpoint._add_body(
-            endpoint=endpoint, data=oai.Operation.construct(requestBody=request_body), schemas=schemas
+            endpoint=endpoint,
+            data=oai.Operation.construct(requestBody=request_body),
+            schemas=schemas,
+            config=MagicMock(),
         )
 
         assert result == (
@@ -239,18 +255,15 @@ def test_add_body_bad_data(self, mocker):
         )
 
     def test_add_body_happy(self, mocker):
-        from openapi_python_client.parser.openapi import Endpoint, Reference, Schemas
+        from openapi_python_client.parser.openapi import Endpoint, Class
         from openapi_python_client.parser.properties import Property
 
         request_body = mocker.MagicMock()
-        form_body_reference = Reference.from_ref(ref="a")
-        multipart_body_reference = Reference.from_ref(ref="b")
-        parse_request_form_body = mocker.patch.object(
-            Endpoint, "parse_request_form_body", return_value=form_body_reference
-        )
-        parse_multipart_body = mocker.patch.object(
-            Endpoint, "parse_multipart_body", return_value=multipart_body_reference
-        )
+        config = mocker.MagicMock()
+        form_body_class = Class(name="A", module_name="a")
+        multipart_body_class = Class(name="B", module_name="b")
+        parse_request_form_body = mocker.patch.object(Endpoint, "parse_request_form_body", return_value=form_body_class)
+        parse_multipart_body = mocker.patch.object(Endpoint, "parse_multipart_body", return_value=multipart_body_class)
 
         json_body = mocker.MagicMock(autospec=Property)
         json_body_imports = mocker.MagicMock()
@@ -259,8 +272,8 @@ def test_add_body_happy(self, mocker):
         parse_request_json_body = mocker.patch.object(
             Endpoint, "parse_request_json_body", return_value=(json_body, parsed_schemas)
         )
-        import_string_from_reference = mocker.patch(
-            f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"]
+        import_string_from_class = mocker.patch(
+            f"{MODULE_NAME}.import_string_from_class", side_effect=["import_1", "import_2"]
         )
 
         endpoint = Endpoint(
@@ -275,31 +288,35 @@ def test_add_body_happy(self, mocker):
         initial_schemas = mocker.MagicMock()
 
         (endpoint, response_schemas) = Endpoint._add_body(
-            endpoint=endpoint, data=oai.Operation.construct(requestBody=request_body), schemas=initial_schemas
+            endpoint=endpoint,
+            data=oai.Operation.construct(requestBody=request_body),
+            schemas=initial_schemas,
+            config=config,
         )
 
         assert response_schemas == parsed_schemas
-        parse_request_form_body.assert_called_once_with(request_body)
-        parse_request_json_body.assert_called_once_with(body=request_body, schemas=initial_schemas, parent_name="name")
-        parse_multipart_body.assert_called_once_with(request_body)
-        import_string_from_reference.assert_has_calls(
+        parse_request_form_body.assert_called_once_with(body=request_body, config=config)
+        parse_request_json_body.assert_called_once_with(
+            body=request_body, schemas=initial_schemas, parent_name="name", config=config
+        )
+        parse_multipart_body.assert_called_once_with(body=request_body, config=config)
+        import_string_from_class.assert_has_calls(
             [
-                mocker.call(form_body_reference, prefix="...models"),
-                mocker.call(multipart_body_reference, prefix="...models"),
+                mocker.call(form_body_class, prefix="...models"),
+                mocker.call(multipart_body_class, prefix="...models"),
             ]
         )
         json_body.get_imports.assert_called_once_with(prefix="...")
         assert endpoint.relative_imports == {"import_1", "import_2", "import_3", json_body_imports}
         assert endpoint.json_body == json_body
-        assert endpoint.form_body_reference == form_body_reference
-        assert endpoint.multipart_body_reference == multipart_body_reference
+        assert endpoint.form_body_class == form_body_class
+        assert endpoint.multipart_body_class == multipart_body_class
 
     def test__add_responses_status_code_error(self, mocker):
         from openapi_python_client.parser.openapi import Endpoint, Schemas
 
         schemas = Schemas()
         response_1_data = mocker.MagicMock()
-        response_2_data = mocker.MagicMock()
         data = {
             "not_a_number": response_1_data,
         }
@@ -312,16 +329,17 @@ def test__add_responses_status_code_error(self, mocker):
             tag="tag",
             relative_imports={"import_3"},
         )
-        parse_error = ParseError(data=mocker.MagicMock())
-        response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas))
+        response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data")
+        config = MagicMock()
 
-        response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas)
+        response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas, config=config)
 
         assert response.errors == [
             ParseError(
                 detail=f"Invalid response status code not_a_number (not a number), response will be ommitted from generated client"
             )
         ]
+        response_from_data.assert_not_called()
 
     def test__add_responses_error(self, mocker):
         from openapi_python_client.parser.openapi import Endpoint, Schemas
@@ -344,13 +362,14 @@ def test__add_responses_error(self, mocker):
         )
         parse_error = ParseError(data=mocker.MagicMock())
         response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas))
+        config = MagicMock()
 
-        response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas)
+        response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas, config=config)
 
         response_from_data.assert_has_calls(
             [
-                mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name"),
-                mocker.call(status_code=404, data=response_2_data, schemas=schemas, parent_name="name"),
+                mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name", config=config),
+                mocker.call(status_code=404, data=response_2_data, schemas=schemas, parent_name="name", config=config),
             ]
         )
         assert response.errors == [
@@ -399,13 +418,18 @@ def test__add_responses(self, mocker):
         response_from_data = mocker.patch(
             f"{MODULE_NAME}.response_from_data", side_effect=[(response_1, schemas_1), (response_2, schemas_2)]
         )
+        config = MagicMock()
 
-        endpoint, response_schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas)
+        endpoint, response_schemas = Endpoint._add_responses(
+            endpoint=endpoint, data=data, schemas=schemas, config=config
+        )
 
         response_from_data.assert_has_calls(
             [
-                mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name"),
-                mocker.call(status_code=404, data=response_2_data, schemas=schemas_1, parent_name="name"),
+                mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name", config=config),
+                mocker.call(
+                    status_code=404, data=response_2_data, schemas=schemas_1, parent_name="name", config=config
+                ),
             ]
         )
         assert endpoint.responses == [response_1, response_2]
@@ -429,8 +453,12 @@ def test__add_parameters_handles_no_params(self):
             tag="tag",
         )
         schemas = Schemas()
+        config = MagicMock()
+
         # Just checking there's no exception here
-        assert Endpoint._add_parameters(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas) == (
+        assert Endpoint._add_parameters(
+            endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas, config=config
+        ) == (
             endpoint,
             schemas,
         )
@@ -451,9 +479,10 @@ def test__add_parameters_parse_error(self, mocker):
         property_schemas = mocker.MagicMock()
         mocker.patch(f"{MODULE_NAME}.property_from_data", return_value=(parse_error, property_schemas))
         param = oai.Parameter.construct(name="test", required=True, param_schema=mocker.MagicMock(), param_in="cookie")
+        config = MagicMock()
 
         result = Endpoint._add_parameters(
-            endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=initial_schemas
+            endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=initial_schemas, config=config
         )
         assert result == (
             ParseError(data=parse_error.data, detail=f"cannot parse parameter of endpoint {endpoint.name}"),
@@ -477,9 +506,10 @@ def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker):
             name="test", required=True, param_schema=mocker.MagicMock(), param_in="error_location"
         )
         schemas = Schemas()
+        config = MagicMock()
 
         result = Endpoint._add_parameters(
-            endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=schemas
+            endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=schemas, config=config
         )
         assert result == (ParseError(data=param, detail="Parameter must be declared in path or query"), parsed_schemas)
 
@@ -531,19 +561,37 @@ def test__add_parameters_happy(self, mocker):
             ]
         )
         initial_schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        (endpoint, schemas) = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=initial_schemas)
+        (endpoint, schemas) = Endpoint._add_parameters(
+            endpoint=endpoint, data=data, schemas=initial_schemas, config=config
+        )
 
         property_from_data.assert_has_calls(
             [
                 mocker.call(
-                    name="path_prop_name", required=True, data=path_schema, schemas=initial_schemas, parent_name="name"
+                    name="path_prop_name",
+                    required=True,
+                    data=path_schema,
+                    schemas=initial_schemas,
+                    parent_name="name",
+                    config=config,
                 ),
                 mocker.call(
-                    name="query_prop_name", required=False, data=query_schema, schemas=schemas_1, parent_name="name"
+                    name="query_prop_name",
+                    required=False,
+                    data=query_schema,
+                    schemas=schemas_1,
+                    parent_name="name",
+                    config=config,
                 ),
                 mocker.call(
-                    name="header_prop_name", required=False, data=header_schema, schemas=schemas_2, parent_name="name"
+                    name="header_prop_name",
+                    required=False,
+                    data=header_schema,
+                    schemas=schemas_2,
+                    parent_name="name",
+                    config=config,
                 ),
             ]
         )
@@ -571,8 +619,11 @@ def test_from_data_bad_params(self, mocker):
             responses=mocker.MagicMock(),
         )
         inital_schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=inital_schemas)
+        result = Endpoint.from_data(
+            data=data, path=path, method=method, tag="default", schemas=inital_schemas, config=config
+        )
 
         assert result == (parse_error, return_schemas)
 
@@ -595,8 +646,11 @@ def test_from_data_bad_responses(self, mocker):
             responses=mocker.MagicMock(),
         )
         initial_schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=initial_schemas)
+        result = Endpoint.from_data(
+            data=data, path=path, method=method, tag="default", schemas=initial_schemas, config=config
+        )
 
         assert result == (parse_error, response_schemas)
 
@@ -623,10 +677,13 @@ def test_from_data_standard(self, mocker):
             responses=mocker.MagicMock(),
         )
         initial_schemas = mocker.MagicMock()
+        config = MagicMock()
 
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description)
 
-        endpoint = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=initial_schemas)
+        endpoint = Endpoint.from_data(
+            data=data, path=path, method=method, tag="default", schemas=initial_schemas, config=config
+        )
 
         assert endpoint == _add_body.return_value
 
@@ -641,9 +698,14 @@ def test_from_data_standard(self, mocker):
             ),
             data=data,
             schemas=initial_schemas,
+            config=config,
+        )
+        _add_responses.assert_called_once_with(
+            endpoint=param_endpoint, data=data.responses, schemas=param_schemas, config=config
+        )
+        _add_body.assert_called_once_with(
+            endpoint=response_endpoint, data=data, schemas=response_schemas, config=config
         )
-        _add_responses.assert_called_once_with(endpoint=param_endpoint, data=data.responses, schemas=param_schemas)
-        _add_body.assert_called_once_with(endpoint=response_endpoint, data=data, schemas=response_schemas)
 
     def test_from_data_no_operation_id(self, mocker):
         from openapi_python_client.parser.openapi import Endpoint
@@ -665,8 +727,9 @@ def test_from_data_no_operation_id(self, mocker):
         )
         schemas = mocker.MagicMock()
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description)
+        config = MagicMock()
 
-        result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=schemas)
+        result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=schemas, config=config)
 
         assert result == _add_body.return_value
 
@@ -681,12 +744,16 @@ def test_from_data_no_operation_id(self, mocker):
             ),
             data=data,
             schemas=schemas,
+            config=config,
         )
         _add_responses.assert_called_once_with(
-            endpoint=_add_parameters.return_value[0], data=data.responses, schemas=_add_parameters.return_value[1]
+            endpoint=_add_parameters.return_value[0],
+            data=data.responses,
+            schemas=_add_parameters.return_value[1],
+            config=config,
         )
         _add_body.assert_called_once_with(
-            endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1]
+            endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config
         )
 
     def test_from_data_no_security(self, mocker):
@@ -709,8 +776,9 @@ def test_from_data_no_security(self, mocker):
         method = mocker.MagicMock()
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description)
         schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        Endpoint.from_data(data=data, path=path, method=method, tag="a", schemas=schemas)
+        Endpoint.from_data(data=data, path=path, method=method, tag="a", schemas=schemas, config=config)
 
         _add_parameters.assert_called_once_with(
             endpoint=Endpoint(
@@ -723,34 +791,38 @@ def test_from_data_no_security(self, mocker):
             ),
             data=data,
             schemas=schemas,
+            config=config,
         )
         _add_responses.assert_called_once_with(
-            endpoint=_add_parameters.return_value[0], data=data.responses, schemas=_add_parameters.return_value[1]
+            endpoint=_add_parameters.return_value[0],
+            data=data.responses,
+            schemas=_add_parameters.return_value[1],
+            config=config,
         )
         _add_body.assert_called_once_with(
-            endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1]
+            endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config
         )
 
 
 class TestImportStringFromReference:
     def test_import_string_from_reference_no_prefix(self, mocker):
-        from openapi_python_client.parser.openapi import import_string_from_reference
-        from openapi_python_client.parser.reference import Reference
+        from openapi_python_client.parser.openapi import import_string_from_class
+        from openapi_python_client.parser.properties import Class
 
-        reference = mocker.MagicMock(autospec=Reference)
-        result = import_string_from_reference(reference)
+        class_ = mocker.MagicMock(autospec=Class)
+        result = import_string_from_class(class_)
 
-        assert result == f"from .{reference.module_name} import {reference.class_name}"
+        assert result == f"from .{class_.module_name} import {class_.name}"
 
     def test_import_string_from_reference_with_prefix(self, mocker):
-        from openapi_python_client.parser.openapi import import_string_from_reference
-        from openapi_python_client.parser.reference import Reference
+        from openapi_python_client.parser.openapi import import_string_from_class
+        from openapi_python_client.parser.properties import Class
 
         prefix = mocker.MagicMock(autospec=str)
-        reference = mocker.MagicMock(autospec=Reference)
-        result = import_string_from_reference(reference=reference, prefix=prefix)
+        class_ = mocker.MagicMock(autospec=Class)
+        result = import_string_from_class(class_=class_, prefix=prefix)
 
-        assert result == f"from {prefix}.{reference.module_name} import {reference.class_name}"
+        assert result == f"from {prefix}.{class_.module_name} import {class_.name}"
 
 
 class TestEndpointCollection:
@@ -776,14 +848,21 @@ def test_from_data(self, mocker):
             side_effect=[(endpoint_1, schemas_1), (endpoint_2, schemas_2), (endpoint_3, schemas_3)],
         )
         schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        result = EndpointCollection.from_data(data=data, schemas=schemas)
+        result = EndpointCollection.from_data(data=data, schemas=schemas, config=config)
 
         endpoint_from_data.assert_has_calls(
             [
-                mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas),
-                mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1),
-                mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2),
+                mocker.call(
+                    data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config
+                ),
+                mocker.call(
+                    data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1, config=config
+                ),
+                mocker.call(
+                    data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config
+                ),
             ],
         )
         assert result == (
@@ -817,14 +896,21 @@ def test_from_data_errors(self, mocker):
             ],
         )
         schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        result, result_schemas = EndpointCollection.from_data(data=data, schemas=schemas)
+        result, result_schemas = EndpointCollection.from_data(data=data, schemas=schemas, config=config)
 
         endpoint_from_data.assert_has_calls(
             [
-                mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas),
-                mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1),
-                mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2),
+                mocker.call(
+                    data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config
+                ),
+                mocker.call(
+                    data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1, config=config
+                ),
+                mocker.call(
+                    data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config
+                ),
             ],
         )
         assert result["default"].parse_errors[0].data == "1"
@@ -854,20 +940,26 @@ def test_from_data_tags_snake_case_sanitizer(self, mocker):
             side_effect=[(endpoint_1, schemas_1), (endpoint_2, schemas_2), (endpoint_3, schemas_3)],
         )
         schemas = mocker.MagicMock()
+        config = MagicMock()
 
-        result = EndpointCollection.from_data(data=data, schemas=schemas)
+        result = EndpointCollection.from_data(data=data, schemas=schemas, config=config)
 
         endpoint_from_data.assert_has_calls(
             [
-                mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas),
+                mocker.call(
+                    data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config
+                ),
                 mocker.call(
                     data=path_1_post,
                     path="path_1",
                     method="post",
                     tag="amf_subscription_info_document",
                     schemas=schemas_1,
+                    config=config,
+                ),
+                mocker.call(
+                    data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config
                 ),
-                mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2),
             ],
         )
         assert result == (
diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py
index ab35e514d..2d6076be6 100644
--- a/tests/test_parser/test_properties/test_model_property.py
+++ b/tests/test_parser/test_properties/test_model_property.py
@@ -212,29 +212,6 @@ def test_bad_additional_props_return_error(self):
         assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real"))
 
 
-@pytest.fixture
-def model_property() -> Callable[..., ModelProperty]:
-    from openapi_python_client.parser.properties import Class
-
-    def _factory(**kwargs):
-        kwargs = {
-            "name": "",
-            "description": "",
-            "required": True,
-            "nullable": True,
-            "default": None,
-            "reference": Class(name="", module_name=""),
-            "required_properties": [],
-            "optional_properties": [],
-            "relative_imports": set(),
-            "additional_properties": False,
-            **kwargs,
-        }
-        return ModelProperty(**kwargs)
-
-    return _factory
-
-
 def string_property(**kwargs) -> StringProperty:
     kwargs = {
         "name": "",
@@ -247,17 +224,17 @@ def string_property(**kwargs) -> StringProperty:
 
 
 class TestProcessProperties:
-    def test_conflicting_properties_different_types(self, model_property):
+    def test_conflicting_properties_different_types(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
         data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
         schemas = Schemas(
             models={
-                "First": model_property(
+                "First": model_property_factory(
                     optional_properties=[StringProperty(name="prop", required=True, nullable=True, default=None)]
                 ),
-                "Second": model_property(
+                "Second": model_property_factory(
                     optional_properties=[DateTimeProperty(name="prop", required=True, nullable=True, default=None)]
                 ),
             }
@@ -267,15 +244,15 @@ def test_conflicting_properties_different_types(self, model_property):
 
         assert isinstance(result, PropertyError)
 
-    def test_conflicting_properties_same_types(self, model_property):
+    def test_conflicting_properties_same_types(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
         data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
         schemas = Schemas(
             models={
-                "First": model_property(optional_properties=[string_property(default="abc")]),
-                "Second": model_property(optional_properties=[string_property()]),
+                "First": model_property_factory(optional_properties=[string_property(default="abc")]),
+                "Second": model_property_factory(optional_properties=[string_property()]),
             }
         )
 
@@ -283,7 +260,7 @@ def test_conflicting_properties_same_types(self, model_property):
 
         assert isinstance(result, PropertyError)
 
-    def test_duplicate_properties(self, model_property):
+    def test_duplicate_properties(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
@@ -291,8 +268,8 @@ def test_duplicate_properties(self, model_property):
         prop = string_property()
         schemas = Schemas(
             models={
-                "First": model_property(optional_properties=[prop]),
-                "Second": model_property(optional_properties=[prop]),
+                "First": model_property_factory(optional_properties=[prop]),
+                "Second": model_property_factory(optional_properties=[prop]),
             }
         )
 
@@ -304,17 +281,19 @@ def test_duplicate_properties(self, model_property):
     @pytest.mark.parametrize("second_nullable", [True, False])
     @pytest.mark.parametrize("first_required", [True, False])
     @pytest.mark.parametrize("second_required", [True, False])
-    def test_mixed_requirements(self, model_property, first_nullable, second_nullable, first_required, second_required):
+    def test_mixed_requirements(
+        self, model_property_factory, first_nullable, second_nullable, first_required, second_required
+    ):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
         data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
         schemas = Schemas(
             models={
-                "First": model_property(
+                "First": model_property_factory(
                     optional_properties=[string_property(required=first_required, nullable=first_nullable)]
                 ),
-                "Second": model_property(
+                "Second": model_property_factory(
                     optional_properties=[string_property(required=second_required, nullable=second_nullable)]
                 ),
             }
diff --git a/tests/test_parser/test_properties/test_schemas.py b/tests/test_parser/test_properties/test_schemas.py
new file mode 100644
index 000000000..334ce0f5f
--- /dev/null
+++ b/tests/test_parser/test_properties/test_schemas.py
@@ -0,0 +1,34 @@
+import pytest
+
+
+def test_class_from_string_default_config():
+    from openapi_python_client.parser.properties import Class
+    from openapi_python_client import Config
+
+    class_ = Class.from_string(string="#/components/schemas/PingResponse", config=Config())
+
+    assert class_.name == "PingResponse"
+    assert class_.module_name == "ping_response"
+
+
+@pytest.mark.parametrize(
+    "class_override, module_override, expected_class, expected_module",
+    (
+        (None, None, "_MyResponse", "_my_response"),
+        ("MyClass", None, "MyClass", "my_class"),
+        ("MyClass", "some_module", "MyClass", "some_module"),
+        (None, "some_module", "_MyResponse", "some_module"),
+    ),
+)
+def test_class_from_string(class_override, module_override, expected_class, expected_module):
+    from openapi_python_client.parser.properties import Class
+    from openapi_python_client.config import Config, ClassOverride
+
+    ref = "#/components/schemas/_MyResponse"
+    config = Config(
+        class_overrides={"_MyResponse": ClassOverride(class_name=class_override, module_name=module_override)}
+    )
+
+    result = Class.from_string(string=ref, config=config)
+    assert result.name == expected_class
+    assert result.module_name == expected_module
diff --git a/tests/test_parser/test_reference.py b/tests/test_parser/test_reference.py
deleted file mode 100644
index 3660a1c35..000000000
--- a/tests/test_parser/test_reference.py
+++ /dev/null
@@ -1,16 +0,0 @@
-def test_from_ref():
-    from openapi_python_client.parser.reference import Reference
-
-    r = Reference.from_ref("#/components/schemas/PingResponse")
-
-    assert r.class_name == "PingResponse"
-    assert r.module_name == "ping_response"
-
-
-def test_from_ref_class_overrides():
-    from openapi_python_client.parser.reference import Reference, class_overrides
-
-    ref = "#/components/schemas/_MyResponse"
-    class_overrides["_MyResponse"] = Reference(class_name="MyResponse", module_name="my_response")
-
-    assert Reference.from_ref(ref) == class_overrides["_MyResponse"]
diff --git a/tests/test_parser/test_responses.py b/tests/test_parser/test_responses.py
index eb20fb338..50023ffef 100644
--- a/tests/test_parser/test_responses.py
+++ b/tests/test_parser/test_responses.py
@@ -1,3 +1,5 @@
+from unittest.mock import MagicMock
+
 import openapi_python_client.schema as oai
 from openapi_python_client.parser.errors import ParseError, PropertyError
 from openapi_python_client.parser.properties import NoneProperty, Schemas, StringProperty
@@ -9,7 +11,11 @@ def test_response_from_data_no_content():
     from openapi_python_client.parser.responses import Response, response_from_data
 
     response, schemas = response_from_data(
-        status_code=200, data=oai.Response.construct(description=""), schemas=Schemas(), parent_name="parent"
+        status_code=200,
+        data=oai.Response.construct(description=""),
+        schemas=Schemas(),
+        parent_name="parent",
+        config=MagicMock(),
     )
 
     assert response == Response(
@@ -23,7 +29,9 @@ def test_response_from_data_unsupported_content_type():
     from openapi_python_client.parser.responses import response_from_data
 
     data = oai.Response.construct(description="", content={"blah": None})
-    response, schemas = response_from_data(status_code=200, data=data, schemas=Schemas(), parent_name="parent")
+    response, schemas = response_from_data(
+        status_code=200, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
+    )
 
     assert response == ParseError(data=data, detail="Unsupported content_type {'blah': None}")
 
@@ -32,7 +40,9 @@ def test_response_from_data_no_content_schema():
     from openapi_python_client.parser.responses import Response, response_from_data
 
     data = oai.Response.construct(description="", content={"application/json": oai.MediaType.construct()})
-    response, schemas = response_from_data(status_code=200, data=data, schemas=Schemas(), parent_name="parent")
+    response, schemas = response_from_data(
+        status_code=200, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
+    )
 
     assert response == Response(
         status_code=200,

From 10163b2e8b7b944352b386fadfca2f387030a715 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sat, 27 Mar 2021 17:49:00 -0600
Subject: [PATCH 05/11] test: Fix more unit tests

---
 openapi_python_client/cli.py                  |   8 +-
 tests/conftest.py                             |   2 +-
 tests/test_cli.py                             |  12 +-
 .../test_parser/test_properties/test_init.py  | 172 ++++++------------
 tests/test_parser/test_responses.py           |  12 +-
 5 files changed, 73 insertions(+), 133 deletions(-)

diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py
index fea7cc34f..953c92234 100644
--- a/openapi_python_client/cli.py
+++ b/openapi_python_client/cli.py
@@ -21,10 +21,10 @@ def _version_callback(value: bool) -> None:
         raise typer.Exit()
 
 
-def _process_config(path: Optional[pathlib.Path]) -> Optional[Config]:
+def _process_config(path: Optional[pathlib.Path]) -> Config:
 
     if not path:
-        return None
+        return Config()
 
     try:
         return Config.load_from_path(path=path)
@@ -137,7 +137,7 @@ def generate(
         typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
         raise typer.Exit(code=1)
 
-    config = Config.load_from_path(config_path) if config_path is not None else Config()
+    config = _process_config(config_path)
     errors = create_new_client(
         url=url,
         path=path,
@@ -174,7 +174,7 @@ def update(
         typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
         raise typer.Exit(code=1)
 
-    config = Config.load_from_path(config_path) if config_path is not None else Config()
+    config = _process_config(config_path)
     errors = update_existing_client(
         url=url,
         path=path,
diff --git a/tests/conftest.py b/tests/conftest.py
index d432982ae..967640ff2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -46,7 +46,7 @@ def _factory(**kwargs):
         kwargs = {
             "name": "test",
             "required": True,
-            "nullable": True,
+            "nullable": False,
             "default": None,
             "class_info": Class(name="", module_name=""),
             "values": {},
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 4954aea25..b6f893ee2 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -4,6 +4,7 @@
 import pytest
 from typer.testing import CliRunner
 
+from openapi_python_client import Config
 from openapi_python_client.parser.errors import GeneratorError, ParseError
 
 runner = CliRunner()
@@ -40,9 +41,14 @@ def test_config_arg(mocker, _create_new_client):
     )
 
     assert result.exit_code == 0
-    load_config.assert_called_once_with(Path(config_path))
+    load_config.assert_called_once_with(path=Path(config_path))
     _create_new_client.assert_called_once_with(
-        url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
+        url=None,
+        path=Path(path),
+        custom_template_path=None,
+        meta=MetaType.POETRY,
+        file_encoding="utf-8",
+        config=load_config.return_value,
     )
 
 
@@ -59,7 +65,7 @@ def test_bad_config(mocker, _create_new_client):
 
     assert result.exit_code == 2
     assert "Unable to parse config" in result.stdout
-    load_config.assert_called_once_with(Path(config_path))
+    load_config.assert_called_once_with(path=Path(config_path))
     _create_new_client.assert_not_called()
 
 
diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py
index 69a1289d1..8dd5fcd9f 100644
--- a/tests/test_parser/test_properties/test_init.py
+++ b/tests/test_parser/test_properties/test_init.py
@@ -1,6 +1,7 @@
 import pytest
 
 import openapi_python_client.schema as oai
+from openapi_python_client import Config
 from openapi_python_client.parser.errors import PropertyError, ValidationError
 from openapi_python_client.parser.properties import BooleanProperty, FloatProperty, IntProperty
 
@@ -398,110 +399,35 @@ def test_get_imports(self, mocker):
 
 
 class TestEnumProperty:
-    def test_get_type_string(self, mocker):
-        fake_reference = mocker.MagicMock(class_name="MyTestEnum")
-
-        from openapi_python_client.parser import properties
-
-        p = properties.EnumProperty(
-            name="test",
-            required=True,
-            default=None,
-            values={},
-            nullable=False,
-            reference=fake_reference,
-            value_type=str,
-        )
+    @pytest.mark.parametrize(
+        "required, nullable, expected",
+        (
+            (False, False, "Union[Unset, {}]"),
+            (True, False, "{}"),
+            (False, True, "Union[Unset, None, {}]"),
+            (True, True, "Optional[{}]"),
+        ),
+    )
+    def test_get_type_string(self, mocker, enum_property_factory, required, nullable, expected):
+        fake_class = mocker.MagicMock()
+        fake_class.name = "MyTestEnum"
 
-        base_type_string = f"MyTestEnum"
+        p = enum_property_factory(class_info=fake_class, required=required, nullable=nullable)
 
-        assert p.get_type_string() == base_type_string
-        assert p.get_type_string(json=True) == "str"
+        assert p.get_type_string() == expected.format(fake_class.name)
+        assert p.get_type_string(no_optional=True) == fake_class.name
+        assert p.get_type_string(json=True) == expected.format("str")
 
-        p = properties.EnumProperty(
-            name="test",
-            required=True,
-            default=None,
-            values={},
-            nullable=True,
-            reference=fake_reference,
-            value_type=str,
-        )
-        assert p.get_type_string() == f"Optional[{base_type_string}]"
-        assert p.get_type_string(no_optional=True) == base_type_string
-
-        p = properties.EnumProperty(
-            name="test",
-            required=False,
-            default=None,
-            values={},
-            nullable=True,
-            reference=fake_reference,
-            value_type=str,
-        )
-        assert p.get_type_string() == f"Union[Unset, None, {base_type_string}]"
-        assert p.get_type_string(no_optional=True) == base_type_string
-
-        p = properties.EnumProperty(
-            name="test",
-            required=False,
-            default=None,
-            values={},
-            nullable=False,
-            reference=fake_reference,
-            value_type=str,
-        )
-        assert p.get_type_string() == f"Union[Unset, {base_type_string}]"
-        assert p.get_type_string(no_optional=True) == base_type_string
-
-    def test_get_imports(self, mocker):
-        fake_reference = mocker.MagicMock(class_name="MyTestEnum", module_name="my_test_enum")
+    def test_get_imports(self, mocker, enum_property_factory):
+        fake_class = mocker.MagicMock(module_name="my_test_enum")
+        fake_class.name = "MyTestEnum"
         prefix = "..."
 
-        from openapi_python_client.parser import properties
-
-        enum_property = properties.EnumProperty(
-            name="test",
-            required=True,
-            default=None,
-            values={},
-            nullable=False,
-            reference=fake_reference,
-            value_type=str,
-        )
-
-        assert enum_property.get_imports(prefix=prefix) == {
-            f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}",
-        }
-
-        enum_property = properties.EnumProperty(
-            name="test",
-            required=False,
-            default=None,
-            values={},
-            nullable=False,
-            reference=fake_reference,
-            value_type=str,
-        )
-        assert enum_property.get_imports(prefix=prefix) == {
-            f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}",
-            "from typing import Union",
-            "from ...types import UNSET, Unset",
-        }
+        enum_property = enum_property_factory(class_info=fake_class, required=False)
 
-        enum_property = properties.EnumProperty(
-            name="test",
-            required=False,
-            default=None,
-            values={},
-            nullable=True,
-            reference=fake_reference,
-            value_type=str,
-        )
         assert enum_property.get_imports(prefix=prefix) == {
-            f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}",
-            "from typing import Union",
-            "from typing import Optional",
+            f"from {prefix}models.{fake_class.module_name} import {fake_class.name}",
+            "from typing import Union",  # Makes sure unset is handled via base class
             "from ...types import UNSET, Unset",
         }
 
@@ -534,7 +460,7 @@ def test_values_from_list_duplicate(self):
 
 class TestPropertyFromData:
     def test_property_from_data_str_enum(self, mocker):
-        from openapi_python_client.parser.properties import EnumProperty, Reference
+        from openapi_python_client.parser.properties import EnumProperty, Class
         from openapi_python_client.schema import Schema
 
         data = Schema(title="AnEnum", enum=["A", "B", "C"], nullable=False, default="B")
@@ -543,10 +469,10 @@ def test_property_from_data_str_enum(self, mocker):
 
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
-        schemas = Schemas(enums={"AnEnum": mocker.MagicMock()})
+        schemas = Schemas(classes_by_name={"AnEnum": mocker.MagicMock()})
 
         prop, new_schemas = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config()
         )
 
         assert prop == EnumProperty(
@@ -554,18 +480,18 @@ def test_property_from_data_str_enum(self, mocker):
             required=True,
             nullable=False,
             values={"A": "A", "B": "B", "C": "C"},
-            reference=Reference(class_name="ParentAnEnum", module_name="parent_an_enum"),
+            class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"),
             value_type=str,
             default="ParentAnEnum.B",
         )
         assert schemas != new_schemas, "Provided Schemas was mutated"
-        assert new_schemas.enums == {
-            "AnEnum": schemas.enums["AnEnum"],
+        assert new_schemas.classes_by_name == {
+            "AnEnum": schemas.classes_by_name["AnEnum"],
             "ParentAnEnum": prop,
         }
 
     def test_property_from_data_int_enum(self, mocker):
-        from openapi_python_client.parser.properties import EnumProperty, Reference
+        from openapi_python_client.parser.properties import EnumProperty, Class
         from openapi_python_client.schema import Schema
 
         data = Schema.construct(title="anEnum", enum=[1, 2, 3], nullable=False, default=3)
@@ -574,10 +500,10 @@ def test_property_from_data_int_enum(self, mocker):
 
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
-        schemas = Schemas(enums={"AnEnum": mocker.MagicMock()})
+        schemas = Schemas(classes_by_name={"AnEnum": mocker.MagicMock()})
 
         prop, new_schemas = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config()
         )
 
         assert prop == EnumProperty(
@@ -585,21 +511,21 @@ def test_property_from_data_int_enum(self, mocker):
             required=True,
             nullable=False,
             values={"VALUE_1": 1, "VALUE_2": 2, "VALUE_3": 3},
-            reference=Reference(class_name="ParentAnEnum", module_name="parent_an_enum"),
+            class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"),
             value_type=int,
             default="ParentAnEnum.VALUE_3",
         )
         assert schemas != new_schemas, "Provided Schemas was mutated"
-        assert new_schemas.enums == {
-            "AnEnum": schemas.enums["AnEnum"],
+        assert new_schemas.classes_by_name == {
+            "AnEnum": schemas.classes_by_name["AnEnum"],
             "ParentAnEnum": prop,
         }
 
     def test_property_from_data_ref_enum(self):
-        from openapi_python_client.parser.properties import EnumProperty, Reference, Schemas, property_from_data
+        from openapi_python_client.parser.properties import EnumProperty, Class, Schemas, property_from_data
 
         name = "some_enum"
-        data = oai.Reference.construct(ref="MyEnum")
+        data = oai.Reference.construct(ref="#/components/schemas/MyEnum")
         existing_enum = EnumProperty(
             name="an_enum",
             required=True,
@@ -607,11 +533,13 @@ def test_property_from_data_ref_enum(self):
             default=None,
             values={"A": "a"},
             value_type=str,
-            reference=Reference(class_name="MyEnum", module_name="my_enum"),
+            class_info=Class(name="MyEnum", module_name="my_enum"),
         )
-        schemas = Schemas(enums={"MyEnum": existing_enum})
+        schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum})
 
-        prop, new_schemas = property_from_data(name=name, required=False, data=data, schemas=schemas, parent_name="")
+        prop, new_schemas = property_from_data(
+            name=name, required=False, data=data, schemas=schemas, parent_name="", config=Config()
+        )
 
         assert prop == EnumProperty(
             name="some_enum",
@@ -620,39 +548,41 @@ def test_property_from_data_ref_enum(self):
             default=None,
             values={"A": "a"},
             value_type=str,
-            reference=Reference(class_name="MyEnum", module_name="my_enum"),
+            class_info=Class(name="MyEnum", module_name="my_enum"),
         )
         assert schemas == new_schemas
 
     def test_property_from_data_ref_model(self):
-        from openapi_python_client.parser.properties import ModelProperty, Reference, Schemas, property_from_data
+        from openapi_python_client.parser.properties import ModelProperty, Class, Schemas, property_from_data
 
         name = "new_name"
         required = False
         class_name = "MyModel"
-        data = oai.Reference.construct(ref=class_name)
+        data = oai.Reference.construct(ref=f"#/components/schemas/{class_name}")
         existing_model = ModelProperty(
             name="old_name",
             required=True,
             nullable=False,
             default=None,
-            reference=Reference(class_name=class_name, module_name="my_model"),
+            class_info=Class(name=class_name, module_name="my_model"),
             required_properties=[],
             optional_properties=[],
             description="",
             relative_imports=set(),
             additional_properties=False,
         )
-        schemas = Schemas(models={class_name: existing_model})
+        schemas = Schemas(classes_by_reference={f"/components/schemas/{class_name}": existing_model})
 
-        prop, new_schemas = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="")
+        prop, new_schemas = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config()
+        )
 
         assert prop == ModelProperty(
             name=name,
             required=required,
             nullable=False,
             default=None,
-            reference=Reference(class_name=class_name, module_name="my_model"),
+            class_info=Class(name=class_name, module_name="my_model"),
             required_properties=[],
             optional_properties=[],
             description="",
diff --git a/tests/test_parser/test_responses.py b/tests/test_parser/test_responses.py
index 50023ffef..025172388 100644
--- a/tests/test_parser/test_responses.py
+++ b/tests/test_parser/test_responses.py
@@ -58,13 +58,15 @@ def test_response_from_data_property_error(mocker):
     data = oai.Response.construct(
         description="", content={"application/json": oai.MediaType.construct(media_type_schema="something")}
     )
+    config = MagicMock()
+
     response, schemas = responses.response_from_data(
-        status_code=400, data=data, schemas=Schemas(), parent_name="parent"
+        status_code=400, data=data, schemas=Schemas(), parent_name="parent", config=config
     )
 
     assert response == PropertyError()
     property_from_data.assert_called_once_with(
-        name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent"
+        name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent", config=config
     )
 
 
@@ -76,8 +78,10 @@ def test_response_from_data_property(mocker):
     data = oai.Response.construct(
         description="", content={"application/json": oai.MediaType.construct(media_type_schema="something")}
     )
+    config = MagicMock()
+
     response, schemas = responses.response_from_data(
-        status_code=400, data=data, schemas=Schemas(), parent_name="parent"
+        status_code=400, data=data, schemas=Schemas(), parent_name="parent", config=config
     )
 
     assert response == responses.Response(
@@ -86,5 +90,5 @@ def test_response_from_data_property(mocker):
         source="response.json()",
     )
     property_from_data.assert_called_once_with(
-        name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent"
+        name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent", config=config
     )

From af8d0ea732590bd6138435033573940e252557d9 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sun, 4 Apr 2021 10:36:57 -0600
Subject: [PATCH 06/11] test: Finish fixing broken unit tests

---
 .../parser/properties/__init__.py             |  34 ++-
 .../parser/properties/schemas.py              |   4 +-
 poetry.lock                                   |  35 +--
 tests/test_cli.py                             |  14 +-
 tests/test_parser/test_openapi.py             |   2 +-
 .../test_parser/test_properties/test_init.py  | 203 +++++++++++-------
 .../test_properties/test_model_property.py    |  91 ++++----
 .../test_properties/test_schemas.py           |   4 +-
 8 files changed, 203 insertions(+), 184 deletions(-)

diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index d9a6ddea0..505284f39 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -483,6 +483,29 @@ def property_from_data(
     parent_name: str,
     config: Config,
 ) -> Tuple[Union[Property, PropertyError], Schemas]:
+    """
+    Build a Property from an OpenAPI schema or reference. This Property represents a single input or output for a
+    generated API operation.
+
+    Args:
+        name: The name of the property, defined in OpenAPI as the key pointing at the schema. This is the parameter used
+            to send this data to an API or that the API will respond with. This will be used to generate a `python_name`
+            which is the name of the variable/attribute in generated Python.
+        required: Whether or not this property is required in whatever source is creating it. OpenAPI defines this by
+            including the property's name in the `required` list. If the property is required, `Unset` will not be
+            included in the generated code's available types.
+        data: The OpenAPI schema or reference that defines the details of this property (e.g. type, sub-properties).
+        schemas: A structure containing all of the parsed schemas so far that will become generated classes. This is
+            used to resolve references and to ensure that conflicting class names are not generated.
+        parent_name: The name of the thing above this property, prepended to generated class names to reduce the chance
+            of duplication.
+        config: Contains the parsed config that the user provided to tweak generation settings. Needed to apply class
+            name overrides for generated classes.
+
+    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).
+    """
     try:
         return _property_from_data(
             name=name, required=required, data=data, schemas=schemas, parent_name=parent_name, config=config
@@ -496,12 +519,12 @@ def build_schemas(
 ) -> Schemas:
     """ Get a list of Schemas from an OpenAPI dict """
     to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items()
-    processing = True
+    still_making_progress = True
     errors: List[PropertyError] = []
 
     # References could have forward References so keep going as long as we are making progress
-    while processing:
-        processing = False
+    while still_making_progress:
+        still_making_progress = False
         errors = []
         next_round = []
         # Only accumulate errors from the last round, since we might fix some along the way
@@ -511,8 +534,7 @@ def build_schemas(
                 continue
             ref_path = parse_reference_path(f"#/components/schemas/{name}")
             if isinstance(ref_path, ParseError):
-                next_round.append((name, data))
-                errors.append(PropertyError(detail=ref_path.detail, data=data))
+                schemas.errors.append(PropertyError(detail=ref_path.detail, data=data))
                 continue
             schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config)
             if isinstance(schemas_or_err, PropertyError):
@@ -520,7 +542,7 @@ def build_schemas(
                 errors.append(schemas_or_err)
                 continue
             schemas = schemas_or_err
-            processing = True  # We made some progress this round, do another after it's done
+            still_making_progress = True
         to_process = next_round
 
     schemas.errors.extend(errors)
diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index 8f4b43129..315b9fb67 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -4,7 +4,6 @@
 from urllib.parse import urlparse
 
 import attr
-from pydantic import BaseModel
 
 from ... import Config
 from ... import schema as oai
@@ -30,7 +29,8 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]
     return cast(_ReferencePath, parsed.fragment)
 
 
-class Class(BaseModel):
+@attr.s(auto_attribs=True, frozen=True)
+class Class:
     """ Info about a generated class which will be in models """
 
     name: _ClassName
diff --git a/poetry.lock b/poetry.lock
index 041aa5585..4dac795bb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -687,6 +687,7 @@ autoflake = [
     {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"},
 ]
 black = [
+    {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"},
     {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
 ]
 certifi = [
@@ -821,39 +822,20 @@ markupsafe = [
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
     {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
-    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
     {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
-    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
     {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
-    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
     {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
-    {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
     {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
 ]
 mccabe = [
@@ -1028,12 +1010,6 @@ regex = [
     {file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"},
     {file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"},
     {file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"},
-    {file = "regex-2020.9.27-cp39-cp39-manylinux1_i686.whl", hash = "sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81"},
-    {file = "regex-2020.9.27-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650"},
-    {file = "regex-2020.9.27-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67"},
-    {file = "regex-2020.9.27-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad"},
-    {file = "regex-2020.9.27-cp39-cp39-win32.whl", hash = "sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302"},
-    {file = "regex-2020.9.27-cp39-cp39-win_amd64.whl", hash = "sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7"},
     {file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"},
 ]
 requests = [
@@ -1079,28 +1055,19 @@ typed-ast = [
     {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
-    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"},
     {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
     {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
     {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
-    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"},
     {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
     {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
     {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
-    {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"},
     {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
     {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
     {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
-    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
-    {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
-    {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
-    {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
     {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
 ]
 typer = [
diff --git a/tests/test_cli.py b/tests/test_cli.py
index b6f893ee2..c551e80dd 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -88,7 +88,7 @@ def test_generate_url_and_path(self, _create_new_client):
 
     def test_generate_url(self, _create_new_client):
         url = "cool.url"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["generate", f"--url={url}"])
 
@@ -99,7 +99,7 @@ def test_generate_url(self, _create_new_client):
 
     def test_generate_path(self, _create_new_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["generate", f"--path={path}"])
 
@@ -115,7 +115,7 @@ def test_generate_path(self, _create_new_client):
 
     def test_generate_meta(self, _create_new_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"])
 
@@ -132,7 +132,7 @@ def test_generate_meta(self, _create_new_client):
     def test_generate_encoding(self, _create_new_client):
         path = "cool/path"
         file_encoding = "utf-8"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["generate", f"--path={path}", f"--file-encoding={file_encoding}"])
 
@@ -219,7 +219,7 @@ def test_update_url_and_path(self, _update_existing_client):
 
     def test_update_url(self, _update_existing_client):
         url = "cool.url"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["update", f"--url={url}"])
 
@@ -230,7 +230,7 @@ def test_update_url(self, _update_existing_client):
 
     def test_update_path(self, _update_existing_client):
         path = "cool/path"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["update", f"--path={path}"])
 
@@ -247,7 +247,7 @@ def test_update_path(self, _update_existing_client):
     def test_update_encoding(self, _update_existing_client):
         path = "cool/path"
         file_encoding = "utf-8"
-        from openapi_python_client.cli import MetaType, app, Config
+        from openapi_python_client.cli import Config, MetaType, app
 
         result = runner.invoke(app, ["update", f"--path={path}", f"--file-encoding={file_encoding}"])
 
diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py
index b9124914e..e5a0e8e30 100644
--- a/tests/test_parser/test_openapi.py
+++ b/tests/test_parser/test_openapi.py
@@ -255,7 +255,7 @@ def test_add_body_bad_data(self, mocker):
         )
 
     def test_add_body_happy(self, mocker):
-        from openapi_python_client.parser.openapi import Endpoint, Class
+        from openapi_python_client.parser.openapi import Class, Endpoint
         from openapi_python_client.parser.properties import Property
 
         request_body = mocker.MagicMock()
diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py
index 8dd5fcd9f..3e9c8cb54 100644
--- a/tests/test_parser/test_properties/test_init.py
+++ b/tests/test_parser/test_properties/test_init.py
@@ -1,3 +1,5 @@
+from unittest.mock import MagicMock, call
+
 import pytest
 
 import openapi_python_client.schema as oai
@@ -460,7 +462,7 @@ def test_values_from_list_duplicate(self):
 
 class TestPropertyFromData:
     def test_property_from_data_str_enum(self, mocker):
-        from openapi_python_client.parser.properties import EnumProperty, Class
+        from openapi_python_client.parser.properties import Class, EnumProperty
         from openapi_python_client.schema import Schema
 
         data = Schema(title="AnEnum", enum=["A", "B", "C"], nullable=False, default="B")
@@ -491,7 +493,7 @@ def test_property_from_data_str_enum(self, mocker):
         }
 
     def test_property_from_data_int_enum(self, mocker):
-        from openapi_python_client.parser.properties import EnumProperty, Class
+        from openapi_python_client.parser.properties import Class, EnumProperty
         from openapi_python_client.schema import Schema
 
         data = Schema.construct(title="anEnum", enum=[1, 2, 3], nullable=False, default=3)
@@ -522,7 +524,7 @@ def test_property_from_data_int_enum(self, mocker):
         }
 
     def test_property_from_data_ref_enum(self):
-        from openapi_python_client.parser.properties import EnumProperty, Class, Schemas, property_from_data
+        from openapi_python_client.parser.properties import Class, EnumProperty, Schemas, property_from_data
 
         name = "some_enum"
         data = oai.Reference.construct(ref="#/components/schemas/MyEnum")
@@ -553,7 +555,7 @@ def test_property_from_data_ref_enum(self):
         assert schemas == new_schemas
 
     def test_property_from_data_ref_model(self):
-        from openapi_python_client.parser.properties import ModelProperty, Class, Schemas, property_from_data
+        from openapi_python_client.parser.properties import Class, ModelProperty, Schemas, property_from_data
 
         name = "new_name"
         required = False
@@ -597,15 +599,15 @@ def test_property_from_data_ref_not_found(self, mocker):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Reference.construct(ref=mocker.MagicMock())
-        from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
+        parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
 
         prop, new_schemas = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock()
         )
 
-        from_ref.assert_called_once_with(data.ref)
+        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
 
@@ -620,7 +622,7 @@ def test_property_from_data_string(self, mocker):
         schemas = Schemas()
 
         p, new_schemas = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock()
         )
 
         assert p == _string_based_property.return_value
@@ -644,7 +646,7 @@ def test_property_from_data_simple_types(self, openapi_type, prop_type, python_t
         schemas = Schemas()
 
         p, new_schemas = property_from_data(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
         )
 
         assert p == prop_type(name=name, required=required, default=python_type(data.default), nullable=False)
@@ -654,12 +656,16 @@ def test_property_from_data_simple_types(self, openapi_type, prop_type, python_t
         data.default = 0
         data.nullable = True
 
-        p, _ = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent")
+        p, _ = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
+        )
         assert p == prop_type(name=name, required=required, default=python_type(data.default), nullable=True)
 
         # Test bad default value
         data.default = "a"
-        p, _ = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent")
+        p, _ = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
+        )
         assert python_type is bool or isinstance(p, PropertyError)
 
     def test_property_from_data_array(self, mocker):
@@ -674,12 +680,15 @@ def test_property_from_data_array(self, mocker):
         build_list_property = mocker.patch(f"{MODULE_NAME}.build_list_property")
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
+        config = MagicMock()
 
-        response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent")
+        response = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+        )
 
         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"
+            data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config
         )
 
     def test_property_from_data_object(self, mocker):
@@ -693,12 +702,15 @@ def test_property_from_data_object(self, mocker):
         build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property")
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
+        config = MagicMock()
 
-        response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent")
+        response = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+        )
 
         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"
+            data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config
         )
 
     def test_property_from_data_union(self, mocker):
@@ -715,12 +727,15 @@ def test_property_from_data_union(self, mocker):
         build_union_property = mocker.patch(f"{MODULE_NAME}.build_union_property")
         mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
         schemas = Schemas()
+        config = MagicMock()
 
-        response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent")
+        response = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
+        )
 
         assert response == build_union_property.return_value
         build_union_property.assert_called_once_with(
-            data=data, name=name, required=required, schemas=schemas, parent_name="parent"
+            data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config
         )
 
     def test_property_from_data_unsupported_type(self, mocker):
@@ -731,7 +746,9 @@ def test_property_from_data_unsupported_type(self, mocker):
         from openapi_python_client.parser.errors import PropertyError
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
-        assert property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent") == (
+        assert property_from_data(
+            name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
+        ) == (
             PropertyError(data=data, detail=f"unknown type {data.type}"),
             Schemas(),
         )
@@ -743,7 +760,7 @@ def test_property_from_data_no_valid_props_in_data(self):
         data = oai.Schema()
 
         prop, new_schemas = property_from_data(
-            name="blah", required=True, data=data, schemas=schemas, parent_name="parent"
+            name="blah", required=True, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
         )
 
         assert prop == NoneProperty(name="blah", required=True, nullable=False, default=None)
@@ -758,7 +775,7 @@ def test_property_from_data_validation_error(self, mocker):
 
         data = oai.Schema()
         err, new_schemas = property_from_data(
-            name="blah", required=True, data=data, schemas=schemas, parent_name="parent"
+            name="blah", required=True, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
         )
         assert err == PropertyError(detail="Failed to validate default value", data=data)
         assert new_schemas == schemas
@@ -775,7 +792,7 @@ 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"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock()
         )
 
         assert p == PropertyError(data=data, detail="type array must have items defined")
@@ -796,16 +813,17 @@ def test_build_list_property_invalid_items(self, mocker):
         property_from_data = mocker.patch.object(
             properties, "property_from_data", return_value=(properties.PropertyError(data="blah"), second_schemas)
         )
+        config = MagicMock()
 
         p, new_schemas = properties.build_list_property(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
         )
 
         assert p == PropertyError(data="blah", detail=f"invalid data in items of array {name}")
         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"
+            name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent", config=config
         )
 
     def test_build_list_property(self, mocker):
@@ -824,9 +842,10 @@ def test_build_list_property(self, mocker):
         )
         mocker.patch("openapi_python_client.utils.snake_case", return_value=name)
         mocker.patch("openapi_python_client.utils.to_valid_python_identifier", return_value=name)
+        config = MagicMock()
 
         p, new_schemas = properties.build_list_property(
-            name=name, required=required, data=data, schemas=schemas, parent_name="parent"
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config
         )
 
         assert isinstance(p, properties.ListProperty)
@@ -834,7 +853,7 @@ def test_build_list_property(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"
+            name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent", config=config
         )
 
 
@@ -855,7 +874,9 @@ def test_property_from_data_union(self, mocker):
 
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
-        p, s = property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent")
+        p, s = property_from_data(
+            name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
+        )
 
         FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=False)
         IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=False)
@@ -877,7 +898,9 @@ def test_property_from_data_union_bad_type(self, mocker):
 
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
-        p, s = property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent")
+        p, s = property_from_data(
+            name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock()
+        )
 
         assert p == PropertyError(detail=f"Invalid property in union {name}", data=oai.Schema(type="garbage"))
 
@@ -966,72 +989,86 @@ def test__string_based_property_unsupported_format(self, mocker):
         assert p == StringProperty(name=name, required=required, nullable=True, default=None)
 
 
-def test_build_schemas(mocker):
-    build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property")
-    in_data = {"1": mocker.MagicMock(enum=None), "2": mocker.MagicMock(enum=None), "3": mocker.MagicMock(enum=None)}
-    model_1 = mocker.MagicMock()
-    schemas_1 = mocker.MagicMock()
-    model_2 = mocker.MagicMock()
-    schemas_2 = mocker.MagicMock(errors=[])
-    error = PropertyError()
-    schemas_3 = mocker.MagicMock()
-
-    # This loops through one for each, then again to retry the error
-    build_model_property.side_effect = [
-        (model_1, schemas_1),
-        (model_2, schemas_2),
-        (error, schemas_3),
-        (error, schemas_3),
-    ]
-
-    from openapi_python_client.parser.properties import Schemas, build_schemas
-
-    result = build_schemas(components=in_data)
-
-    build_model_property.assert_has_calls(
-        [
-            mocker.call(data=in_data["1"], name="1", schemas=Schemas(), required=True, parent_name=None),
-            mocker.call(data=in_data["2"], name="2", schemas=schemas_1, required=True, parent_name=None),
-            mocker.call(data=in_data["3"], name="3", schemas=schemas_2, required=True, parent_name=None),
-            mocker.call(data=in_data["3"], name="3", schemas=schemas_2, required=True, parent_name=None),
-        ]
-    )
-    # schemas_3 was the last to come back from build_model_property, but it should be ignored because it's an error
-    assert result == schemas_2
-    assert result.errors == [error]
-
-
-def test_build_parse_error_on_reference():
-    from openapi_python_client.parser.openapi import build_schemas
-
-    ref_schema = oai.Reference.construct()
-    in_data = {"1": ref_schema}
-    result = build_schemas(components=in_data)
-    assert result.errors[0] == PropertyError(data=ref_schema, detail="Reference schemas are not supported.")
-
+class TestBuildSchemas:
+    def test_skips_references_and_keeps_going(self, mocker):
+        from openapi_python_client.parser.properties import Schemas, build_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()
+
+        result = build_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(
+            ref_path=parse_reference_path.return_value,
+            config=config,
+            data=components["a_schema"],
+            schemas=Schemas(
+                errors=[PropertyError(detail="Reference schemas are not supported.", data=components["a_ref"])]
+            ),
+        )
+        assert result == update_schemas_with_data.return_value
 
-def test_build_enums(mocker):
-    from openapi_python_client.parser.openapi import build_schemas
+    def test_records_bad_uris_and_keeps_going(self, mocker):
+        from openapi_python_client.parser.properties import Schemas, build_schemas
+        from openapi_python_client.schema import Schema
 
-    build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property")
-    schemas = mocker.MagicMock()
-    build_enum_property = mocker.patch(f"{MODULE_NAME}.build_enum_property", return_value=(mocker.MagicMock(), schemas))
-    in_data = {"1": mocker.MagicMock(enum=["val1", "val2", "val3"])}
+        components = {"first": Schema.construct(), "second": 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", side_effect=[PropertyError(detail="some details"), "a_path"]
+        )
+        config = Config()
+
+        result = build_schemas(components=components, schemas=Schemas(), config=config)
+        parse_reference_path.assert_has_calls(
+            [
+                call("#/components/schemas/first"),
+                call("#/components/schemas/second"),
+            ]
+        )
+        update_schemas_with_data.assert_called_once_with(
+            ref_path="a_path",
+            config=config,
+            data=components["second"],
+            schemas=Schemas(errors=[PropertyError(detail="some details", data=components["first"])]),
+        )
+        assert result == update_schemas_with_data.return_value
 
-    build_schemas(components=in_data)
+    def test_retries_failing_properties_while_making_progress(self, mocker):
+        from openapi_python_client.parser.properties import Schemas, build_schemas
+        from openapi_python_client.schema import Schema
 
-    build_enum_property.assert_called()
-    build_model_property.assert_not_called()
+        components = {"first": Schema.construct(), "second": Schema.construct()}
+        update_schemas_with_data = mocker.patch(
+            f"{MODULE_NAME}.update_schemas_with_data", side_effect=[PropertyError(), Schemas(), PropertyError()]
+        )
+        parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path")
+        config = Config()
+
+        result = build_schemas(components=components, schemas=Schemas(), config=config)
+        parse_reference_path.assert_has_calls(
+            [
+                call("#/components/schemas/first"),
+                call("#/components/schemas/second"),
+                call("#/components/schemas/first"),
+            ]
+        )
+        assert update_schemas_with_data.call_count == 3
+        assert result.errors == [PropertyError()]
 
 
 def test_build_enum_property_conflict(mocker):
     from openapi_python_client.parser.properties import Schemas, build_enum_property
 
     data = oai.Schema()
-    schemas = Schemas(enums={"Existing": mocker.MagicMock()})
+    schemas = Schemas(classes_by_name={"Existing": mocker.MagicMock()})
 
     err, schemas = build_enum_property(
-        data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None
+        data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None, config=Config()
     )
 
     assert schemas == schemas
@@ -1045,7 +1082,7 @@ def test_build_enum_property_no_values():
     schemas = Schemas()
 
     err, schemas = build_enum_property(
-        data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None
+        data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None, config=Config()
     )
 
     assert schemas == schemas
@@ -1059,7 +1096,7 @@ def test_build_enum_property_bad_default():
     schemas = Schemas()
 
     err, schemas = build_enum_property(
-        data=data, name="Existing", required=True, schemas=schemas, enum=["A"], parent_name=None
+        data=data, name="Existing", required=True, schemas=schemas, enum=["A"], parent_name=None, config=Config()
     )
 
     assert schemas == schemas
diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py
index 2d6076be6..7db77309a 100644
--- a/tests/test_parser/test_properties/test_model_property.py
+++ b/tests/test_parser/test_properties/test_model_property.py
@@ -1,8 +1,10 @@
 from typing import Callable
+from unittest.mock import MagicMock
 
 import pytest
 
 import openapi_python_client.schema as oai
+from openapi_python_client import Config
 from openapi_python_client.parser.errors import PropertyError
 from openapi_python_client.parser.properties import DateTimeProperty, ModelProperty, StringProperty
 
@@ -94,11 +96,7 @@ 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",
+            data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=MagicMock()
         )
 
         assert model.additional_properties == expected_additional_properties
@@ -116,21 +114,20 @@ def test_happy_path(self):
             description="A class called MyModel",
             nullable=False,
         )
-        schemas = Schemas(models={"OtherModel": None})
+        schemas = Schemas(classes_by_reference={"OtherModel": None}, classes_by_name={"OtherModel": None})
 
         model, new_schemas = build_model_property(
-            data=data,
-            name="prop",
-            schemas=schemas,
-            required=True,
-            parent_name="parent",
+            data=data, name="prop", schemas=schemas, required=True, parent_name="parent", config=Config()
         )
 
         assert new_schemas != schemas
-        assert new_schemas.models == {
+        assert new_schemas.classes_by_name == {
             "OtherModel": None,
             "ParentMyModel": model,
         }
+        assert new_schemas.classes_by_reference == {
+            "OtherModel": None,
+        }
         assert model == ModelProperty(
             name="prop",
             required=True,
@@ -154,14 +151,10 @@ def test_model_name_conflict(self):
         from openapi_python_client.parser.properties import Schemas, build_model_property
 
         data = oai.Schema.construct()
-        schemas = Schemas(models={"OtherModel": None})
+        schemas = Schemas(classes_by_name={"OtherModel": None})
 
         err, new_schemas = build_model_property(
-            data=data,
-            name="OtherModel",
-            schemas=schemas,
-            required=True,
-            parent_name=None,
+            data=data, name="OtherModel", schemas=schemas, required=True, parent_name=None, config=Config()
         )
 
         assert new_schemas == schemas
@@ -178,11 +171,7 @@ def test_bad_props_return_error(self):
         schemas = Schemas()
 
         err, new_schemas = build_model_property(
-            data=data,
-            name="prop",
-            schemas=schemas,
-            required=True,
-            parent_name=None,
+            data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=MagicMock()
         )
 
         assert new_schemas == schemas
@@ -201,11 +190,7 @@ def test_bad_additional_props_return_error(self):
         schemas = Schemas()
 
         err, new_schemas = build_model_property(
-            data=data,
-            name="prop",
-            schemas=schemas,
-            required=True,
-            parent_name=None,
+            data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=MagicMock()
         )
 
         assert new_schemas == schemas
@@ -228,19 +213,21 @@ def test_conflicting_properties_different_types(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
-        data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
+        data = oai.Schema.construct(
+            allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")]
+        )
         schemas = Schemas(
-            models={
-                "First": model_property_factory(
+            classes_by_reference={
+                "/First": model_property_factory(
                     optional_properties=[StringProperty(name="prop", required=True, nullable=True, default=None)]
                 ),
-                "Second": model_property_factory(
+                "/Second": model_property_factory(
                     optional_properties=[DateTimeProperty(name="prop", required=True, nullable=True, default=None)]
                 ),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="")
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
 
         assert isinstance(result, PropertyError)
 
@@ -248,15 +235,17 @@ def test_conflicting_properties_same_types(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
-        data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
+        data = oai.Schema.construct(
+            allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")]
+        )
         schemas = Schemas(
-            models={
-                "First": model_property_factory(optional_properties=[string_property(default="abc")]),
-                "Second": model_property_factory(optional_properties=[string_property()]),
+            classes_by_reference={
+                "/First": model_property_factory(optional_properties=[string_property(default="abc")]),
+                "/Second": model_property_factory(optional_properties=[string_property()]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="")
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
 
         assert isinstance(result, PropertyError)
 
@@ -264,16 +253,18 @@ def test_duplicate_properties(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
-        data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
+        data = oai.Schema.construct(
+            allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")]
+        )
         prop = string_property()
         schemas = Schemas(
-            models={
-                "First": model_property_factory(optional_properties=[prop]),
-                "Second": model_property_factory(optional_properties=[prop]),
+            classes_by_reference={
+                "/First": model_property_factory(optional_properties=[prop]),
+                "/Second": model_property_factory(optional_properties=[prop]),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="")
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
 
         assert result.optional_props == [prop], "There should only be one copy of duplicate properties"
 
@@ -287,19 +278,21 @@ def test_mixed_requirements(
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties
 
-        data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")])
+        data = oai.Schema.construct(
+            allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")]
+        )
         schemas = Schemas(
-            models={
-                "First": model_property_factory(
+            classes_by_reference={
+                "/First": model_property_factory(
                     optional_properties=[string_property(required=first_required, nullable=first_nullable)]
                 ),
-                "Second": model_property_factory(
+                "/Second": model_property_factory(
                     optional_properties=[string_property(required=second_required, nullable=second_nullable)]
                 ),
             }
         )
 
-        result = _process_properties(data=data, schemas=schemas, class_name="")
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock())
 
         nullable = first_nullable and second_nullable
         required = first_required or second_required
@@ -330,7 +323,7 @@ def test_direct_properties_non_ref(self):
         )
         schemas = Schemas()
 
-        result = _process_properties(data=data, schemas=schemas, class_name="")
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock())
 
         assert result.optional_props == [string_property(name="second", required=False, nullable=False)]
         assert result.required_props == [string_property(name="first", required=True, nullable=False)]
diff --git a/tests/test_parser/test_properties/test_schemas.py b/tests/test_parser/test_properties/test_schemas.py
index 334ce0f5f..7d961f802 100644
--- a/tests/test_parser/test_properties/test_schemas.py
+++ b/tests/test_parser/test_properties/test_schemas.py
@@ -2,8 +2,8 @@
 
 
 def test_class_from_string_default_config():
-    from openapi_python_client.parser.properties import Class
     from openapi_python_client import Config
+    from openapi_python_client.parser.properties import Class
 
     class_ = Class.from_string(string="#/components/schemas/PingResponse", config=Config())
 
@@ -21,8 +21,8 @@ def test_class_from_string_default_config():
     ),
 )
 def test_class_from_string(class_override, module_override, expected_class, expected_module):
+    from openapi_python_client.config import ClassOverride, Config
     from openapi_python_client.parser.properties import Class
-    from openapi_python_client.config import Config, ClassOverride
 
     ref = "#/components/schemas/_MyResponse"
     config = Config(

From 503a7a68a4c0b89009517f9215e8c8b82852b5d5 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sun, 4 Apr 2021 10:52:25 -0600
Subject: [PATCH 07/11] test: Fix errors found by E2E test

---
 .../api/tests/defaults_tests_defaults_post.py | 124 +++++++++-------
 .../api/tests/get_user_list.py                |  31 ++--
 .../api/tests/int_enum_tests_int_enum_post.py |  28 ++--
 .../tests/json_body_tests_json_body_post.py   |  28 ++--
 ...tional_value_tests_optional_query_param.py |  15 +-
 .../tests/upload_file_tests_upload_post.py    |  37 ++++-
 .../my_test_api_client/models/a_model.py      | 138 +++++++++++-------
 .../models/a_model_model.py                   |  53 ++++++-
 .../models/a_model_not_required_model.py      |  53 ++++++-
 .../a_model_not_required_nullable_model.py    |  53 ++++++-
 .../models/a_model_nullable_model.py          |  53 ++++++-
 .../models/http_validation_error.py           |   9 +-
 .../models/model_from_all_of.py               |  21 ++-
 .../model_with_additional_properties_refed.py |  20 ++-
 .../models/model_with_property_ref.py         |  12 +-
 .../models/model_with_union_property.py       |  34 +++--
 .../openapi_schema_pydantic/reference.py      |   4 +-
 .../templates/endpoint_macros.py.jinja        |   2 +-
 18 files changed, 511 insertions(+), 204 deletions(-)

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 fc924f5f9..b6aaaea6e 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
@@ -5,6 +5,9 @@
 from dateutil.parser import isoparse
 
 from ...client import Client
+from ...models.an_enum import AnEnum
+from ...models.http_validation_error import HTTPValidationError
+from ...models.model_with_union_property import ModelWithUnionProperty
 from ...types import UNSET, Response, Unset
 
 
@@ -20,14 +23,14 @@ def _get_kwargs(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[None]] = UNSET,
+    list_prop: Union[Unset, List[AnEnum]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[None, Unset, float] = 0.6,
-    enum_prop: Union[Unset, None] = UNSET,
-    model_prop: Union[Unset, None] = UNSET,
-    required_model_prop: None,
-    nullable_model_prop: Union[None, Unset] = UNSET,
-    nullable_required_model_prop: None,
+    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
+    enum_prop: Union[Unset, AnEnum] = UNSET,
+    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
+    required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
+    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
 ) -> Dict[str, Any]:
     url = "{}/tests/defaults".format(client.base_url)
 
@@ -54,11 +57,11 @@ def _get_kwargs(
     if not isinstance(date_prop, Unset):
         json_date_prop = date_prop.isoformat()
 
-    json_list_prop: Union[Unset, List[None]] = UNSET
+    json_list_prop: Union[Unset, List[str]] = UNSET
     if not isinstance(list_prop, Unset):
         json_list_prop = []
         for list_prop_item_data in list_prop:
-            list_prop_item = None
+            list_prop_item = list_prop_item_data.value
 
             json_list_prop.append(list_prop_item)
 
@@ -68,34 +71,42 @@ def _get_kwargs(
     else:
         json_union_prop = union_prop
 
-    json_union_prop_with_ref: Union[None, Unset, float]
+    json_union_prop_with_ref: Union[Unset, float, str]
     if isinstance(union_prop_with_ref, Unset):
         json_union_prop_with_ref = UNSET
-    elif isinstance(union_prop_with_ref, None):
-        json_union_prop_with_ref = None
+    elif isinstance(union_prop_with_ref, AnEnum):
+        json_union_prop_with_ref = UNSET
+        if not isinstance(union_prop_with_ref, Unset):
+            json_union_prop_with_ref = union_prop_with_ref.value
 
     else:
         json_union_prop_with_ref = union_prop_with_ref
 
-    json_enum_prop = None
+    json_enum_prop: Union[Unset, str] = UNSET
+    if not isinstance(enum_prop, Unset):
+        json_enum_prop = enum_prop.value
 
-    json_model_prop = None
+    json_model_prop: Union[Unset, Dict[str, Any]] = UNSET
+    if not isinstance(model_prop, Unset):
+        json_model_prop = model_prop.to_dict()
 
-    json_required_model_prop = None
+    json_required_model_prop = required_model_prop.to_dict()
 
-    json_nullable_model_prop: Union[None, Unset]
+    json_nullable_model_prop: Union[Dict[str, Any], None, Unset]
     if isinstance(nullable_model_prop, Unset):
         json_nullable_model_prop = UNSET
     elif nullable_model_prop is None:
         json_nullable_model_prop = None
     else:
-        json_nullable_model_prop = None
+        json_nullable_model_prop = UNSET
+        if not isinstance(nullable_model_prop, Unset):
+            json_nullable_model_prop = nullable_model_prop.to_dict()
 
-    json_nullable_required_model_prop: None
+    json_nullable_required_model_prop: Union[Dict[str, Any], None]
     if nullable_required_model_prop is None:
         json_nullable_required_model_prop = None
     else:
-        json_nullable_required_model_prop = None
+        json_nullable_required_model_prop = nullable_required_model_prop.to_dict()
 
     params: Dict[str, Any] = {
         "string_prop": string_prop,
@@ -111,11 +122,12 @@ def _get_kwargs(
         "union_prop": json_union_prop,
         "union_prop_with_ref": json_union_prop_with_ref,
         "enum_prop": json_enum_prop,
-        "model_prop": json_model_prop,
-        "required_model_prop": json_required_model_prop,
         "nullable_model_prop": json_nullable_model_prop,
         "nullable_required_model_prop": json_nullable_required_model_prop,
     }
+    if not isinstance(json_model_prop, Unset):
+        params.update(json_model_prop)
+    params.update(json_required_model_prop)
     params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
 
     return {
@@ -127,19 +139,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -160,15 +172,15 @@ def sync_detailed(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[None]] = UNSET,
+    list_prop: Union[Unset, List[AnEnum]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[None, Unset, float] = 0.6,
-    enum_prop: Union[Unset, None] = UNSET,
-    model_prop: Union[Unset, None] = UNSET,
-    required_model_prop: None,
-    nullable_model_prop: Union[None, Unset] = UNSET,
-    nullable_required_model_prop: None,
-) -> Response[Union[None, None]]:
+    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
+    enum_prop: Union[Unset, AnEnum] = UNSET,
+    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
+    required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
+    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         string_prop=string_prop,
@@ -209,15 +221,15 @@ def sync(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[None]] = UNSET,
+    list_prop: Union[Unset, List[AnEnum]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[None, Unset, float] = 0.6,
-    enum_prop: Union[Unset, None] = UNSET,
-    model_prop: Union[Unset, None] = UNSET,
-    required_model_prop: None,
-    nullable_model_prop: Union[None, Unset] = UNSET,
-    nullable_required_model_prop: None,
-) -> Optional[Union[None, None]]:
+    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
+    enum_prop: Union[Unset, AnEnum] = UNSET,
+    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
+    required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
+    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
+) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
     return sync_detailed(
@@ -254,15 +266,15 @@ async def asyncio_detailed(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[None]] = UNSET,
+    list_prop: Union[Unset, List[AnEnum]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[None, Unset, float] = 0.6,
-    enum_prop: Union[Unset, None] = UNSET,
-    model_prop: Union[Unset, None] = UNSET,
-    required_model_prop: None,
-    nullable_model_prop: Union[None, Unset] = UNSET,
-    nullable_required_model_prop: None,
-) -> Response[Union[None, None]]:
+    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
+    enum_prop: Union[Unset, AnEnum] = UNSET,
+    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
+    required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
+    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         string_prop=string_prop,
@@ -302,15 +314,15 @@ async def asyncio(
     float_prop: Union[Unset, float] = 3.14,
     int_prop: Union[Unset, int] = 7,
     boolean_prop: Union[Unset, bool] = False,
-    list_prop: Union[Unset, List[None]] = UNSET,
+    list_prop: Union[Unset, List[AnEnum]] = UNSET,
     union_prop: Union[Unset, float, str] = "not a float",
-    union_prop_with_ref: Union[None, Unset, float] = 0.6,
-    enum_prop: Union[Unset, None] = UNSET,
-    model_prop: Union[Unset, None] = UNSET,
-    required_model_prop: None,
-    nullable_model_prop: Union[None, Unset] = UNSET,
-    nullable_required_model_prop: None,
-) -> Optional[Union[None, None]]:
+    union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6,
+    enum_prop: Union[Unset, AnEnum] = UNSET,
+    model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
+    required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET,
+    nullable_required_model_prop: Union[ModelWithUnionProperty, None],
+) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
     return (
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 5cc5cde5c..ec2216810 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
@@ -4,13 +4,16 @@
 import httpx
 
 from ...client import Client
+from ...models.a_model import AModel
+from ...models.an_enum import AnEnum
+from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response
 
 
 def _get_kwargs(
     *,
     client: Client,
-    an_enum_value: List[None],
+    an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
 ) -> Dict[str, Any]:
     url = "{}/tests/".format(client.base_url)
@@ -20,7 +23,7 @@ def _get_kwargs(
 
     json_an_enum_value = []
     for an_enum_value_item_data in an_enum_value:
-        an_enum_value_item = None
+        an_enum_value_item = an_enum_value_item_data.value
 
         json_an_enum_value.append(an_enum_value_item)
 
@@ -44,24 +47,24 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[List[None], None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = []
         _response_200 = response.json()
         for response_200_item_data in _response_200:
-            response_200_item = None
+            response_200_item = AModel.from_dict(response_200_item_data)
 
             response_200.append(response_200_item)
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[List[None], None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[List[AModel], HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -73,9 +76,9 @@ def _build_response(*, response: httpx.Response) -> Response[Union[List[None], N
 def sync_detailed(
     *,
     client: Client,
-    an_enum_value: List[None],
+    an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[List[None], None]]:
+) -> Response[Union[List[AModel], HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -92,9 +95,9 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    an_enum_value: List[None],
+    an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[List[None], None]]:
+) -> Optional[Union[List[AModel], HTTPValidationError]]:
     """ Get a list of things  """
 
     return sync_detailed(
@@ -107,9 +110,9 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    an_enum_value: List[None],
+    an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Response[Union[List[None], None]]:
+) -> Response[Union[List[AModel], HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         an_enum_value=an_enum_value,
@@ -125,9 +128,9 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    an_enum_value: List[None],
+    an_enum_value: List[AnEnum],
     some_date: Union[datetime.date, datetime.datetime],
-) -> Optional[Union[List[None], None]]:
+) -> Optional[Union[List[AModel], HTTPValidationError]]:
     """ Get a list of things  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
index 96aed7f17..7d14632c4 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py
@@ -3,20 +3,22 @@
 import httpx
 
 from ...client import Client
+from ...models.an_int_enum import AnIntEnum
+from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response
 
 
 def _get_kwargs(
     *,
     client: Client,
-    int_enum: None,
+    int_enum: AnIntEnum,
 ) -> Dict[str, Any]:
     url = "{}/tests/int_enum".format(client.base_url)
 
     headers: Dict[str, Any] = client.get_headers()
     cookies: Dict[str, Any] = client.get_cookies()
 
-    json_int_enum = None
+    json_int_enum = int_enum.value
 
     params: Dict[str, Any] = {
         "int_enum": json_int_enum,
@@ -32,19 +34,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -56,8 +58,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
 def sync_detailed(
     *,
     client: Client,
-    int_enum: None,
-) -> Response[Union[None, None]]:
+    int_enum: AnIntEnum,
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         int_enum=int_enum,
@@ -73,8 +75,8 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    int_enum: None,
-) -> Optional[Union[None, None]]:
+    int_enum: AnIntEnum,
+) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
     return sync_detailed(
@@ -86,8 +88,8 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    int_enum: None,
-) -> Response[Union[None, None]]:
+    int_enum: AnIntEnum,
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         int_enum=int_enum,
@@ -102,8 +104,8 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    int_enum: None,
-) -> Optional[Union[None, None]]:
+    int_enum: AnIntEnum,
+) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
index a9a89a47c..074ab9d89 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py
@@ -3,20 +3,22 @@
 import httpx
 
 from ...client import Client
+from ...models.a_model import AModel
+from ...models.http_validation_error import HTTPValidationError
 from ...types import Response
 
 
 def _get_kwargs(
     *,
     client: Client,
-    json_body: None,
+    json_body: AModel,
 ) -> Dict[str, Any]:
     url = "{}/tests/json_body".format(client.base_url)
 
     headers: Dict[str, Any] = client.get_headers()
     cookies: Dict[str, Any] = client.get_cookies()
 
-    json_json_body = None
+    json_json_body = json_body.to_dict()
 
     return {
         "url": url,
@@ -27,19 +29,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -51,8 +53,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
 def sync_detailed(
     *,
     client: Client,
-    json_body: None,
-) -> Response[Union[None, None]]:
+    json_body: AModel,
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         json_body=json_body,
@@ -68,8 +70,8 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
-    json_body: None,
-) -> Optional[Union[None, None]]:
+    json_body: AModel,
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Try sending a JSON body  """
 
     return sync_detailed(
@@ -81,8 +83,8 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
-    json_body: None,
-) -> Response[Union[None, None]]:
+    json_body: AModel,
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         json_body=json_body,
@@ -97,8 +99,8 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
-    json_body: None,
-) -> Optional[Union[None, None]]:
+    json_body: AModel,
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Try sending a JSON body  """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
index 2f1f1d48b..64431ba2f 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py
@@ -3,6 +3,7 @@
 import httpx
 
 from ...client import Client
+from ...models.http_validation_error import HTTPValidationError
 from ...types import UNSET, Response, Unset
 
 
@@ -34,19 +35,19 @@ def _get_kwargs(
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -59,7 +60,7 @@ def sync_detailed(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Response[Union[None, None]]:
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         query_param=query_param,
@@ -76,7 +77,7 @@ def sync(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Optional[Union[None, None]]:
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Test optional query parameters """
 
     return sync_detailed(
@@ -89,7 +90,7 @@ async def asyncio_detailed(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Response[Union[None, None]]:
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
         query_param=query_param,
@@ -105,7 +106,7 @@ async def asyncio(
     *,
     client: Client,
     query_param: Union[Unset, List[str]] = UNSET,
-) -> Optional[Union[None, None]]:
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Test optional query parameters """
 
     return (
diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
index c06943593..705443d95 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py
@@ -3,12 +3,15 @@
 import httpx
 
 from ...client import Client
-from ...types import UNSET, Response, Unset
+from ...models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost
+from ...models.http_validation_error import HTTPValidationError
+from ...types import UNSET, File, Response, Unset
 
 
 def _get_kwargs(
     *,
     client: Client,
+    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
 ) -> Dict[str, Any]:
     url = "{}/tests/upload".format(client.base_url)
@@ -19,27 +22,37 @@ def _get_kwargs(
     if keep_alive is not UNSET:
         headers["keep-alive"] = keep_alive
 
+    files = {}
+    data = {}
+    for key, value in multipart_data.to_dict().items():
+        if isinstance(value, File):
+            files[key] = value
+        else:
+            data[key] = value
+
     return {
         "url": url,
         "headers": headers,
         "cookies": cookies,
         "timeout": client.get_timeout(),
+        "files": files,
+        "data": data,
     }
 
 
-def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]:
+def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
     if response.status_code == 200:
         response_200 = None
 
         return response_200
     if response.status_code == 422:
-        response_422 = None
+        response_422 = HTTPValidationError.from_dict(response.json())
 
         return response_422
     return None
 
 
-def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
+def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
     return Response(
         status_code=response.status_code,
         content=response.content,
@@ -51,10 +64,12 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]:
 def sync_detailed(
     *,
     client: Client,
+    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Response[Union[None, None]]:
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
+        multipart_data=multipart_data,
         keep_alive=keep_alive,
     )
 
@@ -68,12 +83,14 @@ def sync_detailed(
 def sync(
     *,
     client: Client,
+    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Optional[Union[None, None]]:
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Upload a file  """
 
     return sync_detailed(
         client=client,
+        multipart_data=multipart_data,
         keep_alive=keep_alive,
     ).parsed
 
@@ -81,10 +98,12 @@ def sync(
 async def asyncio_detailed(
     *,
     client: Client,
+    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Response[Union[None, None]]:
+) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
+        multipart_data=multipart_data,
         keep_alive=keep_alive,
     )
 
@@ -97,13 +116,15 @@ async def asyncio_detailed(
 async def asyncio(
     *,
     client: Client,
+    multipart_data: BodyUploadFileTestsUploadPost,
     keep_alive: Union[Unset, bool] = UNSET,
-) -> Optional[Union[None, None]]:
+) -> Optional[Union[None, HTTPValidationError]]:
     """ Upload a file  """
 
     return (
         await asyncio_detailed(
             client=client,
+            multipart_data=multipart_data,
             keep_alive=keep_alive,
         )
     ).parsed
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 92a9738d9..91b62c01b 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
@@ -8,6 +8,10 @@
 from ..models.a_model_not_required_model import AModelNotRequiredModel
 from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel
 from ..models.a_model_nullable_model import AModelNullableModel
+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
 
 T = TypeVar("T", bound="AModel")
@@ -17,28 +21,28 @@
 class AModel:
     """ A Model for testing all the ways custom objects can be used  """
 
-    an_enum_value: None
+    an_enum_value: AnEnum
     a_camel_date_time: Union[datetime.date, datetime.datetime]
     a_date: datetime.date
     required_not_nullable: str
-    one_of_models: None
+    one_of_models: Union[FreeFormModel, ModelWithUnionProperty]
     model: AModelModel
     a_nullable_date: Optional[datetime.date]
     required_nullable: Optional[str]
-    nullable_one_of_models: None
+    nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None]
     nullable_model: Optional[AModelNullableModel]
-    nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET
+    nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
     a_not_required_date: Union[Unset, datetime.date] = UNSET
     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[None, Unset] = UNSET
-    not_required_nullable_one_of_models: Union[None, 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, AModelNotRequiredModel] = UNSET
     not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
-        an_enum_value = None
+        an_enum_value = self.an_enum_value.value
 
         if isinstance(self.a_camel_date_time, datetime.datetime):
             a_camel_date_time = self.a_camel_date_time.isoformat()
@@ -48,21 +52,21 @@ def to_dict(self) -> Dict[str, Any]:
 
         a_date = self.a_date.isoformat()
         required_not_nullable = self.required_not_nullable
-        if isinstance(self.one_of_models, None):
-            one_of_models = None
+        if isinstance(self.one_of_models, FreeFormModel):
+            one_of_models = self.one_of_models.to_dict()
 
         else:
-            one_of_models = None
+            one_of_models = self.one_of_models.to_dict()
 
         model = self.model.to_dict()
 
-        nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET
+        nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET
         if not isinstance(self.nested_list_of_enums, Unset):
             nested_list_of_enums = []
             for nested_list_of_enums_item_data in self.nested_list_of_enums:
                 nested_list_of_enums_item = []
                 for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data:
-                    nested_list_of_enums_item_item = None
+                    nested_list_of_enums_item_item = nested_list_of_enums_item_item_data.value
 
                     nested_list_of_enums_item.append(nested_list_of_enums_item_item)
 
@@ -77,34 +81,42 @@ def to_dict(self) -> Dict[str, Any]:
         required_nullable = self.required_nullable
         not_required_nullable = self.not_required_nullable
         not_required_not_nullable = self.not_required_not_nullable
-        nullable_one_of_models: None
+        nullable_one_of_models: Union[Dict[str, Any], None]
         if self.nullable_one_of_models is None:
             nullable_one_of_models = None
-        elif isinstance(self.nullable_one_of_models, None):
-            nullable_one_of_models = None
+        elif isinstance(self.nullable_one_of_models, FreeFormModel):
+            nullable_one_of_models = self.nullable_one_of_models.to_dict()
 
         else:
-            nullable_one_of_models = None
+            nullable_one_of_models = self.nullable_one_of_models.to_dict()
 
-        not_required_one_of_models: Union[None, Unset]
+        not_required_one_of_models: Union[Dict[str, Any], Unset]
         if isinstance(self.not_required_one_of_models, Unset):
             not_required_one_of_models = UNSET
-        elif isinstance(self.not_required_one_of_models, None):
-            not_required_one_of_models = None
+        elif isinstance(self.not_required_one_of_models, FreeFormModel):
+            not_required_one_of_models = UNSET
+            if not isinstance(self.not_required_one_of_models, Unset):
+                not_required_one_of_models = self.not_required_one_of_models.to_dict()
 
         else:
-            not_required_one_of_models = None
+            not_required_one_of_models = UNSET
+            if not isinstance(self.not_required_one_of_models, Unset):
+                not_required_one_of_models = self.not_required_one_of_models.to_dict()
 
-        not_required_nullable_one_of_models: Union[None, Unset, str]
+        not_required_nullable_one_of_models: Union[Dict[str, Any], None, Unset, str]
         if isinstance(self.not_required_nullable_one_of_models, Unset):
             not_required_nullable_one_of_models = UNSET
         elif self.not_required_nullable_one_of_models is None:
             not_required_nullable_one_of_models = None
-        elif isinstance(self.not_required_nullable_one_of_models, None):
-            not_required_nullable_one_of_models = None
+        elif isinstance(self.not_required_nullable_one_of_models, FreeFormModel):
+            not_required_nullable_one_of_models = UNSET
+            if not isinstance(self.not_required_nullable_one_of_models, Unset):
+                not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict()
 
-        elif isinstance(self.not_required_nullable_one_of_models, None):
-            not_required_nullable_one_of_models = None
+        elif isinstance(self.not_required_nullable_one_of_models, ModelWithUnionProperty):
+            not_required_nullable_one_of_models = UNSET
+            if not isinstance(self.not_required_nullable_one_of_models, Unset):
+                not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict()
 
         else:
             not_required_nullable_one_of_models = self.not_required_nullable_one_of_models
@@ -160,7 +172,7 @@ def to_dict(self) -> Dict[str, Any]:
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        an_enum_value = None
+        an_enum_value = AnEnum(d.pop("an_enum_value"))
 
         def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]:
             try:
@@ -185,22 +197,20 @@ 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) -> None:
-            if data is None:
-                return data
+        def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]:
             try:
-                one_of_models_type0: None
-                if not data is None:
+                one_of_models_type0: FreeFormModel
+                if not isinstance(data, dict):
                     raise TypeError()
-                one_of_models_type0 = UNSET
+                one_of_models_type0 = FreeFormModel.from_dict(data)
 
                 return one_of_models_type0
             except:  # noqa: E722
                 pass
-            if not data is None:
+            if not isinstance(data, dict):
                 raise TypeError()
-            one_of_models_type1: None
-            one_of_models_type1 = UNSET
+            one_of_models_type1: ModelWithUnionProperty
+            one_of_models_type1 = ModelWithUnionProperty.from_dict(data)
 
             return one_of_models_type1
 
@@ -214,7 +224,7 @@ def _parse_one_of_models(data: object) -> None:
             nested_list_of_enums_item = []
             _nested_list_of_enums_item = nested_list_of_enums_item_data
             for nested_list_of_enums_item_item_data in _nested_list_of_enums_item:
-                nested_list_of_enums_item_item = None
+                nested_list_of_enums_item_item = DifferentEnum(nested_list_of_enums_item_item_data)
 
                 nested_list_of_enums_item.append(nested_list_of_enums_item_item)
 
@@ -238,74 +248,90 @@ def _parse_one_of_models(data: object) -> None:
 
         not_required_not_nullable = d.pop("not_required_not_nullable", UNSET)
 
-        def _parse_nullable_one_of_models(data: object) -> None:
+        def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]:
             if data is None:
                 return data
             try:
-                nullable_one_of_models_type0: None
-                if not data is None:
+                nullable_one_of_models_type0: FreeFormModel
+                if not isinstance(data, dict):
                     raise TypeError()
-                nullable_one_of_models_type0 = UNSET
+                nullable_one_of_models_type0 = FreeFormModel.from_dict(data)
 
                 return nullable_one_of_models_type0
             except:  # noqa: E722
                 pass
-            if not data is None:
+            if not isinstance(data, dict):
                 raise TypeError()
-            nullable_one_of_models_type1: None
-            nullable_one_of_models_type1 = UNSET
+            nullable_one_of_models_type1: ModelWithUnionProperty
+            nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(data)
 
             return nullable_one_of_models_type1
 
         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[None, Unset]:
-            if data is None:
-                return data
+        def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, Unset]:
             if isinstance(data, Unset):
                 return data
             try:
-                not_required_one_of_models_type0: Union[Unset, None]
-                if not data is None:
+                not_required_one_of_models_type0: Union[Unset, FreeFormModel]
+                if not isinstance(data, dict):
                     raise TypeError()
                 not_required_one_of_models_type0 = UNSET
+                _not_required_one_of_models_type0 = data
+                if not isinstance(_not_required_one_of_models_type0, Unset):
+                    not_required_one_of_models_type0 = FreeFormModel.from_dict(_not_required_one_of_models_type0)
 
                 return not_required_one_of_models_type0
             except:  # noqa: E722
                 pass
-            if not data is None:
+            if not isinstance(data, dict):
                 raise TypeError()
-            not_required_one_of_models_type1: Union[Unset, None]
+            not_required_one_of_models_type1: Union[Unset, ModelWithUnionProperty]
             not_required_one_of_models_type1 = UNSET
+            _not_required_one_of_models_type1 = data
+            if not isinstance(_not_required_one_of_models_type1, Unset):
+                not_required_one_of_models_type1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type1)
 
             return not_required_one_of_models_type1
 
         not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET))
 
-        def _parse_not_required_nullable_one_of_models(data: object) -> Union[None, Unset, str]:
+        def _parse_not_required_nullable_one_of_models(
+            data: object,
+        ) -> Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]:
             if data is None:
                 return data
             if isinstance(data, Unset):
                 return data
             try:
-                not_required_nullable_one_of_models_type0: Union[Unset, None]
-                if not data is None:
+                not_required_nullable_one_of_models_type0: Union[Unset, FreeFormModel]
+                if not isinstance(data, dict):
                     raise TypeError()
                 not_required_nullable_one_of_models_type0 = UNSET
+                _not_required_nullable_one_of_models_type0 = data
+                if not isinstance(_not_required_nullable_one_of_models_type0, Unset):
+                    not_required_nullable_one_of_models_type0 = FreeFormModel.from_dict(
+                        _not_required_nullable_one_of_models_type0
+                    )
 
                 return not_required_nullable_one_of_models_type0
             except:  # noqa: E722
                 pass
             try:
-                not_required_nullable_one_of_models_type1: Union[Unset, None]
-                if not data is None:
+                not_required_nullable_one_of_models_type1: Union[Unset, ModelWithUnionProperty]
+                if not isinstance(data, dict):
                     raise TypeError()
                 not_required_nullable_one_of_models_type1 = UNSET
+                _not_required_nullable_one_of_models_type1 = data
+                if not isinstance(_not_required_nullable_one_of_models_type1, Unset):
+                    not_required_nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(
+                        _not_required_nullable_one_of_models_type1
+                    )
 
                 return not_required_nullable_one_of_models_type1
             except:  # noqa: E722
                 pass
-            return cast(Union[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/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
index 55ff303f9..0a7a54bd6 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py
@@ -1,7 +1,11 @@
-from typing import Any, Dict, List, Type, TypeVar
+from typing import Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
+from ..models.an_enum import AnEnum
+from ..models.an_int_enum import AnIntEnum
+from ..types import UNSET, Unset
+
 T = TypeVar("T", bound="AModelModel")
 
 
@@ -9,20 +13,65 @@
 class AModelModel:
     """  """
 
+    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        a_property: Union[Unset, int, str]
+        if isinstance(self.a_property, Unset):
+            a_property = UNSET
+        elif isinstance(self.a_property, AnEnum):
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
+
+        else:
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
+        if a_property is not UNSET:
+            field_dict["a_property"] = a_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        a_model_model = cls()
+
+        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
+            if isinstance(data, Unset):
+                return data
+            try:
+                a_property_type0: Union[Unset, AnEnum]
+                if not isinstance(data, str):
+                    raise TypeError()
+                a_property_type0 = UNSET
+                _a_property_type0 = data
+                if not isinstance(_a_property_type0, Unset):
+                    a_property_type0 = AnEnum(_a_property_type0)
+
+                return a_property_type0
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, int):
+                raise TypeError()
+            a_property_type1: Union[Unset, AnIntEnum]
+            a_property_type1 = UNSET
+            _a_property_type1 = data
+            if not isinstance(_a_property_type1, Unset):
+                a_property_type1 = AnIntEnum(_a_property_type1)
+
+            return a_property_type1
+
+        a_property = _parse_a_property(d.pop("a_property", UNSET))
+
+        a_model_model = cls(
+            a_property=a_property,
+        )
 
         a_model_model.additional_properties = d
         return a_model_model
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
index 4c86e1019..fd568db52 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py
@@ -1,7 +1,11 @@
-from typing import Any, Dict, List, Type, TypeVar
+from typing import Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
+from ..models.an_enum import AnEnum
+from ..models.an_int_enum import AnIntEnum
+from ..types import UNSET, Unset
+
 T = TypeVar("T", bound="AModelNotRequiredModel")
 
 
@@ -9,20 +13,65 @@
 class AModelNotRequiredModel:
     """  """
 
+    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        a_property: Union[Unset, int, str]
+        if isinstance(self.a_property, Unset):
+            a_property = UNSET
+        elif isinstance(self.a_property, AnEnum):
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
+
+        else:
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
+        if a_property is not UNSET:
+            field_dict["a_property"] = a_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        a_model_not_required_model = cls()
+
+        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
+            if isinstance(data, Unset):
+                return data
+            try:
+                a_property_type0: Union[Unset, AnEnum]
+                if not isinstance(data, str):
+                    raise TypeError()
+                a_property_type0 = UNSET
+                _a_property_type0 = data
+                if not isinstance(_a_property_type0, Unset):
+                    a_property_type0 = AnEnum(_a_property_type0)
+
+                return a_property_type0
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, int):
+                raise TypeError()
+            a_property_type1: Union[Unset, AnIntEnum]
+            a_property_type1 = UNSET
+            _a_property_type1 = data
+            if not isinstance(_a_property_type1, Unset):
+                a_property_type1 = AnIntEnum(_a_property_type1)
+
+            return a_property_type1
+
+        a_property = _parse_a_property(d.pop("a_property", UNSET))
+
+        a_model_not_required_model = cls(
+            a_property=a_property,
+        )
 
         a_model_not_required_model.additional_properties = d
         return a_model_not_required_model
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
index c0bf0fafb..6413e7f9a 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py
@@ -1,7 +1,11 @@
-from typing import Any, Dict, List, Type, TypeVar
+from typing import Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
+from ..models.an_enum import AnEnum
+from ..models.an_int_enum import AnIntEnum
+from ..types import UNSET, Unset
+
 T = TypeVar("T", bound="AModelNotRequiredNullableModel")
 
 
@@ -9,20 +13,65 @@
 class AModelNotRequiredNullableModel:
     """  """
 
+    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        a_property: Union[Unset, int, str]
+        if isinstance(self.a_property, Unset):
+            a_property = UNSET
+        elif isinstance(self.a_property, AnEnum):
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
+
+        else:
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
+        if a_property is not UNSET:
+            field_dict["a_property"] = a_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        a_model_not_required_nullable_model = cls()
+
+        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
+            if isinstance(data, Unset):
+                return data
+            try:
+                a_property_type0: Union[Unset, AnEnum]
+                if not isinstance(data, str):
+                    raise TypeError()
+                a_property_type0 = UNSET
+                _a_property_type0 = data
+                if not isinstance(_a_property_type0, Unset):
+                    a_property_type0 = AnEnum(_a_property_type0)
+
+                return a_property_type0
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, int):
+                raise TypeError()
+            a_property_type1: Union[Unset, AnIntEnum]
+            a_property_type1 = UNSET
+            _a_property_type1 = data
+            if not isinstance(_a_property_type1, Unset):
+                a_property_type1 = AnIntEnum(_a_property_type1)
+
+            return a_property_type1
+
+        a_property = _parse_a_property(d.pop("a_property", UNSET))
+
+        a_model_not_required_nullable_model = cls(
+            a_property=a_property,
+        )
 
         a_model_not_required_nullable_model.additional_properties = d
         return a_model_not_required_nullable_model
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
index fe66227fb..cc6484d5f 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py
@@ -1,7 +1,11 @@
-from typing import Any, Dict, List, Type, TypeVar
+from typing import Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
+from ..models.an_enum import AnEnum
+from ..models.an_int_enum import AnIntEnum
+from ..types import UNSET, Unset
+
 T = TypeVar("T", bound="AModelNullableModel")
 
 
@@ -9,20 +13,65 @@
 class AModelNullableModel:
     """  """
 
+    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        a_property: Union[Unset, int, str]
+        if isinstance(self.a_property, Unset):
+            a_property = UNSET
+        elif isinstance(self.a_property, AnEnum):
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
+
+        else:
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
+        if a_property is not UNSET:
+            field_dict["a_property"] = a_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        a_model_nullable_model = cls()
+
+        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
+            if isinstance(data, Unset):
+                return data
+            try:
+                a_property_type0: Union[Unset, AnEnum]
+                if not isinstance(data, str):
+                    raise TypeError()
+                a_property_type0 = UNSET
+                _a_property_type0 = data
+                if not isinstance(_a_property_type0, Unset):
+                    a_property_type0 = AnEnum(_a_property_type0)
+
+                return a_property_type0
+            except:  # noqa: E722
+                pass
+            if not isinstance(data, int):
+                raise TypeError()
+            a_property_type1: Union[Unset, AnIntEnum]
+            a_property_type1 = UNSET
+            _a_property_type1 = data
+            if not isinstance(_a_property_type1, Unset):
+                a_property_type1 = AnIntEnum(_a_property_type1)
+
+            return a_property_type1
+
+        a_property = _parse_a_property(d.pop("a_property", UNSET))
+
+        a_model_nullable_model = cls(
+            a_property=a_property,
+        )
 
         a_model_nullable_model.additional_properties = d
         return a_model_nullable_model
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 92ad83e50..feb2cdd6b 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
@@ -2,6 +2,7 @@
 
 import attr
 
+from ..models.validation_error import ValidationError
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="HTTPValidationError")
@@ -11,14 +12,14 @@
 class HTTPValidationError:
     """  """
 
-    detail: Union[Unset, List[None]] = UNSET
+    detail: Union[Unset, List[ValidationError]] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
-        detail: Union[Unset, List[None]] = UNSET
+        detail: Union[Unset, List[Dict[str, Any]]] = UNSET
         if not isinstance(self.detail, Unset):
             detail = []
             for detail_item_data in self.detail:
-                detail_item = None
+                detail_item = detail_item_data.to_dict()
 
                 detail.append(detail_item)
 
@@ -35,7 +36,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         detail = []
         _detail = d.pop("detail", UNSET)
         for detail_item_data in _detail or []:
-            detail_item = None
+            detail_item = ValidationError.from_dict(detail_item_data)
 
             detail.append(detail_item)
 
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
index 7f4ef8bbd..ce26a3bbb 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py
@@ -1,7 +1,9 @@
-from typing import Any, Dict, List, Type, TypeVar
+from typing import Any, Dict, List, Type, TypeVar, Union
 
 import attr
 
+from ..types import UNSET, Unset
+
 T = TypeVar("T", bound="ModelFromAllOf")
 
 
@@ -9,20 +11,35 @@
 class ModelFromAllOf:
     """  """
 
+    a_sub_property: Union[Unset, str] = UNSET
+    another_sub_property: Union[Unset, str] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
+        a_sub_property = self.a_sub_property
+        another_sub_property = self.another_sub_property
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
         field_dict.update({})
+        if a_sub_property is not UNSET:
+            field_dict["a_sub_property"] = a_sub_property
+        if another_sub_property is not UNSET:
+            field_dict["another_sub_property"] = another_sub_property
 
         return field_dict
 
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        model_from_all_of = cls()
+        a_sub_property = d.pop("a_sub_property", UNSET)
+
+        another_sub_property = d.pop("another_sub_property", UNSET)
+
+        model_from_all_of = cls(
+            a_sub_property=a_sub_property,
+            another_sub_property=another_sub_property,
+        )
 
         model_from_all_of.additional_properties = d
         return model_from_all_of
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
index 1cfd6fdc7..b265db582 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py
@@ -2,6 +2,8 @@
 
 import attr
 
+from ..models.an_enum import AnEnum
+
 T = TypeVar("T", bound="ModelWithAdditionalPropertiesRefed")
 
 
@@ -9,12 +11,14 @@
 class ModelWithAdditionalPropertiesRefed:
     """  """
 
-    additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
+    additional_properties: Dict[str, AnEnum] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
 
         field_dict: Dict[str, Any] = {}
-        field_dict.update(self.additional_properties)
+        for prop_name, prop in self.additional_properties.items():
+            field_dict[prop_name] = prop.value
+
         field_dict.update({})
 
         return field_dict
@@ -24,17 +28,23 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
         model_with_additional_properties_refed = cls()
 
-        model_with_additional_properties_refed.additional_properties = d
+        additional_properties = {}
+        for prop_name, prop_dict in d.items():
+            additional_property = AnEnum(prop_dict)
+
+            additional_properties[prop_name] = additional_property
+
+        model_with_additional_properties_refed.additional_properties = additional_properties
         return model_with_additional_properties_refed
 
     @property
     def additional_keys(self) -> List[str]:
         return list(self.additional_properties.keys())
 
-    def __getitem__(self, key: str) -> Any:
+    def __getitem__(self, key: str) -> AnEnum:
         return self.additional_properties[key]
 
-    def __setitem__(self, key: str, value: Any) -> None:
+    def __setitem__(self, key: str, value: AnEnum) -> 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_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py
index cfb47ded3..1553914ba 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
@@ -2,6 +2,7 @@
 
 import attr
 
+from ..models.model_name import ModelName
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="ModelWithPropertyRef")
@@ -11,11 +12,13 @@
 class ModelWithPropertyRef:
     """  """
 
-    inner: Union[Unset, None] = UNSET
+    inner: Union[Unset, ModelName] = UNSET
     additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
 
     def to_dict(self) -> Dict[str, Any]:
-        inner = None
+        inner: Union[Unset, Dict[str, Any]] = UNSET
+        if not isinstance(self.inner, Unset):
+            inner = self.inner.to_dict()
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(self.additional_properties)
@@ -28,7 +31,10 @@ def to_dict(self) -> Dict[str, Any]:
     @classmethod
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
-        inner = None
+        inner: Union[Unset, ModelName] = UNSET
+        _inner = d.pop("inner", UNSET)
+        if not isinstance(_inner, Unset):
+            inner = ModelName.from_dict(_inner)
 
         model_with_property_ref = cls(
             inner=inner,
diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
index c7a849cbf..a3f049533 100644
--- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
+++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py
@@ -2,6 +2,8 @@
 
 import attr
 
+from ..models.an_enum import AnEnum
+from ..models.an_int_enum import AnIntEnum
 from ..types import UNSET, Unset
 
 T = TypeVar("T", bound="ModelWithUnionProperty")
@@ -11,17 +13,21 @@
 class ModelWithUnionProperty:
     """  """
 
-    a_property: Union[None, Unset] = UNSET
+    a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
-        a_property: Union[None, Unset]
+        a_property: Union[Unset, int, str]
         if isinstance(self.a_property, Unset):
             a_property = UNSET
-        elif isinstance(self.a_property, None):
-            a_property = None
+        elif isinstance(self.a_property, AnEnum):
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         else:
-            a_property = None
+            a_property = UNSET
+            if not isinstance(self.a_property, Unset):
+                a_property = self.a_property.value
 
         field_dict: Dict[str, Any] = {}
         field_dict.update({})
@@ -34,24 +40,28 @@ def to_dict(self) -> Dict[str, Any]:
     def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
         d = src_dict.copy()
 
-        def _parse_a_property(data: object) -> Union[None, Unset]:
-            if data is None:
-                return data
+        def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
             if isinstance(data, Unset):
                 return data
             try:
-                a_property_type0: Union[Unset, None]
-                if not data is None:
+                a_property_type0: Union[Unset, AnEnum]
+                if not isinstance(data, str):
                     raise TypeError()
                 a_property_type0 = UNSET
+                _a_property_type0 = data
+                if not isinstance(_a_property_type0, Unset):
+                    a_property_type0 = AnEnum(_a_property_type0)
 
                 return a_property_type0
             except:  # noqa: E722
                 pass
-            if not data is None:
+            if not isinstance(data, int):
                 raise TypeError()
-            a_property_type1: Union[Unset, None]
+            a_property_type1: Union[Unset, AnIntEnum]
             a_property_type1 = UNSET
+            _a_property_type1 = data
+            if not isinstance(_a_property_type1, Unset):
+                a_property_type1 = AnIntEnum(_a_property_type1)
 
             return a_property_type1
 
diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py
index 5a0bfc644..7803b3a54 100644
--- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py
+++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py
@@ -1,4 +1,4 @@
-from pydantic import AnyUrl, BaseModel, Field
+from pydantic import BaseModel, Field
 
 
 class Reference(BaseModel):
@@ -12,7 +12,7 @@ class Reference(BaseModel):
     and not by the JSON Schema specification.
     """
 
-    ref: AnyUrl = Field(alias="$ref")
+    ref: str = Field(alias="$ref")
     """**REQUIRED**. The reference string."""
 
     class Config:
diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja
index 45fe6d7c3..b226403ba 100644
--- a/openapi_python_client/templates/endpoint_macros.py.jinja
+++ b/openapi_python_client/templates/endpoint_macros.py.jinja
@@ -104,7 +104,7 @@ form_data: {{ endpoint.form_body_class.name }},
 {% endif %}
 {# Multipart data if any #}
 {% if endpoint.multipart_body_class %}
-multipart_data: {{ endpoint.multipart_body_class.class_name }},
+multipart_data: {{ endpoint.multipart_body_class.name }},
 {% endif %}
 {# JSON body if any #}
 {% if endpoint.json_body %}

From 0648aa03205d967188f395929f67bf37b2e84d93 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sun, 4 Apr 2021 10:58:13 -0600
Subject: [PATCH 08/11] docs: Update usage.md

---
 openapi_python_client/cli.py |  3 +--
 usage.md                     | 44 +++++++++++++++++++-----------------
 2 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py
index 953c92234..d84062fd4 100644
--- a/openapi_python_client/cli.py
+++ b/openapi_python_client/cli.py
@@ -6,10 +6,9 @@
 import typer
 
 from openapi_python_client import MetaType
+from openapi_python_client.config import Config
 from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError
 
-from .config import Config
-
 app = typer.Typer()
 
 
diff --git a/usage.md b/usage.md
index 4890e0e37..709534d05 100644
--- a/usage.md
+++ b/usage.md
@@ -1,6 +1,6 @@
 # `openapi-python-client`
 
-Generate a Python client from an OpenAPI JSON document 
+Generate a Python client from an OpenAPI JSON document
 
 **Usage**:
 
@@ -10,20 +10,19 @@ $ openapi-python-client [OPTIONS] COMMAND [ARGS]...
 
 **Options**:
 
-* `--version`: Print the version and exit  [default: False]
-* `--config PATH`: Path to the config file to use
-* `--install-completion`: Install completion for the current shell.
-* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
-* `--help`: Show this message and exit.
+- `--version`: Print the version and exit [default: False]
+- `--install-completion`: Install completion for the current shell.
+- `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
+- `--help`: Show this message and exit.
 
 **Commands**:
 
-* `generate`: Generate a new OpenAPI Client library
-* `update`: Update an existing OpenAPI Client library
+- `generate`: Generate a new OpenAPI Client library
+- `update`: Update an existing OpenAPI Client library
 
 ## `openapi-python-client generate`
 
-Generate a new OpenAPI Client library 
+Generate a new OpenAPI Client library
 
 **Usage**:
 
@@ -33,15 +32,17 @@ $ openapi-python-client generate [OPTIONS]
 
 **Options**:
 
-* `--url TEXT`: A URL to read the JSON from
-* `--path PATH`: A path to the JSON file
-* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s)
-* `--meta [none|poetry|setup]`: The type of metadata you want to generate.  [default: poetry]
-* `--help`: Show this message and exit.
+- `--url TEXT`: A URL to read the JSON from
+- `--path PATH`: A path to the JSON file
+- `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s)
+- `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry]
+- `--file-encoding TEXT`: Encoding used when writing generated [default: utf-8]
+- `--config PATH`: Path to the config file to use
+- `--help`: Show this message and exit.
 
 ## `openapi-python-client update`
 
-Update an existing OpenAPI Client library 
+Update an existing OpenAPI Client library
 
 **Usage**:
 
@@ -51,9 +52,10 @@ $ openapi-python-client update [OPTIONS]
 
 **Options**:
 
-* `--url TEXT`: A URL to read the JSON from
-* `--path PATH`: A path to the JSON file
-* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s)
-* `--meta [none|poetry|setup]`: The type of metadata you want to generate.  [default: poetry]
-* `--help`: Show this message and exit.
-
+- `--url TEXT`: A URL to read the JSON from
+- `--path PATH`: A path to the JSON file
+- `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s)
+- `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry]
+- `--file-encoding TEXT`: Encoding used when writing generated [default: utf-8]
+- `--config PATH`: Path to the config file to use
+- `--help`: Show this message and exit.

From 5005d82e1766073f4f7c48a04a85e4be03281904 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sun, 4 Apr 2021 11:11:01 -0600
Subject: [PATCH 09/11] test: Add more unit test coverage

---
 .../test_parser/test_properties/test_init.py  | 19 ++++++++++++++
 .../test_properties/test_model_property.py    | 26 +++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py
index 3e9c8cb54..7d71efc86 100644
--- a/tests/test_parser/test_properties/test_init.py
+++ b/tests/test_parser/test_properties/test_init.py
@@ -611,6 +611,25 @@ def test_property_from_data_ref_not_found(self, mocker):
         assert prop == PropertyError(data=data, detail="Could not find reference in parsed models or enums")
         assert schemas == new_schemas
 
+    def test_property_from_data_invalid_ref(self, mocker):
+        from openapi_python_client.parser.properties import PropertyError, Schemas, property_from_data
+
+        name = mocker.MagicMock()
+        required = mocker.MagicMock()
+        data = oai.Reference.construct(ref=mocker.MagicMock())
+        parse_reference_path = mocker.patch(
+            f"{MODULE_NAME}.parse_reference_path", return_value=PropertyError(detail="bad stuff")
+        )
+        schemas = Schemas()
+
+        prop, new_schemas = property_from_data(
+            name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock()
+        )
+
+        parse_reference_path.assert_called_once_with(data.ref)
+        assert prop == PropertyError(data=data, detail="bad stuff")
+        assert schemas == new_schemas
+
     def test_property_from_data_string(self, mocker):
         from openapi_python_client.parser.properties import Schemas, property_from_data
 
diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py
index 7db77309a..b366c2333 100644
--- a/tests/test_parser/test_properties/test_model_property.py
+++ b/tests/test_parser/test_properties/test_model_property.py
@@ -231,6 +231,32 @@ def test_conflicting_properties_different_types(self, model_property_factory):
 
         assert isinstance(result, PropertyError)
 
+    def test_invalid_reference(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="ThisIsNotGood")])
+        schemas = Schemas()
+
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+
+        assert isinstance(result, PropertyError)
+
+    def test_non_model_reference(self, enum_property_factory):
+        from openapi_python_client.parser.properties import Schemas
+        from openapi_python_client.parser.properties.model_property import _process_properties
+
+        data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="#/First")])
+        schemas = Schemas(
+            classes_by_reference={
+                "/First": enum_property_factory(),
+            }
+        )
+
+        result = _process_properties(data=data, schemas=schemas, class_name="", config=Config())
+
+        assert isinstance(result, PropertyError)
+
     def test_conflicting_properties_same_types(self, model_property_factory):
         from openapi_python_client.parser.properties import Schemas
         from openapi_python_client.parser.properties.model_property import _process_properties

From fed62b053fada87d223578f78d7d46e47eeef005 Mon Sep 17 00:00:00 2001
From: Dylan Anthony <contact@dylananthony.com>
Date: Sun, 4 Apr 2021 11:24:08 -0600
Subject: [PATCH 10/11] fix: nullable passthrough in single union refs

---
 .../api/tests/defaults_tests_defaults_post.py | 31 ++++++++++---------
 .../my_test_api_client/models/a_model.py      | 29 ++++++++++-------
 .../parser/properties/__init__.py             |  2 +-
 3 files changed, 34 insertions(+), 28 deletions(-)

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 29d16b1d2..88e4421f4 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
@@ -29,8 +29,8 @@ def _get_kwargs(
     enum_prop: Union[Unset, AnEnum] = UNSET,
     model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
     required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    nullable_required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET,
+    nullable_required_model_prop: Optional[ModelWithUnionProperty],
 ) -> Dict[str, Any]:
     url = "{}/tests/defaults".format(client.base_url)
 
@@ -92,11 +92,11 @@ def _get_kwargs(
 
     json_required_model_prop = required_model_prop.to_dict()
 
-    json_nullable_model_prop: Union[Unset, Dict[str, Any]] = UNSET
+    json_nullable_model_prop: Union[Unset, None, Dict[str, Any]] = UNSET
     if not isinstance(nullable_model_prop, Unset):
-        json_nullable_model_prop = nullable_model_prop.to_dict()
+        json_nullable_model_prop = nullable_model_prop.to_dict() if nullable_model_prop else None
 
-    json_nullable_required_model_prop = nullable_required_model_prop.to_dict()
+    json_nullable_required_model_prop = nullable_required_model_prop.to_dict() if nullable_required_model_prop else None
 
     params: Dict[str, Any] = {
         "string_prop": string_prop,
@@ -116,9 +116,10 @@ def _get_kwargs(
     if not isinstance(json_model_prop, Unset):
         params.update(json_model_prop)
     params.update(json_required_model_prop)
-    if not isinstance(json_nullable_model_prop, Unset):
+    if not isinstance(json_nullable_model_prop, Unset) and json_nullable_model_prop is not None:
         params.update(json_nullable_model_prop)
-    params.update(json_nullable_required_model_prop)
+    if json_nullable_required_model_prop is not None:
+        params.update(json_nullable_required_model_prop)
     params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
 
     return {
@@ -169,8 +170,8 @@ def sync_detailed(
     enum_prop: Union[Unset, AnEnum] = UNSET,
     model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
     required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    nullable_required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET,
+    nullable_required_model_prop: Optional[ModelWithUnionProperty],
 ) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
@@ -218,8 +219,8 @@ def sync(
     enum_prop: Union[Unset, AnEnum] = UNSET,
     model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
     required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    nullable_required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET,
+    nullable_required_model_prop: Optional[ModelWithUnionProperty],
 ) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
@@ -263,8 +264,8 @@ async def asyncio_detailed(
     enum_prop: Union[Unset, AnEnum] = UNSET,
     model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
     required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    nullable_required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET,
+    nullable_required_model_prop: Optional[ModelWithUnionProperty],
 ) -> Response[Union[None, HTTPValidationError]]:
     kwargs = _get_kwargs(
         client=client,
@@ -311,8 +312,8 @@ async def asyncio(
     enum_prop: Union[Unset, AnEnum] = UNSET,
     model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
     required_model_prop: ModelWithUnionProperty,
-    nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET,
-    nullable_required_model_prop: ModelWithUnionProperty,
+    nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET,
+    nullable_required_model_prop: Optional[ModelWithUnionProperty],
 ) -> Optional[Union[None, HTTPValidationError]]:
     """  """
 
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 29a98a19f..b79f2e8d4 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
@@ -23,10 +23,10 @@ class AModel:
     required_not_nullable: str
     one_of_models: Union[FreeFormModel, ModelWithUnionProperty]
     model: ModelWithUnionProperty
-    nullable_model: ModelWithUnionProperty
     a_nullable_date: Optional[datetime.date]
     required_nullable: Optional[str]
     nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None]
+    nullable_model: Optional[ModelWithUnionProperty]
     nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET
     a_not_required_date: Union[Unset, datetime.date] = UNSET
     attr_1_leading_digit: Union[Unset, str] = UNSET
@@ -35,7 +35,7 @@ class AModel:
     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, ModelWithUnionProperty] = UNSET
+    not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty] = UNSET
 
     def to_dict(self) -> Dict[str, Any]:
         an_enum_value = self.an_enum_value.value
@@ -56,8 +56,6 @@ def to_dict(self) -> Dict[str, Any]:
 
         model = self.model.to_dict()
 
-        nullable_model = self.nullable_model.to_dict()
-
         nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET
         if not isinstance(self.nested_list_of_enums, Unset):
             nested_list_of_enums = []
@@ -119,13 +117,17 @@ def to_dict(self) -> Dict[str, Any]:
         else:
             not_required_nullable_one_of_models = self.not_required_nullable_one_of_models
 
+        nullable_model = self.nullable_model.to_dict() if self.nullable_model else None
+
         not_required_model: Union[Unset, Dict[str, Any]] = UNSET
         if not isinstance(self.not_required_model, Unset):
             not_required_model = self.not_required_model.to_dict()
 
-        not_required_nullable_model: Union[Unset, Dict[str, Any]] = UNSET
+        not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET
         if not isinstance(self.not_required_nullable_model, Unset):
-            not_required_nullable_model = self.not_required_nullable_model.to_dict()
+            not_required_nullable_model = (
+                self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None
+            )
 
         field_dict: Dict[str, Any] = {}
         field_dict.update(
@@ -136,10 +138,10 @@ def to_dict(self) -> Dict[str, Any]:
                 "required_not_nullable": required_not_nullable,
                 "one_of_models": one_of_models,
                 "model": model,
-                "nullable_model": nullable_model,
                 "a_nullable_date": a_nullable_date,
                 "required_nullable": required_nullable,
                 "nullable_one_of_models": nullable_one_of_models,
+                "nullable_model": nullable_model,
             }
         )
         if nested_list_of_enums is not UNSET:
@@ -212,8 +214,6 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro
 
         model = ModelWithUnionProperty.from_dict(d.pop("model"))
 
-        nullable_model = ModelWithUnionProperty.from_dict(d.pop("nullable_model"))
-
         nested_list_of_enums = []
         _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET)
         for nested_list_of_enums_item_data in _nested_list_of_enums or []:
@@ -333,14 +333,19 @@ def _parse_not_required_nullable_one_of_models(
             d.pop("not_required_nullable_one_of_models", UNSET)
         )
 
+        nullable_model = None
+        _nullable_model = d.pop("nullable_model")
+        if _nullable_model is not None:
+            nullable_model = ModelWithUnionProperty.from_dict(_nullable_model)
+
         not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET
         _not_required_model = d.pop("not_required_model", UNSET)
         if not isinstance(_not_required_model, Unset):
             not_required_model = ModelWithUnionProperty.from_dict(_not_required_model)
 
-        not_required_nullable_model: Union[Unset, ModelWithUnionProperty] = UNSET
+        not_required_nullable_model = None
         _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET)
-        if not isinstance(_not_required_nullable_model, Unset):
+        if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset):
             not_required_nullable_model = ModelWithUnionProperty.from_dict(_not_required_nullable_model)
 
         a_model = cls(
@@ -350,7 +355,6 @@ def _parse_not_required_nullable_one_of_models(
             required_not_nullable=required_not_nullable,
             one_of_models=one_of_models,
             model=model,
-            nullable_model=nullable_model,
             nested_list_of_enums=nested_list_of_enums,
             a_nullable_date=a_nullable_date,
             a_not_required_date=a_not_required_date,
@@ -361,6 +365,7 @@ def _parse_not_required_nullable_one_of_models(
             nullable_one_of_models=nullable_one_of_models,
             not_required_one_of_models=not_required_one_of_models,
             not_required_nullable_one_of_models=not_required_nullable_one_of_models,
+            nullable_model=nullable_model,
             not_required_model=not_required_model,
             not_required_nullable_model=not_required_nullable_model,
         )
diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py
index 448dcbc13..b9f29557c 100644
--- a/openapi_python_client/parser/properties/__init__.py
+++ b/openapi_python_client/parser/properties/__init__.py
@@ -407,7 +407,7 @@ def _property_from_ref(
     existing = schemas.classes_by_reference.get(ref_path)
     if existing:
         return (
-            attr.evolve(existing, required=required, name=name),
+            attr.evolve(existing, required=required, name=name, nullable=nullable),
             schemas,
         )
     return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas

From d457227e50b4f5c0c5dc779c93dfc3700a31ccac Mon Sep 17 00:00:00 2001
From: Dylan Anthony <43723790+dbanty@users.noreply.github.com>
Date: Mon, 5 Apr 2021 08:20:52 -0600
Subject: [PATCH 11/11] docs: Improve `properties.Class` docstring

---
 openapi_python_client/parser/properties/schemas.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py
index 315b9fb67..b4e9140b4 100644
--- a/openapi_python_client/parser/properties/schemas.py
+++ b/openapi_python_client/parser/properties/schemas.py
@@ -31,7 +31,7 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]
 
 @attr.s(auto_attribs=True, frozen=True)
 class Class:
-    """ Info about a generated class which will be in models """
+    """ Represents Python class which will be generated from an OpenAPI schema """
 
     name: _ClassName
     module_name: str