diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f0da24d4..711e5a258 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Additions
 - Link to the GitHub repository from PyPI (#26). Thanks @theY4Kman!
 - Support for date properties (#30, #37). Thanks @acgray!
+- Allow naming schemas by property name and Enums by title (#21, #31, #38). Thanks @acgray!
 
 ### Fixes
 - Fixed some typing issues in generated clients and incorporate mypy into end to end tests (#32). Thanks @acgray!
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index bc0e0ae03..ea68d8086 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -156,7 +156,7 @@ def _build_models(self) -> None:
         # Generate enums
         enum_template = self.env.get_template("enum.pyi")
         for enum in self.openapi.enums.values():
-            module_path = models_dir / f"{enum.name}.py"
+            module_path = models_dir / f"{enum.reference.module_name}.py"
             module_path.write_text(enum_template.render(enum=enum))
             imports.append(import_string_from_reference(enum.reference))
 
diff --git a/openapi_python_client/openapi_parser/openapi.py b/openapi_python_client/openapi_parser/openapi.py
index 97d424c46..433bf8edb 100644
--- a/openapi_python_client/openapi_parser/openapi.py
+++ b/openapi_python_client/openapi_parser/openapi.py
@@ -168,14 +168,19 @@ class Schema:
     relative_imports: Set[str]
 
     @staticmethod
-    def from_dict(d: Dict[str, Any], /) -> Schema:
-        """ A single Schema from its dict representation """
+    def from_dict(d: Dict[str, Any], /, name: str) -> Schema:
+        """ A single Schema from its dict representation
+        :param d:    Dict representation of the schema
+        :param name: Name by which the schema is referenced, such as a model name.  Used to infer the type name if a `title` property is not available.
+        """
         required_set = set(d.get("required", []))
         required_properties: List[Property] = []
         optional_properties: List[Property] = []
         relative_imports: Set[str] = set()
 
-        for key, value in d["properties"].items():
+        ref = Reference.from_ref(d.get("title", name))
+
+        for key, value in d.get("properties", {}).items():
             required = key in required_set
             p = property_from_dict(name=key, required=required, data=value)
             if required:
@@ -187,9 +192,12 @@ def from_dict(d: Dict[str, Any], /) -> Schema:
             elif isinstance(p, DateProperty):
                 relative_imports.add("from datetime import date")
             elif isinstance(p, (ReferenceListProperty, EnumListProperty, RefProperty, EnumProperty)) and p.reference:
-                relative_imports.add(import_string_from_reference(p.reference))
+                # don't add an import for self-referencing schemas
+                if p.reference.class_name != ref.class_name:
+                    relative_imports.add(import_string_from_reference(p.reference))
+
         schema = Schema(
-            reference=Reference.from_ref(d["title"]),
+            reference=ref,
             required_properties=required_properties,
             optional_properties=optional_properties,
             relative_imports=relative_imports,
@@ -201,8 +209,8 @@ def from_dict(d: Dict[str, Any], /) -> Schema:
     def dict(d: Dict[str, Dict[str, Any]], /) -> Dict[str, Schema]:
         """ Get a list of Schemas from an OpenAPI dict """
         result = {}
-        for data in d.values():
-            s = Schema.from_dict(data)
+        for name, data in d.items():
+            s = Schema.from_dict(data, name=name)
             result[s.reference.class_name] = s
         return result
 
diff --git a/openapi_python_client/openapi_parser/properties.py b/openapi_python_client/openapi_parser/properties.py
index eb7f14262..9af277e63 100644
--- a/openapi_python_client/openapi_parser/properties.py
+++ b/openapi_python_client/openapi_parser/properties.py
@@ -169,11 +169,10 @@ class EnumProperty(Property):
     """ A property that should use an enum """
 
     values: Dict[str, str]
-    reference: Reference = field(init=False)
+    reference: Reference
 
     def __post_init__(self) -> None:
         super().__post_init__()
-        self.reference = Reference.from_ref(self.name)
         inverse_values = {v: k for k, v in self.values.items()}
         if self.default is not None:
             self.default = f"{self.reference.class_name}.{inverse_values[self.default]}"
@@ -254,6 +253,7 @@ def property_from_dict(name: str, required: bool, data: Dict[str, Any]) -> Prope
             name=name,
             required=required,
             values=EnumProperty.values_from_list(data["enum"]),
+            reference=Reference.from_ref(data.get("title", name)),
             default=data.get("default"),
         )
     if "$ref" in data:
diff --git a/openapi_python_client/openapi_parser/reference.py b/openapi_python_client/openapi_parser/reference.py
index 1b04402aa..7283201e5 100644
--- a/openapi_python_client/openapi_parser/reference.py
+++ b/openapi_python_client/openapi_parser/reference.py
@@ -21,9 +21,10 @@ class Reference:
     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)
+        # ugly hack to avoid stringcase ugly pascalcase output when ref_value isn't snake case
+        class_name = utils.pascal_case(ref_value.replace(" ", ""))
 
         if class_name in class_overrides:
             return class_overrides[class_name]
 
-        return Reference(class_name=class_name, module_name=utils.snake_case(ref_value),)
+        return Reference(class_name=class_name, module_name=utils.snake_case(class_name))
diff --git a/tests/test___init__.py b/tests/test___init__.py
index b84416e3f..a687c20b4 100644
--- a/tests/test___init__.py
+++ b/tests/test___init__.py
@@ -274,10 +274,14 @@ def test__build_models(self, mocker):
             "__init__.py": models_init,
             f"{schema_1.reference.module_name}.py": schema_1_module_path,
             f"{schema_2.reference.module_name}.py": schema_2_module_path,
-            f"{enum_1.name}.py": enum_1_module_path,
-            f"{enum_2.name}.py": enum_2_module_path,
+            f"{enum_1.reference.module_name}.py": enum_1_module_path,
+            f"{enum_2.reference.module_name}.py": enum_2_module_path,
         }
-        models_dir.__truediv__.side_effect = lambda x: module_paths[x]
+
+        def models_dir_get(x):
+            return module_paths[x]
+
+        models_dir.__truediv__.side_effect = models_dir_get
         project.package_dir.__truediv__.return_value = models_dir
         model_render_1 = mocker.MagicMock()
         model_render_2 = mocker.MagicMock()
diff --git a/tests/test_openapi_parser/test_openapi.py b/tests/test_openapi_parser/test_openapi.py
index 2d5f4a6b2..04a5e199e 100644
--- a/tests/test_openapi_parser/test_openapi.py
+++ b/tests/test_openapi_parser/test_openapi.py
@@ -1,7 +1,5 @@
 import pytest
 
-from openapi_python_client.openapi_parser.properties import DateProperty, DateTimeProperty
-
 MODULE_NAME = "openapi_python_client.openapi_parser.openapi"
 
 
@@ -45,7 +43,13 @@ def test__check_enums(self, mocker):
         from openapi_python_client.openapi_parser.properties import EnumProperty, StringProperty
 
         def _make_enum():
-            return EnumProperty(name=str(mocker.MagicMock()), required=True, default=None, values=mocker.MagicMock(),)
+            return EnumProperty(
+                name=str(mocker.MagicMock()),
+                required=True,
+                default=None,
+                values=mocker.MagicMock(),
+                reference=mocker.MagicMock(),
+            )
 
         # Multiple schemas with both required and optional properties for making sure iteration works correctly
         schema_1 = mocker.MagicMock()
@@ -121,7 +125,13 @@ def test__check_enums_bad_duplicate(self, mocker):
 
         schema = mocker.MagicMock()
 
-        enum_1 = EnumProperty(name=str(mocker.MagicMock()), required=True, default=None, values=mocker.MagicMock(),)
+        enum_1 = EnumProperty(
+            name=str(mocker.MagicMock()),
+            required=True,
+            default=None,
+            values=mocker.MagicMock(),
+            reference=mocker.MagicMock(),
+        )
         enum_2 = replace(enum_1, values=mocker.MagicMock())
         schema.required_properties = [enum_1, enum_2]
 
@@ -141,14 +151,19 @@ def test_dict(self, mocker):
 
         result = Schema.dict(in_data)
 
-        from_dict.assert_has_calls([mocker.call(value) for value in in_data.values()])
+        from_dict.assert_has_calls([mocker.call(value, name=name) for (name, value) in in_data.items()])
         assert result == {
             schema_1.reference.class_name: schema_1,
             schema_2.reference.class_name: schema_2,
         }
 
     def test_from_dict(self, mocker):
-        from openapi_python_client.openapi_parser.properties import EnumProperty, StringProperty
+        from openapi_python_client.openapi_parser.properties import (
+            EnumProperty,
+            DateProperty,
+            DateTimeProperty,
+            Reference,
+        )
 
         in_data = {
             "title": mocker.MagicMock(),
@@ -160,7 +175,9 @@ def test_from_dict(self, mocker):
                 "OptionalDate": mocker.MagicMock(),
             },
         }
-        required_property = EnumProperty(name="RequiredEnum", required=True, default=None, values={},)
+        required_property = EnumProperty(
+            name="RequiredEnum", required=True, default=None, values={}, reference=Reference.from_ref("RequiredEnum")
+        )
         optional_property = DateTimeProperty(name="OptionalDateTime", required=False, default=None)
         optional_date_property = DateProperty(name="OptionalDate", required=False, default=None)
         property_from_dict = mocker.patch(
@@ -172,7 +189,7 @@ def test_from_dict(self, mocker):
 
         from openapi_python_client.openapi_parser.openapi import Schema
 
-        result = Schema.from_dict(in_data)
+        result = Schema.from_dict(in_data, name=mocker.MagicMock())
 
         from_ref.assert_called_once_with(in_data["title"])
         property_from_dict.assert_has_calls(
@@ -349,7 +366,7 @@ def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker):
             )
 
     def test__add_parameters_happy(self, mocker):
-        from openapi_python_client.openapi_parser.openapi import Endpoint, EnumProperty
+        from openapi_python_client.openapi_parser.openapi import Endpoint, EnumProperty, DateTimeProperty, DateProperty
 
         endpoint = Endpoint(
             path="path",
@@ -360,7 +377,7 @@ def test__add_parameters_happy(self, mocker):
             tag="tag",
             relative_imports={"import_3"},
         )
-        path_prop = EnumProperty(name="path_enum", required=True, default=None, values={})
+        path_prop = EnumProperty(name="path_enum", required=True, default=None, values={}, reference=mocker.MagicMock())
         query_prop_datetime = DateTimeProperty(name="query_datetime", required=False, default=None)
         query_prop_date = DateProperty(name="query_date", required=False, default=None)
         propety_from_dict = mocker.patch(
diff --git a/tests/test_openapi_parser/test_properties.py b/tests/test_openapi_parser/test_properties.py
index d04de014e..148818c58 100644
--- a/tests/test_openapi_parser/test_properties.py
+++ b/tests/test_openapi_parser/test_properties.py
@@ -153,17 +153,18 @@ def test_get_type_string(self, mocker):
 class TestEnumProperty:
     def test___post_init__(self, mocker):
         name = mocker.MagicMock()
-        snake_case = mocker.patch(f"openapi_python_client.utils.snake_case")
-        fake_reference = mocker.MagicMock(class_name="MyTestEnum")
-        from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
 
+        snake_case = mocker.patch(f"openapi_python_client.utils.snake_case")
         from openapi_python_client.openapi_parser.properties import EnumProperty
 
         enum_property = EnumProperty(
-            name=name, required=True, default="second", values={"FIRST": "first", "SECOND": "second"}
+            name=name,
+            required=True,
+            default="second",
+            values={"FIRST": "first", "SECOND": "second"},
+            reference=(mocker.MagicMock(class_name="MyTestEnum")),
         )
 
-        from_ref.assert_called_once_with(name)
         assert enum_property.default == "MyTestEnum.SECOND"
         assert enum_property.python_name == snake_case(name)
 
@@ -173,7 +174,9 @@ def test_get_type_string(self, mocker):
 
         from openapi_python_client.openapi_parser.properties import EnumProperty
 
-        enum_property = EnumProperty(name="test", required=True, default=None, values={})
+        enum_property = EnumProperty(
+            name="test", required=True, default=None, values={}, reference=mocker.MagicMock(class_name="MyTestEnum")
+        )
 
         assert enum_property.get_type_string() == "MyTestEnum"
         enum_property.required = False
@@ -185,21 +188,22 @@ def test_transform(self, mocker):
 
         from openapi_python_client.openapi_parser.properties import EnumProperty
 
-        enum_property = EnumProperty(name=name, required=True, default=None, values={})
+        enum_property = EnumProperty(name=name, required=True, default=None, values={}, reference=mocker.MagicMock())
 
         assert enum_property.transform() == f"the_property_name.value"
 
     def test_constructor_from_dict(self, mocker):
         fake_reference = mocker.MagicMock(class_name="MyTestEnum")
-        mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
 
         from openapi_python_client.openapi_parser.properties import EnumProperty
 
-        enum_property = EnumProperty(name="test_enum", required=True, default=None, values={})
+        enum_property = EnumProperty(name="test_enum", required=True, default=None, values={}, reference=fake_reference)
 
         assert enum_property.constructor_from_dict("my_dict") == 'MyTestEnum(my_dict["test_enum"])'
 
-        enum_property = EnumProperty(name="test_enum", required=False, default=None, values={})
+        enum_property = EnumProperty(
+            name="test_enum", required=False, default=None, values={}, reference=fake_reference
+        )
 
         assert (
             enum_property.constructor_from_dict("my_dict")
@@ -250,6 +254,7 @@ def test_property_from_dict_enum(self, mocker):
             "enum": mocker.MagicMock(),
         }
         EnumProperty = mocker.patch(f"{MODULE_NAME}.EnumProperty")
+        from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
 
         from openapi_python_client.openapi_parser.properties import property_from_dict
 
@@ -257,7 +262,7 @@ def test_property_from_dict_enum(self, mocker):
 
         EnumProperty.values_from_list.assert_called_once_with(data["enum"])
         EnumProperty.assert_called_once_with(
-            name=name, required=required, values=EnumProperty.values_from_list(), default=None
+            name=name, required=required, values=EnumProperty.values_from_list(), default=None, reference=from_ref()
         )
         assert p == EnumProperty()
 
@@ -268,7 +273,11 @@ def test_property_from_dict_enum(self, mocker):
             name=name, required=required, data=data,
         )
         EnumProperty.assert_called_once_with(
-            name=name, required=required, values=EnumProperty.values_from_list(), default=data["default"]
+            name=name,
+            required=required,
+            values=EnumProperty.values_from_list(),
+            default=data["default"],
+            reference=from_ref(),
         )
 
     def test_property_from_dict_ref(self, mocker):