CVE-2020-15142: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: In openapi-python-client before version 0.5.3, clients generated with a maliciously crafted OpenAPI Document can generate arbitrary Python code. Subsequent execution of this malicious client is arbitrary code execution.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 9
Published: 2020-08-14T17:15Z
Repository: https://github.com/triaxtec/openapi-python-client
Language: Python
Files changed: CHANGELOG.md, end_to_end_tests/fastapi_app/__init__.py, end_to_end_tests/fastapi_app/openapi.json, end_to_end_tests/golden-master/my_test_api_client/api/tests.py, end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py, end_to_end_tests/golden-master/my_test_api_client/models/a_model.py, openapi_python_client/__init__.py, openapi_python_client/parser/errors.py, openapi_python_client/parser/openapi.py, openapi_python_client/parser/properties.py, openapi_python_client/templates/async_endpoint_module.pyi, openapi_python_client/templates/property_templates/date_property.pyi, openapi_python_client/templates/property_templates/datetime_property.pyi, openapi_python_client/templates/property_templates/dict_property.pyi, openapi_python_client/templates/property_templates/union_property.pyi, openapi_python_client/utils.py, tests/test_openapi_parser/test_openapi.py, tests/test_openapi_parser/test_properties.py, tests/test_utils.py
Commit date: 2020-08-13 13:05:36 -0400
Commit message: Merge pull request from GHSA-9x4c-63pf-525f

* All strings used as file/directory names are now sanitized to address the path traversal vulnerabilities

* Switched calls to utils.spinal_case to utils.kebab_case

* Quotation marks are now escaped in all names/descriptions to address arbitrary code execution issues

* Sanitized identifiers are now also checked for keyword collision

* Added validation for default property values where possible

Address arbitrary code execution vulnerability

* Enum keys are now also sanitized

* Update end_to_end_tests/fastapi_app/__init__.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Update openapi_python_client/parser/properties.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Changed NotImplementedErrors to ValidationErrors

* Updated changelog

* Fixed regex

* Update openapi_python_client/utils.py

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>

* Fixed rendering of union, datetime, and date properties. Added e2e test for all defaults

* Fixed rendering of date/datetime default values when timezone is included

* Simplified datetime rendering

* Fixed typo

* Readded support for DictProperty defaults and fixed a bug with json body template rendering

* Fixed dict json body compatibility

Co-authored-by: Ethan Mann <emann@triaxtec.com>
Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 216abaf..fa75668 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,11 +3,19 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 
-## 0.5.3 - Unrelease
+## 0.5.3 - Unreleased
+### Security
+- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
+- All values that get placed into python files (everything from enum names, to endpoint descriptions, to default values) are validated and/or saniziatied to address arbitrary code execution vulnerabilities (CVE-2020-15142)
+
+### Changes
+- Due to security concerns/implementation complexities, default values are temporarily unsupported for any `RefProperty` that doesn't refer to an enum.
+- Defaults for properties must now be valid values for their respective type (e.g. "example string" is an invalid default for an `integer` type property, and the function for an endpoint using it would fail to generate and be skipped). 
+
 ### Additions
 - Added support for header parameters (#117)
 
 ### Fixes
 - JSON bodies will now be assigned correctly in generated clients(#139 & #147). Thanks @pawamoy!
diff --git a/end_to_end_tests/fastapi_app/__init__.py b/end_to_end_tests/fastapi_app/__init__.py
index d21ffa1..b60d4d8 100644
--- a/end_to_end_tests/fastapi_app/__init__.py
+++ b/end_to_end_tests/fastapi_app/__init__.py
@@ -3,11 +3,11 @@ import json
 from datetime import date, datetime
 from enum import Enum
 from pathlib import Path
 from typing import Any, Dict, List, Union
 
-from fastapi import APIRouter, FastAPI, File, Header, Query, UploadFile
+from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
 from pydantic import BaseModel
 
 app = FastAPI(title="My Test API", description="An API for testing openapi-python-client",)
 
 
@@ -41,17 +41,19 @@ class OtherModel(BaseModel):
 class AModel(BaseModel):
     """ A Model for testing all the ways custom objects can be used """
 
     an_enum_value: AnEnum
     nested_list_of_enums: List[List[DifferentEnum]] = []
-    some_dict: Dict[str, str] = {}
+    some_dict: Dict[str, str]
     aCamelDateTime: Union[datetime, date]
     a_date: date
 
 
 @test_router.get("/", response_model=List[AModel], operation_id="getUserList")
-def get_list(an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...)):
+def get_list(
+    an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...),
+):
     """ Get a list of things """
     return
 
 
 @test_router.post("/upload")
@@ -65,10 +67,26 @@ async def upload_file(some_file: UploadFile = File(...), keep_alive: bool = Head
 def json_body(body: AModel):
     """ Try sending a JSON body """
     return
 
 
+@test_router.post("/test_defaults")
+def test_defaults(
+    string_prop: str = Query(default="the default string"),
+    datetime_prop: datetime = Query(default=datetime(1010, 10, 10)),
+    date_prop: date = Query(default=date(1010, 10, 10)),
+    float_prop: float = Query(default=3.14),
+    int_prop: int = Query(default=7),
+    boolean_prop: bool = Query(default=False),
+    list_prop: List[AnEnum] = Query(default=[AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE]),
+    union_prop: Union[float, str] = Query(default="not a float"),
+    enum_prop: AnEnum = Query(default=AnEnum.FIRST_VALUE),
+    dict_prop: Dict[str, str] = Body(default={"key": "val"}),
+):
+    return
+
+
 app.include_router(test_router, prefix="/tests", tags=["tests"])
 
 
 def generate_openapi_json():
     path = Path(__file__).parent / "openapi.json"
diff --git a/end_to_end_tests/fastapi_app/openapi.json b/end_to_end_tests/fastapi_app/openapi.json
index cbea33f..24aeedd 100644
--- a/end_to_end_tests/fastapi_app/openapi.json
+++ b/end_to_end_tests/fastapi_app/openapi.json
@@ -182,18 +182,169 @@
                             }
                         }
                     }
                 }
             }
