From d9001eeecda9df9c8b026abba4bd1de4515c24fb Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Sun, 1 May 2022 12:14:34 -0600 Subject: [PATCH] feat!: Prefer `title` attributes when naming Python parameters/properties. Closes #602 --- .../api/default/__init__.py | 6 +- .../api/default/get_naming_properties.py | 112 ++++++++++++++++++ .../api/tests/get_basic_list_of_booleans.py | 4 +- .../api/tests/get_basic_list_of_floats.py | 4 +- .../api/tests/get_basic_list_of_integers.py | 4 +- .../api/tests/get_basic_list_of_strings.py | 4 +- .../api/tests/get_user_list.py | 74 ++++++------ ..._with_cookie_auth_token_with_cookie_get.py | 16 +-- .../my_test_api_client/models/__init__.py | 1 + .../my_test_api_client/models/a_model.py | 40 +++---- ...roperties_reference_that_are_not_object.py | 12 +- .../get_naming_properties_response_200.py | 73 ++++++++++++ .../models/validation_error.py | 36 +++--- end_to_end_tests/openapi.json | 99 +++++++++++----- .../parser/properties/__init__.py | 18 +-- .../parser/properties/model_property.py | 11 +- tests/conftest.py | 4 +- .../test_parser/test_properties/test_init.py | 7 +- .../test_properties/test_model_property.py | 1 + 19 files changed, 385 insertions(+), 141 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/default/get_naming_properties.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/get_naming_properties_response_200.py 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 index a4580103f..d40d8bd00 100644 --- 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 @@ -2,7 +2,7 @@ import types -from . import get_common_parameters, post_common_parameters +from . import get_common_parameters, get_naming_properties, post_common_parameters class DefaultEndpoints: @@ -13,3 +13,7 @@ def get_common_parameters(cls) -> types.ModuleType: @classmethod def post_common_parameters(cls) -> types.ModuleType: return post_common_parameters + + @classmethod + def get_naming_properties(cls) -> types.ModuleType: + return get_naming_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_naming_properties.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_naming_properties.py new file mode 100644 index 000000000..284aa224e --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_naming_properties.py @@ -0,0 +1,112 @@ +from typing import Any, Dict, Optional + +import httpx + +from ...client import Client +from ...models.get_naming_properties_response_200 import GetNamingPropertiesResponse200 +from ...types import Response + + +def _get_kwargs( + *, + client: Client, +) -> Dict[str, Any]: + url = "{}/naming/properties".format(client.base_url) + + headers: Dict[str, str] = client.get_headers() + cookies: Dict[str, Any] = client.get_cookies() + + return { + "method": "get", + "url": url, + "headers": headers, + "cookies": cookies, + "timeout": client.get_timeout(), + } + + +def _parse_response(*, response: httpx.Response) -> Optional[GetNamingPropertiesResponse200]: + if response.status_code == 200: + response_200 = GetNamingPropertiesResponse200.from_dict(response.json()) + + return response_200 + return None + + +def _build_response(*, response: httpx.Response) -> Response[GetNamingPropertiesResponse200]: + return Response( + status_code=response.status_code, + content=response.content, + headers=response.headers, + parsed=_parse_response(response=response), + ) + + +def sync_detailed( + *, + client: Client, +) -> Response[GetNamingPropertiesResponse200]: + """ + Returns: + Response[GetNamingPropertiesResponse200] + """ + + kwargs = _get_kwargs( + client=client, + ) + + response = httpx.request( + verify=client.verify_ssl, + **kwargs, + ) + + return _build_response(response=response) + + +def sync( + *, + client: Client, +) -> Optional[GetNamingPropertiesResponse200]: + """ + Returns: + Response[GetNamingPropertiesResponse200] + """ + + return sync_detailed( + client=client, + ).parsed + + +async def asyncio_detailed( + *, + client: Client, +) -> Response[GetNamingPropertiesResponse200]: + """ + Returns: + Response[GetNamingPropertiesResponse200] + """ + + kwargs = _get_kwargs( + client=client, + ) + + async with httpx.AsyncClient(verify=client.verify_ssl) as _client: + response = await _client.request(**kwargs) + + return _build_response(response=response) + + +async def asyncio( + *, + client: Client, +) -> Optional[GetNamingPropertiesResponse200]: + """ + Returns: + Response[GetNamingPropertiesResponse200] + """ + + return ( + await asyncio_detailed( + client=client, + ) + ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py index bddce1d9a..2768d73ef 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py @@ -26,9 +26,9 @@ def _get_kwargs( def _parse_response(*, response: httpx.Response) -> Optional[List[bool]]: if response.status_code == 200: - response_200 = cast(List[bool], response.json()) + response_get_basic_list_of_booleans_tests_basic_lists_booleans_get = cast(List[bool], response.json()) - return response_200 + return response_get_basic_list_of_booleans_tests_basic_lists_booleans_get return None diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py index 083fc498e..eb9dfbebd 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py @@ -26,9 +26,9 @@ def _get_kwargs( def _parse_response(*, response: httpx.Response) -> Optional[List[float]]: if response.status_code == 200: - response_200 = cast(List[float], response.json()) + response_get_basic_list_of_floats_tests_basic_lists_floats_get = cast(List[float], response.json()) - return response_200 + return response_get_basic_list_of_floats_tests_basic_lists_floats_get return None diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py index 21b9f4c4f..14baf2dcf 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py @@ -26,9 +26,9 @@ def _get_kwargs( def _parse_response(*, response: httpx.Response) -> Optional[List[int]]: if response.status_code == 200: - response_200 = cast(List[int], response.json()) + response_get_basic_list_of_integers_tests_basic_lists_integers_get = cast(List[int], response.json()) - return response_200 + return response_get_basic_list_of_integers_tests_basic_lists_integers_get return None diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py index 7fb6a52d6..878eedc8f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py @@ -26,9 +26,9 @@ def _get_kwargs( def _parse_response(*, response: httpx.Response) -> Optional[List[str]]: if response.status_code == 200: - response_200 = cast(List[str], response.json()) + response_get_basic_list_of_strings_tests_basic_lists_strings_get = cast(List[str], response.json()) - return response_200 + return response_get_basic_list_of_strings_tests_basic_lists_strings_get return None diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index 29dd706e8..d20efcd2c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -15,8 +15,8 @@ def _get_kwargs( *, client: Client, an_enum_value: List[AnEnum], - an_enum_value_with_null: List[Optional[AnEnumWithNull]], - an_enum_value_with_only_null: List[None], + an_enum_value_with_null_and_string_values: List[Optional[AnEnumWithNull]], + an_enum_value_with_only_null_values: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Dict[str, Any]: url = "{}/tests/".format(client.base_url) @@ -33,19 +33,19 @@ def _get_kwargs( params["an_enum_value"] = json_an_enum_value - json_an_enum_value_with_null = [] - for an_enum_value_with_null_item_data in an_enum_value_with_null: + json_an_enum_value_with_null_and_string_values = [] + for an_enum_value_with_null_item_data in an_enum_value_with_null_and_string_values: an_enum_value_with_null_item = ( an_enum_value_with_null_item_data.value if an_enum_value_with_null_item_data else None ) - json_an_enum_value_with_null.append(an_enum_value_with_null_item) + json_an_enum_value_with_null_and_string_values.append(an_enum_value_with_null_item) - params["an_enum_value_with_null"] = json_an_enum_value_with_null + params["an_enum_value_with_null"] = json_an_enum_value_with_null_and_string_values - json_an_enum_value_with_only_null = an_enum_value_with_only_null + json_an_enum_value_with_only_null_values = an_enum_value_with_only_null_values - params["an_enum_value_with_only_null"] = json_an_enum_value_with_only_null + params["an_enum_value_with_only_null"] = json_an_enum_value_with_only_null_values if isinstance(some_date, datetime.date): json_some_date = some_date.isoformat() @@ -68,14 +68,14 @@ def _get_kwargs( def _parse_response(*, response: httpx.Response) -> Optional[Union[HTTPValidationError, List[AModel]]]: if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: + response_get_list_tests_get = [] + _response_get_list_tests_get = response.json() + for response_200_item_data in _response_get_list_tests_get: response_200_item = AModel.from_dict(response_200_item_data) - response_200.append(response_200_item) + response_get_list_tests_get.append(response_200_item) - return response_200 + return response_get_list_tests_get if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) @@ -100,8 +100,8 @@ def sync_detailed( *, client: Client, an_enum_value: List[AnEnum], - an_enum_value_with_null: List[Optional[AnEnumWithNull]], - an_enum_value_with_only_null: List[None], + an_enum_value_with_null_and_string_values: List[Optional[AnEnumWithNull]], + an_enum_value_with_only_null_values: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Response[Union[HTTPValidationError, List[AModel]]]: """Get List @@ -110,8 +110,8 @@ def sync_detailed( Args: an_enum_value (List[AnEnum]): - an_enum_value_with_null (List[Optional[AnEnumWithNull]]): - an_enum_value_with_only_null (List[None]): + an_enum_value_with_null_and_string_values (List[Optional[AnEnumWithNull]]): + an_enum_value_with_only_null_values (List[None]): some_date (Union[datetime.date, datetime.datetime]): Returns: @@ -121,8 +121,8 @@ def sync_detailed( kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, - an_enum_value_with_null=an_enum_value_with_null, - an_enum_value_with_only_null=an_enum_value_with_only_null, + an_enum_value_with_null_and_string_values=an_enum_value_with_null_and_string_values, + an_enum_value_with_only_null_values=an_enum_value_with_only_null_values, some_date=some_date, ) @@ -138,8 +138,8 @@ def sync( *, client: Client, an_enum_value: List[AnEnum], - an_enum_value_with_null: List[Optional[AnEnumWithNull]], - an_enum_value_with_only_null: List[None], + an_enum_value_with_null_and_string_values: List[Optional[AnEnumWithNull]], + an_enum_value_with_only_null_values: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Optional[Union[HTTPValidationError, List[AModel]]]: """Get List @@ -148,8 +148,8 @@ def sync( Args: an_enum_value (List[AnEnum]): - an_enum_value_with_null (List[Optional[AnEnumWithNull]]): - an_enum_value_with_only_null (List[None]): + an_enum_value_with_null_and_string_values (List[Optional[AnEnumWithNull]]): + an_enum_value_with_only_null_values (List[None]): some_date (Union[datetime.date, datetime.datetime]): Returns: @@ -159,8 +159,8 @@ def sync( return sync_detailed( client=client, an_enum_value=an_enum_value, - an_enum_value_with_null=an_enum_value_with_null, - an_enum_value_with_only_null=an_enum_value_with_only_null, + an_enum_value_with_null_and_string_values=an_enum_value_with_null_and_string_values, + an_enum_value_with_only_null_values=an_enum_value_with_only_null_values, some_date=some_date, ).parsed @@ -169,8 +169,8 @@ async def asyncio_detailed( *, client: Client, an_enum_value: List[AnEnum], - an_enum_value_with_null: List[Optional[AnEnumWithNull]], - an_enum_value_with_only_null: List[None], + an_enum_value_with_null_and_string_values: List[Optional[AnEnumWithNull]], + an_enum_value_with_only_null_values: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Response[Union[HTTPValidationError, List[AModel]]]: """Get List @@ -179,8 +179,8 @@ async def asyncio_detailed( Args: an_enum_value (List[AnEnum]): - an_enum_value_with_null (List[Optional[AnEnumWithNull]]): - an_enum_value_with_only_null (List[None]): + an_enum_value_with_null_and_string_values (List[Optional[AnEnumWithNull]]): + an_enum_value_with_only_null_values (List[None]): some_date (Union[datetime.date, datetime.datetime]): Returns: @@ -190,8 +190,8 @@ async def asyncio_detailed( kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, - an_enum_value_with_null=an_enum_value_with_null, - an_enum_value_with_only_null=an_enum_value_with_only_null, + an_enum_value_with_null_and_string_values=an_enum_value_with_null_and_string_values, + an_enum_value_with_only_null_values=an_enum_value_with_only_null_values, some_date=some_date, ) @@ -205,8 +205,8 @@ async def asyncio( *, client: Client, an_enum_value: List[AnEnum], - an_enum_value_with_null: List[Optional[AnEnumWithNull]], - an_enum_value_with_only_null: List[None], + an_enum_value_with_null_and_string_values: List[Optional[AnEnumWithNull]], + an_enum_value_with_only_null_values: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Optional[Union[HTTPValidationError, List[AModel]]]: """Get List @@ -215,8 +215,8 @@ async def asyncio( Args: an_enum_value (List[AnEnum]): - an_enum_value_with_null (List[Optional[AnEnumWithNull]]): - an_enum_value_with_only_null (List[None]): + an_enum_value_with_null_and_string_values (List[Optional[AnEnumWithNull]]): + an_enum_value_with_only_null_values (List[None]): some_date (Union[datetime.date, datetime.datetime]): Returns: @@ -227,8 +227,8 @@ async def asyncio( await asyncio_detailed( client=client, an_enum_value=an_enum_value, - an_enum_value_with_null=an_enum_value_with_null, - an_enum_value_with_only_null=an_enum_value_with_only_null, + an_enum_value_with_null_and_string_values=an_enum_value_with_null_and_string_values, + an_enum_value_with_only_null_values=an_enum_value_with_only_null_values, some_date=some_date, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py index 337ad0603..99dc35b7f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py @@ -9,14 +9,14 @@ def _get_kwargs( *, client: Client, - my_token: str, + token: str, ) -> Dict[str, Any]: url = "{}/auth/token_with_cookie".format(client.base_url) headers: Dict[str, str] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - cookies["MyToken"] = my_token + cookies["MyToken"] = token return { "method": "get", @@ -39,14 +39,14 @@ def _build_response(*, response: httpx.Response) -> Response[Any]: def sync_detailed( *, client: Client, - my_token: str, + token: str, ) -> Response[Any]: """TOKEN_WITH_COOKIE Test optional cookie parameters Args: - my_token (str): + token (str): Returns: Response[Any] @@ -54,7 +54,7 @@ def sync_detailed( kwargs = _get_kwargs( client=client, - my_token=my_token, + token=token, ) response = httpx.request( @@ -68,14 +68,14 @@ def sync_detailed( async def asyncio_detailed( *, client: Client, - my_token: str, + token: str, ) -> Response[Any]: """TOKEN_WITH_COOKIE Test optional cookie parameters Args: - my_token (str): + token (str): Returns: Response[Any] @@ -83,7 +83,7 @@ async def asyncio_detailed( kwargs = _get_kwargs( client=client, - my_token=my_token, + token=token, ) async with httpx.AsyncClient(verify=client.verify_ssl) as _client: 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 85e5243ec..789e4059a 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 @@ -19,6 +19,7 @@ from .body_upload_file_tests_upload_post_some_optional_object import BodyUploadFileTestsUploadPostSomeOptionalObject from .different_enum import DifferentEnum from .free_form_model import FreeFormModel +from .get_naming_properties_response_200 import GetNamingPropertiesResponse200 from .http_validation_error import HTTPValidationError from .import_ import Import from .model_from_all_of import ModelFromAllOf 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 d52001229..8f582f3e6 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 @@ -32,9 +32,9 @@ class AModel: a_nullable_date (Optional[datetime.date]): a_not_required_date (Union[Unset, datetime.date]): attr_1_leading_digit (Union[Unset, str]): - required_nullable (Optional[str]): - not_required_nullable (Union[Unset, None, str]): - not_required_not_nullable (Union[Unset, str]): + required_and_nullable (Optional[str]): + not_required_and_nullable (Union[Unset, None, str]): + not_required_and_not_nullable (Union[Unset, str]): nullable_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, None]): not_required_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, Unset]): not_required_nullable_one_of_models (Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]): @@ -50,7 +50,7 @@ class AModel: one_of_models: Union[Any, FreeFormModel, ModelWithUnionProperty] model: ModelWithUnionProperty a_nullable_date: Optional[datetime.date] - required_nullable: Optional[str] + required_and_nullable: Optional[str] nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None] nullable_model: Optional[ModelWithUnionProperty] an_allof_enum_with_overridden_default: AnAllOfEnum = AnAllOfEnum.OVERRIDDEN_DEFAULT @@ -59,8 +59,8 @@ class AModel: nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET a_not_required_date: Union[Unset, datetime.date] = UNSET attr_1_leading_digit: Union[Unset, str] = UNSET - not_required_nullable: Union[Unset, None, str] = UNSET - not_required_not_nullable: Union[Unset, str] = UNSET + not_required_and_nullable: Union[Unset, None, str] = UNSET + not_required_and_not_nullable: Union[Unset, str] = UNSET not_required_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, Unset] = UNSET not_required_nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str] = UNSET not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET @@ -114,9 +114,9 @@ def to_dict(self) -> Dict[str, Any]: a_not_required_date = self.a_not_required_date.isoformat() attr_1_leading_digit = self.attr_1_leading_digit - required_nullable = self.required_nullable - not_required_nullable = self.not_required_nullable - not_required_not_nullable = self.not_required_not_nullable + required_and_nullable = self.required_and_nullable + not_required_and_nullable = self.not_required_and_nullable + not_required_and_not_nullable = self.not_required_and_not_nullable nullable_one_of_models: Union[Dict[str, Any], None] if self.nullable_one_of_models is None: nullable_one_of_models = None @@ -183,7 +183,7 @@ def to_dict(self) -> Dict[str, Any]: "one_of_models": one_of_models, "model": model, "a_nullable_date": a_nullable_date, - "required_nullable": required_nullable, + "required_nullable": required_and_nullable, "nullable_one_of_models": nullable_one_of_models, "nullable_model": nullable_model, } @@ -198,10 +198,10 @@ def to_dict(self) -> Dict[str, Any]: field_dict["a_not_required_date"] = a_not_required_date if attr_1_leading_digit is not UNSET: field_dict["1_leading_digit"] = attr_1_leading_digit - if not_required_nullable is not UNSET: - field_dict["not_required_nullable"] = not_required_nullable - if not_required_not_nullable is not UNSET: - field_dict["not_required_not_nullable"] = not_required_not_nullable + if not_required_and_nullable is not UNSET: + field_dict["not_required_nullable"] = not_required_and_nullable + if not_required_and_not_nullable is not UNSET: + field_dict["not_required_not_nullable"] = not_required_and_not_nullable if not_required_one_of_models is not UNSET: field_dict["not_required_one_of_models"] = not_required_one_of_models if not_required_nullable_one_of_models is not UNSET: @@ -301,11 +301,11 @@ def _parse_one_of_models(data: object) -> Union[Any, FreeFormModel, ModelWithUni attr_1_leading_digit = d.pop("1_leading_digit", UNSET) - required_nullable = d.pop("required_nullable") + required_and_nullable = d.pop("required_nullable") - not_required_nullable = d.pop("not_required_nullable", UNSET) + not_required_and_nullable = d.pop("not_required_nullable", UNSET) - not_required_not_nullable = d.pop("not_required_not_nullable", UNSET) + not_required_and_not_nullable = d.pop("not_required_not_nullable", UNSET) def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]: if data is None: @@ -435,9 +435,9 @@ def _parse_not_required_nullable_one_of_models( a_nullable_date=a_nullable_date, a_not_required_date=a_not_required_date, attr_1_leading_digit=attr_1_leading_digit, - required_nullable=required_nullable, - not_required_nullable=not_required_nullable, - not_required_not_nullable=not_required_not_nullable, + required_and_nullable=required_and_nullable, + not_required_and_nullable=not_required_and_nullable, + not_required_and_not_nullable=not_required_and_not_nullable, nullable_one_of_models=nullable_one_of_models, not_required_one_of_models=not_required_one_of_models, not_required_nullable_one_of_models=not_required_nullable_one_of_models, 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 index 1f1c05565..769e5a296 100644 --- 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 @@ -122,10 +122,10 @@ def to_dict(self) -> Dict[str, Any]: 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 + for an_enum_data in self.enum_properties: + an_enum = an_enum_data.value - enum_properties.append(componentsschemas_an_array_of_enum_item) + enum_properties.append(an_enum) str_properties = self.str_properties @@ -260,10 +260,10 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 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) + for an_enum_data in _enum_properties: + an_enum = AnEnum(an_enum_data) - enum_properties.append(componentsschemas_an_array_of_enum_item) + enum_properties.append(an_enum) str_properties = cast(List[str], d.pop("str_properties")) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/get_naming_properties_response_200.py b/end_to_end_tests/golden-record/my_test_api_client/models/get_naming_properties_response_200.py new file mode 100644 index 000000000..9f209aa1b --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/get_naming_properties_response_200.py @@ -0,0 +1,73 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="GetNamingPropertiesResponse200") + + +@attr.s(auto_attribs=True) +class GetNamingPropertiesResponse200: + """ + Attributes: + private (Union[Unset, str]): + a (Union[Unset, str]): + postfix (Union[Unset, str]): + """ + + private: Union[Unset, str] = UNSET + a: Union[Unset, str] = UNSET + postfix: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + private = self.private + a = self.a + postfix = self.postfix + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if private is not UNSET: + field_dict["_a"] = private + if a is not UNSET: + field_dict["a"] = a + if postfix is not UNSET: + field_dict["a_"] = postfix + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + private = d.pop("_a", UNSET) + + a = d.pop("a", UNSET) + + postfix = d.pop("a_", UNSET) + + get_naming_properties_response_200 = cls( + private=private, + a=a, + postfix=postfix, + ) + + get_naming_properties_response_200.additional_properties = d + return get_naming_properties_response_200 + + @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/validation_error.py b/end_to_end_tests/golden-record/my_test_api_client/models/validation_error.py index e2f6539ee..988059103 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/validation_error.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/validation_error.py @@ -9,27 +9,27 @@ class ValidationError: """ Attributes: - loc (List[str]): - msg (str): - type (str): + location (List[str]): + message (str): + error_type (str): """ - loc: List[str] - msg: str - type: str + location: List[str] + message: str + error_type: str def to_dict(self) -> Dict[str, Any]: - loc = self.loc + location = self.location - msg = self.msg - type = self.type + message = self.message + error_type = self.error_type field_dict: Dict[str, Any] = {} field_dict.update( { - "loc": loc, - "msg": msg, - "type": type, + "loc": location, + "msg": message, + "type": error_type, } ) @@ -38,16 +38,16 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - loc = cast(List[str], d.pop("loc")) + location = cast(List[str], d.pop("loc")) - msg = d.pop("msg") + message = d.pop("msg") - type = d.pop("type") + error_type = d.pop("type") validation_error = cls( - loc=loc, - msg=msg, - type=type, + location=location, + message=message, + error_type=error_type, ) return validation_error diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index b958e530b..f2d686c16 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -809,20 +809,20 @@ }, "parameters": [ { - "name": "param", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "param", - "in": "path", - "required": true, - "schema": { - "type": "string" - } + "name": "param", + "in": "query", + "schema": { + "type": "string" } + }, + { + "name": "param", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } ] }, "/same-name-multiple-locations/{param}": { @@ -871,7 +871,9 @@ }, "/tag_with_number": { "get": { - "tags": ["1"], + "tags": [ + "1" + ], "responses": { "200": { "description": "Success" @@ -1043,6 +1045,36 @@ ], "responses": {} } + }, + "/naming/properties": { + "description": "Ensure that Python properties are renamed properly.", + "get": { + "responses": { + "200": { + "description": "Test property name conflict is resolved via `title`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "_a": { + "title": "private", + "type": "string" + }, + "a": { + "type": "string" + }, + "a_": { + "title": "postfix", + "type": "string" + } + } + } + } + } + } + } + } } }, "components": { @@ -1109,7 +1141,7 @@ "default": [] }, "aCamelDateTime": { - "title": "Acameldatetime", + "title": "aCamelDateTime", "anyOf": [ { "type": "string", @@ -1127,18 +1159,15 @@ "format": "date" }, "a_nullable_date": { - "title": "A Nullable Date", "type": "string", "format": "date", "nullable": true }, "a_not_required_date": { - "title": "A Nullable Date", "type": "string", "format": "date" }, "1_leading_digit": { - "title": "Leading Digit", "type": "string" }, "required_nullable": { @@ -1291,7 +1320,11 @@ }, "Body_upload_file_tests_upload_post": { "title": "Body_upload_file_tests_upload_post", - "required": ["some_file", "some_object", "some_nullable_object"], + "required": [ + "some_file", + "some_object", + "some_nullable_object" + ], "type": "object", "properties": { "some_file": { @@ -1333,7 +1366,10 @@ "some_object": { "title": "Some Object", "type": "object", - "required": ["num", "text"], + "required": [ + "num", + "text" + ], "properties": { "num": { "type": "number" @@ -1346,7 +1382,9 @@ "some_optional_object": { "title": "Some Optional Object", "type": "object", - "required": ["foo"], + "required": [ + "foo" + ], "properties": { "foo": { "type": "string" @@ -1569,7 +1607,10 @@ }, "type_enum": { "type": "integer", - "enum": [0, 1] + "enum": [ + 0, + 1 + ] } } }, @@ -1582,11 +1623,15 @@ }, "type": { "type": "string", - "enum": ["submodel"] + "enum": [ + "submodel" + ] }, "type_enum": { "type": "integer", - "enum": [0] + "enum": [ + 0 + ] } } }, @@ -1729,7 +1774,7 @@ } } }, - "ModelWithDateTimeProperty" : { + "ModelWithDateTimeProperty": { "type": "object", "properties": { "datetime": { @@ -1907,10 +1952,10 @@ "type": "string", "format": "byte" }, - "import": { + "import": { "type": "object" }, - "None": { + "None": { "type": "object" }, "model.reference.with.Periods": { diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 524ff5ba0..176729919 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -275,7 +275,7 @@ def _string_based_property( ) -> Union[StringProperty, DateProperty, DateTimeProperty, FileProperty]: """Construct a Property from the type "string" """ string_format = data.schema_format - python_name = utils.PythonIdentifier(value=name, prefix=config.field_prefix) + python_name = utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix) if string_format == "date-time": return DateTimeProperty( name=name, @@ -367,7 +367,7 @@ def build_enum_property( required=required, nullable=False, default="None", - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=None, example=None, ), @@ -395,7 +395,7 @@ def build_enum_property( values=values, value_type=value_type, default=None, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ) @@ -475,7 +475,7 @@ def build_union_property( default=default, inner_properties=sub_properties, nullable=data.nullable, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), @@ -516,7 +516,7 @@ def build_list_property( default=None, inner_property=inner_prop, nullable=data.nullable, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), @@ -601,7 +601,7 @@ def _property_from_data( default=convert("float", data.default), required=required, nullable=data.nullable, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), @@ -614,7 +614,7 @@ def _property_from_data( default=convert("int", data.default), required=required, nullable=data.nullable, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), @@ -627,7 +627,7 @@ def _property_from_data( required=required, default=convert("bool", data.default), nullable=data.nullable, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), @@ -647,7 +647,7 @@ def _property_from_data( required=required, nullable=False, default=None, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), description=data.description, example=data.example, ), diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 6e68a8f8e..f428f3242 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -6,6 +6,7 @@ from ... import Config from ... import schema as oai from ... import utils +from ...utils import PythonIdentifier from ..errors import ParseError, PropertyError from .enum_property import EnumProperty from .property import Property @@ -117,21 +118,21 @@ def _process_properties( ) -> Union[_PropertyData, PropertyError]: from . import property_from_data - properties: Dict[str, Property] = {} + properties: Dict[PythonIdentifier, Property] = {} relative_imports: Set[str] = set() required_set = set(data.required or []) def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]: nonlocal properties - existing = properties.get(new_prop.name) + existing = properties.get(new_prop.python_name) merged_prop_or_error = _merge_properties(existing, new_prop) if existing else new_prop if isinstance(merged_prop_or_error, PropertyError): merged_prop_or_error.header = ( - f"Found conflicting properties named {new_prop.name} when creating {class_name}" + f"Found conflicting properties named {new_prop.python_name} when creating {class_name}" ) return merged_prop_or_error - properties[merged_prop_or_error.name] = merged_prop_or_error + properties[merged_prop_or_error.python_name] = merged_prop_or_error return None unprocessed_props = data.properties or {} @@ -255,7 +256,7 @@ def build_model_property( required=required, name=name, additional_properties=additional_properties, - python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), + python_name=utils.PythonIdentifier(value=data.title or name, prefix=config.field_prefix), example=data.example, ) if class_info.name in schemas.classes_by_name: diff --git a/tests/conftest.py b/tests/conftest.py index 2a683f102..b6e23b636 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ StringProperty, UnionProperty, ) +from openapi_python_client.utils import PythonIdentifier @pytest.fixture @@ -232,6 +233,7 @@ def _common_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: "example": None, **kwargs, } + title = kwargs.pop("title", None) if not kwargs.get("python_name"): - kwargs["python_name"] = kwargs["name"] + kwargs["python_name"] = PythonIdentifier(title or kwargs["name"], "field_") return kwargs diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 3d2de6519..ba567e5c6 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -305,6 +305,7 @@ def test_property_from_data_str_enum(self, enum_property_factory): class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), value_type=str, default="ParentAnEnum.B", + title="AnEnum", ) assert schemas != new_schemas, "Provided Schemas was mutated" assert new_schemas.classes_by_name == { @@ -336,6 +337,7 @@ def test_property_from_data_str_enum_with_null(self, enum_property_factory): class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), value_type=str, default="ParentAnEnum.B", + title="AnEnum", ) assert prop.nullable is True assert schemas != new_schemas, "Provided Schemas was mutated" @@ -358,7 +360,9 @@ def test_property_from_data_null_enum(self, enum_property_factory, none_property name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() ) - assert prop == none_property_factory(name="my_enum", required=required, nullable=False, default="None") + assert prop == none_property_factory( + name="my_enum", required=required, nullable=False, default="None", title="AnEnumWithOnlyNull" + ) def test_property_from_data_int_enum(self, enum_property_factory): from openapi_python_client.parser.properties import Class, Schemas, property_from_data @@ -384,6 +388,7 @@ def test_property_from_data_int_enum(self, enum_property_factory): class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), value_type=int, default="ParentAnEnum.VALUE_3", + title="AnEnum", ) assert schemas != new_schemas, "Provided Schemas was mutated" assert new_schemas.classes_by_name == { diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 7b96cb687..9c8e125b5 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -127,6 +127,7 @@ def test_happy_path(self, model_property_factory, string_property_factory, date_ "from typing import Union", }, additional_properties=True, + title="MyModel", ) def test_model_name_conflict(self):