diff --git a/CHANGELOG.md b/CHANGELOG.md index 2859fe41a..82fdf8bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,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.9.1 + +### Features + +- Allow references to non-object, non-enum types [#371][#418][#425]. Thanks @p1-ra! +- Allow for attrs 21.x in generated clients [#412] +- Allow for using any version of Black [#416] [#411]. Thanks @christhekeele! + +### Fixes + +- Prevent crash when providing a non-string default to a string attribute. [#414] [#415] +- Deserialization of optional nullable properties when no value is returned from the API [#420] [#381]. Thanks @forest-benchling! + ## 0.9.0 - 2021-05-04 ### Breaking Changes @@ -45,9 +58,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore) - Attempt to detect and alert users if they are using an unsupported version of OpenAPI (#281). + - The media type application/vnd.api+json will now be handled just like application/json (#307). Thanks @jrversteegh! + - Support passing models into query parameters (#316). Thanks @forest-benchling! + - Add support for cookie parameters (#326). + - New `--file-encoding` command line option (#330). Sets the encoding used when writing generated files (defaults to utf-8). Thanks @dongfangtianyu! ### Changes @@ -96,13 +113,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Breaking Changes - Any request/response field that is not `required` and wasn't specified is now set to `UNSET` instead of `None`. + - Values that are `UNSET` will not be sent along in API calls + - Schemas defined with `type=object` will now be converted into classes, just like if they were created as ref components. The previous behavior was a combination of skipping and using generic Dicts for these schemas. + - Response schema handling was unified with input schema handling, meaning that responses will behave differently than before. Specifically, instead of the content-type deciding what the generated Python type is, the schema itself will. - As a result of this, endpoints that used to return `bytes` when content-type was application/octet-stream will now return a `File` object if the type of the data is "binary", just like if you were submitting that type instead of receiving it. - Instead of skipping input properties with no type, enum, anyOf, or oneOf declared, the property will be declared as `None`. + - Class (models and Enums) names will now contain the name of their parent element (if any). For example, a property declared in an endpoint will be named like {endpoint*name}*{previous*class*name}. Classes will no longer be deduplicated by appending a number to the end of the generated name, so if two names conflict with this new naming scheme, there will be an error instead. ### Additions @@ -268,6 +289,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changes - The way most imports are handled was changed which _should_ lead to fewer unused imports in generated files. + - Better error messages - Most error messages will contain some useful information about why it failed instead of a stack trace diff --git a/dobby.toml b/dobby.toml index 2a0cc02fd..855ba9f73 100644 --- a/dobby.toml +++ b/dobby.toml @@ -1,5 +1,5 @@ [[workflows]] -name = "Start Task" +name = "task" [[workflows.steps]] type = "SelectGitHubIssue" @@ -7,13 +7,13 @@ name = "Start Task" type = "SwitchBranches" [[workflows]] -name = "Prepare Release" +name = "release" [[workflows.steps]] type = "UpdateProjectFromCommits" [[workflows.steps]] type = "Command" - command = "prettier --write CHANGELOG.md" + command = "npx prettier --write CHANGELOG.md" [github] owner = "triaxtec" diff --git a/end_to_end_tests/custom-templates-golden-record/README.md b/end_to_end_tests/custom-templates-golden-record/README.md new file mode 100644 index 000000000..e5106eea7 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/README.md @@ -0,0 +1 @@ +my-test-api-client \ No newline at end of file diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py new file mode 100644 index 000000000..3ee5dbaf0 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py @@ -0,0 +1,21 @@ +""" Contains methods for accessing the API """ + +from typing import Type + +from my_test_api_client.api.default import DefaultEndpoints +from my_test_api_client.api.parameters import ParametersEndpoints +from my_test_api_client.api.tests import TestsEndpoints + + +class MyTestApiClientApi: + @classmethod + def tests(cls) -> Type[TestsEndpoints]: + return TestsEndpoints + + @classmethod + def default(cls) -> Type[DefaultEndpoints]: + return DefaultEndpoints + + @classmethod + def parameters(cls) -> Type[ParametersEndpoints]: + return ParametersEndpoints diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py new file mode 100644 index 000000000..4d0eb4fb5 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py @@ -0,0 +1,15 @@ +""" Contains methods for accessing the API Endpoints """ + +import types + +from my_test_api_client.api.default import get_common_parameters, post_common_parameters + + +class DefaultEndpoints: + @classmethod + def get_common_parameters(cls) -> types.ModuleType: + return get_common_parameters + + @classmethod + def post_common_parameters(cls) -> types.ModuleType: + return post_common_parameters diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py new file mode 100644 index 000000000..b92c6d96b --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py @@ -0,0 +1,11 @@ +""" Contains methods for accessing the API Endpoints """ + +import types + +from my_test_api_client.api.parameters import get_same_name_multiple_locations_param + + +class ParametersEndpoints: + @classmethod + def get_same_name_multiple_locations_param(cls) -> types.ModuleType: + return get_same_name_multiple_locations_param diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py new file mode 100644 index 000000000..2cfd13809 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py @@ -0,0 +1,128 @@ +""" Contains methods for accessing the API Endpoints """ + +import types + +from my_test_api_client.api.tests import ( + defaults_tests_defaults_post, + get_basic_list_of_booleans, + get_basic_list_of_floats, + get_basic_list_of_integers, + get_basic_list_of_strings, + get_user_list, + int_enum_tests_int_enum_post, + json_body_tests_json_body_post, + no_response_tests_no_response_get, + octet_stream_tests_octet_stream_get, + optional_value_tests_optional_query_param, + test_inline_objects, + token_with_cookie_auth_token_with_cookie_get, + unsupported_content_tests_unsupported_content_get, + upload_file_tests_upload_post, +) + + +class TestsEndpoints: + @classmethod + def get_user_list(cls) -> types.ModuleType: + """ + Get a list of things + """ + return get_user_list + + @classmethod + def get_basic_list_of_strings(cls) -> types.ModuleType: + """ + Get a list of strings + """ + return get_basic_list_of_strings + + @classmethod + def get_basic_list_of_integers(cls) -> types.ModuleType: + """ + Get a list of integers + """ + return get_basic_list_of_integers + + @classmethod + def get_basic_list_of_floats(cls) -> types.ModuleType: + """ + Get a list of floats + """ + return get_basic_list_of_floats + + @classmethod + def get_basic_list_of_booleans(cls) -> types.ModuleType: + """ + Get a list of booleans + """ + return get_basic_list_of_booleans + + @classmethod + def upload_file_tests_upload_post(cls) -> types.ModuleType: + """ + Upload a file + """ + return upload_file_tests_upload_post + + @classmethod + def json_body_tests_json_body_post(cls) -> types.ModuleType: + """ + Try sending a JSON body + """ + return json_body_tests_json_body_post + + @classmethod + def defaults_tests_defaults_post(cls) -> types.ModuleType: + """ + Defaults + """ + return defaults_tests_defaults_post + + @classmethod + def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType: + """ + Octet Stream + """ + return octet_stream_tests_octet_stream_get + + @classmethod + def no_response_tests_no_response_get(cls) -> types.ModuleType: + """ + No Response + """ + return no_response_tests_no_response_get + + @classmethod + def unsupported_content_tests_unsupported_content_get(cls) -> types.ModuleType: + """ + Unsupported Content + """ + return unsupported_content_tests_unsupported_content_get + + @classmethod + def int_enum_tests_int_enum_post(cls) -> types.ModuleType: + """ + Int Enum + """ + return int_enum_tests_int_enum_post + + @classmethod + def test_inline_objects(cls) -> types.ModuleType: + """ + Test Inline Objects + """ + return test_inline_objects + + @classmethod + def optional_value_tests_optional_query_param(cls) -> types.ModuleType: + """ + Test optional query parameters + """ + return optional_value_tests_optional_query_param + + @classmethod + def token_with_cookie_auth_token_with_cookie_get(cls) -> types.ModuleType: + """ + Test optional cookie parameters + """ + return token_with_cookie_auth_token_with_cookie_get diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py new file mode 100644 index 000000000..8152214c1 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -0,0 +1,68 @@ +from typing import Any, Dict + +import httpx + +from ...client import Client +from ...models.a_form_data import AFormData +from ...types import Response + + +def _get_kwargs( + *, + client: Client, + form_data: AFormData, +) -> Dict[str, Any]: + url = "{}/tests/post_form_data".format(client.base_url) + + headers: Dict[str, Any] = client.get_headers() + cookies: Dict[str, Any] = client.get_cookies() + + return { + "url": url, + "headers": headers, + "cookies": cookies, + "timeout": client.get_timeout(), + "data": form_data.to_dict(), + } + + +def _build_response(*, response: httpx.Response) -> Response[None]: + return Response( + status_code=response.status_code, + content=response.content, + headers=response.headers, + parsed=None, + ) + + +def sync_detailed( + *, + client: Client, + form_data: AFormData, +) -> Response[None]: + kwargs = _get_kwargs( + client=client, + form_data=form_data, + ) + + response = httpx.post( + **kwargs, + ) + + return _build_response(response=response) + + +async def asyncio_detailed( + *, + client: Client, + form_data: AFormData, +) -> Response[None]: + kwargs = _get_kwargs( + client=client, + form_data=form_data, + ) + + async with httpx.AsyncClient() as _client: + response = await _client.post(**kwargs) + + return _build_response(response=response) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 5bbd77d7d..0efd7dcb6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -1,6 +1,11 @@ """ Contains all the data models used in inputs/outputs """ +from .a_form_data import AFormData from .a_model import AModel +from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject +from .a_model_with_indirect_reference_property import AModelWithIndirectReferenceProperty +from .a_model_with_indirect_self_reference_property import AModelWithIndirectSelfReferenceProperty +from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .all_of_sub_model import AllOfSubModel from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py new file mode 100644 index 000000000..f5c34f5be --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py @@ -0,0 +1,63 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AFormData") + + +@attr.s(auto_attribs=True) +class AFormData: + """ """ + + an_required_field: str + an_optional_field: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + an_required_field = self.an_required_field + an_optional_field = self.an_optional_field + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "an_required_field": an_required_field, + } + ) + if an_optional_field is not UNSET: + field_dict["an_optional_field"] = an_optional_field + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + an_required_field = d.pop("an_required_field") + + an_optional_field = d.pop("an_optional_field", UNSET) + + a_form_data = cls( + an_required_field=an_required_field, + an_optional_field=an_optional_field, + ) + + a_form_data.additional_properties = d + return a_form_data + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py index 38ce8d114..0e2c06d83 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -186,7 +186,6 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]: try: - a_camel_date_time_type_0: datetime.datetime if not isinstance(data, str): raise TypeError() a_camel_date_time_type_0 = isoparse(data) @@ -196,7 +195,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date pass if not isinstance(data, str): raise TypeError() - a_camel_date_time_type_1: datetime.date a_camel_date_time_type_1 = isoparse(data).date() return a_camel_date_time_type_1 @@ -209,7 +207,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]: try: - one_of_models_type_0: FreeFormModel if not isinstance(data, dict): raise TypeError() one_of_models_type_0 = FreeFormModel.from_dict(data) @@ -219,7 +216,6 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro pass if not isinstance(data, dict): raise TypeError() - one_of_models_type_1: ModelWithUnionProperty one_of_models_type_1 = ModelWithUnionProperty.from_dict(data) return one_of_models_type_1 @@ -228,9 +224,11 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro model = ModelWithUnionProperty.from_dict(d.pop("model")) - an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET _an_optional_allof_enum = d.pop("an_optional_allof_enum", UNSET) - if not isinstance(_an_optional_allof_enum, Unset): + an_optional_allof_enum: Union[Unset, AnAllOfEnum] + if isinstance(_an_optional_allof_enum, Unset): + an_optional_allof_enum = UNSET + else: an_optional_allof_enum = AnAllOfEnum(_an_optional_allof_enum) nested_list_of_enums = [] @@ -245,14 +243,18 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro nested_list_of_enums.append(nested_list_of_enums_item) - a_nullable_date = None _a_nullable_date = d.pop("a_nullable_date") - if _a_nullable_date is not None: + a_nullable_date: Optional[datetime.date] + if _a_nullable_date is None: + a_nullable_date = None + else: a_nullable_date = isoparse(_a_nullable_date).date() - a_not_required_date: Union[Unset, datetime.date] = UNSET _a_not_required_date = d.pop("a_not_required_date", UNSET) - if not isinstance(_a_not_required_date, Unset): + a_not_required_date: Union[Unset, datetime.date] + if isinstance(_a_not_required_date, Unset): + a_not_required_date = UNSET + else: a_not_required_date = isoparse(_a_not_required_date).date() attr_1_leading_digit = d.pop("1_leading_digit", UNSET) @@ -267,7 +269,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit if data is None: return data try: - nullable_one_of_models_type_0: FreeFormModel if not isinstance(data, dict): raise TypeError() nullable_one_of_models_type_0 = FreeFormModel.from_dict(data) @@ -277,7 +278,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit pass if not isinstance(data, dict): raise TypeError() - nullable_one_of_models_type_1: ModelWithUnionProperty nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict(data) return nullable_one_of_models_type_1 @@ -288,12 +288,13 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode if isinstance(data, Unset): return data try: - not_required_one_of_models_type_0: Union[Unset, FreeFormModel] if not isinstance(data, dict): raise TypeError() - not_required_one_of_models_type_0 = UNSET _not_required_one_of_models_type_0 = data - if not isinstance(_not_required_one_of_models_type_0, Unset): + not_required_one_of_models_type_0: Union[Unset, FreeFormModel] + if isinstance(_not_required_one_of_models_type_0, Unset): + not_required_one_of_models_type_0 = UNSET + else: not_required_one_of_models_type_0 = FreeFormModel.from_dict(_not_required_one_of_models_type_0) return not_required_one_of_models_type_0 @@ -301,10 +302,11 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode pass if not isinstance(data, dict): raise TypeError() - not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] - not_required_one_of_models_type_1 = UNSET _not_required_one_of_models_type_1 = data - if not isinstance(_not_required_one_of_models_type_1, Unset): + not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_one_of_models_type_1, Unset): + not_required_one_of_models_type_1 = UNSET + else: not_required_one_of_models_type_1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type_1) return not_required_one_of_models_type_1 @@ -319,12 +321,13 @@ def _parse_not_required_nullable_one_of_models( if isinstance(data, Unset): return data try: - not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel] if not isinstance(data, dict): raise TypeError() - not_required_nullable_one_of_models_type_0 = UNSET _not_required_nullable_one_of_models_type_0 = data - if not isinstance(_not_required_nullable_one_of_models_type_0, Unset): + not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel] + if isinstance(_not_required_nullable_one_of_models_type_0, Unset): + not_required_nullable_one_of_models_type_0 = UNSET + else: not_required_nullable_one_of_models_type_0 = FreeFormModel.from_dict( _not_required_nullable_one_of_models_type_0 ) @@ -333,12 +336,13 @@ def _parse_not_required_nullable_one_of_models( except: # noqa: E722 pass try: - not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] if not isinstance(data, dict): raise TypeError() - not_required_nullable_one_of_models_type_1 = UNSET _not_required_nullable_one_of_models_type_1 = data - if not isinstance(_not_required_nullable_one_of_models_type_1, Unset): + not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_nullable_one_of_models_type_1, Unset): + not_required_nullable_one_of_models_type_1 = UNSET + else: not_required_nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict( _not_required_nullable_one_of_models_type_1 ) @@ -352,19 +356,27 @@ def _parse_not_required_nullable_one_of_models( d.pop("not_required_nullable_one_of_models", UNSET) ) - nullable_model = None _nullable_model = d.pop("nullable_model") - if _nullable_model is not None: + nullable_model: Optional[ModelWithUnionProperty] + if _nullable_model is None: + nullable_model = None + else: nullable_model = ModelWithUnionProperty.from_dict(_nullable_model) - not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET _not_required_model = d.pop("not_required_model", UNSET) - if not isinstance(_not_required_model, Unset): + not_required_model: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_model, Unset): + not_required_model = UNSET + else: not_required_model = ModelWithUnionProperty.from_dict(_not_required_model) - not_required_nullable_model = None _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET) - if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset): + not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty] + if _not_required_nullable_model is None: + not_required_nullable_model = None + elif isinstance(_not_required_nullable_model, Unset): + not_required_nullable_model = UNSET + else: not_required_nullable_model = ModelWithUnionProperty.from_dict(_not_required_nullable_model) a_model = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py new file mode 100644 index 000000000..14058761d --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..models.an_enum import AnEnum +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AModelWithIndirectReferenceProperty") + + +@attr.s(auto_attribs=True) +class AModelWithIndirectReferenceProperty: + """ """ + + an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + an_enum_indirect_ref: Union[Unset, str] = UNSET + if not isinstance(self.an_enum_indirect_ref, Unset): + an_enum_indirect_ref = self.an_enum_indirect_ref.value + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if an_enum_indirect_ref is not UNSET: + field_dict["an_enum_indirect_ref"] = an_enum_indirect_ref + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET + _an_enum_indirect_ref = d.pop("an_enum_indirect_ref", UNSET) + if not isinstance(_an_enum_indirect_ref, Unset): + an_enum_indirect_ref = AnEnum(_an_enum_indirect_ref) + + a_model_with_indirect_reference_property = cls( + an_enum_indirect_ref=an_enum_indirect_ref, + ) + + a_model_with_indirect_reference_property.additional_properties = d + return a_model_with_indirect_reference_property + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py new file mode 100644 index 000000000..2c101f264 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py @@ -0,0 +1,77 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..models.an_enum import AnEnum +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AModelWithIndirectSelfReferenceProperty") + + +@attr.s(auto_attribs=True) +class AModelWithIndirectSelfReferenceProperty: + """ """ + + required_self_ref: AModelWithIndirectSelfReferenceProperty + an_enum: Union[Unset, AnEnum] = UNSET + optional_self_ref: Union[Unset, AModelWithIndirectSelfReferenceProperty] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + required_self_ref = self.required_self_ref + an_enum: Union[Unset, str] = UNSET + if not isinstance(self.an_enum, Unset): + an_enum = self.an_enum.value + + optional_self_ref = self.optional_self_ref + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "required_self_ref": required_self_ref, + } + ) + if an_enum is not UNSET: + field_dict["an_enum"] = an_enum + if optional_self_ref is not UNSET: + field_dict["optional_self_ref"] = optional_self_ref + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + required_self_ref = d.pop("required_self_ref") + + an_enum: Union[Unset, AnEnum] = UNSET + _an_enum = d.pop("an_enum", UNSET) + if not isinstance(_an_enum, Unset): + an_enum = AnEnum(_an_enum) + + optional_self_ref = d.pop("optional_self_ref", UNSET) + + a_model_with_indirect_self_reference_property = cls( + required_self_ref=required_self_ref, + an_enum=an_enum, + optional_self_ref=optional_self_ref, + ) + + a_model_with_indirect_self_reference_property.additional_properties = d + return a_model_with_indirect_self_reference_property + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py new file mode 100644 index 000000000..4b95ec80e --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py @@ -0,0 +1,341 @@ +import datetime +from io import BytesIO +from typing import Any, BinaryIO, Dict, List, Optional, TextIO, Tuple, Type, TypeVar, Union, cast + +import attr +from dateutil.parser import isoparse + +from ..models.an_enum import AnEnum +from ..types import File + +T = TypeVar("T", bound="AModelWithPropertiesReferenceThatAreNotObject") + + +@attr.s(auto_attribs=True) +class AModelWithPropertiesReferenceThatAreNotObject: + """ """ + + enum_properties_ref: List[AnEnum] + str_properties_ref: List[str] + date_properties_ref: List[datetime.date] + datetime_properties_ref: List[datetime.datetime] + int_32_properties_ref: List[int] + int_64_properties_ref: List[int] + float_properties_ref: List[float] + double_properties_ref: List[float] + file_properties_ref: List[File] + bytestream_properties_ref: List[str] + enum_properties: List[AnEnum] + str_properties: List[str] + date_properties: List[datetime.date] + datetime_properties: List[datetime.datetime] + int_32_properties: List[int] + int_64_properties: List[int] + float_properties: List[float] + double_properties: List[float] + file_properties: List[File] + bytestream_properties: List[str] + enum_property_ref: AnEnum + str_property_ref: str + date_property_ref: datetime.date + datetime_property_ref: datetime.datetime + int_32_property_ref: int + int_64_property_ref: int + float_property_ref: float + double_property_ref: float + file_property_ref: File + bytestream_property_ref: str + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + enum_properties_ref = [] + for componentsschemas_an_other_array_of_enum_item_data in self.enum_properties_ref: + componentsschemas_an_other_array_of_enum_item = componentsschemas_an_other_array_of_enum_item_data.value + + enum_properties_ref.append(componentsschemas_an_other_array_of_enum_item) + + str_properties_ref = self.str_properties_ref + + date_properties_ref = [] + for componentsschemas_an_other_array_of_date_item_data in self.date_properties_ref: + componentsschemas_an_other_array_of_date_item = ( + componentsschemas_an_other_array_of_date_item_data.isoformat() + ) + date_properties_ref.append(componentsschemas_an_other_array_of_date_item) + + datetime_properties_ref = [] + for componentsschemas_an_other_array_of_date_time_item_data in self.datetime_properties_ref: + componentsschemas_an_other_array_of_date_time_item = ( + componentsschemas_an_other_array_of_date_time_item_data.isoformat() + ) + + datetime_properties_ref.append(componentsschemas_an_other_array_of_date_time_item) + + int_32_properties_ref = self.int_32_properties_ref + + int_64_properties_ref = self.int_64_properties_ref + + float_properties_ref = self.float_properties_ref + + double_properties_ref = self.double_properties_ref + + file_properties_ref = [] + for componentsschemas_an_other_array_of_file_item_data in self.file_properties_ref: + componentsschemas_an_other_array_of_file_item = ( + componentsschemas_an_other_array_of_file_item_data.to_tuple() + ) + + file_properties_ref.append(componentsschemas_an_other_array_of_file_item) + + bytestream_properties_ref = self.bytestream_properties_ref + + enum_properties = [] + for componentsschemas_an_array_of_enum_item_data in self.enum_properties: + componentsschemas_an_array_of_enum_item = componentsschemas_an_array_of_enum_item_data.value + + enum_properties.append(componentsschemas_an_array_of_enum_item) + + str_properties = self.str_properties + + date_properties = [] + for componentsschemas_an_array_of_date_item_data in self.date_properties: + componentsschemas_an_array_of_date_item = componentsschemas_an_array_of_date_item_data.isoformat() + date_properties.append(componentsschemas_an_array_of_date_item) + + datetime_properties = [] + for componentsschemas_an_array_of_date_time_item_data in self.datetime_properties: + componentsschemas_an_array_of_date_time_item = componentsschemas_an_array_of_date_time_item_data.isoformat() + + datetime_properties.append(componentsschemas_an_array_of_date_time_item) + + int_32_properties = self.int_32_properties + + int_64_properties = self.int_64_properties + + float_properties = self.float_properties + + double_properties = self.double_properties + + file_properties = [] + for componentsschemas_an_array_of_file_item_data in self.file_properties: + componentsschemas_an_array_of_file_item = componentsschemas_an_array_of_file_item_data.to_tuple() + + file_properties.append(componentsschemas_an_array_of_file_item) + + bytestream_properties = self.bytestream_properties + + enum_property_ref = self.enum_property_ref.value + + str_property_ref = self.str_property_ref + date_property_ref = self.date_property_ref.isoformat() + datetime_property_ref = self.datetime_property_ref.isoformat() + + int_32_property_ref = self.int_32_property_ref + int_64_property_ref = self.int_64_property_ref + float_property_ref = self.float_property_ref + double_property_ref = self.double_property_ref + file_property_ref = self.file_property_ref.to_tuple() + + bytestream_property_ref = self.bytestream_property_ref + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "enum_properties_ref": enum_properties_ref, + "str_properties_ref": str_properties_ref, + "date_properties_ref": date_properties_ref, + "datetime_properties_ref": datetime_properties_ref, + "int32_properties_ref": int_32_properties_ref, + "int64_properties_ref": int_64_properties_ref, + "float_properties_ref": float_properties_ref, + "double_properties_ref": double_properties_ref, + "file_properties_ref": file_properties_ref, + "bytestream_properties_ref": bytestream_properties_ref, + "enum_properties": enum_properties, + "str_properties": str_properties, + "date_properties": date_properties, + "datetime_properties": datetime_properties, + "int32_properties": int_32_properties, + "int64_properties": int_64_properties, + "float_properties": float_properties, + "double_properties": double_properties, + "file_properties": file_properties, + "bytestream_properties": bytestream_properties, + "enum_property_ref": enum_property_ref, + "str_property_ref": str_property_ref, + "date_property_ref": date_property_ref, + "datetime_property_ref": datetime_property_ref, + "int32_property_ref": int_32_property_ref, + "int64_property_ref": int_64_property_ref, + "float_property_ref": float_property_ref, + "double_property_ref": double_property_ref, + "file_property_ref": file_property_ref, + "bytestream_property_ref": bytestream_property_ref, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + enum_properties_ref = [] + _enum_properties_ref = d.pop("enum_properties_ref") + for componentsschemas_an_other_array_of_enum_item_data in _enum_properties_ref: + componentsschemas_an_other_array_of_enum_item = AnEnum(componentsschemas_an_other_array_of_enum_item_data) + + enum_properties_ref.append(componentsschemas_an_other_array_of_enum_item) + + str_properties_ref = cast(List[str], d.pop("str_properties_ref")) + + date_properties_ref = [] + _date_properties_ref = d.pop("date_properties_ref") + for componentsschemas_an_other_array_of_date_item_data in _date_properties_ref: + componentsschemas_an_other_array_of_date_item = isoparse( + componentsschemas_an_other_array_of_date_item_data + ).date() + + date_properties_ref.append(componentsschemas_an_other_array_of_date_item) + + datetime_properties_ref = [] + _datetime_properties_ref = d.pop("datetime_properties_ref") + for componentsschemas_an_other_array_of_date_time_item_data in _datetime_properties_ref: + componentsschemas_an_other_array_of_date_time_item = isoparse( + componentsschemas_an_other_array_of_date_time_item_data + ) + + datetime_properties_ref.append(componentsschemas_an_other_array_of_date_time_item) + + int_32_properties_ref = cast(List[int], d.pop("int32_properties_ref")) + + int_64_properties_ref = cast(List[int], d.pop("int64_properties_ref")) + + float_properties_ref = cast(List[float], d.pop("float_properties_ref")) + + double_properties_ref = cast(List[float], d.pop("double_properties_ref")) + + file_properties_ref = [] + _file_properties_ref = d.pop("file_properties_ref") + for componentsschemas_an_other_array_of_file_item_data in _file_properties_ref: + componentsschemas_an_other_array_of_file_item = File( + payload=BytesIO(componentsschemas_an_other_array_of_file_item_data) + ) + + file_properties_ref.append(componentsschemas_an_other_array_of_file_item) + + bytestream_properties_ref = cast(List[str], d.pop("bytestream_properties_ref")) + + enum_properties = [] + _enum_properties = d.pop("enum_properties") + for componentsschemas_an_array_of_enum_item_data in _enum_properties: + componentsschemas_an_array_of_enum_item = AnEnum(componentsschemas_an_array_of_enum_item_data) + + enum_properties.append(componentsschemas_an_array_of_enum_item) + + str_properties = cast(List[str], d.pop("str_properties")) + + date_properties = [] + _date_properties = d.pop("date_properties") + for componentsschemas_an_array_of_date_item_data in _date_properties: + componentsschemas_an_array_of_date_item = isoparse(componentsschemas_an_array_of_date_item_data).date() + + date_properties.append(componentsschemas_an_array_of_date_item) + + datetime_properties = [] + _datetime_properties = d.pop("datetime_properties") + for componentsschemas_an_array_of_date_time_item_data in _datetime_properties: + componentsschemas_an_array_of_date_time_item = isoparse(componentsschemas_an_array_of_date_time_item_data) + + datetime_properties.append(componentsschemas_an_array_of_date_time_item) + + int_32_properties = cast(List[int], d.pop("int32_properties")) + + int_64_properties = cast(List[int], d.pop("int64_properties")) + + float_properties = cast(List[float], d.pop("float_properties")) + + double_properties = cast(List[float], d.pop("double_properties")) + + file_properties = [] + _file_properties = d.pop("file_properties") + for componentsschemas_an_array_of_file_item_data in _file_properties: + componentsschemas_an_array_of_file_item = File( + payload=BytesIO(componentsschemas_an_array_of_file_item_data) + ) + + file_properties.append(componentsschemas_an_array_of_file_item) + + bytestream_properties = cast(List[str], d.pop("bytestream_properties")) + + enum_property_ref = AnEnum(d.pop("enum_property_ref")) + + str_property_ref = d.pop("str_property_ref") + + date_property_ref = isoparse(d.pop("date_property_ref")).date() + + datetime_property_ref = isoparse(d.pop("datetime_property_ref")) + + int_32_property_ref = d.pop("int32_property_ref") + + int_64_property_ref = d.pop("int64_property_ref") + + float_property_ref = d.pop("float_property_ref") + + double_property_ref = d.pop("double_property_ref") + + file_property_ref = File(payload=BytesIO(d.pop("file_property_ref"))) + + bytestream_property_ref = d.pop("bytestream_property_ref") + + a_model_with_properties_reference_that_are_not_object = cls( + enum_properties_ref=enum_properties_ref, + str_properties_ref=str_properties_ref, + date_properties_ref=date_properties_ref, + datetime_properties_ref=datetime_properties_ref, + int_32_properties_ref=int_32_properties_ref, + int_64_properties_ref=int_64_properties_ref, + float_properties_ref=float_properties_ref, + double_properties_ref=double_properties_ref, + file_properties_ref=file_properties_ref, + bytestream_properties_ref=bytestream_properties_ref, + enum_properties=enum_properties, + str_properties=str_properties, + date_properties=date_properties, + datetime_properties=datetime_properties, + int_32_properties=int_32_properties, + int_64_properties=int_64_properties, + float_properties=float_properties, + double_properties=double_properties, + file_properties=file_properties, + bytestream_properties=bytestream_properties, + enum_property_ref=enum_property_ref, + str_property_ref=str_property_ref, + date_property_ref=date_property_ref, + datetime_property_ref=datetime_property_ref, + int_32_property_ref=int_32_property_ref, + int_64_property_ref=int_64_property_ref, + float_property_ref=float_property_ref, + double_property_ref=double_property_ref, + file_property_ref=file_property_ref, + bytestream_property_ref=bytestream_property_ref, + ) + + a_model_with_properties_reference_that_are_not_object.additional_properties = d + return a_model_with_properties_reference_that_are_not_object + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py index ecfa97b10..08a016dd8 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py @@ -46,7 +46,6 @@ def _parse_additional_property( data: object, ) -> Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]: try: - additional_property_type_0: ModelWithAnyJsonPropertiesAdditionalPropertyType0 if not isinstance(data, dict): raise TypeError() additional_property_type_0 = ModelWithAnyJsonPropertiesAdditionalPropertyType0.from_dict(data) @@ -55,7 +54,6 @@ def _parse_additional_property( except: # noqa: E722 pass try: - additional_property_type_1: List[str] if not isinstance(data, list): raise TypeError() additional_property_type_1 = cast(List[str], data) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py index 5dc264152..ee28313bd 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py @@ -33,9 +33,11 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] = UNSET _a_date_holder = d.pop("a_date_holder", UNSET) - if not isinstance(_a_date_holder, Unset): + a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] + if isinstance(_a_date_holder, Unset): + a_date_holder = UNSET + else: a_date_holder = ModelWithPrimitiveAdditionalPropertiesADateHolder.from_dict(_a_date_holder) model_with_primitive_additional_properties = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py index 6ebba75a6..e28a14e91 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py @@ -31,9 +31,11 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - inner: Union[Unset, ModelName] = UNSET _inner = d.pop("inner", UNSET) - if not isinstance(_inner, Unset): + inner: Union[Unset, ModelName] + if isinstance(_inner, Unset): + inner = UNSET + else: inner = ModelName.from_dict(_inner) model_with_property_ref = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py index 87034d5e7..b7fa116e3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py @@ -44,12 +44,13 @@ def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: if isinstance(data, Unset): return data try: - a_property_type_0: Union[Unset, AnEnum] if not isinstance(data, str): raise TypeError() - a_property_type_0 = UNSET _a_property_type_0 = data - if not isinstance(_a_property_type_0, Unset): + a_property_type_0: Union[Unset, AnEnum] + if isinstance(_a_property_type_0, Unset): + a_property_type_0 = UNSET + else: a_property_type_0 = AnEnum(_a_property_type_0) return a_property_type_0 @@ -57,10 +58,11 @@ def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: pass if not isinstance(data, int): raise TypeError() - a_property_type_1: Union[Unset, AnIntEnum] - a_property_type_1 = UNSET _a_property_type_1 = data - if not isinstance(_a_property_type_1, Unset): + a_property_type_1: Union[Unset, AnIntEnum] + if isinstance(_a_property_type_1, Unset): + a_property_type_1 = UNSET + else: a_property_type_1 = AnIntEnum(_a_property_type_1) return a_property_type_1 diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py index 822f45cc2..27e91a80a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py @@ -46,12 +46,13 @@ def _parse_fruit( if isinstance(data, Unset): return data try: - fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0] if not isinstance(data, dict): raise TypeError() - fruit_type_0 = UNSET _fruit_type_0 = data - if not isinstance(_fruit_type_0, Unset): + fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0] + if isinstance(_fruit_type_0, Unset): + fruit_type_0 = UNSET + else: fruit_type_0 = ModelWithUnionPropertyInlinedFruitType0.from_dict(_fruit_type_0) return fruit_type_0 @@ -59,10 +60,11 @@ def _parse_fruit( pass if not isinstance(data, dict): raise TypeError() - fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1] - fruit_type_1 = UNSET _fruit_type_1 = data - if not isinstance(_fruit_type_1, Unset): + fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1] + if isinstance(_fruit_type_1, Unset): + fruit_type_1 = UNSET + else: fruit_type_1 = ModelWithUnionPropertyInlinedFruitType1.from_dict(_fruit_type_1) return fruit_type_1 diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 585b97cde..094bd920a 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -188,6 +188,36 @@ } } }, + "/tests/post_form_data": { + "post": { + "tags": [ + "tests" + ], + "sumnary": "Post from data", + "description": "Post form data", + "operationId": "post_form_data", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/AFormData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/tests/upload": { "post": { "tags": [ @@ -752,19 +782,25 @@ ], "get": { "responses": { - "200": {"description": "Success"} + "200": { + "description": "Success" + } } }, "post": { "responses": { - "200": {"description": "Success"} + "200": { + "description": "Success" + } } } }, "/same-name-multiple-locations/{param}": { "description": "Test that if you have a property of the same name in multiple locations, it produces valid code", "get": { - "tags": ["parameters"], + "tags": [ + "parameters" + ], "parameters": [ { "name": "param", @@ -791,6 +827,20 @@ }, "components": { "schemas": { + "AFormData": { + "type": "object", + "properties": { + "an_optional_field": { + "type": "string" + }, + "an_required_field": { + "type": "string" + } + }, + "required": [ + "an_required_field" + ] + }, "AModel": { "title": "AModel", "required": [ @@ -893,10 +943,10 @@ "one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -904,10 +954,10 @@ "nullable_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -915,10 +965,10 @@ "not_required_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -926,10 +976,10 @@ "not_required_nullable_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" }, { "type": "string" @@ -940,7 +990,7 @@ "model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -948,7 +998,7 @@ "nullable_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -956,7 +1006,7 @@ "not_required_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -964,7 +1014,7 @@ "not_required_nullable_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -975,23 +1025,37 @@ }, "AnEnum": { "title": "AnEnum", - "enum": ["FIRST_VALUE", "SECOND_VALUE"], + "enum": [ + "FIRST_VALUE", + "SECOND_VALUE" + ], "description": "For testing Enums in all the ways they can be used " }, "AnAllOfEnum": { "title": "AnAllOfEnum", - "enum": ["foo", "bar", "a_default", "overridden_default"], + "enum": [ + "foo", + "bar", + "a_default", + "overridden_default" + ], "default": "a_default" }, "AnIntEnum": { "title": "AnIntEnum", - "enum": [-1, 1, 2], + "enum": [ + -1, + 1, + 2 + ], "type": "integer", "description": "An enumeration." }, "Body_upload_file_tests_upload_post": { "title": "Body_upload_file_tests_upload_post", - "required": ["some_file"], + "required": [ + "some_file" + ], "type": "object", "properties": { "some_file": { @@ -1009,7 +1073,10 @@ }, "DifferentEnum": { "title": "DifferentEnum", - "enum": ["DIFFERENT", "OTHER"], + "enum": [ + "DIFFERENT", + "OTHER" + ], "description": "An enumeration." }, "HTTPValidationError": { @@ -1028,7 +1095,11 @@ }, "ValidationError": { "title": "ValidationError", - "required": ["loc", "msg", "type"], + "required": [ + "loc", + "msg", + "type" + ], "type": "object", "properties": { "loc": { @@ -1055,8 +1126,12 @@ "properties": { "a_property": { "oneOf": [ - {"$ref": "#/components/schemas/AnEnum"}, - {"$ref": "#/components/schemas/AnIntEnum"} + { + "$ref": "#/components/schemas/AnEnum" + }, + { + "$ref": "#/components/schemas/AnIntEnum" + } ] } }, @@ -1071,13 +1146,17 @@ { "type": "object", "properties": { - "apples": {"type": "string"} + "apples": { + "type": "string" + } } }, { "type": "object", "properties": { - "bananas": {"type": "string"} + "bananas": { + "type": "string" + } } } ] @@ -1099,7 +1178,9 @@ "additionalProperties": { "type": "object", "properties": { - "extra_props_prop": {"type": "string"} + "extra_props_prop": { + "type": "string" + } }, "additionalProperties": {} } @@ -1163,8 +1244,12 @@ "title": "ModelFromAllOf", "type": "object", "allOf": [ - {"$ref": "#/components/schemas/AllOfSubModel"}, - {"$ref": "#/components/schemas/AnotherAllOfSubModel"} + { + "$ref": "#/components/schemas/AllOfSubModel" + }, + { + "$ref": "#/components/schemas/AnotherAllOfSubModel" + } ] }, "AllOfSubModel": { @@ -1192,8 +1277,605 @@ "ModelWithPropertyRef": { "type": "object", "properties": { - "inner": {"$ref": "#/components/schemas/model_reference_doesnt_match"} + "inner": { + "$ref": "#/components/schemas/model_reference_doesnt_match" + } + } + }, + "AModelWithPropertiesReferenceThatAreNotObject": { + "type": "object", + "required": [ + "enum_properties_ref", + "str_properties_ref", + "date_properties_ref", + "datetime_properties_ref", + "int32_properties_ref", + "int64_properties_ref", + "float_properties_ref", + "double_properties_ref", + "file_properties_ref", + "bytestream_properties_ref", + "enum_properties", + "str_properties", + "date_properties", + "datetime_properties", + "int32_properties", + "int64_properties", + "float_properties", + "double_properties", + "file_properties", + "bytestream_properties", + "enum_property_ref", + "str_property_ref", + "date_property_ref", + "datetime_property_ref", + "int32_property_ref", + "int64_property_ref", + "float_property_ref", + "double_property_ref", + "file_property_ref", + "bytestream_property_ref" + ], + "properties": { + "enum_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfEnum" + }, + "str_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfString" + }, + "date_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDate" + }, + "datetime_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDateTime" + }, + "int32_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt32" + }, + "int64_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt64" + }, + "float_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFloat" + }, + "double_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDouble" + }, + "file_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFile" + }, + "bytestream_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfByteStream" + }, + "enum_properties": { + "$ref": "#/components/schemas/AnArrayOfEnum" + }, + "str_properties": { + "$ref": "#/components/schemas/AnArrayOfString" + }, + "date_properties": { + "$ref": "#/components/schemas/AnArrayOfDate" + }, + "datetime_properties": { + "$ref": "#/components/schemas/AnArrayOfDateTime" + }, + "int32_properties": { + "$ref": "#/components/schemas/AnArrayOfInt32" + }, + "int64_properties": { + "$ref": "#/components/schemas/AnArrayOfInt64" + }, + "float_properties": { + "$ref": "#/components/schemas/AnArrayOfFloat" + }, + "double_properties": { + "$ref": "#/components/schemas/AnArrayOfDouble" + }, + "file_properties": { + "$ref": "#/components/schemas/AnArrayOfFile" + }, + "bytestream_properties": { + "$ref": "#/components/schemas/AnArrayOfByteStream" + }, + "enum_property_ref": { + "$ref": "#/components/schemas/AnEnum" + }, + "str_property_ref": { + "$ref": "#/components/schemas/AString" + }, + "date_property_ref": { + "$ref": "#/components/schemas/ADate" + }, + "datetime_property_ref": { + "$ref": "#/components/schemas/ADateTime" + }, + "int32_property_ref": { + "$ref": "#/components/schemas/AnInt32" + }, + "int64_property_ref": { + "$ref": "#/components/schemas/AnInt64" + }, + "float_property_ref": { + "$ref": "#/components/schemas/AFloat" + }, + "double_property_ref": { + "$ref": "#/components/schemas/ADouble" + }, + "file_property_ref": { + "$ref": "#/components/schemas/AFile" + }, + "bytestream_property_ref": { + "$ref": "#/components/schemas/AByteStream" + } + } + }, + "AnArrayOfEnum": { + "type": "array", + "items": { + "title": "AnEnum", + "enum": [ + "FIRST_VALUE", + "SECOND_VALUE" + ], + "description": "For testing Enums in all the ways they can be used " + } + }, + "AnOtherArrayOfEnum": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnEnum" + } + }, + "AnArrayOfString": { + "type": "array", + "items": { + "type": "string" + } + }, + "AnOtherArrayOfString": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AString" + } + }, + "AString": { + "type": "string", + "pattern": "^helloworld.*" + }, + "AnArrayOfDate": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + "AnOtherArrayOfDate": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADate" + } + }, + "ADate": { + "type": "string", + "format": "date" + }, + "AnArrayOfDateTime": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "AnOtherArrayOfDateTime": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADateTime" + } + }, + "ADateTime": { + "type": "string", + "format": "date-time" + }, + "AnArrayOfInt32": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "AnOtherArrayOfInt32": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt32" + } + }, + "AnInt32": { + "type": "integer", + "format": "int32" + }, + "AnArrayOfInt64": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "AnOtherArrayOfInt64": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt64" + } + }, + "AnInt64": { + "type": "integer", + "format": "int64" + }, + "AnArrayOfFloat": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfFloat": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFloat" + } + }, + "AFloat": { + "type": "number", + "format": "float" + }, + "AnArrayOfDouble": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfDouble": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADouble" + } + }, + "ADouble": { + "type": "number", + "format": "double" + }, + "AnArrayOfFile": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + }, + "AnOtherArrayOfFile": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFile" + } + }, + "AFile": { + "type": "string", + "format": "binary" + }, + "AnArrayOfByteStream": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + }, + "AnOtherArrayOfByteStream": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AByteStream" + } + }, + "AByteStream": { + "type": "string", + "format": "byte" + }, + "AModelWithIndirectReferenceProperty": { + "title": "AModelWithIndirectReferenceProperty", + "type": "object", + "properties": { + "an_enum_indirect_ref": { + "$ref": "#/components/schemas/AnEnumDeeperIndirectReference" + } + } + }, + "AnEnumDeeperIndirectReference": { + "$ref": "#/components/schemas/AnEnumIndirectReference" + }, + "AnEnumIndirectReference": { + "$ref": "#/components/schemas/AnEnum" + }, + "AModelWithIndirectSelfReferenceProperty": { + "type": "object", + "properties": { + "an_enum": { + "$ref": "#/components/schemas/AnEnum" + }, + "required_self_ref": { + "$ref": "#/components/schemas/AnDeeperIndirectSelfReference" + }, + "optional_self_ref": { + "$ref": "#/components/schemas/AnDeeperIndirectSelfReference" + } + }, + "required": [ + "required_self_ref" + ] + }, + "AnDeeperIndirectSelfReference": { + "$ref": "#/components/schemas/AnIndirectSelfReference" + }, + "AnIndirectSelfReference": { + "$ref": "#/components/schemas/AModelWithIndirectSelfReferenceProperty" + }, + "AModelWithPropertiesReferenceThatAreNotObject": { + "type": "object", + "properties": { + "enum_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfEnum" + }, + "str_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfString" + }, + "date_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDate" + }, + "datetime_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDateTime" + }, + "int32_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt32" + }, + "int64_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt64" + }, + "float_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFloat" + }, + "double_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDouble" + }, + "file_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFile" + }, + "bytestream_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfByteStream" + }, + "enum_properties": { + "$ref": "#/components/schemas/AnArrayOfEnum" + }, + "str_properties": { + "$ref": "#/components/schemas/AnArrayOfString" + }, + "date_properties": { + "$ref": "#/components/schemas/AnArrayOfDate" + }, + "datetime_properties": { + "$ref": "#/components/schemas/AnArrayOfDateTime" + }, + "int32_properties": { + "$ref": "#/components/schemas/AnArrayOfInt32" + }, + "int64_properties": { + "$ref": "#/components/schemas/AnArrayOfInt64" + }, + "float_properties": { + "$ref": "#/components/schemas/AnArrayOfFloat" + }, + "double_properties": { + "$ref": "#/components/schemas/AnArrayOfDouble" + }, + "file_properties": { + "$ref": "#/components/schemas/AnArrayOfFile" + }, + "bytestream_properties": { + "$ref": "#/components/schemas/AnArrayOfByteStream" + }, + "enum_property_ref": { + "$ref": "#/components/schemas/AnEnum" + }, + "str_property_ref": { + "$ref": "#/components/schemas/AString" + }, + "date_property_ref": { + "$ref": "#/components/schemas/ADate" + }, + "datetime_property_ref": { + "$ref": "#/components/schemas/ADateTime" + }, + "int32_property_ref": { + "$ref": "#/components/schemas/AnInt32" + }, + "int64_property_ref": { + "$ref": "#/components/schemas/AnInt64" + }, + "float_property_ref": { + "$ref": "#/components/schemas/AFloat" + }, + "double_property_ref": { + "$ref": "#/components/schemas/ADouble" + }, + "file_property_ref": { + "$ref": "#/components/schemas/AFile" + }, + "bytestream_property_ref": { + "$ref": "#/components/schemas/AByteStream" + } + } + }, + "AnArrayOfEnum": { + "type": "array", + "items": { + "title": "AnEnum", + "enum": ["FIRST_VALUE", "SECOND_VALUE"], + "description": "For testing Enums in all the ways they can be used " + } + }, + "AnOtherArrayOfEnum": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnEnum" } + }, + "AnArrayOfString": { + "type": "array", + "items": { + "type": "string" + } + }, + "AnOtherArrayOfString": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AString" + } + }, + "AString": { + "type": "string", + "pattern": "^helloworld.*" + }, + "AnArrayOfDate": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + "AnOtherArrayOfDate": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADate" + } + }, + "ADate": { + "type": "string", + "format": "date" + }, + "AnArrayOfDateTime": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "AnOtherArrayOfDateTime": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADateTime" + } + }, + "ADateTime": { + "type": "string", + "format": "date-time" + }, + "AnArrayOfInt32": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "AnOtherArrayOfInt32": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt32" + } + }, + "AnInt32": { + "type": "integer", + "format": "int32" + }, + "AnArrayOfInt64": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "AnOtherArrayOfInt64": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt64" + } + }, + "AnInt64": { + "type": "integer", + "format": "int64" + }, + "AnArrayOfFloat": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfFloat": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFloat" + } + }, + "AFloat": { + "type": "number", + "format": "float" + }, + "AnArrayOfDouble": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfDouble": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADouble" + } + }, + "ADouble": { + "type": "number", + "format": "double" + }, + "AnArrayOfFile": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + }, + "AnOtherArrayOfFile": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFile" + } + }, + "AFile": { + "type": "string", + "format": "binary" + }, + "AnArrayOfByteStream": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + }, + "AnOtherArrayOfByteStream": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AByteStream" + } + }, + "AByteStream": { + "type": "string", + "format": "byte" } } } diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py index aaa6aa850..1d4dc943d 100644 --- a/end_to_end_tests/regen_golden_record.py +++ b/end_to_end_tests/regen_golden_record.py @@ -1,12 +1,16 @@ """ Regenerate golden-record """ +import filecmp +import os import shutil +import tempfile from pathlib import Path from typer.testing import CliRunner from openapi_python_client.cli import app -if __name__ == "__main__": + +def regen_golden_record(): runner = CliRunner() openapi_path = Path(__file__).parent / "openapi.json" @@ -24,3 +28,52 @@ if result.exception: raise result.exception output_path.rename(gr_path) + + +def regen_custom_template_golden_record(): + runner = CliRunner() + openapi_path = Path(__file__).parent / "openapi.json" + tpl_dir = Path(__file__).parent / "test_custom_templates" + + gr_path = Path(__file__).parent / "golden-record" + tpl_gr_path = Path(__file__).parent / "custom-templates-golden-record" + + output_path = Path(tempfile.mkdtemp()) + config_path = Path(__file__).parent / "config.yml" + + shutil.rmtree(tpl_gr_path, ignore_errors=True) + + os.chdir(str(output_path.absolute())) + result = runner.invoke( + app, ["generate", f"--config={config_path}", f"--path={openapi_path}", f"--custom-template-path={tpl_dir}"] + ) + + if result.stdout: + generated_output_path = output_path / "my-test-api-client" + for f in generated_output_path.glob("**/*"): # nb: works for Windows and Unix + relative_to_generated = f.relative_to(generated_output_path) + gr_file = gr_path / relative_to_generated + if not gr_file.exists(): + print(f"{gr_file} does not exist, ignoring") + continue + + if not gr_file.is_file(): + continue + + if not filecmp.cmp(gr_file, f, shallow=False): + target_file = tpl_gr_path / relative_to_generated + target_dir = target_file.parent + + target_dir.mkdir(parents=True, exist_ok=True) + shutil.copy(f"{f}", f"{target_file}") + + shutil.rmtree(output_path, ignore_errors=True) + + if result.exception: + shutil.rmtree(output_path, ignore_errors=True) + raise result.exception + + +if __name__ == "__main__": + regen_golden_record() + regen_custom_template_golden_record() diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja new file mode 100644 index 000000000..3655db659 --- /dev/null +++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja @@ -0,0 +1,13 @@ +""" Contains methods for accessing the API """ + +from typing import Type +{% for tag, collection in endpoint_collections_by_tag %} +from {{ package_name }}.api.{{ tag }} import {{ utils.pascal_case(tag) }}Endpoints +{% endfor %} + +class {{ utils.pascal_case(package_name) }}Api: +{% for tag, collection in endpoint_collections_by_tag %} + @classmethod + def {{ tag }}(cls) -> Type[{{ utils.pascal_case(tag) }}Endpoints]: + return {{ utils.pascal_case(tag) }}Endpoints +{% endfor %} diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja new file mode 100644 index 000000000..fbbd40e4f --- /dev/null +++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja @@ -0,0 +1,24 @@ +""" Contains methods for accessing the API Endpoints """ + +import types +{% for endpoint in endpoints %} +from {{ package_name }}.api.{{ tag }} import {{ utils.snake_case(endpoint.name) }} +{% endfor %} + +class {{ utils.pascal_case(tag) }}Endpoints: + +{% for endpoint in endpoints %} + + @classmethod + def {{ utils.snake_case(endpoint.name) }}(cls) -> types.ModuleType: + {% if endpoint.description %} + """ + {{ endpoint.description }} + """ + {% elif endpoint.summary %} + """ + {{ endpoint.summary }} + """ + {% endif %} + return {{ utils.snake_case(endpoint.name) }} +{% endfor %} diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py index fa4d21598..ad552092a 100644 --- a/end_to_end_tests/test_end_to_end.py +++ b/end_to_end_tests/test_end_to_end.py @@ -12,7 +12,10 @@ def _compare_directories( record: Path, test_subject: Path, - expected_differences: Optional[Dict[str, str]] = None, + expected_differences: Optional[ + Dict[str, str] + ] = None, # key: path relative to generated directory, value: expected generated content + depth=0, ): first_printable = record.relative_to(Path.cwd()) second_printable = test_subject.relative_to(Path.cwd()) @@ -22,27 +25,41 @@ def _compare_directories( pytest.fail(f"{first_printable} or {second_printable} was missing: {missing_files}", pytrace=False) expected_differences = expected_differences or {} - _, mismatch, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False) - mismatch = set(mismatch) - - for file_name in mismatch | set(expected_differences.keys()): - if file_name not in expected_differences: - continue - if file_name not in mismatch: - pytest.fail(f"Expected {file_name} to be different but it was not", pytrace=False) - generated = (test_subject / file_name).read_text() - assert generated == expected_differences[file_name], f"Unexpected output in {file_name}" - del expected_differences[file_name] - mismatch.remove(file_name) - - if mismatch: + _, mismatches, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False) + mismatches = set(mismatches) + + expected_path_mismatches = [] + for file_name in mismatches: + + mismatch_file_path = test_subject.joinpath(file_name) + for expected_differences_path in expected_differences.keys(): + + if mismatch_file_path.match(str(expected_differences_path)): + + generated_content = (test_subject / file_name).read_text() + expected_content = expected_differences[expected_differences_path] + assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}" + expected_path_mismatches.append(expected_differences_path) + + for path_mismatch in expected_path_mismatches: + matched_file_name = path_mismatch.name + mismatches.remove(matched_file_name) + del expected_differences[path_mismatch] + + if mismatches: pytest.fail( - f"{first_printable} and {second_printable} had differing files: {mismatch}, and errors {errors}", + f"{first_printable} and {second_printable} had differing files: {mismatches}, and errors {errors}", pytrace=False, ) for sub_path in dc.common_dirs: - _compare_directories(record / sub_path, test_subject / sub_path, expected_differences=expected_differences) + _compare_directories( + record / sub_path, test_subject / sub_path, expected_differences=expected_differences, depth=depth + 1 + ) + + if depth == 0 and len(expected_differences.keys()) > 0: + failure = "\n".join([f"Expected {path} to be different but it was not" for path in expected_differences.keys()]) + pytest.fail(failure, pytrace=False) def run_e2e_test(extra_args=None, expected_differences=None): @@ -60,6 +77,7 @@ def run_e2e_test(extra_args=None, expected_differences=None): if result.exit_code != 0: raise result.exception + _compare_directories(gr_path, output_path, expected_differences=expected_differences) import mypy.api @@ -75,7 +93,21 @@ def test_end_to_end(): def test_custom_templates(): + expected_differences = {} # key: path relative to generated directory, value: expected generated content + expected_difference_paths = [ + Path("README.md"), + Path("my_test_api_client").joinpath("api", "__init__.py"), + Path("my_test_api_client").joinpath("api", "tests", "__init__.py"), + Path("my_test_api_client").joinpath("api", "default", "__init__.py"), + Path("my_test_api_client").joinpath("api", "parameters", "__init__.py"), + ] + + golden_tpls_root_dir = Path(__file__).parent.joinpath("custom-templates-golden-record") + for expected_difference_path in expected_difference_paths: + path = Path("my-test-api-client").joinpath(expected_difference_path) + expected_differences[path] = (golden_tpls_root_dir / expected_difference_path).read_text() + run_e2e_test( - extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates"], - expected_differences={"README.md": "my-test-api-client"}, + extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"], + expected_differences=expected_differences, ) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 2a7cf574b..43f813483 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -67,6 +67,7 @@ def __init__( else: loader = package_loader self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True) + self.env.globals.update(utils=utils) self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" self.project_dir: Path = Path.cwd() @@ -239,15 +240,30 @@ def _build_api(self) -> None: client_path.write_text(client_template.render(), encoding=self.file_encoding) # Generate endpoints + endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag.items() api_dir = self.package_dir / "api" api_dir.mkdir() - api_init = api_dir / "__init__.py" - api_init.write_text('""" Contains methods for accessing the API """', encoding=self.file_encoding) + api_init_path = api_dir / "__init__.py" + api_init_template = self.env.get_template("api_init.py.jinja") + api_init_path.write_text( + api_init_template.render( + package_name=self.package_name, + endpoint_collections_by_tag=endpoint_collections_by_tag, + ), + encoding=self.file_encoding, + ) endpoint_template = self.env.get_template("endpoint_module.py.jinja") - for tag, collection in self.openapi.endpoint_collections_by_tag.items(): + for tag, collection in endpoint_collections_by_tag: tag_dir = api_dir / tag tag_dir.mkdir() + + endpoint_init_path = tag_dir / "__init__.py" + endpoint_init_template = self.env.get_template("endpoint_init.py.jinja") + endpoint_init_path.write_text( + endpoint_init_template.render(package_name=self.package_name, tag=tag, endpoints=collection.endpoints), + encoding=self.file_encoding, + ) (tag_dir / "__init__.py").touch() for endpoint in collection.endpoints: diff --git a/openapi_python_client/__main__.py b/openapi_python_client/__main__.py index 4cafccbaf..f8cd76f35 100644 --- a/openapi_python_client/__main__.py +++ b/openapi_python_client/__main__.py @@ -1,3 +1,3 @@ -from .cli import cli +from .cli import app -cli() +app() diff --git a/openapi_python_client/parser/errors.py b/openapi_python_client/parser/errors.py index 562e54428..6855ef6bd 100644 --- a/openapi_python_client/parser/errors.py +++ b/openapi_python_client/parser/errors.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Optional +from typing import Any, Optional __all__ = ["ErrorLevel", "GeneratorError", "ParseError", "PropertyError", "ValidationError"] @@ -39,5 +39,12 @@ class PropertyError(ParseError): header = "Problem creating a Property: " +@dataclass +class RecursiveReferenceInterupt(PropertyError): + """Error raised when a property have an recursive reference to itself""" + + schemas: Optional[Any] = None # TODO: shall not use Any here, shall be Schemas, to fix later + + class ValidationError(Exception): pass diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index ca8793d09..f0b9774ce 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -151,7 +151,14 @@ def _add_body( body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config ) if isinstance(json_body, ParseError): - return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas + return ( + ParseError( + header=f"Cannot parse body of endpoint {endpoint.name}", + detail=json_body.detail, + data=json_body.data, + ), + schemas, + ) endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, config=config) diff --git a/openapi_python_client/parser/properties/.property.py.swp b/openapi_python_client/parser/properties/.property.py.swp new file mode 100644 index 000000000..b8aa28d43 Binary files /dev/null and b/openapi_python_client/parser/properties/.property.py.swp differ diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 908745a6a..539559d17 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -17,12 +17,12 @@ from ... import Config from ... import schema as oai from ... import utils -from ..errors import ParseError, PropertyError, ValidationError +from ..errors import ParseError, PropertyError, RecursiveReferenceInterupt, ValidationError from .converter import convert, convert_chain from .enum_property import EnumProperty from .model_property import ModelProperty, build_model_property from .property import Property -from .schemas import Class, Schemas, parse_reference_path, update_schemas_with_data +from .schemas import Class, Schemas, _Holder, _ReferencePath, parse_reference_path, update_schemas_with @attr.s(auto_attribs=True, frozen=True) @@ -34,6 +34,59 @@ class NoneProperty(Property): template: ClassVar[Optional[str]] = "none_property.py.jinja" +@attr.s(auto_attribs=True, frozen=True) +class LazySelfReferenceProperty(Property): + """A property used to resolve recursive reference. + It proxyfy the required method call to its binded Property owner + """ + + owner: _Holder[Union[ModelProperty, EnumProperty, RecursiveReferenceInterupt]] + _resolved: bool = False + + def get_base_type_string(self) -> str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_base_type_string() + + def get_base_json_type_string(self) -> str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_base_json_type_string() + + def get_type_string(self, no_optional: bool = False, json: bool = False) -> str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_type_string(no_optional, json) + + def get_instance_type_string(self) -> str: + self._ensure_resolved() + return super().get_instance_type_string() + + def to_string(self) -> str: + self._ensure_resolved() + + if not self.required: + return f"{self.python_name}: Union[Unset, {self.get_type_string()}] = UNSET" + else: + return f"{self.python_name}: {self.get_type_string()}" + + def _ensure_resolved(self) -> None: + if self._resolved: + return + + if not isinstance(self.owner.data, Property): + raise RuntimeError(f"LazySelfReferenceProperty {self.name} owner shall have been resolved.") + else: + object.__setattr__(self, "_resolved", True) + object.__setattr__(self, "nullable", self.owner.data.nullable) + + @attr.s(auto_attribs=True, frozen=True) class StringProperty(Property): """A property of type str""" @@ -352,6 +405,7 @@ def build_union_property( *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: sub_properties: List[Property] = [] + for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)): sub_prop, schemas = property_from_data( name=f"{name}_type{i}", @@ -410,11 +464,18 @@ def _property_from_ref( ref_path = parse_reference_path(data.ref) if isinstance(ref_path, ParseError): return PropertyError(data=data, detail=ref_path.detail), schemas + existing = schemas.classes_by_reference.get(ref_path) - if not existing: + if not existing or not existing.data: return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas - prop = attr.evolve(existing, required=required, name=name) + if isinstance(existing.data, RecursiveReferenceInterupt): + return ( + LazySelfReferenceProperty(required=required, name=name, nullable=False, default=None, owner=existing), + schemas, + ) + + prop = attr.evolve(existing.data, required=required, name=name) if parent: prop = attr.evolve(prop, nullable=parent.nullable) if isinstance(prop, EnumProperty): @@ -439,8 +500,8 @@ def _property_from_data( if isinstance(data, oai.Reference): return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas) + sub_data: List[Union[oai.Schema, oai.Reference]] = data.allOf + data.anyOf + data.oneOf # A union of a single reference should just be passed through to that reference (don't create copy class) - sub_data = (data.allOf or []) + data.anyOf + data.oneOf if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference): return _property_from_ref(name=name, required=required, parent=data, data=sub_data[0], schemas=schemas) @@ -550,29 +611,47 @@ def build_schemas( to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() still_making_progress = True errors: List[PropertyError] = [] + recursive_references_waiting_reprocess: Dict[str, Union[oai.Reference, oai.Schema]] = dict() + visited: Set[_ReferencePath] = set() + depth = 0 # References could have forward References so keep going as long as we are making progress while still_making_progress: still_making_progress = False errors = [] next_round = [] + # Only accumulate errors from the last round, since we might fix some along the way for name, data in to_process: - if isinstance(data, oai.Reference): - schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported.")) - continue ref_path = parse_reference_path(f"#/components/schemas/{name}") if isinstance(ref_path, ParseError): schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue - schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) + + schemas_or_err = update_schemas_with( + ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config + ) if isinstance(schemas_or_err, PropertyError): - next_round.append((name, data)) - errors.append(schemas_or_err) - continue + ### TODO: Recursive reference detection does not properly works, disabled for now + # visited.add(ref_path) + + if isinstance(schemas_or_err, RecursiveReferenceInterupt): + up_schemas = schemas_or_err.schemas + assert isinstance(up_schemas, Schemas) # TODO fix typedef in RecursiveReferenceInterupt + schemas_or_err = up_schemas + recursive_references_waiting_reprocess[name] = data + else: + next_round.append((name, data)) + errors.append(schemas_or_err) + continue + schemas = schemas_or_err still_making_progress = True + depth += 1 to_process = next_round + if len(recursive_references_waiting_reprocess.keys()): + schemas = build_schemas(components=recursive_references_waiting_reprocess, schemas=schemas, config=config) + schemas.errors.extend(errors) return schemas diff --git a/openapi_python_client/parser/properties/converter.py b/openapi_python_client/parser/properties/converter.py index b493755fc..d8556e118 100644 --- a/openapi_python_client/parser/properties/converter.py +++ b/openapi_python_client/parser/properties/converter.py @@ -31,7 +31,7 @@ def convert(type_string: str, value: Any) -> Optional[Any]: raise ValidationError() try: return _CONVERTERS[type_string](value) - except (KeyError, ValueError) as e: + except (KeyError, ValueError, AttributeError) as e: raise ValidationError from e @@ -58,8 +58,10 @@ def convert_chain(type_strings: Iterable[str], value: Any) -> Optional[Any]: raise ValidationError() -def _convert_string(value: str) -> Optional[str]: - return f"{utils.remove_string_escapes(value)!r}" +def _convert_string(value: Any) -> Optional[str]: + if isinstance(value, str): + value = utils.remove_string_escapes(value) + return repr(value) def _convert_datetime(value: str) -> Optional[str]: diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index a40460886..70db0c5ed 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -11,6 +11,11 @@ from .schemas import Class, Schemas, parse_reference_path +@attr.s(auto_attribs=True, frozen=True) +class RecusiveProperty(Property): + pass + + @attr.s(auto_attribs=True, frozen=True) class ModelProperty(Property): """A property which refers to another Schema""" @@ -88,14 +93,17 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: return prop_or_error unprocessed_props = data.properties or {} - for sub_prop in data.allOf or []: + for sub_prop in data.allOf: if isinstance(sub_prop, oai.Reference): ref_path = parse_reference_path(sub_prop.ref) if isinstance(ref_path, ParseError): return PropertyError(detail=ref_path.detail, data=sub_prop) - sub_model = schemas.classes_by_reference.get(ref_path) - if sub_model is None: + + sub_model_ref = schemas.classes_by_reference.get(ref_path) + if sub_model_ref is None or not isinstance(sub_model_ref.data, Property): return PropertyError(f"Reference {sub_prop.ref} not found") + + sub_model = sub_model_ref.data if not isinstance(sub_model, ModelProperty): return PropertyError("Cannot take allOf a non-object") for prop in chain(sub_model.required_properties, sub_model.optional_properties): diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index a81879dfc..6e369e6ab 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -1,6 +1,6 @@ -__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with_data"] +__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with", "_ReferencePath"] -from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast +from typing import TYPE_CHECKING, Dict, Generic, List, NewType, Optional, Set, TypeVar, Union, cast from urllib.parse import urlparse import attr @@ -8,16 +8,14 @@ from ... import Config from ... import schema as oai from ... import utils -from ..errors import ParseError, PropertyError +from ..errors import ParseError, PropertyError, RecursiveReferenceInterupt if TYPE_CHECKING: # pragma: no cover - from .enum_property import EnumProperty - from .model_property import ModelProperty + from .property import Property else: - EnumProperty = "EnumProperty" - ModelProperty = "ModelProperty" - + Property = "Property" +T = TypeVar("T") _ReferencePath = NewType("_ReferencePath", str) _ClassName = NewType("_ClassName", str) @@ -29,6 +27,11 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError] return cast(_ReferencePath, parsed.fragment) +@attr.s(auto_attribs=True) +class _Holder(Generic[T]): + data: Optional[T] + + @attr.s(auto_attribs=True, frozen=True) class Class: """Represents Python class which will be generated from an OpenAPI schema""" @@ -58,26 +61,67 @@ def from_string(*, string: str, config: Config) -> "Class": class Schemas: """Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums)""" - classes_by_reference: Dict[_ReferencePath, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) - classes_by_name: Dict[_ClassName, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) + classes_by_reference: Dict[ + _ReferencePath, _Holder[Union[Property, RecursiveReferenceInterupt]] + ] = attr.ib(factory=dict) + classes_by_name: Dict[ + _ClassName, _Holder[Union[Property, RecursiveReferenceInterupt]] + ] = attr.ib(factory=dict) errors: List[ParseError] = attr.ib(factory=list) -def update_schemas_with_data( - *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config +def update_schemas_with( + *, + ref_path: _ReferencePath, + data: Union[oai.Reference, oai.Schema], + schemas: Schemas, + visited: Set[_ReferencePath], + config: Config, ) -> Union[Schemas, PropertyError]: - from . import build_enum_property, build_model_property - - prop: Union[PropertyError, ModelProperty, EnumProperty] - if data.enum is not None: - prop, schemas = build_enum_property( - data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None, config=config + if isinstance(data, oai.Reference): + return _update_schemas_with_reference( + ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config ) else: - prop, schemas = build_model_property( - data=data, name=ref_path, schemas=schemas, required=True, parent_name=None, config=config - ) + return _update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config) + + +def _update_schemas_with_reference( + *, ref_path: _ReferencePath, data: oai.Reference, schemas: Schemas, visited: Set[_ReferencePath], config: Config +) -> Union[Schemas, PropertyError]: + reference_pointer = parse_reference_path(data.ref) + if isinstance(reference_pointer, ParseError): + return PropertyError(detail=reference_pointer.detail, data=data) + + resolved_reference = schemas.classes_by_reference.get(reference_pointer) + if resolved_reference: + return attr.evolve(schemas, classes_by_reference={ref_path: resolved_reference, **schemas.classes_by_reference}) + else: + return PropertyError(f"Reference {ref_path} could not be resolved", data=data) + + +def _update_schemas_with_data( + *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, visited: Set[_ReferencePath], config: Config +) -> Union[Schemas, PropertyError]: + from . import property_from_data + + prop: Union[PropertyError, Property] + prop, schemas = property_from_data( + data=data, name=ref_path, schemas=schemas, required=True, parent_name="", config=config + ) + + holder = schemas.classes_by_reference.get(ref_path) if isinstance(prop, PropertyError): + if ref_path in visited and not holder: + holder = _Holder(data=RecursiveReferenceInterupt()) + schemas = attr.evolve(schemas, classes_by_reference={ref_path: holder, **schemas.classes_by_reference}) + return RecursiveReferenceInterupt(schemas=schemas) return prop - schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference}) + + if holder: + holder.data = prop + else: + schemas = attr.evolve( + schemas, classes_by_reference={ref_path: _Holder(data=prop), **schemas.classes_by_reference} + ) return schemas diff --git a/openapi_python_client/schema/openapi_schema_pydantic/README.md b/openapi_python_client/schema/openapi_schema_pydantic/README.md index 0e4d40146..f58b36909 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/README.md +++ b/openapi_python_client/schema/openapi_schema_pydantic/README.md @@ -1,5 +1,8 @@ Everything in this directory (including the rest of this file after this paragraph) is a vendored copy of [openapi-schem-pydantic](https://github.com/kuimono/openapi-schema-pydantic) and is licensed under the LICENSE file in this directory. +Included vendored version is the [following](https://github.com/kuimono/openapi-schema-pydantic/commit/0836b429086917feeb973de3367a7ac4c2b3a665) +Small patches has been applied to it. + ## Alias Due to the reserved words in python and pydantic, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py index 9edb7d3d9..6b02446a8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py @@ -36,8 +36,11 @@ "ServerVariable", "Tag", "XML", + "Callback", ] + +from .callback import Callback from .components import Components from .contact import Contact from .discriminator import Discriminator diff --git a/openapi_python_client/schema/openapi_schema_pydantic/callback.py b/openapi_python_client/schema/openapi_schema_pydantic/callback.py new file mode 100644 index 000000000..9d95ffe89 --- /dev/null +++ b/openapi_python_client/schema/openapi_schema_pydantic/callback.py @@ -0,0 +1,22 @@ +from typing import TYPE_CHECKING, Dict + +if TYPE_CHECKING: + from .path_item import PathItem + +Callback = Dict[str, "PathItem"] +""" +A map of possible out-of band callbacks related to the parent operation. +Each value in the map is a [Path Item Object](#pathItemObject) +that describes a set of requests that may be initiated by the API provider and the expected responses. +The key value used to identify the path item object is an expression, evaluated at runtime, +that identifies a URL to use for the callback operation. +""" + +"""Patterned Fields""" + +# {expression}: 'PathItem' = ... +""" +A Path Item Object used to define a callback request and expected responses. + +A [complete example](../examples/v3.0/callback-example.yaml) is available. +""" diff --git a/openapi_python_client/schema/openapi_schema_pydantic/components.py b/openapi_python_client/schema/openapi_schema_pydantic/components.py index 8ad888906..a9a2f0339 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/components.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/components.py @@ -1,7 +1,8 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra +from .callback import Callback from .example import Example from .header import Header from .link import Link @@ -44,7 +45,11 @@ class Components(BaseModel): links: Optional[Dict[str, Union[Link, Reference]]] = None """An object to hold reusable [Link Objects](#linkObject).""" + callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None + """An object to hold reusable [Callback Objects](#callbackObject).""" + class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/contact.py b/openapi_python_client/schema/openapi_schema_pydantic/contact.py index c41aa8406..cbe2fb8eb 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/contact.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/contact.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class Contact(BaseModel): @@ -26,6 +26,7 @@ class Contact(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ {"name": "API Support", "url": "http://www.example.com/support", "email": "support@example.com"} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py index 1dfd06a5b..c2deb5a17 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class Discriminator(BaseModel): @@ -25,6 +25,7 @@ class Discriminator(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py index 9bf2ea177..a9c08a67e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py @@ -1,9 +1,12 @@ -from typing import Dict, Optional +from typing import TYPE_CHECKING, Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .reference import Reference +if TYPE_CHECKING: + from .header import Header + class Encoding(BaseModel): """A single encoding definition applied to a single schema property.""" @@ -22,7 +25,7 @@ class Encoding(BaseModel): or a comma-separated list of the two types. """ - headers: Optional[Dict[str, Reference]] = None + headers: Optional[Dict[str, Union["Header", Reference]]] = None """ A map allowing additional information to be provided as headers, for example `Content-Disposition`. @@ -60,6 +63,7 @@ class Encoding(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/example.py b/openapi_python_client/schema/openapi_schema_pydantic/example.py index 6da3710b1..4ac234b37 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/example.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/example.py @@ -1,6 +1,6 @@ from typing import Any, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class Example(BaseModel): @@ -33,6 +33,7 @@ class Example(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ {"summary": "A foo example", "value": {"foo": "bar"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py index 92676a2ae..467c98150 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class ExternalDocumentation(BaseModel): @@ -19,4 +19,5 @@ class ExternalDocumentation(BaseModel): """ class Config: + extra = Extra.allow schema_extra = {"examples": [{"description": "Find more info here", "url": "https://example.com"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/header.py b/openapi_python_client/schema/openapi_schema_pydantic/header.py index 5e59e8a66..8d6ee2ff1 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/header.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/header.py @@ -1,4 +1,4 @@ -from pydantic import Field +from pydantic import Extra, Field from ..parameter_location import ParameterLocation from .parameter import Parameter @@ -18,6 +18,7 @@ class Header(Parameter): param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in") class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/info.py b/openapi_python_client/schema/openapi_schema_pydantic/info.py index 36caba733..fcff5b742 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/info.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/info.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra from .contact import Contact from .license import License @@ -47,6 +47,7 @@ class Info(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/license.py b/openapi_python_client/schema/openapi_schema_pydantic/license.py index 567a5d117..7e8b7f3d9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/license.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/license.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class License(BaseModel): @@ -20,4 +20,5 @@ class License(BaseModel): """ class Config: + extra = Extra.allow schema_extra = {"examples": [{"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/link.py b/openapi_python_client/schema/openapi_schema_pydantic/link.py index 97bbc8aeb..de1403a8c 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/link.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/link.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .server import Server @@ -63,6 +63,7 @@ class Link(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ {"operationId": "getUserAddressByUUID", "parameters": {"userUuid": "$response.body#/uuid"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py index 7c00d3bc1..387f33b92 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from .encoding import Encoding from .example import Example @@ -49,6 +49,7 @@ class MediaType(BaseModel): """ class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py index cdf474bee..7b3c93bfe 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class OAuthFlow(BaseModel): @@ -15,7 +15,7 @@ class OAuthFlow(BaseModel): This MUST be in the form of a URL. """ - tokenUrl: Optional[str] = None + tokenUrl: Optional[AnyUrl] = None """ **REQUIRED** for `oauth2 ("password", "clientCredentials", "authorizationCode")`. The token URL to be used for this flow. @@ -35,6 +35,7 @@ class OAuthFlow(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py index fcb9ba348..e23b3d5dd 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .oauth_flow import OAuthFlow @@ -33,3 +33,6 @@ class OAuthFlows(BaseModel): Previously called `accessCode` in OpenAPI 2.0. """ + + class Config: + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py index dd480a8f5..cbfaa2038 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .components import Components from .external_documentation import ExternalDocumentation @@ -58,3 +58,6 @@ class OpenAPI(BaseModel): """ Additional external documentation. """ + + class Config: + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 860c3c24a..98b2bd2ad 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -1,7 +1,8 @@ -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra +from .callback import Callback from .external_documentation import ExternalDocumentation from .parameter import Parameter from .reference import Reference @@ -70,6 +71,14 @@ class Operation(BaseModel): **REQUIRED**. The list of possible responses as they are returned from executing this operation. """ + callbacks: Optional[Dict[str, Callback]] = None + """ + A map of possible out-of band callbacks related to the parent operation. + The key is a unique identifier for the Callback Object. + Each value in the map is a [Callback Object](#callbackObject) + that describes a request that may be initiated by the API provider and the expected responses. + """ + deprecated: bool = False """ Declares this operation to be deprecated. @@ -95,6 +104,7 @@ class Operation(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py index 52f1b6885..56070a720 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from ..parameter_location import ParameterLocation from .example import Example @@ -26,7 +26,7 @@ class Parameter(BaseModel): - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring within the [path](#pathsPath) field in the [Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further information. - - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`. + - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored. - For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property. """ @@ -142,6 +142,7 @@ class Parameter(BaseModel): """ class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py index 911c1e805..cdbd6b564 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py @@ -1,6 +1,6 @@ from typing import List, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from .operation import Operation from .parameter import Parameter @@ -92,6 +92,7 @@ class PathItem(BaseModel): """ class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py index 7803b3a54..851b28301 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field class Reference(BaseModel): @@ -16,6 +16,7 @@ class Reference(BaseModel): """**REQUIRED**. The reference string.""" class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [{"$ref": "#/components/schemas/Pet"}, {"$ref": "Pet.json"}, {"$ref": "definitions.json#/Pet"}] diff --git a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py index 626b795d7..3f7a602d9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .media_type import MediaType @@ -31,6 +31,7 @@ class RequestBody(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/response.py b/openapi_python_client/schema/openapi_schema_pydantic/response.py index 8c8e539ec..24899815e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/response.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/response.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .header import Header from .link import Link @@ -44,6 +44,7 @@ class Response(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 5941d79f5..87492eadc 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from .discriminator import Discriminator from .external_documentation import ExternalDocumentation @@ -224,7 +224,7 @@ class Schema(BaseModel): types defined by keyword. Recall: "number" includes "integer". """ - allOf: Optional[List[Union[Reference, "Schema"]]] = None + allOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** @@ -241,7 +241,7 @@ class Schema(BaseModel): value. """ - oneOf: List[Union[Reference, "Schema"]] = [] + oneOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** @@ -258,7 +258,7 @@ class Schema(BaseModel): keyword's value. """ - anyOf: List[Union[Reference, "Schema"]] = [] + anyOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** @@ -472,6 +472,7 @@ class Schema(BaseModel): """ class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py index aee65bc2b..7bd3b4e8e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, Field +from pydantic import AnyUrl, BaseModel, Extra, Field from .oauth_flows import OAuthFlows @@ -66,6 +66,7 @@ class SecurityScheme(BaseModel): """ class Config: + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server.py b/openapi_python_client/schema/openapi_schema_pydantic/server.py index 43c511f34..949fec8c8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .server_variable import ServerVariable @@ -31,6 +31,7 @@ class Server(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ {"url": "https://development.gigantic-server.com/v1", "description": "Development server"}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py index 224c79411..8aa4fac46 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class ServerVariable(BaseModel): @@ -26,3 +26,6 @@ class ServerVariable(BaseModel): An optional description for the server variable. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ + + class Config: + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/tag.py b/openapi_python_client/schema/openapi_schema_pydantic/tag.py index 531de02b4..1db40d325 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/tag.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/tag.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .external_documentation import ExternalDocumentation @@ -28,4 +28,5 @@ class Tag(BaseModel): """ class Config: + extra = Extra.allow schema_extra = {"examples": [{"name": "pet", "description": "Pets operations"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/xml.py b/openapi_python_client/schema/openapi_schema_pydantic/xml.py index 9ddaf13e3..cfc9c64cb 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/xml.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/xml.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class XML(BaseModel): @@ -48,6 +48,7 @@ class XML(BaseModel): """ class Config: + extra = Extra.allow schema_extra = { "examples": [ {"namespace": "http://example.com/schema/sample", "prefix": "sample"}, diff --git a/openapi_python_client/templates/api_init.py.jinja b/openapi_python_client/templates/api_init.py.jinja new file mode 100644 index 000000000..dc035f4ce --- /dev/null +++ b/openapi_python_client/templates/api_init.py.jinja @@ -0,0 +1 @@ +""" Contains methods for accessing the API """ diff --git a/openapi_python_client/templates/endpoint_init.py.jinja b/openapi_python_client/templates/endpoint_init.py.jinja new file mode 100644 index 000000000..e69de29bb diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 06876141a..687705def 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -1,7 +1,6 @@ from typing import Any, Dict, List, Optional, Union, cast import httpx -from attr import asdict from ...client import AuthenticatedClient, Client from ...types import Response, UNSET{% if endpoint.multipart_body_class %}, File {% endif %} @@ -52,7 +51,7 @@ def _get_kwargs( "cookies": cookies, "timeout": client.get_timeout(), {% if endpoint.form_body_class %} - "data": asdict(form_data), + "data": form_data.to_dict(), {% elif endpoint.multipart_body_class %} "files": files, "data": data, diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja index 8541db32d..0cc98b105 100644 --- a/openapi_python_client/templates/model.py.jinja +++ b/openapi_python_client/templates/model.py.jinja @@ -1,4 +1,4 @@ -from typing import Any, Dict, Type, TypeVar +from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO {% if model.additional_properties %} from typing import List diff --git a/openapi_python_client/templates/property_templates/property_macros.py.jinja b/openapi_python_client/templates/property_templates/property_macros.py.jinja index 92669cdc2..d578d1d4f 100644 --- a/openapi_python_client/templates/property_templates/property_macros.py.jinja +++ b/openapi_python_client/templates/property_templates/property_macros.py.jinja @@ -1,16 +1,20 @@ {% macro construct_template(construct_function, property, source, initial_value=None) %} {% if property.required and not property.nullable %} {{ property.python_name }} = {{ construct_function(property, source) }} -{% else %} -{% if initial_value != None %} -{{ property.python_name }} = {{ initial_value }} -{% elif property.nullable %} -{{ property.python_name }} = None -{% else %} -{{ property.python_name }}: {{ property.get_type_string() }} = UNSET -{% endif %} +{% else %}{# Must be nullable OR non-required #} _{{ property.python_name }} = {{ source }} -if {% if property.nullable %}_{{ property.python_name }} is not None{% endif %}{% if property.nullable and not property.required %} and {% endif %}{% if not property.required %}not isinstance(_{{ property.python_name }}, Unset){% endif %}: +{{ property.python_name }}: {{ property.get_type_string() }} + {% if property.nullable %} +if _{{ property.python_name }} is None: + {{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}None{% endif %} + + {% endif %} + {% if not property.required %} +{% if property.nullable %}elif{% else %}if{% endif %} isinstance(_{{ property.python_name }}, Unset): + {{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}UNSET{% endif %} + + {% endif %} +else: {{ property.python_name }} = {{ construct_function(property, "_" + property.python_name) }} {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/union_property.py.jinja b/openapi_python_client/templates/property_templates/union_property.py.jinja index 684ae942d..87ea9820f 100644 --- a/openapi_python_client/templates/property_templates/union_property.py.jinja +++ b/openapi_python_client/templates/property_templates/union_property.py.jinja @@ -11,7 +11,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri {% for inner_property in property.inner_properties_with_template() %} {% if not loop.last or property.has_properties_without_templates %} try: - {{ inner_property.python_name }}: {{ inner_property.get_type_string() }} {% from "property_templates/" + inner_property.template import construct, check_type_for_construct %} if not {{ check_type_for_construct(inner_property, "data") }}: raise TypeError() @@ -23,7 +22,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri {% from "property_templates/" + inner_property.template import construct, check_type_for_construct %} if not {{ check_type_for_construct(inner_property, "data") }}: raise TypeError() - {{ inner_property.python_name }}: {{ inner_property.get_type_string() }} {{ construct(inner_property, "data", initial_value="UNSET") | indent(4) }} return {{ inner_property.python_name }} {% endif %} diff --git a/poetry.lock b/poetry.lock index 7db8957b7..0dba5fd26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,25 +259,25 @@ colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] name = "jinja2" -version = "2.11.3" +version = "3.0.0" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -MarkupSafe = ">=0.23" +MarkupSafe = ">=2.0.0rc2" [package.extras] -i18n = ["Babel (>=0.8)"] +i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "1.1.1" +version = "2.0.0" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -521,7 +521,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "rfc3986" -version = "1.4.0" +version = "1.5.0" description = "Validating URI References per RFC 3986" category = "main" optional = false @@ -670,7 +670,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "b125cc4bc5456b335d0412e87dc21175738b4138a6eb9f4689b79d07171b8273" +content-hash = "41d160b8f136bcba70b7be42ffc5b11f9011671f49c88f51e75a537aa15adec1" [metadata.files] appdirs = [ @@ -806,62 +806,44 @@ isort = [ {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jinja2 = [ - {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, - {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, + {file = "Jinja2-3.0.0-py3-none-any.whl", hash = "sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6"}, + {file = "Jinja2-3.0.0.tar.gz", hash = "sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"}, ] markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"}, + {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1048,8 +1030,8 @@ requests = [ {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, ] rfc3986 = [ - {file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, - {file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"}, + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] safety = [ {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, diff --git a/pyproject.toml b/pyproject.toml index d39350c1a..37847579b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openapi-python-client" -version = "0.9.0" +version = "0.9.1" description = "Generate modern Python clients from OpenAPI" repository = "https://github.com/triaxtec/openapi-python-client" license = "MIT" @@ -20,11 +20,11 @@ include = ["CHANGELOG.md", "openapi_python_client/py.typed"] [tool.poetry.dependencies] python = "^3.6.2" -jinja2 = "^2.11.1" +jinja2 = "^3.0.0" typer = "^0.3" colorama = {version = "^0.4.3", markers = "sys_platform == 'win32'"} shellingham = "^1.3.2" -black = "^21.4b0" +black = "*" isort = "^5.0.5" pyyaml = "^5.3.1" importlib_metadata = {version = "^2.0.0", python = "<3.8"} diff --git a/tests/test___main__.py b/tests/test___main__.py index 4e2a51546..79ef06e0a 100644 --- a/tests/test___main__.py +++ b/tests/test___main__.py @@ -1,7 +1,7 @@ def test_main(mocker): - cli = mocker.patch("openapi_python_client.cli.cli") + app = mocker.patch("openapi_python_client.cli.app") # noinspection PyUnresolvedReferences from openapi_python_client import __main__ - cli.assert_called_once() + app.assert_called_once() diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index c496b7dc2..a220d243c 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -234,7 +234,7 @@ def test_add_body_bad_data(self, mocker): from openapi_python_client.parser.openapi import Endpoint, Schemas mocker.patch.object(Endpoint, "parse_request_form_body") - parse_error = ParseError(data=mocker.MagicMock()) + parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) other_schemas = mocker.MagicMock() mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(parse_error, other_schemas)) endpoint = self.make_endpoint() @@ -249,7 +249,11 @@ def test_add_body_bad_data(self, mocker): ) assert result == ( - ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=parse_error.data), + ParseError( + header=f"Cannot parse body of endpoint {endpoint.name}", + detail=parse_error.detail, + data=parse_error.data, + ), other_schemas, ) diff --git a/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py b/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py index 0b44852df..23208c971 100644 --- a/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py +++ b/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py @@ -7,9 +7,13 @@ if not isinstance(some_source, Unset): some_destination = some_source.isoformat() if some_source else None -a_prop = None _a_prop = some_destination -if _a_prop is not None and not isinstance(_a_prop, Unset): +a_prop: Union[Unset, None, datetime.date] +if _a_prop is None: + a_prop = None +elif isinstance(_a_prop, Unset): + a_prop = UNSET +else: a_prop = isoparse(_a_prop).date() diff --git a/tests/test_templates/test_property_templates/test_date_property/required_nullable.py b/tests/test_templates/test_property_templates/test_date_property/required_nullable.py index f974c3210..79dd66ba4 100644 --- a/tests/test_templates/test_property_templates/test_date_property/required_nullable.py +++ b/tests/test_templates/test_property_templates/test_date_property/required_nullable.py @@ -4,9 +4,11 @@ from dateutil.parser import isoparse some_source = date(2020, 10, 12) some_destination = some_source.isoformat() if some_source else None -a_prop = None _a_prop = some_destination -if _a_prop is not None: +a_prop: Optional[datetime.date] +if _a_prop is None: + a_prop = None +else: a_prop = isoparse(_a_prop).date()