+        },
+        "/tests/test_defaults": {
+            "post": {
+                "tags": [
+                    "tests"
+                ],
+                "summary": "Test Defaults",
+                "operationId": "test_defaults_tests_test_defaults_post",
+                "parameters": [
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "String Prop",
+                            "type": "string",
+                            "default": "the default string"
+                        },
+                        "name": "string_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Datetime Prop",
+                            "type": "string",
+                            "format": "date-time",
+                            "default": "1010-10-10T00:00:00"
+                        },
+                        "name": "datetime_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Date Prop",
+                            "type": "string",
+                            "format": "date",
+                            "default": "1010-10-10"
+                        },
+                        "name": "date_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Float Prop",
+                            "type": "number",
+                            "default": 3.14
+                        },
+                        "name": "float_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Int Prop",
+                            "type": "integer",
+                            "default": 7
+                        },
+                        "name": "int_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Boolean Prop",
+                            "type": "boolean",
+                            "default": false
+                        },
+                        "name": "boolean_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "List Prop",
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/components/schemas/AnEnum"
+                            },
+                            "default": [
+                                "FIRST_VALUE",
+                                "SECOND_VALUE"
+                            ]
+                        },
+                        "name": "list_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "title": "Union Prop",
+                            "anyOf": [
+                                {
+                                    "type": "number"
+                                },
+                                {
+                                    "type": "string"
+                                }
+                            ],
+                            "default": "not a float"
+                        },
+                        "name": "union_prop",
+                        "in": "query"
+                    },
+                    {
+                        "required": false,
+                        "schema": {
+                            "$ref": "#/components/schemas/AnEnum"
+                        },
+                        "name": "enum_prop",
+                        "in": "query"
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "title": "Dict Prop",
+                                "type": "object",
+                                "additionalProperties": {
+                                    "type": "string"
+                                },
+                                "default": {
+                                    "key": "val"
+                                }
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {}
+                            }
+                        }
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        }
+                    }
+                }
+            }
         }
     },
     "components": {
         "schemas": {
             "AModel": {
                 "title": "AModel",
                 "required": [
                     "an_enum_value",
+                    "some_dict",
                     "aCamelDateTime",
                     "a_date"
                 ],
                 "type": "object",
                 "properties": {
@@ -214,12 +365,11 @@
                     "some_dict": {
                         "title": "Some Dict",
                         "type": "object",
                         "additionalProperties": {
                             "type": "string"
-                        },
-                        "default": {}
+                        }
                     },
                     "aCamelDateTime": {
                         "title": "Acameldatetime",
                         "anyOf": [
                             {
diff --git a/end_to_end_tests/golden-master/my_test_api_client/api/tests.py b/end_to_end_tests/golden-master/my_test_api_client/api/tests.py
index 3f7d094..ad50571 100644
--- a/end_to_end_tests/golden-master/my_test_api_client/api/tests.py
+++ b/end_to_end_tests/golden-master/my_test_api_client/api/tests.py
@@ -1,7 +1,7 @@
-from dataclasses import asdict
-from datetime import date, datetime
+import datetime
+from dataclasses import asdict, field
 from typing import Any, Dict, List, Optional, Union, cast
 
 import httpx
 
 from ..client import AuthenticatedClient, Client
@@ -11,11 +11,11 @@ from ..models.an_enum import AnEnum
 from ..models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost
 from ..models.http_validation_error import HTTPValidationError
 
 
 def get_user_list(
-    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
+    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
 ) -> Union[
     List[AModel], HTTPValidationError,
 ]:
 
     """ Get a list of things  """
@@ -27,11 +27,11 @@ def get_user_list(
     for an_enum_value_item_data in an_enum_value:
         an_enum_value_item = an_enum_value_item_data.value
 
         json_an_enum_value.append(an_enum_value_item)
 
-    if isinstance(some_date, date):
+    if isinstance(some_date, datetime.date):
         json_some_date = some_date.isoformat()
 
     else:
         json_some_date = some_date.isoformat()
 
@@ -92,5 +92,83 @@ def json_body_tests_json_body_post(
         return None
     if response.status_code == 422:
         return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
     else:
         raise ApiResponseError(response=response)
+
+
+def test_defaults_tests_test_defaults_post(
+    *,
+    client: Client,
+    json_body: Dict[Any, Any],
+    string_prop: Optional[str] = "the default string",
+    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
+    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
+    float_prop: Optional[float] = 3.14,
+    int_prop: Optional[int] = 7,
+    boolean_prop: Optional[bool] = False,
+    list_prop: Optional[List[AnEnum]] = field(
+        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
+    ),
+    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
+    enum_prop: Optional[AnEnum] = None,
+) -> Union[
+    None, HTTPValidationError,
+]:
+
+    """  """
+    url = "{}/tests/test_defaults".format(client.base_url)
+
+    headers: Dict[str, Any] = client.get_headers()
+
+    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None
+
+    json_date_prop = date_prop.isoformat() if date_prop else None
+
+    if list_prop is None:
+        json_list_prop = None
+    else:
+        json_list_prop = []
+        for list_prop_item_data in list_prop:
+            list_prop_item = list_prop_item_data.value
+
+            json_list_prop.append(list_prop_item)
+
+    if union_prop is None:
+        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
+    elif isinstance(union_prop, float):
+        json_union_prop = union_prop
+    else:
+        json_union_prop = union_prop
+
+    json_enum_prop = enum_prop.value if enum_prop else None
+
+    params: Dict[str, Any] = {}
+    if string_prop is not None:
+        params["string_prop"] = string_prop
+    if datetime_prop is not None:
+        params["datetime_prop"] = json_datetime_prop
+    if date_prop is not None:
+        params["date_prop"] = json_date_prop
+    if float_prop is not None:
+        params["float_prop"] = float_prop
+    if int_prop is not None:
+        params["int_prop"] = int_prop
+    if boolean_prop is not None:
+        params["boolean_prop"] = boolean_prop
+    if list_prop is not None:
+        params["list_prop"] = json_list_prop
+    if union_prop is not None:
+        params["union_prop"] = json_union_prop
+    if enum_prop is not None:
+        params["enum_prop"] = json_enum_prop
+
+    json_json_body = json_body
+
+    response = httpx.post(url=url, headers=headers, json=json_json_body, params=params,)
+
+    if response.status_code == 200:
+        return None
+    if response.status_code == 422:
+        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
+    else:
+        raise ApiResponseError(response=response)
diff --git a/end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py b/end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py
index 11bb660..0dcc0e3 100644
--- a/end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py
+++ b/end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py
@@ -1,7 +1,7 @@
-from dataclasses import asdict
-from datetime import date, datetime
+import datetime
+from dataclasses import asdict, field
 from typing import Any, Dict, List, Optional, Union, cast
 
 import httpx
 
 from ..client import AuthenticatedClient, Client
@@ -11,11 +11,11 @@ from ..models.an_enum import AnEnum
 from ..models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost
 from ..models.http_validation_error import HTTPValidationError
 
 
 async def get_user_list(
-    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
+    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
 ) -> Union[
     List[AModel], HTTPValidationError,
 ]:
 
     """ Get a list of things  """
@@ -27,11 +27,11 @@ async def get_user_list(
     for an_enum_value_item_data in an_enum_value:
         an_enum_value_item = an_enum_value_item_data.value
 
         json_an_enum_value.append(an_enum_value_item)
 
-    if isinstance(some_date, date):
+    if isinstance(some_date, datetime.date):
         json_some_date = some_date.isoformat()
 
     else:
         json_some_date = some_date.isoformat()
 
@@ -95,5 +95,84 @@ async def json_body_tests_json_body_post(
         return None
     if response.status_code == 422:
         return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
     else:
         raise ApiResponseError(response=response)
+
+
+async def test_defaults_tests_test_defaults_post(
+    *,
+    client: Client,
+    json_body: Dict[Any, Any],
+    string_prop: Optional[str] = "the default string",
+    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
+    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
+    float_prop: Optional[float] = 3.14,
+    int_prop: Optional[int] = 7,
+    boolean_prop: Optional[bool] = False,
+    list_prop: Optional[List[AnEnum]] = field(
+        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
+    ),
+    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
+    enum_prop: Optional[AnEnum] = None,
+) -> Union[
+    None, HTTPValidationError,
+]:
+
+    """  """
+    url = "{}/tests/test_defaults".format(client.base_url,)
+
+    headers: Dict[str, Any] = client.get_headers()
+
+    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None
+
+    json_date_prop = date_prop.isoformat() if date_prop else None
+
+    if list_prop is None:
+        json_list_prop = None
+    else:
+        json_list_prop = []
+        for list_prop_item_data in list_prop:
+            list_prop_item = list_prop_item_data.value
+
+            json_list_prop.append(list_prop_item)
+
+    if union_prop is None:
+        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
+    elif isinstance(union_prop, float):
+        json_union_prop = union_prop
+    else:
+        json_union_prop = union_prop
+
+    json_enum_prop = enum_prop.value if enum_prop else None
+
+    params: Dict[str, Any] = {}
+    if string_prop is not None:
+        params["string_prop"] = string_prop
+    if datetime_prop is not None:
+        params["datetime_prop"] = json_datetime_prop
+    if date_prop is not None:
+        params["date_prop"] = json_date_prop
+    if float_prop is not None:
+        params["float_prop"] = float_prop
+    if int_prop is not None:
+        params["int_prop"] = int_prop
+    if boolean_prop is not None:
+        params["boolean_prop"] = boolean_prop
+    if list_prop is not None:
+        params["list_prop"] = json_list_prop
+    if union_prop is not None:
+        params["union_prop"] = json_union_prop
+    if enum_prop is not None:
+        params["enum_prop"] = json_enum_prop
+
+    json_json_body = json_body
+
+    async with httpx.AsyncClient() as _client:
+        response = await _client.post(url=url, headers=headers, json=json_json_body, params=params,)
+
+    if response.status_code == 200:
+        return None
+    if response.status_code == 422:
+        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
+    else:
+        raise ApiResponseError(response=response)
diff --git a/end_to_end_tests/golden-master/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-master/my_test_api_client/models/a_model.py
index 19ef5b3..85e595d 100644
--- a/end_to_end_tests/golden-master/my_test_api_client/models/a_model.py
+++ b/end_to_end_tests/golden-master/my_test_api_client/models/a_model.py
@@ -1,9 +1,9 @@
 from __future__ import annotations
 
+import datetime
 from dataclasses import dataclass, field
-from datetime import date, datetime
 from typing import Any, Dict, List, Optional, Union, cast
 
 from .an_enum import AnEnum
 from .different_enum import DifferentEnum
 
@@ -11,21 +11,23 @@ from .different_enum import DifferentEnum
 @dataclass
 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]
-    a_date: date
+    some_dict: Dict[Any, Any]
+    a_camel_date_time: Union[datetime.datetime, datetime.date]
+    a_date: datetime.date
     nested_list_of_enums: Optional[List[List[DifferentEnum]]] = field(
         default_factory=lambda: cast(Optional[List[List[DifferentEnum]]], [])
     )
-    some_dict: Optional[Dict[Any, Any]] = field(default_factory=lambda: cast(Optional[Dict[Any, Any]], {}))
 
     def to_dict(self) -> Dict[str, Any]:
         an_enum_value = self.an_enum_value.value
 
-        if isinstance(self.a_camel_date_time, datetime):
+        some_dict = self.some_dict
+
+        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()
 
@@ -42,39 +44,39 @@ class AModel:
 
                     nested_list_of_enums_item.append(nested_list_of_enums_item_item)
 
                 nested_list_of_enums.append(nested_list_of_enums_item)
 
-        some_dict = self.some_dict
-
         return {
             "an_enum_value": an_enum_value,
+            "some_dict": some_dict,
             "aCamelDateTime": a_camel_date_time,
             "a_date": a_date,
             "nested_list_of_enums": nested_list_of_enums,
-            "some_dict": some_dict,
         }
 
     @staticmethod
     def from_dict(d: Dict[str, Any]) -> AModel:
         an_enum_value = AnEnum(d["an_enum_value"])
 
-        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime, date]:
-            a_camel_date_time: Union[datetime, date]
+        some_dict = d["some_dict"]
+
+        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, datetime.date]:
+            a_camel_date_time: Union[datetime.datetime, datetime.date]
             try:
-                a_camel_date_time = datetime.fromisoformat(d["aCamelDateTime"])
+                a_camel_date_time = datetime.datetime.fromisoformat(d["aCamelDateTime"])
 
                 return a_camel_date_time
             except:
                 pass
-            a_camel_date_time = date.fromisoformat(d["aCamelDateTime"])
+            a_camel_date_time = datetime.date.fromisoformat(d["aCamelDateTime"])
 
             return a_camel_date_time
 
         a_camel_date_time = _parse_a_camel_date_time(d["aCamelDateTime"])
 
-        a_date = date.fromisoformat(d["a_date"])
+        a_date = datetime.date.fromisoformat(d["a_date"])
 
         nested_list_of_enums = []
         for nested_list_of_enums_item_data in d.get("nested_list_of_enums") or []:
             nested_list_of_enums_item = []
             for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data:
@@ -82,14 +84,12 @@ class AModel:
 
                 nested_list_of_enums_item.append(nested_list_of_enums_item_item)
 
             nested_list_of_enums.append(nested_list_of_enums_item)
 
-        some_dict = d.get("some_dict")
-
         return AModel(
             an_enum_value=an_enum_value,
+            some_dict=some_dict,
             a_camel_date_time=a_camel_date_time,
             a_date=a_date,
             nested_list_of_enums=nested_list_of_enums,
-            some_dict=some_dict,
         )
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index d8846ac..1f0f32d 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -94,11 +94,13 @@ class Project:
         self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
         self.project_dir: Path = Path.cwd() / self.project_name
 
         self.package_name: str = self.package_name_override or self.project_name.replace("-", "_")
         self.package_dir: Path = self.project_dir / self.package_name
-        self.package_description: str = f"A client library for accessing {self.openapi.title}"
+        self.package_description: str = utils.remove_string_escapes(
+            f"A client library for accessing {self.openapi.title}"
+        )
         self.version: str = openapi.version
 
         self.env.filters.update(self.TEMPLATE_FILTERS)
 
     def build(self) -> Sequence[GeneratorError]:
diff --git a/openapi_python_client/parser/errors.py b/openapi_python_client/parser/errors.py
index c440551..6e432ff 100644
--- a/openapi_python_client/parser/errors.py
+++ b/openapi_python_client/parser/errors.py
@@ -35,5 +35,9 @@ class ParseError(GeneratorError):
 @dataclass
 class PropertyError(ParseError):
     """ Error raised when there's a problem creating a Property """
 
     header = "Problem creating a Property: "
+
+
+class ValidationError(Exception):
+    pass
diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py
index 9f100d3..d9d13f2 100644
--- a/openapi_python_client/parser/openapi.py
+++ b/openapi_python_client/parser/openapi.py
@@ -6,10 +6,11 @@ from enum import Enum
 from typing import Any, Dict, List, Optional, Set, Union
 
 from pydantic import ValidationError
 
 from .. import schema as oai
+from .. import utils
 from .errors import GeneratorError, ParseError, PropertyError
 from .properties import EnumProperty, Property, property_from_data
 from .reference import Reference
 from .responses import ListRefResponse, RefResponse, Response, response_from_data
 
@@ -180,11 +181,11 @@ class Endpoint:
             return ParseError(data=data, detail="Path operations with operationId are not yet supported")
 
         endpoint = Endpoint(
             path=path,
             method=method,
-            description=data.description,
+            description=utils.remove_string_escapes(data.description) if data.description else "",
             name=data.operationId,
             requires_security=bool(data.security),
             tag=tag,
         )
 
diff --git a/openapi_python_client/parser/properties.py b/openapi_python_client/parser/properties.py
index c63bee4..fb2d2d6 100644
--- a/openapi_python_client/parser/properties.py
+++ b/openapi_python_client/parser/properties.py
@@ -1,13 +1,14 @@
 from __future__ import annotations
 
 from dataclasses import InitVar, dataclass, field
+from datetime import date, datetime
 from typing import Any, ClassVar, Dict, Generic, List, Optional, Set, TypeVar, Union
 
 from .. import schema as oai
 from .. import utils
-from .errors import PropertyError
+from .errors import PropertyError, ValidationError
 from .reference import Reference
 
 
 @dataclass
 class Property:
@@ -17,10 +18,13 @@ class Property:
     Attributes:
         template: Name of the template file (if any) to use for this property. Must be stored in
             templates/property_templates and must contain two macros: construct and transform. Construct will be used to
             build this property from JSON data (a response from an API). Transform will be used to convert this property
             to JSON data (when sending a request to the API).
+
+    Raises:
+        ValidationError: Raised when the default value fails to be converted to the expected type
     """
 
     name: str
     required: bool
     default: Optional[Any]
@@ -30,14 +34,20 @@ class Property:
 
     python_name: str = field(init=False)
 
     def __post_init__(self) -> None:
         self.python_name = utils.snake_case(self.name)
+        if self.default is not None:
+            self.default = self._validate_default(default=self.default)
+
+    def _validate_default(self, default: Any) -> Any:
+        """ Check that the default value is valid for the property's type + perform any necessary sanitization """
+        raise ValidationError
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return self._type_string
         return f"Optional[{self._type_string}]"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -72,55 +82,67 @@ class StringProperty(Property):
     max_length: Optional[int] = None
     pattern: Optional[str] = None
 
     _type_string: ClassVar[str] = "str"
 
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f'"{self.default}"'
+    def _validate_default(self, default: Any) -> str:
+        return f'"{utils.remove_string_escapes(default)}"'
 
 
 @dataclass
 class DateTimeProperty(Property):
     """
     A property of type datetime.datetime
     """
 
-    _type_string: ClassVar[str] = "datetime"
+    _type_string: ClassVar[str] = "datetime.datetime"
     template: ClassVar[str] = "datetime_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
         Get a set of import strings that should be included when this property is used somewhere
 
         Args:
             prefix: A prefix to put before any relative (local) module names.
         """
         imports = super().get_imports(prefix=prefix)
-        imports.update({"from datetime import datetime", "from typing import cast"})
+        imports.update({"import datetime", "from typing import cast"})
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        for format_string in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"):
+            try:
+                return repr(datetime.strptime(default, format_string))
+            except (TypeError, ValueError):
+                continue
+        raise ValidationError
+
 
 @dataclass
 class DateProperty(Property):
     """ A property of type datetime.date """
 
-    _type_string: ClassVar[str] = "date"
+    _type_string: ClassVar[str] = "datetime.date"
     template: ClassVar[str] = "date_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
         Get a set of import strings that should be included when this property is used somewhere
 
         Args:
             prefix: A prefix to put before any relative (local) module names.
         """
         imports = super().get_imports(prefix=prefix)
-        imports.update({"from datetime import date", "from typing import cast"})
+        imports.update({"import datetime", "from typing import cast"})
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        try:
+            return repr(date.fromisoformat(default))
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class FileProperty(Property):
     """ A property used for uploading files """
 
@@ -144,25 +166,41 @@ class FloatProperty(Property):
     """ A property of type float """
 
     default: Optional[float] = None
     _type_string: ClassVar[str] = "float"
 
+    def _validate_default(self, default: Any) -> float:
+        try:
+            return float(default)
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class IntProperty(Property):
     """ A property of type int """
 
     default: Optional[int] = None
     _type_string: ClassVar[str] = "int"
 
+    def _validate_default(self, default: Any) -> int:
+        try:
+            return int(default)
+        except (TypeError, ValueError) as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class BooleanProperty(Property):
     """ Property for bool """
 
     _type_string: ClassVar[str] = "bool"
 
+    def _validate_default(self, default: Any) -> bool:
+        # no try/except needed as anything that comes from the initial load from json/yaml will be boolable
+        return bool(default)
+
 
 InnerProp = TypeVar("InnerProp", bound=Property)
 
 
 @dataclass
@@ -170,18 +208,13 @@ class ListProperty(Property, Generic[InnerProp]):
     """ A property representing a list (array) of other properties """
 
     inner_property: InnerProp
     template: ClassVar[str] = "list_property.pyi"
 
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
-
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return f"List[{self.inner_property.get_type_string()}]"
         return f"Optional[List[{self.inner_property.get_type_string()}]]"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -196,23 +229,33 @@ class ListProperty(Property, Generic[InnerProp]):
         if self.default is not None:
             imports.add("from dataclasses import field")
             imports.add("from typing import cast")
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        if not isinstance(default, list):
+            raise ValidationError()
+
+        default = list(map(self.inner_property._validate_default, default))
+        if isinstance(self.inner_property, RefProperty):  # Fix enums to use the actual value
+            default = str(default).replace("'", "")
+
+        return f"field(default_factory=lambda: cast({self.get_type_string()}, {default}))"
+
 
 @dataclass
 class UnionProperty(Property):
     """ A property representing a Union (anyOf) of other properties """
 
     inner_properties: List[Property]
     template: ClassVar[str] = "union_property.pyi"
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
         inner_types = [p.get_type_string() for p in self.inner_properties]
         inner_prop_string = ", ".join(inner_types)
-        if self.required:
+        if self.required or no_optional:
             return f"Union[{inner_prop_string}]"
         return f"Optional[Union[{inner_prop_string}]]"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -225,10 +268,19 @@ class UnionProperty(Property):
         for inner_prop in self.inner_properties:
             imports.update(inner_prop.get_imports(prefix=prefix))
         imports.add("from typing import Union")
         return imports
 
+    def _validate_default(self, default: Any) -> Any:
+        for property in self.inner_properties:
+            try:
+                val = property._validate_default(default)
+                return val
+            except ValidationError:
+                continue
+        raise ValidationError()
+
 
 _existing_enums: Dict[str, EnumProperty] = {}
 
 
 @dataclass
@@ -240,24 +292,21 @@ class EnumProperty(Property):
     title: InitVar[str]
 
     template: ClassVar[str] = "enum_property.pyi"
 
     def __post_init__(self, title: str) -> None:  # type: ignore
-        super().__post_init__()
         reference = Reference.from_ref(title)
         dedup_counter = 0
         while reference.class_name in _existing_enums:
             existing = _existing_enums[reference.class_name]
             if self.values == existing.values:
                 break  # This is the same Enum, we're good
             dedup_counter += 1
             reference = Reference.from_ref(f"{reference.class_name}{dedup_counter}")
 
         self.reference = reference
-        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]}"
+        super().__post_init__()
         _existing_enums[self.reference.class_name] = self
 
     @staticmethod
     def get_all_enums() -> Dict[str, EnumProperty]:
         """ Get all the EnumProperties that have been registered keyed by class name """
@@ -266,14 +315,14 @@ class EnumProperty(Property):
     @staticmethod
     def get_enum(name: str) -> Optional[EnumProperty]:
         """ Get all the EnumProperties that have been registered keyed by class name """
         return _existing_enums.get(name)
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
 
-        if self.required:
+        if self.required or no_optional:
             return self.reference.class_name
         return f"Optional[{self.reference.class_name}]"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -296,14 +345,22 @@ class EnumProperty(Property):
                 key = value.upper()
             else:
                 key = f"VALUE_{i}"
             if key in output:
                 raise ValueError(f"Duplicate key {key} in Enum")
-            output[key] = value
+            sanitized_key = utils.fix_keywords(utils.sanitize(key))
+            output[sanitized_key] = utils.remove_string_escapes(value)
 
         return output
 
+    def _validate_default(self, default: Any) -> str:
+        inverse_values = {v: k for k, v in self.values.items()}
+        try:
+            return f"{self.reference.class_name}.{inverse_values[default]}"
+        except KeyError as e:
+            raise ValidationError() from e
+
 
 @dataclass
 class RefProperty(Property):
     """ A property which refers to another Schema """
 
@@ -314,13 +371,13 @@ class RefProperty(Property):
         enum = EnumProperty.get_enum(self.reference.class_name)
         if enum:
             return "enum_property.pyi"
         return "ref_property.pyi"
 
-    def get_type_string(self) -> str:
+    def get_type_string(self, no_optional: bool = False) -> str:
         """ Get a string representation of type that should be used when declaring this property """
-        if self.required:
+        if self.required or no_optional:
             return self.reference.class_name
         return f"Optional[{self.reference.class_name}]"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
@@ -337,21 +394,24 @@ class RefProperty(Property):
                 "from typing import cast",
             }
         )
         return imports
 
+    def _validate_default(self, default: Any) -> Any:
+        enum = EnumProperty.get_enum(self.reference.class_name)
+        if enum:
+            return enum._validate_default(default)
+        else:
+            raise ValidationError
+
 
 @dataclass
 class DictProperty(Property):
     """ Property that is a general Dict """
 
     _type_string: ClassVar[str] = "Dict[Any, Any]"
-
-    def __post_init__(self) -> None:
-        super().__post_init__()
-        if self.default is not None:
-            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
+    template: ClassVar[str] = "dict_property.pyi"
 
     def get_imports(self, *, prefix: str) -> Set[str]:
         """
         Get a set of import strings that should be included when this property is used somewhere
 
@@ -363,10 +423,15 @@ class DictProperty(Property):
         if self.default is not None:
             imports.add("from dataclasses import field")
             imports.add("from typing import cast")
         return imports
 
+    def _validate_default(self, default: Any) -> str:
+        if isinstance(default, dict):
+            return repr(default)
+        raise ValidationError
+
 
 def _string_based_property(
     name: str, required: bool, data: oai.Schema
 ) -> Union[StringProperty, DateProperty, DateTimeProperty, FileProperty]:
     """ Construct a Property from the type "string" """
@@ -379,14 +444,15 @@ def _string_based_property(
         return FileProperty(name=name, required=required, default=data.default)
     else:
         return StringProperty(name=name, default=data.default, required=required, pattern=data.pattern)
 
 
-def property_from_data(
+def _property_from_data(
     name: str, required: bool, data: Union[oai.Reference, oai.Schema]
 ) -> Union[Property, PropertyError]:
     """ Generate a Property from the OpenAPI dictionary representation of it """
+    name = utils.remove_string_escapes(name)
     if isinstance(data, oai.Reference):
         return RefProperty(name=name, required=required, reference=Reference.from_ref(data.ref), default=None)
     if data.enum:
         return EnumProperty(
             name=name,
@@ -421,5 +487,14 @@ def property_from_data(
             return PropertyError(data=inner_prop.data, detail=f"invalid data in items of array {name}")
         return ListProperty(name=name, required=required, default=data.default, inner_property=inner_prop,)
     elif data.type == "object":
         return DictProperty(name=name, required=required, default=data.default)
     return PropertyError(data=data, detail=f"unknown type {data.type}")
+
+
+def property_from_data(
+    name: str, required: bool, data: Union[oai.Reference, oai.Schema]
+) -> Union[Property, PropertyError]:
+    try:
+        return _property_from_data(name=name, required=required, data=data)
+    except ValidationError:
+        return PropertyError(detail="Failed to validate default value", data=data)
diff --git a/openapi_python_client/templates/async_endpoint_module.pyi b/openapi_python_client/templates/async_endpoint_module.pyi
index 38836ec..a781b8f 100644
--- a/openapi_python_client/templates/async_endpoint_module.pyi
+++ b/openapi_python_client/templates/async_endpoint_module.pyi
@@ -68,11 +68,11 @@ async def {{ endpoint.name | snakecase }}(
             {% endif %}
             {% if endpoint.multipart_body_reference %}
             files=multipart_data.to_dict(),
             {% endif %}
             {% if endpoint.json_body %}
-            json={{ "json_" +  endpoint.json_body.python_name }},
+            json={{ "json_" + endpoint.json_body.python_name }},
             {% endif %}
             {% if endpoint.query_parameters %}
             params=params,
             {% endif %}
         )
diff --git a/openapi_python_client/templates/property_templates/date_property.pyi b/openapi_python_client/templates/property_templates/date_property.pyi
index 7a78fc6..736557e 100644
--- a/openapi_python_client/templates/property_templates/date_property.pyi
+++ b/openapi_python_client/templates/property_templates/date_property.pyi
@@ -1,12 +1,12 @@
 {% macro construct(property, source) %}
 {% if property.required %}
-{{ property.python_name }} = date.fromisoformat({{ source }})
+{{ property.python_name }} = datetime.date.fromisoformat({{ source }})
 {% else %}
 {{ property.python_name }} = None
 if {{ source }} is not None:
-    {{ property.python_name }} = date.fromisoformat(cast(str, {{ source }}))
+    {{ property.python_name }} = datetime.date.fromisoformat(cast(str, {{ source }}))
 {% endif %}
 {% endmacro %}
 
 {% macro transform(property, source, destination) %}
 {% if property.required %}
diff --git a/openapi_python_client/templates/property_templates/datetime_property.pyi b/openapi_python_client/templates/property_templates/datetime_property.pyi
index 6de916e..5b55f9c 100644
--- a/openapi_python_client/templates/property_templates/datetime_property.pyi
+++ b/openapi_python_client/templates/property_templates/datetime_property.pyi
@@ -1,12 +1,12 @@
 {% macro construct(property, source) %}
 {% if property.required %}
-{{ property.python_name }} = datetime.fromisoformat({{ source }})
+{{ property.python_name }} = datetime.datetime.fromisoformat({{ source }})
 {% else %}
 {{ property.python_name }} = None
 if {{ source }} is not None:
-    {{ property.python_name }} = datetime.fromisoformat(cast(str, {{ source }}))
+    {{ property.python_name }} = datetime.datetime.fromisoformat(cast(str, {{ source }}))
 {% endif %}
 {% endmacro %}
 
 {% macro transform(property, source, destination) %}
 {% if property.required %}
diff --git a/openapi_python_client/templates/property_templates/dict_property.pyi b/openapi_python_client/templates/property_templates/dict_property.pyi
new file mode 100644
index 0000000..2feabd1
--- /dev/null
+++ b/openapi_python_client/templates/property_templates/dict_property.pyi
@@ -0,0 +1,17 @@
+{% macro construct(property, source) %}
+{% if property.required %}
+{{ property.python_name }} = {{ source }}
+{% else %}
+{{ property.python_name }} = None
+if {{ source }} is not None:
+    {{ property.python_name }} = {{ source }}
+{% endif %}
+{% endmacro %}
+
+{% macro transform(property, source, destination) %}
+{% if property.required %}
+{{ destination }} = {{ source }}
+{% else %}
+{{ destination }} = {{ source }} if {{ source }} else None
+{% endif %}
+{% endmacro %}
diff --git a/openapi_python_client/templates/property_templates/union_property.pyi b/openapi_python_client/templates/property_templates/union_property.pyi
index 228c4fc..cbc13e7 100644
--- a/openapi_python_client/templates/property_templates/union_property.pyi
+++ b/openapi_python_client/templates/property_templates/union_property.pyi
@@ -22,17 +22,17 @@ def _parse_{{ property.python_name }}(data: Dict[str, Any]) -> {{ property.get_t
 {% endmacro %}
 
 {% macro transform(property, source, destination) %}
 {% if not property.required %}
 if {{ source }} is None:
-    {{ destination }} = None
+    {{ destination }}: {{ property.get_type_string() }} = None
 {% endif %}
 {% for inner_property in property.inner_properties %}
     {% if loop.first and property.required %}{# No if None statement before this #}
-if isinstance({{ source }}, {{ inner_property.get_type_string() }}):
+if isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
     {% elif not loop.last %}
-elif isinstance({{ source }}, {{ inner_property.get_type_string() }}):
+elif isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
     {% else %}
 else:
     {% endif %}
 {% if inner_property.template %}
 {% from "property_templates/" + inner_property.template import transform %}
diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py
index bf20931..22ad2c9 100644
--- a/openapi_python_client/utils.py
+++ b/openapi_python_client/utils.py
@@ -1,25 +1,36 @@
 import re
+from keyword import iskeyword
 
 import stringcase
 
 
-def _sanitize(value: str) -> str:
-    return re.sub(r"[^\w _-]+", "", value)
+def sanitize(value: str) -> str:
+    return re.sub(r"[^\w _\-]+", "", value)
+
+
+def fix_keywords(value: str) -> str:
+    if iskeyword(value):
+        return f"{value}_"
+    return value
 
 
 def group_title(value: str) -> str:
-    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ -_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
+    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ \-_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
     value = re.sub(r"(^|[ _-])([A-Z])", lambda m: m.group(1) + m.group(2).lower(), value)
     return value
 
 
 def snake_case(value: str) -> str:
-    return stringcase.snakecase(group_title(_sanitize(value)))
+    return fix_keywords(stringcase.snakecase(group_title(sanitize(value))))
 
 
 def pascal_case(value: str) -> str:
-    return stringcase.pascalcase(_sanitize(value))
+    return fix_keywords(stringcase.pascalcase(sanitize(value)))
 
 
 def kebab_case(value: str) -> str:
-    return stringcase.spinalcase(group_title(_sanitize(value)))
+    return fix_keywords(stringcase.spinalcase(group_title(sanitize(value))))
+
+
+def remove_string_escapes(value: str) -> str:
+    return value.replace('"', r"\"")
diff --git a/tests/test_openapi_parser/test_openapi.py b/tests/test_openapi_parser/test_openapi.py
index b025e73..38a0dec 100644
--- a/tests/test_openapi_parser/test_openapi.py
+++ b/tests/test_openapi_parser/test_openapi.py
@@ -1,10 +1,10 @@
 from pydantic import ValidationError
 from pydantic.error_wrappers import ErrorWrapper
 
 import openapi_python_client.schema as oai
-from openapi_python_client import GeneratorError
+from openapi_python_client import GeneratorError, utils
 from openapi_python_client.parser.errors import ParseError
 
 MODULE_NAME = "openapi_python_client.parser.openapi"
 
 
@@ -544,10 +544,12 @@ class TestEndpoint:
             operationId=mocker.MagicMock(),
             security={"blah": "bloo"},
             responses=mocker.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")
 
         assert endpoint == _add_body.return_value
 
         _add_parameters.assert_called_once_with(
diff --git a/tests/test_openapi_parser/test_properties.py b/tests/test_openapi_parser/test_properties.py
index ea3ba34..740767d 100644
--- a/tests/test_openapi_parser/test_properties.py
+++ b/tests/test_openapi_parser/test_properties.py
@@ -1,14 +1,28 @@
+from datetime import date, datetime
+
 import pytest
 
 import openapi_python_client.schema as oai
-from openapi_python_client.parser.errors import PropertyError
+from openapi_python_client.parser.errors import PropertyError, ValidationError
+from openapi_python_client.parser.reference import Reference
 
 MODULE_NAME = "openapi_python_client.parser.properties"
 
 
 class TestProperty:
+    def test___post_init(self, mocker):
+        from openapi_python_client.parser.properties import Property
+
+        validate_default = mocker.patch(f"{MODULE_NAME}.Property._validate_default")
+
+        Property(name="a name", required=True, default=None)
+        validate_default.assert_not_called()
+
+        Property(name="a name", required=True, default="the default value")
+        validate_default.assert_called_with(default="the default value")
+
     def test_get_type_string(self):
         from openapi_python_client.parser.properties import Property
 
         p = Property(name="test", required=True, default=None)
         p._type_string = "TestType"
@@ -41,68 +55,97 @@ class TestProperty:
         assert p.get_imports(prefix="") == set()
 
         p.required = False
         assert p.get_imports(prefix="") == {"from typing import Optional"}
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import Property
 
-class TestStringProperty:
-    def test___post_init__(self):
-        from openapi_python_client.parser.properties import StringProperty
+        # should be okay if default isn't specified
+        p = Property(name="a name", required=True, default=None)
 
-        sp = StringProperty(name="test", required=True, default="A Default Value",)
+        with pytest.raises(ValidationError):
+            p._validate_default("a default value")
 
-        assert sp.default == '"A Default Value"'
+        with pytest.raises(ValidationError):
+            p = Property(name="a name", required=True, default="")
 
+
+class TestStringProperty:
     def test_get_type_string(self):
         from openapi_python_client.parser.properties import StringProperty
 
         p = StringProperty(name="test", required=True, default=None)
 
         assert p.get_type_string() == "str"
         p.required = False
         assert p.get_type_string() == "Optional[str]"
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import StringProperty
+
+        p = StringProperty(name="a name", required=True, default="the default value")
+        assert p.default == '"the default value"'
+
 
 class TestDateTimeProperty:
     def test_get_imports(self, mocker):
         from openapi_python_client.parser.properties import DateTimeProperty
 
         name = mocker.MagicMock()
         mocker.patch("openapi_python_client.utils.snake_case")
         p = DateTimeProperty(name=name, required=True, default=None)
         assert p.get_imports(prefix="") == {
-            "from datetime import datetime",
+            "import datetime",
             "from typing import cast",
         }
 
         p.required = False
         assert p.get_imports(prefix="") == {
             "from typing import Optional",
-            "from datetime import datetime",
+            "import datetime",
             "from typing import cast",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DateTimeProperty
+
+        with pytest.raises(ValidationError):
+            p = DateTimeProperty(name="a name", required=True, default="not a datetime")
+
+        p = DateTimeProperty(name="a name", required=True, default="2017-07-21T17:32:28Z")
+        assert p.default == "datetime.datetime(2017, 7, 21, 17, 32, 28, tzinfo=datetime.timezone.utc)"
+
 
 class TestDateProperty:
     def test_get_imports(self, mocker):
         from openapi_python_client.parser.properties import DateProperty
 
         name = mocker.MagicMock()
         mocker.patch("openapi_python_client.utils.snake_case")
         p = DateProperty(name=name, required=True, default=None)
         assert p.get_imports(prefix="") == {
-            "from datetime import date",
+            "import datetime",
             "from typing import cast",
         }
 
         p.required = False
         assert p.get_imports(prefix="") == {
             "from typing import Optional",
-            "from datetime import date",
+            "import datetime",
             "from typing import cast",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DateProperty
+
+        with pytest.raises(ValidationError):
+            p = DateProperty(name="a name", required=True, default="not a date")
+
+        p = DateProperty(name="a name", required=True, default="1010-10-10")
+        assert p.default == "datetime.date(1010, 10, 10)"
+
 
 class TestFileProperty:
     def test_get_imports(self, mocker):
         from openapi_python_client.parser.properties import FileProperty
 
@@ -117,10 +160,58 @@ class TestFileProperty:
             "from typing import Optional",
             f"from {prefix}.types import File",
             "from dataclasses import astuple",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import FileProperty
+
+        # should be okay if default isn't specified
+        p = FileProperty(name="a name", required=True, default=None)
+
+        with pytest.raises(ValidationError):
+            p = FileProperty(name="a name", required=True, default="")
+
+
+class TestFloatProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import FloatProperty
+
+        # should be okay if default isn't specified
+        p = FloatProperty(name="a name", required=True, default=None)
+
+        p = FloatProperty(name="a name", required=True, default="123.123")
+        assert p.default == 123.123
+
+        with pytest.raises(ValidationError):
+            p = FloatProperty(name="a name", required=True, default="not a float")
+
+
+class TestIntProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import IntProperty
+
+        # should be okay if default isn't specified
+        p = IntProperty(name="a name", required=True, default=None)
+
+        p = IntProperty(name="a name", required=True, default="123")
+        assert p.default == 123
+
+        with pytest.raises(ValidationError):
+            p = IntProperty(name="a name", required=True, default="not an int")
+
+
+class TestBooleanProperty:
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import BooleanProperty
+
+        # should be okay if default isn't specified
+        p = BooleanProperty(name="a name", required=True, default=None)
+
+        p = BooleanProperty(name="a name", required=True, default="Literally anything will work")
+        assert p.default == True
+
 
 class TestListProperty:
     def test_get_type_string(self, mocker):
         from openapi_python_client.parser.properties import ListProperty
 
@@ -163,10 +254,34 @@ class TestListProperty:
             "from typing import List",
             "from typing import cast",
             "from dataclasses import field",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import ListProperty
+
+        inner_property = mocker.MagicMock()
+        inner_type_string = mocker.MagicMock()
+        inner_property.get_type_string.return_value = inner_type_string
+        inner_property._validate_default.return_value = "y"
+
+        p = ListProperty(name="a name", required=True, default=["x"], inner_property=inner_property)
+        assert p.default == f"field(default_factory=lambda: cast(List[{inner_type_string}], ['y']))"
+
+        with pytest.raises(ValidationError):
+            p = ListProperty(name="a name", required=True, default="x", inner_property=inner_property)
+
+    def test__validate_default_enum_items(self, mocker):
+        from openapi_python_client.parser.properties import ListProperty, RefProperty
+
+        inner_enum_property = mocker.MagicMock(spec=RefProperty)
+        inner_enum_property.get_type_string.return_value = "AnEnum"
+        inner_enum_property._validate_default.return_value = "AnEnum.val1"
+
+        p = ListProperty(name="a name", required=True, default=["val1"], inner_property=inner_enum_property)
+        assert p.default == f"field(default_factory=lambda: cast(List[AnEnum], [AnEnum.val1]))"
+
 
 class TestUnionProperty:
     def test_get_type_string(self, mocker):
         from openapi_python_client.parser.properties import UnionProperty
 
@@ -207,10 +322,32 @@ class TestUnionProperty:
             inner_import_2,
             "from typing import Union",
             "from typing import Optional",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import UnionProperty
+
+        inner_property_1 = mocker.MagicMock()
+        inner_property_1.get_type_string.return_value = "inner_type_string_1"
+        inner_property_1._validate_default.side_effect = ValidationError()
+        inner_property_2 = mocker.MagicMock()
+        inner_property_2.get_type_string.return_value = "inner_type_string_2"
+        inner_property_2._validate_default.return_value = "the default value"
+        p = UnionProperty(
+            name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
+        )
+
+        assert p.default == "the default value"
+
+        inner_property_2._validate_default.side_effect = ValidationError()
+
+        with pytest.raises(ValidationError):
+            p = UnionProperty(
+                name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
+            )
+
 
 class TestEnumProperty:
     def test___post_init__(self, mocker):
         name = mocker.MagicMock()
 
@@ -325,10 +462,28 @@ class TestEnumProperty:
 
         properties._existing_enums = {"test": "an enum"}
         assert properties.EnumProperty.get_enum("test") == "an enum"
         properties._existing_enums = {}
 
+    def test__validate_default(self, mocker):
+        fake_reference = mocker.MagicMock(class_name="MyTestEnum", module_name="my_test_enum")
+        mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)
+
+        from openapi_python_client.parser import properties
+
+        enum_property = properties.EnumProperty(
+            name="test", required=True, default="test", values={"TEST": "test"}, title="a_title"
+        )
+        assert enum_property.default == "MyTestEnum.TEST"
+
+        with pytest.raises(ValidationError):
+            enum_property = properties.EnumProperty(
+                name="test", required=True, default="bad_val", values={"TEST": "test"}, title="a_title"
+            )
+
+        properties._existing_enums = {}
+
 
 class TestRefProperty:
     def test_template(self, mocker):
         from openapi_python_client.parser.properties import RefProperty
 
@@ -374,18 +529,24 @@ class TestRefProperty:
             "from typing import Dict",
             "from typing import cast",
             "from typing import Optional",
         }
 
+    def test__validate_default(self, mocker):
+        from openapi_python_client.parser.properties import RefProperty
+
+        with pytest.raises(ValidationError):
+            p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
 
-class TestDictProperty:
-    def test___post_init__(self):
-        from openapi_python_client.parser.properties import DictProperty
+        enum_property = mocker.MagicMock()
+        enum_property._validate_default.return_value = "val1"
+        mocker.patch(f"{MODULE_NAME}.EnumProperty.get_enum", return_value=enum_property)
+        p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
+        assert p.default == "val1"
 
-        p = DictProperty(name="blah", required=True, default={})
-        assert p.default == "field(default_factory=lambda: cast(Dict[Any, Any], {}))"
 
+class TestDictProperty:
     def test_get_imports(self, mocker):
         from openapi_python_client.parser.properties import DictProperty
 
         name = mocker.MagicMock()
         mocker.patch("openapi_python_client.utils.snake_case")
@@ -407,17 +568,26 @@ class TestDictProperty:
             "from typing import Dict",
             "from typing import cast",
             "from dataclasses import field",
         }
 
+    def test__validate_default(self):
+        from openapi_python_client.parser.properties import DictProperty
+
+        p = DictProperty(name="a name", required=True, default={"key": "value"})
+
+        with pytest.raises(ValidationError):
+            p = DictProperty(name="a name", required=True, default="not a dict")
+
 
 class TestPropertyFromData:
     def test_property_from_data_enum(self, mocker):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = mocker.MagicMock(title=None)
         EnumProperty = mocker.patch(f"{MODULE_NAME}.EnumProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -441,10 +611,11 @@ class TestPropertyFromData:
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Reference.construct(ref=mocker.MagicMock())
         from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref")
         RefProperty = mocker.patch(f"{MODULE_NAME}.RefProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -455,10 +626,12 @@ class TestPropertyFromData:
     def test_property_from_data_string(self, mocker):
         _string_based_property = mocker.patch(f"{MODULE_NAME}._string_based_property")
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema.construct(type="string")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
+
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
         assert p == _string_based_property.return_value
@@ -476,10 +649,11 @@ class TestPropertyFromData:
     def test_property_from_data_simple_types(self, mocker, openapi_type, python_type):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema.construct(type=openapi_type)
         clazz = mocker.patch(f"{MODULE_NAME}.{python_type}")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -499,16 +673,17 @@ class TestPropertyFromData:
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema(type="array", items={"type": "number", "default": "0.0"},)
         ListProperty = mocker.patch(f"{MODULE_NAME}.ListProperty")
         FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
-        FloatProperty.assert_called_once_with(name=f"{name}_item", required=True, default="0.0")
+        FloatProperty.assert_called_once_with(name=name, required=True, default="0.0")
         ListProperty.assert_called_once_with(
             name=name, required=required, default=None, inner_property=FloatProperty.return_value
         )
         assert p == ListProperty.return_value
 
@@ -525,10 +700,11 @@ class TestPropertyFromData:
 
     def test_property_from_data_array_invalid_items(self, mocker):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema(type="array", items={},)
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -539,10 +715,11 @@ class TestPropertyFromData:
         required = mocker.MagicMock()
         data = oai.Schema(anyOf=[{"type": "number", "default": "0.0"}, {"type": "integer", "default": "0"},])
         UnionProperty = mocker.patch(f"{MODULE_NAME}.UnionProperty")
         FloatProperty = mocker.patch(f"{MODULE_NAME}.FloatProperty")
         IntProperty = mocker.patch(f"{MODULE_NAME}.IntProperty")
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -558,10 +735,11 @@ class TestPropertyFromData:
 
     def test_property_from_data_union_bad_type(self, mocker):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
         data = oai.Schema(anyOf=[{}])
+        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
 
         from openapi_python_client.parser.properties import property_from_data
 
         p = property_from_data(name=name, required=required, data=data)
 
@@ -586,10 +764,21 @@ class TestPropertyFromData:
         data = oai.Schema()
         assert property_from_data(name="blah", required=True, data=data) == PropertyError(
             data=data, detail="Schemas must either have one of enum, anyOf, or type defined."
         )
 
+    def test_property_from_data_validation_error(self, mocker):
+        from openapi_python_client.parser.errors import PropertyError
+        from openapi_python_client.parser.properties import property_from_data
+
+        mocker.patch(f"{MODULE_NAME}._property_from_data").side_effect = ValidationError()
+
+        data = oai.Schema()
+        assert property_from_data(name="blah", required=True, data=data) == PropertyError(
+            detail="Failed to validate default value", data=data
+        )
+
 
 class TestStringBasedProperty:
     def test__string_based_property_no_format(self, mocker):
         name = mocker.MagicMock()
         required = mocker.MagicMock()
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3bed213..2541272 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -20,5 +20,17 @@ def test_snake_case_from_camel():
     assert utils.snake_case("httpResponseLowerCamel") == "http_response_lower_camel"
 
 
 def test_kebab_case():
     assert utils.kebab_case("keep_alive") == "keep-alive"
+
+
+def test__sanitize():
+    assert utils.sanitize("something*~with lots_- of weird things}=") == "somethingwith lots_- of weird things"
+
+
+def test_no_string_escapes():
+    assert utils.remove_string_escapes('an "evil" string') == 'an \\"evil\\" string'
+
+
+def test__fix_keywords():
+    assert utils.fix_keywords("None") == "None_"

Vulnerable code: ## 0.5.3 - Unrelease
from fastapi import APIRouter, FastAPI, File, Header, Query, UploadFile
    some_dict: Dict[str, str] = {}
def get_list(an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...)):
                        },
                        "default": {}
from dataclasses import asdict
from datetime import date, datetime
    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
    if isinstance(some_date, date):
from dataclasses import asdict
from datetime import date, datetime
    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[date, datetime],
    if isinstance(some_date, date):
from datetime import date, datetime
    a_camel_date_time: Union[datetime, date]
    a_date: date
    some_dict: Optional[Dict[Any, Any]] = field(default_factory=lambda: cast(Optional[Dict[Any, Any]], {}))
        if isinstance(self.a_camel_date_time, datetime):
        some_dict = self.some_dict

            "some_dict": some_dict,
        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime, date]:
            a_camel_date_time: Union[datetime, date]
                a_camel_date_time = datetime.fromisoformat(d["aCamelDateTime"])
            a_camel_date_time = date.fromisoformat(d["aCamelDateTime"])
        a_date = date.fromisoformat(d["a_date"])
        some_dict = d.get("some_dict")

            some_dict=some_dict,
        self.package_description: str = f"A client library for accessing {self.openapi.title}"
            description=data.description,
from .errors import PropertyError
    def get_type_string(self) -> str:
        if self.required:
    def __post_init__(self) -> None:
        super().__post_init__()
        if self.default is not None:
            self.default = f'"{self.default}"'
    _type_string: ClassVar[str] = "datetime"
        imports.update({"from datetime import datetime", "from typing import cast"})
    _type_string: ClassVar[str] = "date"
        imports.update({"from datetime import date", "from typing import cast"})
    def __post_init__(self) -> None:
        super().__post_init__()
        if self.default is not None:
            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"

    def get_type_string(self) -> str:
        if self.required:
    def get_type_string(self) -> str:
        if self.required:
        super().__post_init__()
        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]}"
    def get_type_string(self) -> str:
        if self.required:
            output[key] = value
    def get_type_string(self) -> str:
        if self.required:

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.default is not None:
            self.default = f"field(default_factory=lambda: cast({self.get_type_string()}, {self.default}))"
def property_from_data(
            json={{ "json_" +  endpoint.json_body.python_name }},
{{ property.python_name }} = date.fromisoformat({{ source }})
    {{ property.python_name }} = date.fromisoformat(cast(str, {{ source }}))
{{ property.python_name }} = datetime.fromisoformat({{ source }})
    {{ property.python_name }} = datetime.fromisoformat(cast(str, {{ source }}))
    {{ destination }} = None
if isinstance({{ source }}, {{ inner_property.get_type_string() }}):
elif isinstance({{ source }}, {{ inner_property.get_type_string() }}):
def _sanitize(value: str) -> str:
    return re.sub(r"[^\w _-]+", "", value)
    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ -_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
    return stringcase.snakecase(group_title(_sanitize(value)))
    return stringcase.pascalcase(_sanitize(value))
    return stringcase.spinalcase(group_title(_sanitize(value)))
from openapi_python_client import GeneratorError
from openapi_python_client.parser.errors import PropertyError
class TestStringProperty:
    def test___post_init__(self):
        from openapi_python_client.parser.properties import StringProperty
        sp = StringProperty(name="test", required=True, default="A Default Value",)
        assert sp.default == '"A Default Value"'
            "from datetime import datetime",
            "from datetime import datetime",
            "from datetime import date",
            "from datetime import date",
class TestDictProperty:
    def test___post_init__(self):
        from openapi_python_client.parser.properties import DictProperty
        p = DictProperty(name="blah", required=True, default={})
        assert p.default == "field(default_factory=lambda: cast(Dict[Any, Any], {}))"
        FloatProperty.assert_called_once_with(name=f"{name}_item", required=True, default="0.0")
Fixed code: ## 0.5.3 - Unreleased
### Security
- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
- All values that get placed into python files (everything from enum names, to endpoint descriptions, to default values) are validated and/or saniziatied to address arbitrary code execution vulnerabilities (CVE-2020-15142)

### Changes
- Due to security concerns/implementation complexities, default values are temporarily unsupported for any `RefProperty` that doesn't refer to an enum.
- Defaults for properties must now be valid values for their respective type (e.g. "example string" is an invalid default for an `integer` type property, and the function for an endpoint using it would fail to generate and be skipped). 

from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
    some_dict: Dict[str, str]
def get_list(
    an_enum_value: List[AnEnum] = Query(...), some_date: Union[date, datetime] = Query(...),
):
@test_router.post("/test_defaults")
def test_defaults(
    string_prop: str = Query(default="the default string"),
    datetime_prop: datetime = Query(default=datetime(1010, 10, 10)),
    date_prop: date = Query(default=date(1010, 10, 10)),
    float_prop: float = Query(default=3.14),
    int_prop: int = Query(default=7),
    boolean_prop: bool = Query(default=False),
    list_prop: List[AnEnum] = Query(default=[AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE]),
    union_prop: Union[float, str] = Query(default="not a float"),
    enum_prop: AnEnum = Query(default=AnEnum.FIRST_VALUE),
    dict_prop: Dict[str, str] = Body(default={"key": "val"}),
):
    return


        },
        "/tests/test_defaults": {
            "post": {
                "tags": [
                    "tests"
                ],
                "summary": "Test Defaults",
                "operationId": "test_defaults_tests_test_defaults_post",
                "parameters": [
                    {
                        "required": false,
                        "schema": {
                            "title": "String Prop",
                            "type": "string",
                            "default": "the default string"
                        },
                        "name": "string_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Datetime Prop",
                            "type": "string",
                            "format": "date-time",
                            "default": "1010-10-10T00:00:00"
                        },
                        "name": "datetime_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Date Prop",
                            "type": "string",
                            "format": "date",
                            "default": "1010-10-10"
                        },
                        "name": "date_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Float Prop",
                            "type": "number",
                            "default": 3.14
                        },
                        "name": "float_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Int Prop",
                            "type": "integer",
                            "default": 7
                        },
                        "name": "int_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Boolean Prop",
                            "type": "boolean",
                            "default": false
                        },
                        "name": "boolean_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "List Prop",
                            "type": "array",
                            "items": {
                                "$ref": "#/components/schemas/AnEnum"
                            },
                            "default": [
                                "FIRST_VALUE",
                                "SECOND_VALUE"
                            ]
                        },
                        "name": "list_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "title": "Union Prop",
                            "anyOf": [
                                {
                                    "type": "number"
                                },
                                {
                                    "type": "string"
                                }
                            ],
                            "default": "not a float"
                        },
                        "name": "union_prop",
                        "in": "query"
                    },
                    {
                        "required": false,
                        "schema": {
                            "$ref": "#/components/schemas/AnEnum"
                        },
                        "name": "enum_prop",
                        "in": "query"
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "title": "Dict Prop",
                                "type": "object",
                                "additionalProperties": {
                                    "type": "string"
                                },
                                "default": {
                                    "key": "val"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {}
                            }
                        }
                    },
                    "422": {
                        "description": "Validation Error",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HTTPValidationError"
                                }
                            }
                        }
                    }
                }
            }
                    "some_dict",
                        }
import datetime
from dataclasses import asdict, field
    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
    if isinstance(some_date, datetime.date):


def test_defaults_tests_test_defaults_post(
    *,
    client: Client,
    json_body: Dict[Any, Any],
    string_prop: Optional[str] = "the default string",
    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
    float_prop: Optional[float] = 3.14,
    int_prop: Optional[int] = 7,
    boolean_prop: Optional[bool] = False,
    list_prop: Optional[List[AnEnum]] = field(
        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
    ),
    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
    enum_prop: Optional[AnEnum] = None,
) -> Union[
    None, HTTPValidationError,
]:

    """  """
    url = "{}/tests/test_defaults".format(client.base_url)

    headers: Dict[str, Any] = client.get_headers()

    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None

    json_date_prop = date_prop.isoformat() if date_prop else None

    if list_prop is None:
        json_list_prop = None
    else:
        json_list_prop = []
        for list_prop_item_data in list_prop:
            list_prop_item = list_prop_item_data.value

            json_list_prop.append(list_prop_item)

    if union_prop is None:
        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
    elif isinstance(union_prop, float):
        json_union_prop = union_prop
    else:
        json_union_prop = union_prop

    json_enum_prop = enum_prop.value if enum_prop else None

    params: Dict[str, Any] = {}
    if string_prop is not None:
        params["string_prop"] = string_prop
    if datetime_prop is not None:
        params["datetime_prop"] = json_datetime_prop
    if date_prop is not None:
        params["date_prop"] = json_date_prop
    if float_prop is not None:
        params["float_prop"] = float_prop
    if int_prop is not None:
        params["int_prop"] = int_prop
    if boolean_prop is not None:
        params["boolean_prop"] = boolean_prop
    if list_prop is not None:
        params["list_prop"] = json_list_prop
    if union_prop is not None:
        params["union_prop"] = json_union_prop
    if enum_prop is not None:
        params["enum_prop"] = json_enum_prop

    json_json_body = json_body

    response = httpx.post(url=url, headers=headers, json=json_json_body, params=params,)

    if response.status_code == 200:
        return None
    if response.status_code == 422:
        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
    else:
        raise ApiResponseError(response=response)
import datetime
from dataclasses import asdict, field
    *, client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime],
    if isinstance(some_date, datetime.date):


async def test_defaults_tests_test_defaults_post(
    *,
    client: Client,
    json_body: Dict[Any, Any],
    string_prop: Optional[str] = "the default string",
    datetime_prop: Optional[datetime.datetime] = datetime.datetime(1010, 10, 10, 0, 0),
    date_prop: Optional[datetime.date] = datetime.date(1010, 10, 10),
    float_prop: Optional[float] = 3.14,
    int_prop: Optional[int] = 7,
    boolean_prop: Optional[bool] = False,
    list_prop: Optional[List[AnEnum]] = field(
        default_factory=lambda: cast(Optional[List[AnEnum]], [AnEnum.FIRST_VALUE, AnEnum.SECOND_VALUE])
    ),
    union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
    enum_prop: Optional[AnEnum] = None,
) -> Union[
    None, HTTPValidationError,
]:

    """  """
    url = "{}/tests/test_defaults".format(client.base_url,)

    headers: Dict[str, Any] = client.get_headers()

    json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None

    json_date_prop = date_prop.isoformat() if date_prop else None

    if list_prop is None:
        json_list_prop = None
    else:
        json_list_prop = []
        for list_prop_item_data in list_prop:
            list_prop_item = list_prop_item_data.value

            json_list_prop.append(list_prop_item)

    if union_prop is None:
        json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
    elif isinstance(union_prop, float):
        json_union_prop = union_prop
    else:
        json_union_prop = union_prop

    json_enum_prop = enum_prop.value if enum_prop else None

    params: Dict[str, Any] = {}
    if string_prop is not None:
        params["string_prop"] = string_prop
    if datetime_prop is not None:
        params["datetime_prop"] = json_datetime_prop
    if date_prop is not None:
        params["date_prop"] = json_date_prop
    if float_prop is not None:
        params["float_prop"] = float_prop
    if int_prop is not None:
        params["int_prop"] = int_prop
    if boolean_prop is not None:
        params["boolean_prop"] = boolean_prop
    if list_prop is not None:
        params["list_prop"] = json_list_prop
    if union_prop is not None:
        params["union_prop"] = json_union_prop
    if enum_prop is not None:
        params["enum_prop"] = json_enum_prop

    json_json_body = json_body

    async with httpx.AsyncClient() as _client:
        response = await _client.post(url=url, headers=headers, json=json_json_body, params=params,)

    if response.status_code == 200:
        return None
    if response.status_code == 422:
        return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
    else:
        raise ApiResponseError(response=response)
import datetime
    some_dict: Dict[Any, Any]
    a_camel_date_time: Union[datetime.datetime, datetime.date]
    a_date: datetime.date
        some_dict = self.some_dict

        if isinstance(self.a_camel_date_time, datetime.datetime):
            "some_dict": some_dict,
        some_dict = d["some_dict"]

        def _parse_a_camel_date_time(data: Dict[str, Any]) -> Union[datetime.datetime, datetime.date]:
            a_camel_date_time: Union[datetime.datetime, datetime.date]
                a_camel_date_time = datetime.datetime.fromisoformat(d["aCamelDateTime"])
            a_camel_date_time = datetime.date.fromisoformat(d["aCamelDateTime"])
        a_date = datetime.date.fromisoformat(d["a_date"])
            some_dict=some_dict,
        self.package_description: str = utils.remove_string_escapes(
            f"A client library for accessing {self.openapi.title}"
        )


class ValidationError(Exception):
    pass
from .. import utils
            description=utils.remove_string_escapes(data.description) if data.description else "",
from datetime import date, datetime
from .errors import PropertyError, ValidationError

    Raises:
        ValidationError: Raised when the default value fails to be converted to the expected type
        if self.default is not None:
            self.default = self._validate_default(default=self.default)

    def _validate_default(self, default: Any) -> Any:
        """ Check that the default value is valid for the property's type + perform any necessary sanitization """
        raise ValidationError
    def get_type_string(self, no_optional: bool = False) -> str:
        if self.required or no_optional:
    def _validate_default(self, default: Any) -> str:
        return f'"{utils.remove_string_escapes(default)}"'
    _type_string: ClassVar[str] = "datetime.datetime"
        imports.update({"import datetime", "from typing import cast"})
    def _validate_default(self, default: Any) -> str:
        for format_string in ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"):
            try:
                return repr(datetime.strptime(default, format_string))
            except (TypeError, ValueError):
                continue
        raise ValidationError

    _type_string: ClassVar[str] = "datetime.date"
        imports.update({"import datetime", "from typing import cast"})
    def _validate_default(self, default: Any) -> str:
        try:
            return repr(date.fromisoformat(default))
        except (TypeError, ValueError) as e:
            raise ValidationError() from e

    def _validate_default(self, default: Any) -> float:
        try:
            return float(default)
        except (TypeError, ValueError) as e:
            raise ValidationError() from e

    def _validate_default(self, default: Any) -> int:
        try:
            return int(default)
        except (TypeError, ValueError) as e:
            raise ValidationError() from e

    def _validate_default(self, default: Any) -> bool:
        # no try/except needed as anything that comes from the initial load from json/yaml will be boolable
        return bool(default)

    def get_type_string(self, no_optional: bool = False) -> str:
        if self.required or no_optional:
    def _validate_default(self, default: Any) -> str:
        if not isinstance(default, list):
            raise ValidationError()

        default = list(map(self.inner_property._validate_default, default))
        if isinstance(self.inner_property, RefProperty):  # Fix enums to use the actual value
            default = str(default).replace("'", "")

        return f"field(default_factory=lambda: cast({self.get_type_string()}, {default}))"

    def get_type_string(self, no_optional: bool = False) -> str:
        if self.required or no_optional:
    def _validate_default(self, default: Any) -> Any:
        for property in self.inner_properties:
            try:
                val = property._validate_default(default)
                return val
            except ValidationError:
                continue
        raise ValidationError()

        super().__post_init__()
    def get_type_string(self, no_optional: bool = False) -> str:
        if self.required or no_optional:
            sanitized_key = utils.fix_keywords(utils.sanitize(key))
            output[sanitized_key] = utils.remove_string_escapes(value)
    def _validate_default(self, default: Any) -> str:
        inverse_values = {v: k for k, v in self.values.items()}
        try:
            return f"{self.reference.class_name}.{inverse_values[default]}"
        except KeyError as e:
            raise ValidationError() from e

    def get_type_string(self, no_optional: bool = False) -> str:
        if self.required or no_optional:
    def _validate_default(self, default: Any) -> Any:
        enum = EnumProperty.get_enum(self.reference.class_name)
        if enum:
            return enum._validate_default(default)
        else:
            raise ValidationError

    template: ClassVar[str] = "dict_property.pyi"
    def _validate_default(self, default: Any) -> str:
        if isinstance(default, dict):
            return repr(default)
        raise ValidationError

def _property_from_data(
    name = utils.remove_string_escapes(name)


def property_from_data(
    name: str, required: bool, data: Union[oai.Reference, oai.Schema]
) -> Union[Property, PropertyError]:
    try:
        return _property_from_data(name=name, required=required, data=data)
    except ValidationError:
        return PropertyError(detail="Failed to validate default value", data=data)
            json={{ "json_" + endpoint.json_body.python_name }},
{{ property.python_name }} = datetime.date.fromisoformat({{ source }})
    {{ property.python_name }} = datetime.date.fromisoformat(cast(str, {{ source }}))
{{ property.python_name }} = datetime.datetime.fromisoformat({{ source }})
    {{ property.python_name }} = datetime.datetime.fromisoformat(cast(str, {{ source }}))
{% macro construct(property, source) %}
{% if property.required %}
{{ property.python_name }} = {{ source }}
{% else %}
{{ property.python_name }} = None
if {{ source }} is not None:
    {{ property.python_name }} = {{ source }}
{% endif %}
{% endmacro %}

{% macro transform(property, source, destination) %}
{% if property.required %}
{{ destination }} = {{ source }}
{% else %}
{{ destination }} = {{ source }} if {{ source }} else None
{% endif %}
{% endmacro %}
    {{ destination }}: {{ property.get_type_string() }} = None
if isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
elif isinstance({{ source }}, {{ inner_property.get_type_string(no_optional=True) }}):
from keyword import iskeyword
def sanitize(value: str) -> str:
    return re.sub(r"[^\w _\-]+", "", value)


def fix_keywords(value: str) -> str:
    if iskeyword(value):
        return f"{value}_"
    return value
    value = re.sub(r"([A-Z]{2,})([A-Z][a-z]|[ \-_]|$)", lambda m: m.group(1).title() + m.group(2), value.strip())
    return fix_keywords(stringcase.snakecase(group_title(sanitize(value))))
    return fix_keywords(stringcase.pascalcase(sanitize(value)))
    return fix_keywords(stringcase.spinalcase(group_title(sanitize(value))))


def remove_string_escapes(value: str) -> str:
    return value.replace('"', r"\"")
from openapi_python_client import GeneratorError, utils
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description)

from datetime import date, datetime

from openapi_python_client.parser.errors import PropertyError, ValidationError
from openapi_python_client.parser.reference import Reference
    def test___post_init(self, mocker):
        from openapi_python_client.parser.properties import Property

        validate_default = mocker.patch(f"{MODULE_NAME}.Property._validate_default")

        Property(name="a name", required=True, default=None)
        validate_default.assert_not_called()

        Property(name="a name", required=True, default="the default value")
        validate_default.assert_called_with(default="the default value")

    def test__validate_default(self):
        from openapi_python_client.parser.properties import Property
        # should be okay if default isn't specified
        p = Property(name="a name", required=True, default=None)
        with pytest.raises(ValidationError):
            p._validate_default("a default value")
        with pytest.raises(ValidationError):
            p = Property(name="a name", required=True, default="")

class TestStringProperty:
    def test__validate_default(self):
        from openapi_python_client.parser.properties import StringProperty

        p = StringProperty(name="a name", required=True, default="the default value")
        assert p.default == '"the default value"'

            "import datetime",
            "import datetime",
    def test__validate_default(self):
        from openapi_python_client.parser.properties import DateTimeProperty

        with pytest.raises(ValidationError):
            p = DateTimeProperty(name="a name", required=True, default="not a datetime")

        p = DateTimeProperty(name="a name", required=True, default="2017-07-21T17:32:28Z")
        assert p.default == "datetime.datetime(2017, 7, 21, 17, 32, 28, tzinfo=datetime.timezone.utc)"

            "import datetime",
            "import datetime",
    def test__validate_default(self):
        from openapi_python_client.parser.properties import DateProperty

        with pytest.raises(ValidationError):
            p = DateProperty(name="a name", required=True, default="not a date")

        p = DateProperty(name="a name", required=True, default="1010-10-10")
        assert p.default == "datetime.date(1010, 10, 10)"

    def test__validate_default(self):
        from openapi_python_client.parser.properties import FileProperty

        # should be okay if default isn't specified
        p = FileProperty(name="a name", required=True, default=None)

        with pytest.raises(ValidationError):
            p = FileProperty(name="a name", required=True, default="")


class TestFloatProperty:
    def test__validate_default(self):
        from openapi_python_client.parser.properties import FloatProperty

        # should be okay if default isn't specified
        p = FloatProperty(name="a name", required=True, default=None)

        p = FloatProperty(name="a name", required=True, default="123.123")
        assert p.default == 123.123

        with pytest.raises(ValidationError):
            p = FloatProperty(name="a name", required=True, default="not a float")


class TestIntProperty:
    def test__validate_default(self):
        from openapi_python_client.parser.properties import IntProperty

        # should be okay if default isn't specified
        p = IntProperty(name="a name", required=True, default=None)

        p = IntProperty(name="a name", required=True, default="123")
        assert p.default == 123

        with pytest.raises(ValidationError):
            p = IntProperty(name="a name", required=True, default="not an int")


class TestBooleanProperty:
    def test__validate_default(self):
        from openapi_python_client.parser.properties import BooleanProperty

        # should be okay if default isn't specified
        p = BooleanProperty(name="a name", required=True, default=None)

        p = BooleanProperty(name="a name", required=True, default="Literally anything will work")
        assert p.default == True

    def test__validate_default(self, mocker):
        from openapi_python_client.parser.properties import ListProperty

        inner_property = mocker.MagicMock()
        inner_type_string = mocker.MagicMock()
        inner_property.get_type_string.return_value = inner_type_string
        inner_property._validate_default.return_value = "y"

        p = ListProperty(name="a name", required=True, default=["x"], inner_property=inner_property)
        assert p.default == f"field(default_factory=lambda: cast(List[{inner_type_string}], ['y']))"

        with pytest.raises(ValidationError):
            p = ListProperty(name="a name", required=True, default="x", inner_property=inner_property)

    def test__validate_default_enum_items(self, mocker):
        from openapi_python_client.parser.properties import ListProperty, RefProperty

        inner_enum_property = mocker.MagicMock(spec=RefProperty)
        inner_enum_property.get_type_string.return_value = "AnEnum"
        inner_enum_property._validate_default.return_value = "AnEnum.val1"

        p = ListProperty(name="a name", required=True, default=["val1"], inner_property=inner_enum_property)
        assert p.default == f"field(default_factory=lambda: cast(List[AnEnum], [AnEnum.val1]))"

    def test__validate_default(self, mocker):
        from openapi_python_client.parser.properties import UnionProperty

        inner_property_1 = mocker.MagicMock()
        inner_property_1.get_type_string.return_value = "inner_type_string_1"
        inner_property_1._validate_default.side_effect = ValidationError()
        inner_property_2 = mocker.MagicMock()
        inner_property_2.get_type_string.return_value = "inner_type_string_2"
        inner_property_2._validate_default.return_value = "the default value"
        p = UnionProperty(
            name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
        )

        assert p.default == "the default value"

        inner_property_2._validate_default.side_effect = ValidationError()

        with pytest.raises(ValidationError):
            p = UnionProperty(
                name="test", required=True, default="a value", inner_properties=[inner_property_1, inner_property_2]
            )

    def test__validate_default(self, mocker):
        fake_reference = mocker.MagicMock(class_name="MyTestEnum", module_name="my_test_enum")
        mocker.patch(f"{MODULE_NAME}.Reference.from_ref", return_value=fake_reference)

        from openapi_python_client.parser import properties

        enum_property = properties.EnumProperty(
            name="test", required=True, default="test", values={"TEST": "test"}, title="a_title"
        )
        assert enum_property.default == "MyTestEnum.TEST"

        with pytest.raises(ValidationError):
            enum_property = properties.EnumProperty(
                name="test", required=True, default="bad_val", values={"TEST": "test"}, title="a_title"
            )

        properties._existing_enums = {}

    def test__validate_default(self, mocker):
        from openapi_python_client.parser.properties import RefProperty

        with pytest.raises(ValidationError):
            p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
        enum_property = mocker.MagicMock()
        enum_property._validate_default.return_value = "val1"
        mocker.patch(f"{MODULE_NAME}.EnumProperty.get_enum", return_value=enum_property)
        p = RefProperty(name="a name", required=True, default="", reference=mocker.MagicMock())
        assert p.default == "val1"
class TestDictProperty:
    def test__validate_default(self):
        from openapi_python_client.parser.properties import DictProperty

        p = DictProperty(name="a name", required=True, default={"key": "value"})

        with pytest.raises(ValidationError):
            p = DictProperty(name="a name", required=True, default="not a dict")

        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)

        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        FloatProperty.assert_called_once_with(name=name, required=True, default="0.0")
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
        mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name)
    def test_property_from_data_validation_error(self, mocker):
        from openapi_python_client.parser.errors import PropertyError
        from openapi_python_client.parser.properties import property_from_data

        mocker.patch(f"{MODULE_NAME}._property_from_data").side_effect = ValidationError()

        data = oai.Schema()
        assert property_from_data(name="blah", required=True, data=data) == PropertyError(
            detail="Failed to validate default value", data=data
        )



def test__sanitize():
    assert utils.sanitize("something*~with lots_- of weird things}=") == "somethingwith lots_- of weird things"


def test_no_string_escapes():
    assert utils.remove_string_escapes('an "evil" string') == 'an \\"evil\\" string'


def test__fix_keywords():
    assert utils.fix_keywords("None") == "None_"