From b58fe0998af4bd398ca9a33686423b27d01dd56e Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Tue, 23 Mar 2021 18:59:52 -0600 Subject: [PATCH 01/11] refactorWIP refactor reference / class name handling to fix resolution errors --- .../api/tests/get_user_list.py | 22 +- .../tests/json_body_tests_json_body_post.py | 111 ------ .../my_test_api_client/models/__init__.py | 7 +- .../my_test_api_client/models/a_model.py | 377 ------------------ .../models/a_model_model.py | 93 ----- .../models/a_model_not_required_model.py | 93 ----- .../a_model_not_required_nullable_model.py | 93 ----- .../models/a_model_nullable_model.py | 93 ----- .../my_test_api_client/models/model_name.py | 44 ++ .../models/model_with_property_ref.py | 60 +++ end_to_end_tests/openapi.json | 26 +- openapi_python_client/__init__.py | 51 ++- openapi_python_client/cli.py | 16 +- openapi_python_client/config.py | 31 +- openapi_python_client/parser/__init__.py | 4 +- openapi_python_client/parser/errors.py | 2 +- openapi_python_client/parser/openapi.py | 57 ++- .../parser/properties/__init__.py | 71 ++-- .../parser/properties/enum_property.py | 8 +- .../parser/properties/model_property.py | 38 +- .../parser/properties/property.py | 7 +- .../parser/properties/schemas.py | 64 ++- openapi_python_client/parser/reference.py | 27 -- openapi_python_client/parser/responses.py | 17 +- .../openapi_schema_pydantic/reference.py | 4 +- openapi_python_client/utils.py | 10 +- 26 files changed, 348 insertions(+), 1078 deletions(-) delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model.py delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py delete mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/model_name.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py delete mode 100644 openapi_python_client/parser/reference.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index ec2216810..fa7b39b86 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -4,7 +4,6 @@ import httpx from ...client import Client -from ...models.a_model import AModel from ...models.an_enum import AnEnum from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response @@ -47,16 +46,7 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]: - if response.status_code == 200: - response_200 = [] - _response_200 = response.json() - for response_200_item_data in _response_200: - response_200_item = AModel.from_dict(response_200_item_data) - - response_200.append(response_200_item) - - return response_200 +def _parse_response(*, response: httpx.Response) -> Optional[HTTPValidationError]: if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) @@ -64,7 +54,7 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], return None -def _build_response(*, response: httpx.Response) -> Response[Union[List[AModel], HTTPValidationError]]: +def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError]: return Response( status_code=response.status_code, content=response.content, @@ -78,7 +68,7 @@ def sync_detailed( client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Response[Union[List[AModel], HTTPValidationError]]: +) -> Response[HTTPValidationError]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -97,7 +87,7 @@ def sync( client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[Union[List[AModel], HTTPValidationError]]: +) -> Optional[HTTPValidationError]: """ Get a list of things """ return sync_detailed( @@ -112,7 +102,7 @@ async def asyncio_detailed( client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Response[Union[List[AModel], HTTPValidationError]]: +) -> Response[HTTPValidationError]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -130,7 +120,7 @@ async def asyncio( client: Client, an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[Union[List[AModel], HTTPValidationError]]: +) -> Optional[HTTPValidationError]: """ Get a list of things """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py deleted file mode 100644 index 074ab9d89..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ /dev/null @@ -1,111 +0,0 @@ -from typing import Any, Dict, Optional, Union - -import httpx - -from ...client import Client -from ...models.a_model import AModel -from ...models.http_validation_error import HTTPValidationError -from ...types import Response - - -def _get_kwargs( - *, - client: Client, - json_body: AModel, -) -> Dict[str, Any]: - url = "{}/tests/json_body".format(client.base_url) - - headers: Dict[str, Any] = client.get_headers() - cookies: Dict[str, Any] = client.get_cookies() - - json_json_body = json_body.to_dict() - - return { - "url": url, - "headers": headers, - "cookies": cookies, - "timeout": client.get_timeout(), - "json": json_json_body, - } - - -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: - if response.status_code == 200: - response_200 = None - - return response_200 - if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) - - return response_422 - return None - - -def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: - return Response( - status_code=response.status_code, - content=response.content, - headers=response.headers, - parsed=_parse_response(response=response), - ) - - -def sync_detailed( - *, - client: Client, - json_body: AModel, -) -> Response[Union[None, HTTPValidationError]]: - kwargs = _get_kwargs( - client=client, - json_body=json_body, - ) - - response = httpx.post( - **kwargs, - ) - - return _build_response(response=response) - - -def sync( - *, - client: Client, - json_body: AModel, -) -> Optional[Union[None, HTTPValidationError]]: - """ Try sending a JSON body """ - - return sync_detailed( - client=client, - json_body=json_body, - ).parsed - - -async def asyncio_detailed( - *, - client: Client, - json_body: AModel, -) -> Response[Union[None, HTTPValidationError]]: - kwargs = _get_kwargs( - client=client, - json_body=json_body, - ) - - async with httpx.AsyncClient() as _client: - response = await _client.post(**kwargs) - - return _build_response(response=response) - - -async def asyncio( - *, - client: Client, - json_body: AModel, -) -> Optional[Union[None, HTTPValidationError]]: - """ Try sending a JSON body """ - - return ( - await asyncio_detailed( - client=client, - json_body=json_body, - ) - ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 97515ed35..ee7e8833b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -1,10 +1,5 @@ """ Contains all the data models used in inputs/outputs """ -from .a_model import AModel -from .a_model_model import AModelModel -from .a_model_not_required_model import AModelNotRequiredModel -from .a_model_not_required_nullable_model import AModelNotRequiredNullableModel -from .a_model_nullable_model import AModelNullableModel from .all_of_sub_model import AllOfSubModel from .an_enum import AnEnum from .an_int_enum import AnIntEnum @@ -14,6 +9,7 @@ from .free_form_model import FreeFormModel from .http_validation_error import HTTPValidationError from .model_from_all_of import ModelFromAllOf +from .model_name import ModelName from .model_with_additional_properties_inlined import ModelWithAdditionalPropertiesInlined from .model_with_additional_properties_inlined_additional_property import ( ModelWithAdditionalPropertiesInlinedAdditionalProperty, @@ -23,6 +19,7 @@ from .model_with_any_json_properties_additional_property_type0 import ModelWithAnyJsonPropertiesAdditionalPropertyType0 from .model_with_primitive_additional_properties import ModelWithPrimitiveAdditionalProperties from .model_with_primitive_additional_properties_a_date_holder import ModelWithPrimitiveAdditionalPropertiesADateHolder +from .model_with_property_ref import ModelWithPropertyRef from .model_with_union_property import ModelWithUnionProperty from .model_with_union_property_inlined import ModelWithUnionPropertyInlined from .model_with_union_property_inlined_fruit_type0 import ModelWithUnionPropertyInlinedFruitType0 diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py deleted file mode 100644 index 91b62c01b..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ /dev/null @@ -1,377 +0,0 @@ -import datetime -from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast - -import attr -from dateutil.parser import isoparse - -from ..models.a_model_model import AModelModel -from ..models.a_model_not_required_model import AModelNotRequiredModel -from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel -from ..models.a_model_nullable_model import AModelNullableModel -from ..models.an_enum import AnEnum -from ..models.different_enum import DifferentEnum -from ..models.free_form_model import FreeFormModel -from ..models.model_with_union_property import ModelWithUnionProperty -from ..types import UNSET, Unset - -T = TypeVar("T", bound="AModel") - - -@attr.s(auto_attribs=True) -class AModel: - """ A Model for testing all the ways custom objects can be used """ - - an_enum_value: AnEnum - a_camel_date_time: Union[datetime.date, datetime.datetime] - a_date: datetime.date - required_not_nullable: str - one_of_models: Union[FreeFormModel, ModelWithUnionProperty] - model: AModelModel - a_nullable_date: Optional[datetime.date] - required_nullable: Optional[str] - nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None] - nullable_model: Optional[AModelNullableModel] - nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET - a_not_required_date: Union[Unset, datetime.date] = UNSET - attr_1_leading_digit: Union[Unset, str] = UNSET - not_required_nullable: Union[Unset, None, str] = UNSET - not_required_not_nullable: Union[Unset, str] = UNSET - not_required_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, Unset] = UNSET - not_required_nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str] = UNSET - not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET - not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET - - def to_dict(self) -> Dict[str, Any]: - an_enum_value = self.an_enum_value.value - - if isinstance(self.a_camel_date_time, datetime.datetime): - a_camel_date_time = self.a_camel_date_time.isoformat() - - else: - a_camel_date_time = self.a_camel_date_time.isoformat() - - a_date = self.a_date.isoformat() - required_not_nullable = self.required_not_nullable - if isinstance(self.one_of_models, FreeFormModel): - one_of_models = self.one_of_models.to_dict() - - else: - one_of_models = self.one_of_models.to_dict() - - model = self.model.to_dict() - - nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET - if not isinstance(self.nested_list_of_enums, Unset): - nested_list_of_enums = [] - for nested_list_of_enums_item_data in self.nested_list_of_enums: - nested_list_of_enums_item = [] - for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data: - nested_list_of_enums_item_item = nested_list_of_enums_item_item_data.value - - nested_list_of_enums_item.append(nested_list_of_enums_item_item) - - nested_list_of_enums.append(nested_list_of_enums_item) - - a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None - a_not_required_date: Union[Unset, str] = UNSET - if not isinstance(self.a_not_required_date, Unset): - a_not_required_date = self.a_not_required_date.isoformat() - - attr_1_leading_digit = self.attr_1_leading_digit - required_nullable = self.required_nullable - not_required_nullable = self.not_required_nullable - not_required_not_nullable = self.not_required_not_nullable - nullable_one_of_models: Union[Dict[str, Any], None] - if self.nullable_one_of_models is None: - nullable_one_of_models = None - elif isinstance(self.nullable_one_of_models, FreeFormModel): - nullable_one_of_models = self.nullable_one_of_models.to_dict() - - else: - nullable_one_of_models = self.nullable_one_of_models.to_dict() - - not_required_one_of_models: Union[Dict[str, Any], Unset] - if isinstance(self.not_required_one_of_models, Unset): - not_required_one_of_models = UNSET - elif isinstance(self.not_required_one_of_models, FreeFormModel): - not_required_one_of_models = UNSET - if not isinstance(self.not_required_one_of_models, Unset): - not_required_one_of_models = self.not_required_one_of_models.to_dict() - - else: - not_required_one_of_models = UNSET - if not isinstance(self.not_required_one_of_models, Unset): - not_required_one_of_models = self.not_required_one_of_models.to_dict() - - not_required_nullable_one_of_models: Union[Dict[str, Any], None, Unset, str] - if isinstance(self.not_required_nullable_one_of_models, Unset): - not_required_nullable_one_of_models = UNSET - elif self.not_required_nullable_one_of_models is None: - not_required_nullable_one_of_models = None - elif isinstance(self.not_required_nullable_one_of_models, FreeFormModel): - not_required_nullable_one_of_models = UNSET - if not isinstance(self.not_required_nullable_one_of_models, Unset): - not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict() - - elif isinstance(self.not_required_nullable_one_of_models, ModelWithUnionProperty): - not_required_nullable_one_of_models = UNSET - if not isinstance(self.not_required_nullable_one_of_models, Unset): - not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict() - - else: - not_required_nullable_one_of_models = self.not_required_nullable_one_of_models - - nullable_model = self.nullable_model.to_dict() if self.nullable_model else None - - not_required_model: Union[Unset, Dict[str, Any]] = UNSET - if not isinstance(self.not_required_model, Unset): - not_required_model = self.not_required_model.to_dict() - - not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET - if not isinstance(self.not_required_nullable_model, Unset): - not_required_nullable_model = ( - self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None - ) - - field_dict: Dict[str, Any] = {} - field_dict.update( - { - "an_enum_value": an_enum_value, - "aCamelDateTime": a_camel_date_time, - "a_date": a_date, - "required_not_nullable": required_not_nullable, - "one_of_models": one_of_models, - "model": model, - "a_nullable_date": a_nullable_date, - "required_nullable": required_nullable, - "nullable_one_of_models": nullable_one_of_models, - "nullable_model": nullable_model, - } - ) - if nested_list_of_enums is not UNSET: - field_dict["nested_list_of_enums"] = nested_list_of_enums - if a_not_required_date is not UNSET: - field_dict["a_not_required_date"] = a_not_required_date - if attr_1_leading_digit is not UNSET: - field_dict["1_leading_digit"] = attr_1_leading_digit - if not_required_nullable is not UNSET: - field_dict["not_required_nullable"] = not_required_nullable - if not_required_not_nullable is not UNSET: - field_dict["not_required_not_nullable"] = not_required_not_nullable - if not_required_one_of_models is not UNSET: - field_dict["not_required_one_of_models"] = not_required_one_of_models - if not_required_nullable_one_of_models is not UNSET: - field_dict["not_required_nullable_one_of_models"] = not_required_nullable_one_of_models - if not_required_model is not UNSET: - field_dict["not_required_model"] = not_required_model - if not_required_nullable_model is not UNSET: - field_dict["not_required_nullable_model"] = not_required_nullable_model - - return field_dict - - @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() - an_enum_value = AnEnum(d.pop("an_enum_value")) - - def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]: - try: - a_camel_date_time_type0: datetime.datetime - if not isinstance(data, str): - raise TypeError() - a_camel_date_time_type0 = isoparse(data) - - return a_camel_date_time_type0 - except: # noqa: E722 - pass - if not isinstance(data, str): - raise TypeError() - a_camel_date_time_type1: datetime.date - a_camel_date_time_type1 = isoparse(data).date() - - return a_camel_date_time_type1 - - a_camel_date_time = _parse_a_camel_date_time(d.pop("aCamelDateTime")) - - a_date = isoparse(d.pop("a_date")).date() - - required_not_nullable = d.pop("required_not_nullable") - - def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]: - try: - one_of_models_type0: FreeFormModel - if not isinstance(data, dict): - raise TypeError() - one_of_models_type0 = FreeFormModel.from_dict(data) - - return one_of_models_type0 - except: # noqa: E722 - pass - if not isinstance(data, dict): - raise TypeError() - one_of_models_type1: ModelWithUnionProperty - one_of_models_type1 = ModelWithUnionProperty.from_dict(data) - - return one_of_models_type1 - - one_of_models = _parse_one_of_models(d.pop("one_of_models")) - - model = AModelModel.from_dict(d.pop("model")) - - nested_list_of_enums = [] - _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET) - for nested_list_of_enums_item_data in _nested_list_of_enums or []: - nested_list_of_enums_item = [] - _nested_list_of_enums_item = nested_list_of_enums_item_data - for nested_list_of_enums_item_item_data in _nested_list_of_enums_item: - nested_list_of_enums_item_item = DifferentEnum(nested_list_of_enums_item_item_data) - - nested_list_of_enums_item.append(nested_list_of_enums_item_item) - - nested_list_of_enums.append(nested_list_of_enums_item) - - a_nullable_date = None - _a_nullable_date = d.pop("a_nullable_date") - if _a_nullable_date is not None: - a_nullable_date = isoparse(_a_nullable_date).date() - - a_not_required_date: Union[Unset, datetime.date] = UNSET - _a_not_required_date = d.pop("a_not_required_date", UNSET) - if not isinstance(_a_not_required_date, Unset): - a_not_required_date = isoparse(_a_not_required_date).date() - - attr_1_leading_digit = d.pop("1_leading_digit", UNSET) - - required_nullable = d.pop("required_nullable") - - not_required_nullable = d.pop("not_required_nullable", UNSET) - - not_required_not_nullable = d.pop("not_required_not_nullable", UNSET) - - def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]: - if data is None: - return data - try: - nullable_one_of_models_type0: FreeFormModel - if not isinstance(data, dict): - raise TypeError() - nullable_one_of_models_type0 = FreeFormModel.from_dict(data) - - return nullable_one_of_models_type0 - except: # noqa: E722 - pass - if not isinstance(data, dict): - raise TypeError() - nullable_one_of_models_type1: ModelWithUnionProperty - nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(data) - - return nullable_one_of_models_type1 - - nullable_one_of_models = _parse_nullable_one_of_models(d.pop("nullable_one_of_models")) - - def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, Unset]: - if isinstance(data, Unset): - return data - try: - not_required_one_of_models_type0: Union[Unset, FreeFormModel] - if not isinstance(data, dict): - raise TypeError() - not_required_one_of_models_type0 = UNSET - _not_required_one_of_models_type0 = data - if not isinstance(_not_required_one_of_models_type0, Unset): - not_required_one_of_models_type0 = FreeFormModel.from_dict(_not_required_one_of_models_type0) - - return not_required_one_of_models_type0 - except: # noqa: E722 - pass - if not isinstance(data, dict): - raise TypeError() - not_required_one_of_models_type1: Union[Unset, ModelWithUnionProperty] - not_required_one_of_models_type1 = UNSET - _not_required_one_of_models_type1 = data - if not isinstance(_not_required_one_of_models_type1, Unset): - not_required_one_of_models_type1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type1) - - return not_required_one_of_models_type1 - - not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET)) - - def _parse_not_required_nullable_one_of_models( - data: object, - ) -> Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]: - if data is None: - return data - if isinstance(data, Unset): - return data - try: - not_required_nullable_one_of_models_type0: Union[Unset, FreeFormModel] - if not isinstance(data, dict): - raise TypeError() - not_required_nullable_one_of_models_type0 = UNSET - _not_required_nullable_one_of_models_type0 = data - if not isinstance(_not_required_nullable_one_of_models_type0, Unset): - not_required_nullable_one_of_models_type0 = FreeFormModel.from_dict( - _not_required_nullable_one_of_models_type0 - ) - - return not_required_nullable_one_of_models_type0 - except: # noqa: E722 - pass - try: - not_required_nullable_one_of_models_type1: Union[Unset, ModelWithUnionProperty] - if not isinstance(data, dict): - raise TypeError() - not_required_nullable_one_of_models_type1 = UNSET - _not_required_nullable_one_of_models_type1 = data - if not isinstance(_not_required_nullable_one_of_models_type1, Unset): - not_required_nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict( - _not_required_nullable_one_of_models_type1 - ) - - return not_required_nullable_one_of_models_type1 - except: # noqa: E722 - pass - return cast(Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str], data) - - not_required_nullable_one_of_models = _parse_not_required_nullable_one_of_models( - d.pop("not_required_nullable_one_of_models", UNSET) - ) - - nullable_model = None - _nullable_model = d.pop("nullable_model") - if _nullable_model is not None: - nullable_model = AModelNullableModel.from_dict(_nullable_model) - - not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET - _not_required_model = d.pop("not_required_model", UNSET) - if not isinstance(_not_required_model, Unset): - not_required_model = AModelNotRequiredModel.from_dict(_not_required_model) - - not_required_nullable_model = None - _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET) - if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset): - not_required_nullable_model = AModelNotRequiredNullableModel.from_dict(_not_required_nullable_model) - - a_model = cls( - an_enum_value=an_enum_value, - a_camel_date_time=a_camel_date_time, - a_date=a_date, - required_not_nullable=required_not_nullable, - one_of_models=one_of_models, - model=model, - nested_list_of_enums=nested_list_of_enums, - a_nullable_date=a_nullable_date, - a_not_required_date=a_not_required_date, - attr_1_leading_digit=attr_1_leading_digit, - required_nullable=required_nullable, - not_required_nullable=not_required_nullable, - not_required_not_nullable=not_required_not_nullable, - nullable_one_of_models=nullable_one_of_models, - not_required_one_of_models=not_required_one_of_models, - not_required_nullable_one_of_models=not_required_nullable_one_of_models, - nullable_model=nullable_model, - not_required_model=not_required_model, - not_required_nullable_model=not_required_nullable_model, - ) - - return a_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py deleted file mode 100644 index 0a7a54bd6..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Any, Dict, List, Type, TypeVar, Union - -import attr - -from ..models.an_enum import AnEnum -from ..models.an_int_enum import AnIntEnum -from ..types import UNSET, Unset - -T = TypeVar("T", bound="AModelModel") - - -@attr.s(auto_attribs=True) -class AModelModel: - """ """ - - a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET - additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) - - def to_dict(self) -> Dict[str, Any]: - a_property: Union[Unset, int, str] - if isinstance(self.a_property, Unset): - a_property = UNSET - elif isinstance(self.a_property, AnEnum): - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - else: - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - field_dict: Dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update({}) - if a_property is not UNSET: - field_dict["a_property"] = a_property - - return field_dict - - @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() - - def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: - if isinstance(data, Unset): - return data - try: - a_property_type0: Union[Unset, AnEnum] - if not isinstance(data, str): - raise TypeError() - a_property_type0 = UNSET - _a_property_type0 = data - if not isinstance(_a_property_type0, Unset): - a_property_type0 = AnEnum(_a_property_type0) - - return a_property_type0 - except: # noqa: E722 - pass - if not isinstance(data, int): - raise TypeError() - a_property_type1: Union[Unset, AnIntEnum] - a_property_type1 = UNSET - _a_property_type1 = data - if not isinstance(_a_property_type1, Unset): - a_property_type1 = AnIntEnum(_a_property_type1) - - return a_property_type1 - - a_property = _parse_a_property(d.pop("a_property", UNSET)) - - a_model_model = cls( - a_property=a_property, - ) - - a_model_model.additional_properties = d - return a_model_model - - @property - def additional_keys(self) -> List[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py deleted file mode 100644 index fd568db52..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Any, Dict, List, Type, TypeVar, Union - -import attr - -from ..models.an_enum import AnEnum -from ..models.an_int_enum import AnIntEnum -from ..types import UNSET, Unset - -T = TypeVar("T", bound="AModelNotRequiredModel") - - -@attr.s(auto_attribs=True) -class AModelNotRequiredModel: - """ """ - - a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET - additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) - - def to_dict(self) -> Dict[str, Any]: - a_property: Union[Unset, int, str] - if isinstance(self.a_property, Unset): - a_property = UNSET - elif isinstance(self.a_property, AnEnum): - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - else: - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - field_dict: Dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update({}) - if a_property is not UNSET: - field_dict["a_property"] = a_property - - return field_dict - - @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() - - def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: - if isinstance(data, Unset): - return data - try: - a_property_type0: Union[Unset, AnEnum] - if not isinstance(data, str): - raise TypeError() - a_property_type0 = UNSET - _a_property_type0 = data - if not isinstance(_a_property_type0, Unset): - a_property_type0 = AnEnum(_a_property_type0) - - return a_property_type0 - except: # noqa: E722 - pass - if not isinstance(data, int): - raise TypeError() - a_property_type1: Union[Unset, AnIntEnum] - a_property_type1 = UNSET - _a_property_type1 = data - if not isinstance(_a_property_type1, Unset): - a_property_type1 = AnIntEnum(_a_property_type1) - - return a_property_type1 - - a_property = _parse_a_property(d.pop("a_property", UNSET)) - - a_model_not_required_model = cls( - a_property=a_property, - ) - - a_model_not_required_model.additional_properties = d - return a_model_not_required_model - - @property - def additional_keys(self) -> List[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py deleted file mode 100644 index 6413e7f9a..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Any, Dict, List, Type, TypeVar, Union - -import attr - -from ..models.an_enum import AnEnum -from ..models.an_int_enum import AnIntEnum -from ..types import UNSET, Unset - -T = TypeVar("T", bound="AModelNotRequiredNullableModel") - - -@attr.s(auto_attribs=True) -class AModelNotRequiredNullableModel: - """ """ - - a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET - additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) - - def to_dict(self) -> Dict[str, Any]: - a_property: Union[Unset, int, str] - if isinstance(self.a_property, Unset): - a_property = UNSET - elif isinstance(self.a_property, AnEnum): - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - else: - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - field_dict: Dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update({}) - if a_property is not UNSET: - field_dict["a_property"] = a_property - - return field_dict - - @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() - - def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: - if isinstance(data, Unset): - return data - try: - a_property_type0: Union[Unset, AnEnum] - if not isinstance(data, str): - raise TypeError() - a_property_type0 = UNSET - _a_property_type0 = data - if not isinstance(_a_property_type0, Unset): - a_property_type0 = AnEnum(_a_property_type0) - - return a_property_type0 - except: # noqa: E722 - pass - if not isinstance(data, int): - raise TypeError() - a_property_type1: Union[Unset, AnIntEnum] - a_property_type1 = UNSET - _a_property_type1 = data - if not isinstance(_a_property_type1, Unset): - a_property_type1 = AnIntEnum(_a_property_type1) - - return a_property_type1 - - a_property = _parse_a_property(d.pop("a_property", UNSET)) - - a_model_not_required_nullable_model = cls( - a_property=a_property, - ) - - a_model_not_required_nullable_model.additional_properties = d - return a_model_not_required_nullable_model - - @property - def additional_keys(self) -> List[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py deleted file mode 100644 index cc6484d5f..000000000 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Any, Dict, List, Type, TypeVar, Union - -import attr - -from ..models.an_enum import AnEnum -from ..models.an_int_enum import AnIntEnum -from ..types import UNSET, Unset - -T = TypeVar("T", bound="AModelNullableModel") - - -@attr.s(auto_attribs=True) -class AModelNullableModel: - """ """ - - a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET - additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) - - def to_dict(self) -> Dict[str, Any]: - a_property: Union[Unset, int, str] - if isinstance(self.a_property, Unset): - a_property = UNSET - elif isinstance(self.a_property, AnEnum): - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - else: - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value - - field_dict: Dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update({}) - if a_property is not UNSET: - field_dict["a_property"] = a_property - - return field_dict - - @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() - - def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: - if isinstance(data, Unset): - return data - try: - a_property_type0: Union[Unset, AnEnum] - if not isinstance(data, str): - raise TypeError() - a_property_type0 = UNSET - _a_property_type0 = data - if not isinstance(_a_property_type0, Unset): - a_property_type0 = AnEnum(_a_property_type0) - - return a_property_type0 - except: # noqa: E722 - pass - if not isinstance(data, int): - raise TypeError() - a_property_type1: Union[Unset, AnIntEnum] - a_property_type1 = UNSET - _a_property_type1 = data - if not isinstance(_a_property_type1, Unset): - a_property_type1 = AnIntEnum(_a_property_type1) - - return a_property_type1 - - a_property = _parse_a_property(d.pop("a_property", UNSET)) - - a_model_nullable_model = cls( - a_property=a_property, - ) - - a_model_nullable_model.additional_properties = d - return a_model_nullable_model - - @property - def additional_keys(self) -> List[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_name.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_name.py new file mode 100644 index 000000000..75b12f284 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_name.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, List, Type, TypeVar + +import attr + +T = TypeVar("T", bound="ModelName") + + +@attr.s(auto_attribs=True) +class ModelName: + """ """ + + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + model_name = cls() + + model_name.additional_properties = d + return model_name + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py new file mode 100644 index 000000000..1553914ba --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..models.model_name import ModelName +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ModelWithPropertyRef") + + +@attr.s(auto_attribs=True) +class ModelWithPropertyRef: + """ """ + + inner: Union[Unset, ModelName] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + inner: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.inner, Unset): + inner = self.inner.to_dict() + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if inner is not UNSET: + field_dict["inner"] = inner + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + inner: Union[Unset, ModelName] = UNSET + _inner = d.pop("inner", UNSET) + if not isinstance(_inner, Unset): + inner = ModelName.from_dict(_inner) + + model_with_property_ref = cls( + inner=inner, + ) + + model_with_property_ref.additional_properties = d + return model_with_property_ref + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 359419473..7ef631faf 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -819,10 +819,10 @@ "one_of_models": { "oneOf": [ { - "ref": "#components/schemas/FreeFormModel" + "ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#components/schemas/ModelWithUnionProperty" + "ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -830,10 +830,10 @@ "nullable_one_of_models": { "oneOf": [ { - "ref": "#components/schemas/FreeFormModel" + "ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#components/schemas/ModelWithUnionProperty" + "ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -841,10 +841,10 @@ "not_required_one_of_models": { "oneOf": [ { - "ref": "#components/schemas/FreeFormModel" + "ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#components/schemas/ModelWithUnionProperty" + "ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -852,10 +852,10 @@ "not_required_nullable_one_of_models": { "oneOf": [ { - "ref": "#components/schemas/FreeFormModel" + "ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#components/schemas/ModelWithUnionProperty" + "ref": "#/components/schemas/ModelWithUnionProperty" }, { "type": "string" @@ -1110,6 +1110,16 @@ "type": "string" } } + }, + "model_reference_doesnt_match": { + "title": "ModelName", + "type": "object" + }, + "ModelWithPropertyRef": { + "type": "object", + "properties": { + "inner": {"$ref": "#/components/schemas/model_reference_doesnt_match"} + } } } } diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index b5ad8afeb..46fa94a51 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -14,7 +14,8 @@ from openapi_python_client import utils -from .parser import GeneratorData, import_string_from_reference +from .config import Config +from .parser import GeneratorData, import_string_from_class from .parser.errors import GeneratorError from .utils import snake_case @@ -41,15 +42,12 @@ class MetaType(str, Enum): class Project: - project_name_override: Optional[str] = None - package_name_override: Optional[str] = None - package_version_override: Optional[str] = None - def __init__( self, *, openapi: GeneratorData, meta: MetaType, + config: Optional[Config], custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> None: @@ -70,17 +68,17 @@ def __init__( loader = package_loader self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True) - self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" + self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" self.project_dir: Path = Path.cwd() if meta != MetaType.NONE: self.project_dir /= self.project_name - self.package_name: str = self.package_name_override or self.project_name.replace("-", "_") + self.package_name: str = config.package_name_override or self.project_name.replace("-", "_") self.package_dir: Path = self.project_dir / self.package_name self.package_description: str = utils.remove_string_escapes( f"A client library for accessing {self.openapi.title}" ) - self.version: str = self.package_version_override or openapi.version + self.version: str = config.package_version_override or openapi.version self.env.filters.update(TEMPLATE_FILTERS) @@ -215,21 +213,21 @@ def _build_models(self) -> None: imports = [] model_template = self.env.get_template("model.py.jinja") - for model in self.openapi.models.values(): - module_path = models_dir / f"{model.reference.module_name}.py" + for model in self.openapi.models: + module_path = models_dir / f"{model.class_info.module_name}.py" module_path.write_text(model_template.render(model=model), encoding=self.file_encoding) - imports.append(import_string_from_reference(model.reference)) + imports.append(import_string_from_class(model.class_info)) # Generate enums str_enum_template = self.env.get_template("str_enum.py.jinja") int_enum_template = self.env.get_template("int_enum.py.jinja") - for enum in self.openapi.enums.values(): - module_path = models_dir / f"{enum.reference.module_name}.py" + for enum in self.openapi.enums: + module_path = models_dir / f"{enum.class_info.module_name}.py" if enum.value_type is int: module_path.write_text(int_enum_template.render(enum=enum), encoding=self.file_encoding) else: module_path.write_text(str_enum_template.render(enum=enum), encoding=self.file_encoding) - imports.append(import_string_from_reference(enum.reference)) + imports.append(import_string_from_class(enum.class_info)) models_init_template = self.env.get_template("models_init.py.jinja") models_init.write_text(models_init_template.render(imports=imports), encoding=self.file_encoding) @@ -261,6 +259,7 @@ def _get_project_for_url_or_path( url: Optional[str], path: Optional[Path], meta: MetaType, + config: Optional[Config], custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Union[Project, GeneratorError]: @@ -270,7 +269,13 @@ def _get_project_for_url_or_path( openapi = GeneratorData.from_dict(data_dict) if isinstance(openapi, GeneratorError): return openapi - return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding) + return Project( + openapi=openapi, + custom_template_path=custom_template_path, + meta=meta, + file_encoding=file_encoding, + config=config, + ) def create_new_client( @@ -278,6 +283,7 @@ def create_new_client( url: Optional[str], path: Optional[Path], meta: MetaType, + config: Optional[Config], custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: @@ -288,7 +294,12 @@ def create_new_client( A list containing any errors encountered when generating. """ project = _get_project_for_url_or_path( - url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding + url=url, + path=path, + custom_template_path=custom_template_path, + meta=meta, + file_encoding=file_encoding, + config=config, ) if isinstance(project, GeneratorError): return [project] @@ -300,6 +311,7 @@ def update_existing_client( url: Optional[str], path: Optional[Path], meta: MetaType, + config: Optional[Config], custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: @@ -310,7 +322,12 @@ def update_existing_client( A list containing any errors encountered when generating. """ project = _get_project_for_url_or_path( - url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding + url=url, + path=path, + custom_template_path=custom_template_path, + meta=meta, + file_encoding=file_encoding, + config=config, ) if isinstance(project, GeneratorError): return [project] diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index 1f94f37ea..9a60a302e 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -8,6 +8,8 @@ from openapi_python_client import MetaType from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError +from .config import Config + app = typer.Typer() @@ -19,14 +21,13 @@ def _version_callback(value: bool) -> None: raise typer.Exit() -def _process_config(path: Optional[pathlib.Path]) -> None: - from .config import Config +def _process_config(path: Optional[pathlib.Path]) -> Optional[Config]: if not path: - return + return None try: - Config.load_from_path(path=path) + return Config.load_from_path(path=path) except: # noqa raise typer.BadParameter("Unable to parse config") @@ -35,9 +36,6 @@ def _process_config(path: Optional[pathlib.Path]) -> None: @app.callback(name="openapi-python-client") def cli( version: bool = typer.Option(False, "--version", callback=_version_callback, help="Print the version and exit"), - config: Optional[pathlib.Path] = typer.Option( - None, callback=_process_config, help="Path to the config file to use" - ), ) -> None: """ Generate a Python client from an OpenAPI JSON document """ pass @@ -117,8 +115,9 @@ def generate( url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"), path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"), custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore - file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), meta: MetaType = _meta_option, + file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), + config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"), ) -> None: """ Generate a new OpenAPI Client library """ from . import create_new_client @@ -149,6 +148,7 @@ def update( custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore meta: MetaType = _meta_option, file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), + config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"), ) -> None: """ Update an existing OpenAPI Client library """ from . import update_existing_client diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py index b4fa787a9..947d0a405 100644 --- a/openapi_python_client/config.py +++ b/openapi_python_client/config.py @@ -4,39 +4,18 @@ import yaml from pydantic import BaseModel - -class ClassOverride(BaseModel): - class_name: str - module_name: str +from openapi_python_client.parser.properties import Class class Config(BaseModel): - class_overrides: Optional[Dict[str, ClassOverride]] + class_overrides: Dict[str, Class] = {} project_name_override: Optional[str] package_name_override: Optional[str] package_version_override: Optional[str] - field_prefix: Optional[str] - - def load_config(self) -> None: - """ Sets globals based on Config """ - from openapi_python_client import Project - - from . import utils - from .parser import reference - - if self.class_overrides is not None: - for class_name, class_data in self.class_overrides.items(): - reference.class_overrides[class_name] = reference.Reference(**dict(class_data)) - - Project.project_name_override = self.project_name_override - Project.package_name_override = self.package_name_override - Project.package_version_override = self.package_version_override - - if self.field_prefix is not None: - utils.FIELD_PREFIX = self.field_prefix + field_prefix: str = "field_" @staticmethod - def load_from_path(path: Path) -> None: + def load_from_path(path: Path) -> "Config": """ Creates a Config from provided JSON or YAML file and sets a bunch of globals from it """ config_data = yaml.safe_load(path.read_text()) - Config(**config_data).load_config() + return Config(**config_data) diff --git a/openapi_python_client/parser/__init__.py b/openapi_python_client/parser/__init__.py index 65fd27df9..6c20f52d1 100644 --- a/openapi_python_client/parser/__init__.py +++ b/openapi_python_client/parser/__init__.py @@ -1,5 +1,5 @@ """ Classes representing the data in the OpenAPI schema """ -__all__ = ["GeneratorData", "import_string_from_reference"] +__all__ = ["GeneratorData", "import_string_from_class"] -from .openapi import GeneratorData, import_string_from_reference +from .openapi import GeneratorData, import_string_from_class diff --git a/openapi_python_client/parser/errors.py b/openapi_python_client/parser/errors.py index 5c49c7c78..9a5ced0f5 100644 --- a/openapi_python_client/parser/errors.py +++ b/openapi_python_client/parser/errors.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional -__all__ = ["GeneratorError", "ParseError", "PropertyError", "ValidationError"] +__all__ = ["ErrorLevel", "GeneratorError", "ParseError", "PropertyError", "ValidationError"] from pydantic import BaseModel diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 2361c3c66..53a0c70a4 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -1,15 +1,15 @@ from copy import deepcopy from dataclasses import dataclass, field from enum import Enum -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union from pydantic import ValidationError +from .. import Config from .. import schema as oai from .. import utils from .errors import GeneratorError, ParseError, PropertyError -from .properties import EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data -from .reference import Reference +from .properties import Class, EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data from .responses import Response, response_from_data @@ -22,9 +22,9 @@ class ParameterLocation(str, Enum): COOKIE = "cookie" -def import_string_from_reference(reference: Reference, prefix: str = "") -> str: +def import_string_from_class(class_: Class, prefix: str = "") -> str: """ Create a string which is used to import a reference """ - return f"from {prefix}.{reference.module_name} import {reference.class_name}" + return f"from {prefix}.{class_.module_name} import {class_.name}" @dataclass @@ -96,27 +96,27 @@ class Endpoint: header_parameters: List[Property] = field(default_factory=list) cookie_parameters: List[Property] = field(default_factory=list) responses: List[Response] = field(default_factory=list) - form_body_reference: Optional[Reference] = None + form_body_class: Optional[Class] = None json_body: Optional[Property] = None - multipart_body_reference: Optional[Reference] = None + multipart_body_class: Optional[Class] = None errors: List[ParseError] = field(default_factory=list) @staticmethod - def parse_request_form_body(body: oai.RequestBody) -> Optional[Reference]: + def parse_request_form_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]: """ Return form_body_reference """ body_content = body.content form_body = body_content.get("application/x-www-form-urlencoded") if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference): - return Reference.from_ref(form_body.media_type_schema.ref) + return Class.from_string(string=form_body.media_type_schema.ref, schemas=schemas) return None @staticmethod - def parse_multipart_body(body: oai.RequestBody) -> Optional[Reference]: + def parse_multipart_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]: """ Return form_body_reference """ body_content = body.content json_body = body_content.get("multipart/form-data") if json_body is not None and isinstance(json_body.media_type_schema, oai.Reference): - return Reference.from_ref(json_body.media_type_schema.ref) + return Class.from_string(string=json_body.media_type_schema.ref, schemas=schemas) return None @staticmethod @@ -145,23 +145,19 @@ def _add_body( if data.requestBody is None or isinstance(data.requestBody, oai.Reference): return endpoint, schemas - endpoint.form_body_reference = Endpoint.parse_request_form_body(data.requestBody) + endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, schemas=schemas) json_body, schemas = Endpoint.parse_request_json_body( body=data.requestBody, schemas=schemas, parent_name=endpoint.name ) if isinstance(json_body, ParseError): return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas - endpoint.multipart_body_reference = Endpoint.parse_multipart_body(data.requestBody) + endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, schemas=schemas) - if endpoint.form_body_reference: - endpoint.relative_imports.add( - import_string_from_reference(endpoint.form_body_reference, prefix="...models") - ) - if endpoint.multipart_body_reference: - endpoint.relative_imports.add( - import_string_from_reference(endpoint.multipart_body_reference, prefix="...models") - ) + if endpoint.form_body_class: + endpoint.relative_imports.add(import_string_from_class(endpoint.form_body_class, prefix="...models")) + if endpoint.multipart_body_class: + endpoint.relative_imports.add(import_string_from_class(endpoint.multipart_body_class, prefix="...models")) if json_body is not None: endpoint.json_body = json_body endpoint.relative_imports.update(endpoint.json_body.get_imports(prefix="...")) @@ -273,13 +269,13 @@ class GeneratorData: title: str description: Optional[str] version: str - models: Dict[str, ModelProperty] + models: Iterator[ModelProperty] errors: List[ParseError] endpoint_collections_by_tag: Dict[str, EndpointCollection] - enums: Dict[str, EnumProperty] + enums: Iterator[EnumProperty] @staticmethod - def from_dict(d: Dict[str, Any]) -> Union["GeneratorData", GeneratorError]: + def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", GeneratorError]: """ Create an OpenAPI from dict """ try: openapi = oai.OpenAPI.parse_obj(d) @@ -295,19 +291,20 @@ def from_dict(d: Dict[str, Any]) -> Union["GeneratorData", GeneratorError]: header="openapi-python-client only supports OpenAPI 3.x", detail=f"The version of the provided document was {openapi.openapi}", ) - if openapi.components is None or openapi.components.schemas is None: - schemas = Schemas() - else: - schemas = build_schemas(components=openapi.components.schemas) + schemas = Schemas(class_overrides=config.class_overrides) + if openapi.components and openapi.components.schemas: + schemas = build_schemas(components=openapi.components.schemas, schemas=schemas) endpoint_collections_by_tag, schemas = EndpointCollection.from_data(data=openapi.paths, schemas=schemas) - enums = schemas.enums + + enums = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, EnumProperty)) + models = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty)) return GeneratorData( title=openapi.info.title, description=openapi.info.description, version=openapi.info.version, endpoint_collections_by_tag=endpoint_collections_by_tag, - models=schemas.models, + models=models, errors=schemas.errors, enums=enums, ) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index dfa832468..58c65139b 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -1,3 +1,13 @@ +__all__ = [ + "Class", + "EnumProperty", + "ModelProperty", + "Property", + "Schemas", + "build_schemas", + "property_from_data", +] + from itertools import chain from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union @@ -5,13 +15,12 @@ from ... import schema as oai from ... import utils -from ..errors import PropertyError, ValidationError -from ..reference import Reference +from ..errors import ParseError, PropertyError, ValidationError from .converter import convert, convert_chain from .enum_property import EnumProperty from .model_property import ModelProperty, build_model_property from .property import Property -from .schemas import Schemas +from .schemas import Class, Schemas, parse_reference_path, update_schemas_with_data @attr.s(auto_attribs=True, frozen=True) @@ -286,15 +295,15 @@ def build_enum_property( class_name = data.title or name if parent_name: class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}" - reference = Reference.from_ref(class_name) + class_info = Class.from_string(string=class_name, schemas=schemas) values = EnumProperty.values_from_list(enum) - if reference.class_name in schemas.enums: - existing = schemas.enums[reference.class_name] - if values != existing.values: + if class_info.name in schemas.classes_by_name: + existing = schemas.classes_by_name[class_info.name] + if not isinstance(existing, EnumProperty) or values != existing.values: return ( PropertyError( - detail=f"Found conflicting enums named {reference.class_name} with incompatible values.", data=data + detail=f"Found conflicting enums named {class_info.name} with incompatible values.", data=data ), schemas, ) @@ -309,12 +318,10 @@ def build_enum_property( if data.default is not None: inverse_values = {v: k for k, v in values.items()} try: - default = f"{reference.class_name}.{inverse_values[data.default]}" + default = f"{class_info.name}.{inverse_values[data.default]}" except KeyError: return ( - PropertyError( - detail=f"{data.default} is an invalid default for enum {reference.class_name}", data=data - ), + PropertyError(detail=f"{data.default} is an invalid default for enum {class_info.name}", data=data), schemas, ) @@ -323,11 +330,11 @@ def build_enum_property( required=required, default=default, nullable=data.nullable, - reference=reference, + class_info=class_info, values=values, value_type=value_type, ) - schemas = attr.evolve(schemas, enums={**schemas.enums, prop.reference.class_name: prop}) + schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop}) return prop, schemas @@ -388,8 +395,10 @@ def _property_from_data( """ Generate a Property from the OpenAPI dictionary representation of it """ name = utils.remove_string_escapes(name) if isinstance(data, oai.Reference): - reference = Reference.from_ref(data.ref) - existing = schemas.enums.get(reference.class_name) or schemas.models.get(reference.class_name) + ref_path = parse_reference_path(data.ref) + if isinstance(ref_path, ParseError): + return PropertyError(data=data, detail=ref_path.detail), schemas + existing = schemas.classes_by_reference.get(ref_path) if existing: return ( attr.evolve(existing, required=required, name=name), @@ -457,23 +466,8 @@ def property_from_data( return PropertyError(detail="Failed to validate default value", data=data), schemas -def update_schemas_with_data(name: str, data: oai.Schema, schemas: Schemas) -> Union[Schemas, PropertyError]: - prop: Union[PropertyError, ModelProperty, EnumProperty] - if data.enum is not None: - prop, schemas = build_enum_property( - data=data, name=name, required=True, schemas=schemas, enum=data.enum, parent_name=None - ) - else: - prop, schemas = build_model_property(data=data, name=name, schemas=schemas, required=True, parent_name=None) - if isinstance(prop, PropertyError): - return prop - else: - return schemas - - -def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) -> Schemas: +def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas) -> Schemas: """ Get a list of Schemas from an OpenAPI dict """ - schemas = Schemas() to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() processing = True errors: List[PropertyError] = [] @@ -488,13 +482,18 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) -> if isinstance(data, oai.Reference): schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported.")) continue - schemas_or_err = update_schemas_with_data(name, data, schemas) + ref_path = parse_reference_path(f"#/components/schemas/{name}") + if isinstance(ref_path, ParseError): + next_round.append((name, data)) + errors.append(PropertyError(detail=ref_path.detail, data=data)) + continue + schemas_or_err = update_schemas_with_data(ref_path, data, schemas) if isinstance(schemas_or_err, PropertyError): next_round.append((name, data)) errors.append(schemas_or_err) - else: - schemas = schemas_or_err - processing = True # We made some progress this round, do another after it's done + continue + schemas = schemas_or_err + processing = True # We made some progress this round, do another after it's done to_process = next_round schemas.errors.extend(errors) diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py index fca4fc881..9e62643f1 100644 --- a/openapi_python_client/parser/properties/enum_property.py +++ b/openapi_python_client/parser/properties/enum_property.py @@ -5,8 +5,8 @@ import attr from ... import utils -from ..reference import Reference from .property import Property +from .schemas import Class ValueType = Union[str, int] @@ -16,14 +16,14 @@ class EnumProperty(Property): """ A property that should use an enum """ values: Dict[str, ValueType] - reference: Reference + class_info: Class value_type: Type[ValueType] default: Optional[Any] = attr.ib() template: ClassVar[str] = "enum_property.py.jinja" def get_base_type_string(self, json: bool = False) -> str: - return self.reference.class_name + return self.class_info.name def get_base_json_type_string(self, json: bool = False) -> str: return self.value_type.__name__ @@ -37,7 +37,7 @@ def get_imports(self, *, prefix: str) -> Set[str]: back to the root of the generated client. """ imports = super().get_imports(prefix=prefix) - imports.add(f"from {prefix}models.{self.reference.module_name} import {self.reference.class_name}") + imports.add(f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}") return imports @staticmethod diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 35717e8ba..df848ddae 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -3,19 +3,21 @@ import attr +from ... import Config from ... import schema as oai from ... import utils -from ..errors import PropertyError -from ..reference import Reference +from ...utils import to_valid_python_identifier +from ..errors import ParseError, PropertyError +from . import parse_reference_path from .property import Property -from .schemas import Schemas +from .schemas import Class, Schemas @attr.s(auto_attribs=True, frozen=True) class ModelProperty(Property): """ A property which refers to another Schema """ - reference: Reference + class_info: Class required_properties: List[Property] optional_properties: List[Property] description: str @@ -27,7 +29,7 @@ class ModelProperty(Property): json_is_dict: ClassVar[bool] = True def get_base_type_string(self, json: bool = False) -> str: - return self.reference.class_name + return self.class_info.name def get_imports(self, *, prefix: str) -> Set[str]: """ @@ -40,7 +42,7 @@ def get_imports(self, *, prefix: str) -> Set[str]: imports = super().get_imports(prefix=prefix) imports.update( { - f"from {prefix}models.{self.reference.module_name} import {self.reference.class_name}", + f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}", "from typing import Dict", "from typing import cast", } @@ -88,10 +90,14 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: unprocessed_props = data.properties or {} for sub_prop in data.allOf or []: if isinstance(sub_prop, oai.Reference): - source_name = Reference.from_ref(sub_prop.ref).class_name - sub_model = schemas.models.get(source_name) + ref_path = parse_reference_path(sub_prop.ref) + if isinstance(ref_path, ParseError): + return PropertyError(detail=ref_path.detail, data=sub_prop) + sub_model = schemas.classes_by_reference.get(ref_path) if sub_model is None: return PropertyError(f"Reference {sub_prop.ref} not found") + if not isinstance(sub_model, ModelProperty): + return PropertyError("Cannot take allOf a non-object") for prop in chain(sub_model.required_properties, sub_model.optional_properties): prop_or_error = _check_existing(prop) if isinstance(prop_or_error, PropertyError): @@ -155,7 +161,7 @@ def _get_additional_properties( def build_model_property( - *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str] + *, data: oai.Schema, name: str, schemas: Schemas, required: bool, parent_name: Optional[str], config: Config ) -> Tuple[Union[ModelProperty, PropertyError], Schemas]: """ A single ModelProperty from its OAI data @@ -167,11 +173,12 @@ def build_model_property( schemas: Existing Schemas which have already been processed (to check name conflicts) required: Whether or not this property is required by the parent (affects typing) parent_name: The name of the property that this property is inside of (affects class naming) + config: Config data for this run of the generator, used to modifying names """ class_name = data.title or name if parent_name: class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}" - ref = Reference.from_ref(class_name) + class_info = Class.from_string(string=class_name, schemas=schemas) property_data = _process_properties(data=data, schemas=schemas, class_name=class_name) if isinstance(property_data, PropertyError): @@ -187,7 +194,7 @@ def build_model_property( return additional_properties, schemas prop = ModelProperty( - reference=ref, + class_info=class_info, required_properties=property_data.required_props, optional_properties=property_data.optional_props, relative_imports=property_data.relative_imports, @@ -197,12 +204,11 @@ def build_model_property( required=required, name=name, additional_properties=additional_properties, + python_name=to_valid_python_identifier(value=name, field_prefix=config.field_prefix), ) - if prop.reference.class_name in schemas.models: - error = PropertyError( - data=data, detail=f'Attempted to generate duplicate models with name "{prop.reference.class_name}"' - ) + if class_info.name in schemas.classes_by_name: + error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"') return error, schemas - schemas = attr.evolve(schemas, models={**schemas.models, prop.reference.class_name: prop}) + schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop}) return prop, schemas diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py index 7eb5161f9..3b70e67b6 100644 --- a/openapi_python_client/parser/properties/property.py +++ b/openapi_python_client/parser/properties/property.py @@ -2,7 +2,7 @@ import attr -from ... import utils +from ...utils import PythonIdentifier @attr.s(auto_attribs=True, frozen=True) @@ -26,14 +26,11 @@ class Property: _type_string: ClassVar[str] = "" _json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization default: Optional[str] = attr.ib() - python_name: str = attr.ib(init=False) + python_name: PythonIdentifier template: ClassVar[Optional[str]] = None json_is_dict: ClassVar[bool] = False - def __attrs_post_init__(self) -> None: - object.__setattr__(self, "python_name", utils.to_valid_python_identifier(utils.snake_case(self.name))) - def get_base_type_string(self) -> str: return self._type_string diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index c30f6a059..c3cbd2466 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -1,10 +1,15 @@ -__all__ = ["Schemas"] +__all__ = ["Class", "Schemas", "parse_reference_path"] -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast +from urllib.parse import urlparse import attr +from pydantic import BaseModel -from ..errors import ParseError +from ... import Config +from ... import schema as oai +from ... import utils +from ..errors import ParseError, PropertyError if TYPE_CHECKING: # pragma: no cover from .enum_property import EnumProperty @@ -14,10 +19,57 @@ ModelProperty = "ModelProperty" +_ReferencePath = NewType("_ReferencePath", str) +_ClassName = NewType("_ClassName", str) + + +def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]: + parsed = urlparse(ref_path_raw) + if parsed.scheme is not None or parsed.path is not None: + return ParseError(detail="Remote references are not supported yet.") + return cast(_ReferencePath, parsed.fragment) + + +class Class(BaseModel): + """ Info about a generated class which will be in models """ + + name: _ClassName + module_name: str + + @staticmethod + def from_string(*, string: str, config: Config) -> "Class": + """ Get a Class from an arbitrary string """ + class_name = string.split("/")[-1] # Get rid of ref path stuff + class_name = utils.pascal_case(class_name) + + if class_name in config.class_overrides: + return config.class_overrides[class_name] + + return Class(name=cast(_ClassName, class_name), module_name=utils.snake_case(class_name)) + + @attr.s(auto_attribs=True, frozen=True) class Schemas: - """ Structure for containing all defined, shareable, and resuabled schemas (attr classes and Enums) """ + """ Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums) """ - enums: Dict[str, EnumProperty] = attr.ib(factory=dict) - models: Dict[str, ModelProperty] = attr.ib(factory=dict) + classes_by_reference: Dict[_ReferencePath, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) + classes_by_name: Dict[_ClassName, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) errors: List[ParseError] = attr.ib(factory=list) + + +def update_schemas_with_data( + ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas +) -> Union[Schemas, PropertyError]: + from . import build_enum_property, build_model_property + + prop: Union[PropertyError, ModelProperty, EnumProperty] + if data.enum is not None: + prop, schemas = build_enum_property( + data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None + ) + else: + prop, schemas = build_model_property(data=data, name=ref_path, schemas=schemas, required=True, parent_name=None) + if isinstance(prop, PropertyError): + return prop + schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference}) + return schemas diff --git a/openapi_python_client/parser/reference.py b/openapi_python_client/parser/reference.py deleted file mode 100644 index 809d24441..000000000 --- a/openapi_python_client/parser/reference.py +++ /dev/null @@ -1,27 +0,0 @@ -""" A Reference is ultimately a Class which will be in models, usually defined in a body input or return type """ - -from dataclasses import dataclass -from typing import Dict - -from .. import utils - -class_overrides: Dict[str, "Reference"] = {} - - -@dataclass -class Reference: - """ A reference to a class which will be in models """ - - class_name: str - module_name: str - - @staticmethod - def from_ref(ref: str) -> "Reference": - """ Get a Reference from the openapi #/schemas/blahblah string """ - ref_value = ref.split("/")[-1] - class_name = utils.pascal_case(ref_value) - - if class_name in class_overrides: - return class_overrides[class_name] - - return Reference(class_name=class_name, module_name=utils.snake_case(class_name)) diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py index 3d01a0eab..75ccfd418 100644 --- a/openapi_python_client/parser/responses.py +++ b/openapi_python_client/parser/responses.py @@ -4,7 +4,9 @@ import attr +from .. import Config from .. import schema as oai +from ..utils import to_valid_python_identifier from .errors import ParseError, PropertyError from .properties import NoneProperty, Property, Schemas, property_from_data @@ -26,23 +28,30 @@ class Response: } -def empty_response(status_code: int, response_name: str) -> Response: +def empty_response(*, status_code: int, response_name: str, config: Config) -> Response: + """ Return an empty response, for when no response type is defined """ return Response( status_code=status_code, - prop=NoneProperty(name=response_name, default=None, nullable=False, required=True), + prop=NoneProperty( + name=response_name, + default=None, + nullable=False, + required=True, + python_name=to_valid_python_identifier(value=response_name, field_prefix=config.field_prefix), + ), source="None", ) def response_from_data( - *, status_code: int, data: Union[oai.Response, oai.Reference], schemas: Schemas, parent_name: str + *, status_code: int, data: Union[oai.Response, oai.Reference], schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[Response, ParseError], Schemas]: """ Generate a Response from the OpenAPI dictionary representation of it """ response_name = f"response_{status_code}" if isinstance(data, oai.Reference) or data.content is None: return ( - empty_response(status_code, response_name), + empty_response(status_code=status_code, response_name=response_name, config=config), schemas, ) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py index 7803b3a54..5a0bfc644 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field +from pydantic import AnyUrl, BaseModel, Field class Reference(BaseModel): @@ -12,7 +12,7 @@ class Reference(BaseModel): and not by the JSON Schema specification. """ - ref: str = Field(alias="$ref") + ref: AnyUrl = Field(alias="$ref") """**REQUIRED**. The reference string.""" class Config: diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py index 6cdcb2119..9bad71450 100644 --- a/openapi_python_client/utils.py +++ b/openapi_python_client/utils.py @@ -1,6 +1,7 @@ import builtins import re from keyword import iskeyword +from typing import NewType, cast import stringcase @@ -47,11 +48,10 @@ def remove_string_escapes(value: str) -> str: return value.replace('"', r"\"") -# This can be changed by config.Config.load_config -FIELD_PREFIX = "field_" +PythonIdentifier = NewType("PythonIdentifier", str) -def to_valid_python_identifier(value: str) -> str: +def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdentifier: """ Given a string, attempt to coerce it into a valid Python identifier by stripping out invalid characters and, if necessary, prepending a prefix. @@ -62,6 +62,6 @@ def to_valid_python_identifier(value: str) -> str: new_value = fix_reserved_words(fix_keywords(sanitize(value))) if new_value.isidentifier(): - return new_value + return cast(PythonIdentifier, new_value) - return f"{FIELD_PREFIX}{new_value}" + return cast(PythonIdentifier, f"{field_prefix}{new_value}") From da8a09e1c51bd053790afaac593fb37992461639 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sat, 27 Mar 2021 10:58:32 -0600 Subject: [PATCH 02/11] refactorRevert field_prefix refactor, continue class name refactor [WIP] --- openapi_python_client/config.py | 6 +++- openapi_python_client/parser/openapi.py | 36 ++++++++++--------- .../parser/properties/__init__.py | 1 + .../parser/properties/model_property.py | 4 +-- .../parser/properties/property.py | 7 ++-- .../parser/properties/schemas.py | 6 ++-- openapi_python_client/parser/responses.py | 6 ++-- openapi_python_client/utils.py | 10 +++--- 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py index 947d0a405..3deb79e71 100644 --- a/openapi_python_client/config.py +++ b/openapi_python_client/config.py @@ -17,5 +17,9 @@ class Config(BaseModel): @staticmethod def load_from_path(path: Path) -> "Config": """ Creates a Config from provided JSON or YAML file and sets a bunch of globals from it """ + from . import utils + config_data = yaml.safe_load(path.read_text()) - return Config(**config_data) + config = Config(**config_data) + utils.FIELD_PREFIX = config.field_prefix + return config diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 53a0c70a4..d40923ec1 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -37,7 +37,7 @@ class EndpointCollection: @staticmethod def from_data( - *, data: Dict[str, oai.PathItem], schemas: Schemas + *, data: Dict[str, oai.PathItem], schemas: Schemas, config: Config ) -> Tuple[Dict[str, "EndpointCollection"], Schemas]: """ Parse the openapi paths data to get EndpointCollections by tag """ endpoints_by_tag: Dict[str, EndpointCollection] = {} @@ -52,7 +52,7 @@ def from_data( tag = utils.snake_case((operation.tags or ["default"])[0]) collection = endpoints_by_tag.setdefault(tag, EndpointCollection(tag=tag)) endpoint, schemas = Endpoint.from_data( - data=operation, path=path, method=method, tag=tag, schemas=schemas + data=operation, path=path, method=method, tag=tag, schemas=schemas, config=config ) if isinstance(endpoint, ParseError): endpoint.header = ( @@ -102,21 +102,21 @@ class Endpoint: errors: List[ParseError] = field(default_factory=list) @staticmethod - def parse_request_form_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]: + def parse_request_form_body(*, body: oai.RequestBody, config: Config) -> Optional[Class]: """ Return form_body_reference """ body_content = body.content form_body = body_content.get("application/x-www-form-urlencoded") if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference): - return Class.from_string(string=form_body.media_type_schema.ref, schemas=schemas) + return Class.from_string(string=form_body.media_type_schema.ref, config=config) return None @staticmethod - def parse_multipart_body(*, body: oai.RequestBody, schemas: Schemas) -> Optional[Class]: + def parse_multipart_body(*, body: oai.RequestBody, config: Config) -> Optional[Class]: """ Return form_body_reference """ body_content = body.content json_body = body_content.get("multipart/form-data") if json_body is not None and isinstance(json_body.media_type_schema, oai.Reference): - return Class.from_string(string=json_body.media_type_schema.ref, schemas=schemas) + return Class.from_string(string=json_body.media_type_schema.ref, config=config) return None @staticmethod @@ -138,21 +138,21 @@ def parse_request_json_body( @staticmethod def _add_body( - *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas + *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config ) -> Tuple[Union[ParseError, "Endpoint"], Schemas]: """ Adds form or JSON body to Endpoint if included in data """ endpoint = deepcopy(endpoint) if data.requestBody is None or isinstance(data.requestBody, oai.Reference): return endpoint, schemas - endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, schemas=schemas) + endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, config=config) json_body, schemas = Endpoint.parse_request_json_body( body=data.requestBody, schemas=schemas, parent_name=endpoint.name ) if isinstance(json_body, ParseError): return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas - endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, schemas=schemas) + endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, config=config) if endpoint.form_body_class: endpoint.relative_imports.add(import_string_from_class(endpoint.form_body_class, prefix="...models")) @@ -164,7 +164,9 @@ def _add_body( return endpoint, schemas @staticmethod - def _add_responses(*, endpoint: "Endpoint", data: oai.Responses, schemas: Schemas) -> Tuple["Endpoint", Schemas]: + def _add_responses( + *, endpoint: "Endpoint", data: oai.Responses, schemas: Schemas, config: Config + ) -> Tuple["Endpoint", Schemas]: endpoint = deepcopy(endpoint) for code, response_data in data.items(): @@ -183,7 +185,7 @@ def _add_responses(*, endpoint: "Endpoint", data: oai.Responses, schemas: Schema continue response, schemas = response_from_data( - status_code=status_code, data=response_data, schemas=schemas, parent_name=endpoint.name + status_code=status_code, data=response_data, schemas=schemas, parent_name=endpoint.name, config=config ) if isinstance(response, ParseError): endpoint.errors.append( @@ -235,7 +237,7 @@ def _add_parameters( @staticmethod def from_data( - *, data: oai.Operation, path: str, method: str, tag: str, schemas: Schemas + *, data: oai.Operation, path: str, method: str, tag: str, schemas: Schemas, config: Config ) -> Tuple[Union["Endpoint", ParseError], Schemas]: """ Construct an endpoint from the OpenAPI data """ @@ -256,8 +258,8 @@ def from_data( result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas) if isinstance(result, ParseError): return result, schemas - result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas) - result, schemas = Endpoint._add_body(endpoint=result, data=data, schemas=schemas) + result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config) + result, schemas = Endpoint._add_body(endpoint=result, data=data, schemas=schemas, config=config) return result, schemas @@ -291,10 +293,12 @@ def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", Ge header="openapi-python-client only supports OpenAPI 3.x", detail=f"The version of the provided document was {openapi.openapi}", ) - schemas = Schemas(class_overrides=config.class_overrides) + schemas = Schemas() if openapi.components and openapi.components.schemas: schemas = build_schemas(components=openapi.components.schemas, schemas=schemas) - endpoint_collections_by_tag, schemas = EndpointCollection.from_data(data=openapi.paths, schemas=schemas) + endpoint_collections_by_tag, schemas = EndpointCollection.from_data( + data=openapi.paths, schemas=schemas, config=config + ) enums = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, EnumProperty)) models = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty)) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 58c65139b..0cd100618 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -2,6 +2,7 @@ "Class", "EnumProperty", "ModelProperty", + "NoneProperty", "Property", "Schemas", "build_schemas", diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index df848ddae..2e400cb6d 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -6,7 +6,6 @@ from ... import Config from ... import schema as oai from ... import utils -from ...utils import to_valid_python_identifier from ..errors import ParseError, PropertyError from . import parse_reference_path from .property import Property @@ -178,7 +177,7 @@ def build_model_property( class_name = data.title or name if parent_name: class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}" - class_info = Class.from_string(string=class_name, schemas=schemas) + class_info = Class.from_string(string=class_name, config=config) property_data = _process_properties(data=data, schemas=schemas, class_name=class_name) if isinstance(property_data, PropertyError): @@ -204,7 +203,6 @@ def build_model_property( required=required, name=name, additional_properties=additional_properties, - python_name=to_valid_python_identifier(value=name, field_prefix=config.field_prefix), ) if class_info.name in schemas.classes_by_name: error = PropertyError(data=data, detail=f'Attempted to generate duplicate models with name "{class_info.name}"') diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py index 3b70e67b6..7eb5161f9 100644 --- a/openapi_python_client/parser/properties/property.py +++ b/openapi_python_client/parser/properties/property.py @@ -2,7 +2,7 @@ import attr -from ...utils import PythonIdentifier +from ... import utils @attr.s(auto_attribs=True, frozen=True) @@ -26,11 +26,14 @@ class Property: _type_string: ClassVar[str] = "" _json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization default: Optional[str] = attr.ib() - python_name: PythonIdentifier + python_name: str = attr.ib(init=False) template: ClassVar[Optional[str]] = None json_is_dict: ClassVar[bool] = False + def __attrs_post_init__(self) -> None: + object.__setattr__(self, "python_name", utils.to_valid_python_identifier(utils.snake_case(self.name))) + def get_base_type_string(self) -> str: return self._type_string diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index c3cbd2466..19a794d8d 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -58,7 +58,7 @@ class Schemas: def update_schemas_with_data( - ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas + ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config ) -> Union[Schemas, PropertyError]: from . import build_enum_property, build_model_property @@ -68,7 +68,9 @@ def update_schemas_with_data( data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None ) else: - prop, schemas = build_model_property(data=data, name=ref_path, schemas=schemas, required=True, parent_name=None) + prop, schemas = build_model_property( + data=data, name=ref_path, schemas=schemas, required=True, parent_name=None, config=config + ) if isinstance(prop, PropertyError): return prop schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference}) diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py index 75ccfd418..c99a453d1 100644 --- a/openapi_python_client/parser/responses.py +++ b/openapi_python_client/parser/responses.py @@ -6,7 +6,6 @@ from .. import Config from .. import schema as oai -from ..utils import to_valid_python_identifier from .errors import ParseError, PropertyError from .properties import NoneProperty, Property, Schemas, property_from_data @@ -28,7 +27,7 @@ class Response: } -def empty_response(*, status_code: int, response_name: str, config: Config) -> Response: +def empty_response(status_code: int, response_name: str) -> Response: """ Return an empty response, for when no response type is defined """ return Response( status_code=status_code, @@ -37,7 +36,6 @@ def empty_response(*, status_code: int, response_name: str, config: Config) -> R default=None, nullable=False, required=True, - python_name=to_valid_python_identifier(value=response_name, field_prefix=config.field_prefix), ), source="None", ) @@ -51,7 +49,7 @@ def response_from_data( response_name = f"response_{status_code}" if isinstance(data, oai.Reference) or data.content is None: return ( - empty_response(status_code=status_code, response_name=response_name, config=config), + empty_response(status_code=status_code, response_name=response_name), schemas, ) diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py index 9bad71450..6cdcb2119 100644 --- a/openapi_python_client/utils.py +++ b/openapi_python_client/utils.py @@ -1,7 +1,6 @@ import builtins import re from keyword import iskeyword -from typing import NewType, cast import stringcase @@ -48,10 +47,11 @@ def remove_string_escapes(value: str) -> str: return value.replace('"', r"\"") -PythonIdentifier = NewType("PythonIdentifier", str) +# This can be changed by config.Config.load_config +FIELD_PREFIX = "field_" -def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdentifier: +def to_valid_python_identifier(value: str) -> str: """ Given a string, attempt to coerce it into a valid Python identifier by stripping out invalid characters and, if necessary, prepending a prefix. @@ -62,6 +62,6 @@ def to_valid_python_identifier(*, value: str, field_prefix: str) -> PythonIdenti new_value = fix_reserved_words(fix_keywords(sanitize(value))) if new_value.isidentifier(): - return cast(PythonIdentifier, new_value) + return new_value - return cast(PythonIdentifier, f"{field_prefix}{new_value}") + return f"{FIELD_PREFIX}{new_value}" From ff8bdb919d0aa54cace8ff9ed964c133ca4bbd06 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sat, 27 Mar 2021 12:04:00 -0600 Subject: [PATCH 03/11] test: Fix all the type errors and start fixing unit tests --- .../api/tests/defaults_tests_defaults_post.py | 124 +++---- .../api/tests/get_user_list.py | 37 +- .../api/tests/int_enum_tests_int_enum_post.py | 28 +- .../tests/json_body_tests_json_body_post.py | 109 ++++++ ...tional_value_tests_optional_query_param.py | 15 +- .../tests/upload_file_tests_upload_post.py | 37 +- .../my_test_api_client/models/__init__.py | 5 + .../my_test_api_client/models/a_model.py | 351 ++++++++++++++++++ .../models/a_model_model.py | 44 +++ .../models/a_model_not_required_model.py | 44 +++ .../a_model_not_required_nullable_model.py | 44 +++ .../models/a_model_nullable_model.py | 44 +++ .../models/http_validation_error.py | 9 +- .../models/model_from_all_of.py | 21 +- .../model_with_additional_properties_refed.py | 20 +- .../models/model_with_property_ref.py | 12 +- .../models/model_with_union_property.py | 34 +- end_to_end_tests/regen_golden_record.py | 2 +- end_to_end_tests/test_end_to_end.py | 2 +- openapi_python_client/__init__.py | 10 +- openapi_python_client/cli.py | 22 +- openapi_python_client/config.py | 7 +- openapi_python_client/parser/openapi.py | 20 +- .../parser/properties/__init__.py | 50 ++- .../parser/properties/model_property.py | 20 +- .../parser/properties/schemas.py | 20 +- openapi_python_client/parser/responses.py | 1 + .../templates/endpoint_macros.py.jinja | 12 +- .../templates/endpoint_module.py.jinja | 8 +- .../templates/int_enum.py.jinja | 2 +- .../templates/model.py.jinja | 15 +- .../property_templates/enum_property.py.jinja | 2 +- .../model_property.py.jinja | 2 +- .../templates/str_enum.py.jinja | 2 +- tests/test___init__.py | 112 +++--- .../test_properties/test_model_property.py | 23 +- 36 files changed, 967 insertions(+), 343 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py index b6aaaea6e..fc924f5f9 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py @@ -5,9 +5,6 @@ from dateutil.parser import isoparse from ...client import Client -from ...models.an_enum import AnEnum -from ...models.http_validation_error import HTTPValidationError -from ...models.model_with_union_property import ModelWithUnionProperty from ...types import UNSET, Response, Unset @@ -23,14 +20,14 @@ def _get_kwargs( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[AnEnum]] = UNSET, + list_prop: Union[Unset, List[None]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, - enum_prop: Union[Unset, AnEnum] = UNSET, - model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, - nullable_required_model_prop: Union[ModelWithUnionProperty, None], + union_prop_with_ref: Union[None, Unset, float] = 0.6, + enum_prop: Union[Unset, None] = UNSET, + model_prop: Union[Unset, None] = UNSET, + required_model_prop: None, + nullable_model_prop: Union[None, Unset] = UNSET, + nullable_required_model_prop: None, ) -> Dict[str, Any]: url = "{}/tests/defaults".format(client.base_url) @@ -57,11 +54,11 @@ def _get_kwargs( if not isinstance(date_prop, Unset): json_date_prop = date_prop.isoformat() - json_list_prop: Union[Unset, List[str]] = UNSET + json_list_prop: Union[Unset, List[None]] = UNSET if not isinstance(list_prop, Unset): json_list_prop = [] for list_prop_item_data in list_prop: - list_prop_item = list_prop_item_data.value + list_prop_item = None json_list_prop.append(list_prop_item) @@ -71,42 +68,34 @@ def _get_kwargs( else: json_union_prop = union_prop - json_union_prop_with_ref: Union[Unset, float, str] + json_union_prop_with_ref: Union[None, Unset, float] if isinstance(union_prop_with_ref, Unset): json_union_prop_with_ref = UNSET - elif isinstance(union_prop_with_ref, AnEnum): - json_union_prop_with_ref = UNSET - if not isinstance(union_prop_with_ref, Unset): - json_union_prop_with_ref = union_prop_with_ref.value + elif isinstance(union_prop_with_ref, None): + json_union_prop_with_ref = None else: json_union_prop_with_ref = union_prop_with_ref - json_enum_prop: Union[Unset, str] = UNSET - if not isinstance(enum_prop, Unset): - json_enum_prop = enum_prop.value + json_enum_prop = None - json_model_prop: Union[Unset, Dict[str, Any]] = UNSET - if not isinstance(model_prop, Unset): - json_model_prop = model_prop.to_dict() + json_model_prop = None - json_required_model_prop = required_model_prop.to_dict() + json_required_model_prop = None - json_nullable_model_prop: Union[Dict[str, Any], None, Unset] + json_nullable_model_prop: Union[None, Unset] if isinstance(nullable_model_prop, Unset): json_nullable_model_prop = UNSET elif nullable_model_prop is None: json_nullable_model_prop = None else: - json_nullable_model_prop = UNSET - if not isinstance(nullable_model_prop, Unset): - json_nullable_model_prop = nullable_model_prop.to_dict() + json_nullable_model_prop = None - json_nullable_required_model_prop: Union[Dict[str, Any], None] + json_nullable_required_model_prop: None if nullable_required_model_prop is None: json_nullable_required_model_prop = None else: - json_nullable_required_model_prop = nullable_required_model_prop.to_dict() + json_nullable_required_model_prop = None params: Dict[str, Any] = { "string_prop": string_prop, @@ -122,12 +111,11 @@ def _get_kwargs( "union_prop": json_union_prop, "union_prop_with_ref": json_union_prop_with_ref, "enum_prop": json_enum_prop, + "model_prop": json_model_prop, + "required_model_prop": json_required_model_prop, "nullable_model_prop": json_nullable_model_prop, "nullable_required_model_prop": json_nullable_required_model_prop, } - if not isinstance(json_model_prop, Unset): - params.update(json_model_prop) - params.update(json_required_model_prop) params = {k: v for k, v in params.items() if v is not UNSET and v is not None} return { @@ -139,19 +127,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) + response_422 = None return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: return Response( status_code=response.status_code, content=response.content, @@ -172,15 +160,15 @@ def sync_detailed( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[AnEnum]] = UNSET, + list_prop: Union[Unset, List[None]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, - enum_prop: Union[Unset, AnEnum] = UNSET, - model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, - nullable_required_model_prop: Union[ModelWithUnionProperty, None], -) -> Response[Union[None, HTTPValidationError]]: + union_prop_with_ref: Union[None, Unset, float] = 0.6, + enum_prop: Union[Unset, None] = UNSET, + model_prop: Union[Unset, None] = UNSET, + required_model_prop: None, + nullable_model_prop: Union[None, Unset] = UNSET, + nullable_required_model_prop: None, +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, string_prop=string_prop, @@ -221,15 +209,15 @@ def sync( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[AnEnum]] = UNSET, + list_prop: Union[Unset, List[None]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, - enum_prop: Union[Unset, AnEnum] = UNSET, - model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, - nullable_required_model_prop: Union[ModelWithUnionProperty, None], -) -> Optional[Union[None, HTTPValidationError]]: + union_prop_with_ref: Union[None, Unset, float] = 0.6, + enum_prop: Union[Unset, None] = UNSET, + model_prop: Union[Unset, None] = UNSET, + required_model_prop: None, + nullable_model_prop: Union[None, Unset] = UNSET, + nullable_required_model_prop: None, +) -> Optional[Union[None, None]]: """ """ return sync_detailed( @@ -266,15 +254,15 @@ async def asyncio_detailed( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[AnEnum]] = UNSET, + list_prop: Union[Unset, List[None]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, - enum_prop: Union[Unset, AnEnum] = UNSET, - model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, - nullable_required_model_prop: Union[ModelWithUnionProperty, None], -) -> Response[Union[None, HTTPValidationError]]: + union_prop_with_ref: Union[None, Unset, float] = 0.6, + enum_prop: Union[Unset, None] = UNSET, + model_prop: Union[Unset, None] = UNSET, + required_model_prop: None, + nullable_model_prop: Union[None, Unset] = UNSET, + nullable_required_model_prop: None, +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, string_prop=string_prop, @@ -314,15 +302,15 @@ async def asyncio( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[AnEnum]] = UNSET, + list_prop: Union[Unset, List[None]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, - enum_prop: Union[Unset, AnEnum] = UNSET, - model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, - nullable_required_model_prop: Union[ModelWithUnionProperty, None], -) -> Optional[Union[None, HTTPValidationError]]: + union_prop_with_ref: Union[None, Unset, float] = 0.6, + enum_prop: Union[Unset, None] = UNSET, + model_prop: Union[Unset, None] = UNSET, + required_model_prop: None, + nullable_model_prop: Union[None, Unset] = UNSET, + nullable_required_model_prop: None, +) -> Optional[Union[None, None]]: """ """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index fa7b39b86..5cc5cde5c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -4,15 +4,13 @@ import httpx from ...client import Client -from ...models.an_enum import AnEnum -from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response def _get_kwargs( *, client: Client, - an_enum_value: List[AnEnum], + an_enum_value: List[None], some_date: Union[datetime.date, datetime.datetime], ) -> Dict[str, Any]: url = "{}/tests/".format(client.base_url) @@ -22,7 +20,7 @@ def _get_kwargs( json_an_enum_value = [] for an_enum_value_item_data in an_enum_value: - an_enum_value_item = an_enum_value_item_data.value + an_enum_value_item = None json_an_enum_value.append(an_enum_value_item) @@ -46,15 +44,24 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[HTTPValidationError]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[List[None], None]]: + if response.status_code == 200: + response_200 = [] + _response_200 = response.json() + for response_200_item_data in _response_200: + response_200_item = None + + response_200.append(response_200_item) + + return response_200 if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) + response_422 = None return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError]: +def _build_response(*, response: httpx.Response) -> Response[Union[List[None], None]]: return Response( status_code=response.status_code, content=response.content, @@ -66,9 +73,9 @@ def _build_response(*, response: httpx.Response) -> Response[HTTPValidationError def sync_detailed( *, client: Client, - an_enum_value: List[AnEnum], + an_enum_value: List[None], some_date: Union[datetime.date, datetime.datetime], -) -> Response[HTTPValidationError]: +) -> Response[Union[List[None], None]]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -85,9 +92,9 @@ def sync_detailed( def sync( *, client: Client, - an_enum_value: List[AnEnum], + an_enum_value: List[None], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[HTTPValidationError]: +) -> Optional[Union[List[None], None]]: """ Get a list of things """ return sync_detailed( @@ -100,9 +107,9 @@ def sync( async def asyncio_detailed( *, client: Client, - an_enum_value: List[AnEnum], + an_enum_value: List[None], some_date: Union[datetime.date, datetime.datetime], -) -> Response[HTTPValidationError]: +) -> Response[Union[List[None], None]]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -118,9 +125,9 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - an_enum_value: List[AnEnum], + an_enum_value: List[None], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[HTTPValidationError]: +) -> Optional[Union[List[None], None]]: """ Get a list of things """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py index 7d14632c4..96aed7f17 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py @@ -3,22 +3,20 @@ import httpx from ...client import Client -from ...models.an_int_enum import AnIntEnum -from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response def _get_kwargs( *, client: Client, - int_enum: AnIntEnum, + int_enum: None, ) -> Dict[str, Any]: url = "{}/tests/int_enum".format(client.base_url) headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - json_int_enum = int_enum.value + json_int_enum = None params: Dict[str, Any] = { "int_enum": json_int_enum, @@ -34,19 +32,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) + response_422 = None return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: return Response( status_code=response.status_code, content=response.content, @@ -58,8 +56,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPVal def sync_detailed( *, client: Client, - int_enum: AnIntEnum, -) -> Response[Union[None, HTTPValidationError]]: + int_enum: None, +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, int_enum=int_enum, @@ -75,8 +73,8 @@ def sync_detailed( def sync( *, client: Client, - int_enum: AnIntEnum, -) -> Optional[Union[None, HTTPValidationError]]: + int_enum: None, +) -> Optional[Union[None, None]]: """ """ return sync_detailed( @@ -88,8 +86,8 @@ def sync( async def asyncio_detailed( *, client: Client, - int_enum: AnIntEnum, -) -> Response[Union[None, HTTPValidationError]]: + int_enum: None, +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, int_enum=int_enum, @@ -104,8 +102,8 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - int_enum: AnIntEnum, -) -> Optional[Union[None, HTTPValidationError]]: + int_enum: None, +) -> Optional[Union[None, None]]: """ """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py new file mode 100644 index 000000000..a9a89a47c --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -0,0 +1,109 @@ +from typing import Any, Dict, Optional, Union + +import httpx + +from ...client import Client +from ...types import Response + + +def _get_kwargs( + *, + client: Client, + json_body: None, +) -> Dict[str, Any]: + url = "{}/tests/json_body".format(client.base_url) + + headers: Dict[str, Any] = client.get_headers() + cookies: Dict[str, Any] = client.get_cookies() + + json_json_body = None + + return { + "url": url, + "headers": headers, + "cookies": cookies, + "timeout": client.get_timeout(), + "json": json_json_body, + } + + +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: + if response.status_code == 200: + response_200 = None + + return response_200 + if response.status_code == 422: + response_422 = None + + return response_422 + return None + + +def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: + return Response( + status_code=response.status_code, + content=response.content, + headers=response.headers, + parsed=_parse_response(response=response), + ) + + +def sync_detailed( + *, + client: Client, + json_body: None, +) -> Response[Union[None, None]]: + kwargs = _get_kwargs( + client=client, + json_body=json_body, + ) + + response = httpx.post( + **kwargs, + ) + + return _build_response(response=response) + + +def sync( + *, + client: Client, + json_body: None, +) -> Optional[Union[None, None]]: + """ Try sending a JSON body """ + + return sync_detailed( + client=client, + json_body=json_body, + ).parsed + + +async def asyncio_detailed( + *, + client: Client, + json_body: None, +) -> Response[Union[None, None]]: + kwargs = _get_kwargs( + client=client, + json_body=json_body, + ) + + async with httpx.AsyncClient() as _client: + response = await _client.post(**kwargs) + + return _build_response(response=response) + + +async def asyncio( + *, + client: Client, + json_body: None, +) -> Optional[Union[None, None]]: + """ Try sending a JSON body """ + + return ( + await asyncio_detailed( + client=client, + json_body=json_body, + ) + ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py index 64431ba2f..2f1f1d48b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py @@ -3,7 +3,6 @@ import httpx from ...client import Client -from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response, Unset @@ -35,19 +34,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) + response_422 = None return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: return Response( status_code=response.status_code, content=response.content, @@ -60,7 +59,7 @@ def sync_detailed( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Response[Union[None, HTTPValidationError]]: +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, query_param=query_param, @@ -77,7 +76,7 @@ def sync( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[None, HTTPValidationError]]: +) -> Optional[Union[None, None]]: """ Test optional query parameters """ return sync_detailed( @@ -90,7 +89,7 @@ async def asyncio_detailed( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Response[Union[None, HTTPValidationError]]: +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, query_param=query_param, @@ -106,7 +105,7 @@ async def asyncio( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[None, HTTPValidationError]]: +) -> Optional[Union[None, None]]: """ Test optional query parameters """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index 705443d95..c06943593 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -3,15 +3,12 @@ import httpx from ...client import Client -from ...models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost -from ...models.http_validation_error import HTTPValidationError -from ...types import UNSET, File, Response, Unset +from ...types import UNSET, Response, Unset def _get_kwargs( *, client: Client, - multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, ) -> Dict[str, Any]: url = "{}/tests/upload".format(client.base_url) @@ -22,37 +19,27 @@ def _get_kwargs( if keep_alive is not UNSET: headers["keep-alive"] = keep_alive - files = {} - data = {} - for key, value in multipart_data.to_dict().items(): - if isinstance(value, File): - files[key] = value - else: - data[key] = value - return { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), - "files": files, - "data": data, } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = HTTPValidationError.from_dict(response.json()) + response_422 = None return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: return Response( status_code=response.status_code, content=response.content, @@ -64,12 +51,10 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPVal def sync_detailed( *, client: Client, - multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Response[Union[None, HTTPValidationError]]: +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, - multipart_data=multipart_data, keep_alive=keep_alive, ) @@ -83,14 +68,12 @@ def sync_detailed( def sync( *, client: Client, - multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Optional[Union[None, HTTPValidationError]]: +) -> Optional[Union[None, None]]: """ Upload a file """ return sync_detailed( client=client, - multipart_data=multipart_data, keep_alive=keep_alive, ).parsed @@ -98,12 +81,10 @@ def sync( async def asyncio_detailed( *, client: Client, - multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Response[Union[None, HTTPValidationError]]: +) -> Response[Union[None, None]]: kwargs = _get_kwargs( client=client, - multipart_data=multipart_data, keep_alive=keep_alive, ) @@ -116,15 +97,13 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Optional[Union[None, HTTPValidationError]]: +) -> Optional[Union[None, None]]: """ Upload a file """ return ( await asyncio_detailed( client=client, - multipart_data=multipart_data, keep_alive=keep_alive, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index ee7e8833b..bd49638c2 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -1,5 +1,10 @@ """ Contains all the data models used in inputs/outputs """ +from .a_model import AModel +from .a_model_model import AModelModel +from .a_model_not_required_model import AModelNotRequiredModel +from .a_model_not_required_nullable_model import AModelNotRequiredNullableModel +from .a_model_nullable_model import AModelNullableModel from .all_of_sub_model import AllOfSubModel from .an_enum import AnEnum from .an_int_enum import AnIntEnum diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py new file mode 100644 index 000000000..92a9738d9 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -0,0 +1,351 @@ +import datetime +from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast + +import attr +from dateutil.parser import isoparse + +from ..models.a_model_model import AModelModel +from ..models.a_model_not_required_model import AModelNotRequiredModel +from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel +from ..models.a_model_nullable_model import AModelNullableModel +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AModel") + + +@attr.s(auto_attribs=True) +class AModel: + """ A Model for testing all the ways custom objects can be used """ + + an_enum_value: None + a_camel_date_time: Union[datetime.date, datetime.datetime] + a_date: datetime.date + required_not_nullable: str + one_of_models: None + model: AModelModel + a_nullable_date: Optional[datetime.date] + required_nullable: Optional[str] + nullable_one_of_models: None + nullable_model: Optional[AModelNullableModel] + nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET + a_not_required_date: Union[Unset, datetime.date] = UNSET + attr_1_leading_digit: Union[Unset, str] = UNSET + not_required_nullable: Union[Unset, None, str] = UNSET + not_required_not_nullable: Union[Unset, str] = UNSET + not_required_one_of_models: Union[None, Unset] = UNSET + not_required_nullable_one_of_models: Union[None, Unset, str] = UNSET + not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET + not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET + + def to_dict(self) -> Dict[str, Any]: + an_enum_value = None + + if isinstance(self.a_camel_date_time, datetime.datetime): + a_camel_date_time = self.a_camel_date_time.isoformat() + + else: + a_camel_date_time = self.a_camel_date_time.isoformat() + + a_date = self.a_date.isoformat() + required_not_nullable = self.required_not_nullable + if isinstance(self.one_of_models, None): + one_of_models = None + + else: + one_of_models = None + + model = self.model.to_dict() + + nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET + if not isinstance(self.nested_list_of_enums, Unset): + nested_list_of_enums = [] + for nested_list_of_enums_item_data in self.nested_list_of_enums: + nested_list_of_enums_item = [] + for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data: + nested_list_of_enums_item_item = None + + nested_list_of_enums_item.append(nested_list_of_enums_item_item) + + nested_list_of_enums.append(nested_list_of_enums_item) + + a_nullable_date = self.a_nullable_date.isoformat() if self.a_nullable_date else None + a_not_required_date: Union[Unset, str] = UNSET + if not isinstance(self.a_not_required_date, Unset): + a_not_required_date = self.a_not_required_date.isoformat() + + attr_1_leading_digit = self.attr_1_leading_digit + required_nullable = self.required_nullable + not_required_nullable = self.not_required_nullable + not_required_not_nullable = self.not_required_not_nullable + nullable_one_of_models: None + if self.nullable_one_of_models is None: + nullable_one_of_models = None + elif isinstance(self.nullable_one_of_models, None): + nullable_one_of_models = None + + else: + nullable_one_of_models = None + + not_required_one_of_models: Union[None, Unset] + if isinstance(self.not_required_one_of_models, Unset): + not_required_one_of_models = UNSET + elif isinstance(self.not_required_one_of_models, None): + not_required_one_of_models = None + + else: + not_required_one_of_models = None + + not_required_nullable_one_of_models: Union[None, Unset, str] + if isinstance(self.not_required_nullable_one_of_models, Unset): + not_required_nullable_one_of_models = UNSET + elif self.not_required_nullable_one_of_models is None: + not_required_nullable_one_of_models = None + elif isinstance(self.not_required_nullable_one_of_models, None): + not_required_nullable_one_of_models = None + + elif isinstance(self.not_required_nullable_one_of_models, None): + not_required_nullable_one_of_models = None + + else: + not_required_nullable_one_of_models = self.not_required_nullable_one_of_models + + nullable_model = self.nullable_model.to_dict() if self.nullable_model else None + + not_required_model: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.not_required_model, Unset): + not_required_model = self.not_required_model.to_dict() + + not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET + if not isinstance(self.not_required_nullable_model, Unset): + not_required_nullable_model = ( + self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None + ) + + field_dict: Dict[str, Any] = {} + field_dict.update( + { + "an_enum_value": an_enum_value, + "aCamelDateTime": a_camel_date_time, + "a_date": a_date, + "required_not_nullable": required_not_nullable, + "one_of_models": one_of_models, + "model": model, + "a_nullable_date": a_nullable_date, + "required_nullable": required_nullable, + "nullable_one_of_models": nullable_one_of_models, + "nullable_model": nullable_model, + } + ) + if nested_list_of_enums is not UNSET: + field_dict["nested_list_of_enums"] = nested_list_of_enums + if a_not_required_date is not UNSET: + field_dict["a_not_required_date"] = a_not_required_date + if attr_1_leading_digit is not UNSET: + field_dict["1_leading_digit"] = attr_1_leading_digit + if not_required_nullable is not UNSET: + field_dict["not_required_nullable"] = not_required_nullable + if not_required_not_nullable is not UNSET: + field_dict["not_required_not_nullable"] = not_required_not_nullable + if not_required_one_of_models is not UNSET: + field_dict["not_required_one_of_models"] = not_required_one_of_models + if not_required_nullable_one_of_models is not UNSET: + field_dict["not_required_nullable_one_of_models"] = not_required_nullable_one_of_models + if not_required_model is not UNSET: + field_dict["not_required_model"] = not_required_model + if not_required_nullable_model is not UNSET: + field_dict["not_required_nullable_model"] = not_required_nullable_model + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + an_enum_value = None + + def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]: + try: + a_camel_date_time_type0: datetime.datetime + if not isinstance(data, str): + raise TypeError() + a_camel_date_time_type0 = isoparse(data) + + return a_camel_date_time_type0 + except: # noqa: E722 + pass + if not isinstance(data, str): + raise TypeError() + a_camel_date_time_type1: datetime.date + a_camel_date_time_type1 = isoparse(data).date() + + return a_camel_date_time_type1 + + a_camel_date_time = _parse_a_camel_date_time(d.pop("aCamelDateTime")) + + a_date = isoparse(d.pop("a_date")).date() + + required_not_nullable = d.pop("required_not_nullable") + + def _parse_one_of_models(data: object) -> None: + if data is None: + return data + try: + one_of_models_type0: None + if not data is None: + raise TypeError() + one_of_models_type0 = UNSET + + return one_of_models_type0 + except: # noqa: E722 + pass + if not data is None: + raise TypeError() + one_of_models_type1: None + one_of_models_type1 = UNSET + + return one_of_models_type1 + + one_of_models = _parse_one_of_models(d.pop("one_of_models")) + + model = AModelModel.from_dict(d.pop("model")) + + nested_list_of_enums = [] + _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET) + for nested_list_of_enums_item_data in _nested_list_of_enums or []: + nested_list_of_enums_item = [] + _nested_list_of_enums_item = nested_list_of_enums_item_data + for nested_list_of_enums_item_item_data in _nested_list_of_enums_item: + nested_list_of_enums_item_item = None + + nested_list_of_enums_item.append(nested_list_of_enums_item_item) + + nested_list_of_enums.append(nested_list_of_enums_item) + + a_nullable_date = None + _a_nullable_date = d.pop("a_nullable_date") + if _a_nullable_date is not None: + a_nullable_date = isoparse(_a_nullable_date).date() + + a_not_required_date: Union[Unset, datetime.date] = UNSET + _a_not_required_date = d.pop("a_not_required_date", UNSET) + if not isinstance(_a_not_required_date, Unset): + a_not_required_date = isoparse(_a_not_required_date).date() + + attr_1_leading_digit = d.pop("1_leading_digit", UNSET) + + required_nullable = d.pop("required_nullable") + + not_required_nullable = d.pop("not_required_nullable", UNSET) + + not_required_not_nullable = d.pop("not_required_not_nullable", UNSET) + + def _parse_nullable_one_of_models(data: object) -> None: + if data is None: + return data + try: + nullable_one_of_models_type0: None + if not data is None: + raise TypeError() + nullable_one_of_models_type0 = UNSET + + return nullable_one_of_models_type0 + except: # noqa: E722 + pass + if not data is None: + raise TypeError() + nullable_one_of_models_type1: None + nullable_one_of_models_type1 = UNSET + + return nullable_one_of_models_type1 + + nullable_one_of_models = _parse_nullable_one_of_models(d.pop("nullable_one_of_models")) + + def _parse_not_required_one_of_models(data: object) -> Union[None, Unset]: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + not_required_one_of_models_type0: Union[Unset, None] + if not data is None: + raise TypeError() + not_required_one_of_models_type0 = UNSET + + return not_required_one_of_models_type0 + except: # noqa: E722 + pass + if not data is None: + raise TypeError() + not_required_one_of_models_type1: Union[Unset, None] + not_required_one_of_models_type1 = UNSET + + return not_required_one_of_models_type1 + + not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET)) + + def _parse_not_required_nullable_one_of_models(data: object) -> Union[None, Unset, str]: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + not_required_nullable_one_of_models_type0: Union[Unset, None] + if not data is None: + raise TypeError() + not_required_nullable_one_of_models_type0 = UNSET + + return not_required_nullable_one_of_models_type0 + except: # noqa: E722 + pass + try: + not_required_nullable_one_of_models_type1: Union[Unset, None] + if not data is None: + raise TypeError() + not_required_nullable_one_of_models_type1 = UNSET + + return not_required_nullable_one_of_models_type1 + except: # noqa: E722 + pass + return cast(Union[None, Unset, str], data) + + not_required_nullable_one_of_models = _parse_not_required_nullable_one_of_models( + d.pop("not_required_nullable_one_of_models", UNSET) + ) + + nullable_model = None + _nullable_model = d.pop("nullable_model") + if _nullable_model is not None: + nullable_model = AModelNullableModel.from_dict(_nullable_model) + + not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET + _not_required_model = d.pop("not_required_model", UNSET) + if not isinstance(_not_required_model, Unset): + not_required_model = AModelNotRequiredModel.from_dict(_not_required_model) + + not_required_nullable_model = None + _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET) + if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset): + not_required_nullable_model = AModelNotRequiredNullableModel.from_dict(_not_required_nullable_model) + + a_model = cls( + an_enum_value=an_enum_value, + a_camel_date_time=a_camel_date_time, + a_date=a_date, + required_not_nullable=required_not_nullable, + one_of_models=one_of_models, + model=model, + nested_list_of_enums=nested_list_of_enums, + a_nullable_date=a_nullable_date, + a_not_required_date=a_not_required_date, + attr_1_leading_digit=attr_1_leading_digit, + required_nullable=required_nullable, + not_required_nullable=not_required_nullable, + not_required_not_nullable=not_required_not_nullable, + nullable_one_of_models=nullable_one_of_models, + not_required_one_of_models=not_required_one_of_models, + not_required_nullable_one_of_models=not_required_nullable_one_of_models, + nullable_model=nullable_model, + not_required_model=not_required_model, + not_required_nullable_model=not_required_nullable_model, + ) + + return a_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py new file mode 100644 index 000000000..55ff303f9 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, List, Type, TypeVar + +import attr + +T = TypeVar("T", bound="AModelModel") + + +@attr.s(auto_attribs=True) +class AModelModel: + """ """ + + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a_model_model = cls() + + a_model_model.additional_properties = d + return a_model_model + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py new file mode 100644 index 000000000..4c86e1019 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, List, Type, TypeVar + +import attr + +T = TypeVar("T", bound="AModelNotRequiredModel") + + +@attr.s(auto_attribs=True) +class AModelNotRequiredModel: + """ """ + + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a_model_not_required_model = cls() + + a_model_not_required_model.additional_properties = d + return a_model_not_required_model + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py new file mode 100644 index 000000000..c0bf0fafb --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, List, Type, TypeVar + +import attr + +T = TypeVar("T", bound="AModelNotRequiredNullableModel") + + +@attr.s(auto_attribs=True) +class AModelNotRequiredNullableModel: + """ """ + + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a_model_not_required_nullable_model = cls() + + a_model_not_required_nullable_model.additional_properties = d + return a_model_not_required_nullable_model + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py new file mode 100644 index 000000000..fe66227fb --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py @@ -0,0 +1,44 @@ +from typing import Any, Dict, List, Type, TypeVar + +import attr + +T = TypeVar("T", bound="AModelNullableModel") + + +@attr.s(auto_attribs=True) +class AModelNullableModel: + """ """ + + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a_model_nullable_model = cls() + + a_model_nullable_model.additional_properties = d + return a_model_nullable_model + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py index feb2cdd6b..92ad83e50 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py @@ -2,7 +2,6 @@ import attr -from ..models.validation_error import ValidationError from ..types import UNSET, Unset T = TypeVar("T", bound="HTTPValidationError") @@ -12,14 +11,14 @@ class HTTPValidationError: """ """ - detail: Union[Unset, List[ValidationError]] = UNSET + detail: Union[Unset, List[None]] = UNSET def to_dict(self) -> Dict[str, Any]: - detail: Union[Unset, List[Dict[str, Any]]] = UNSET + detail: Union[Unset, List[None]] = UNSET if not isinstance(self.detail, Unset): detail = [] for detail_item_data in self.detail: - detail_item = detail_item_data.to_dict() + detail_item = None detail.append(detail_item) @@ -36,7 +35,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: detail = [] _detail = d.pop("detail", UNSET) for detail_item_data in _detail or []: - detail_item = ValidationError.from_dict(detail_item_data) + detail_item = None detail.append(detail_item) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py index ce26a3bbb..7f4ef8bbd 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py @@ -1,9 +1,7 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, Dict, List, Type, TypeVar import attr -from ..types import UNSET, Unset - T = TypeVar("T", bound="ModelFromAllOf") @@ -11,35 +9,20 @@ class ModelFromAllOf: """ """ - a_sub_property: Union[Unset, str] = UNSET - another_sub_property: Union[Unset, str] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: - a_sub_property = self.a_sub_property - another_sub_property = self.another_sub_property field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) - if a_sub_property is not UNSET: - field_dict["a_sub_property"] = a_sub_property - if another_sub_property is not UNSET: - field_dict["another_sub_property"] = another_sub_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_sub_property = d.pop("a_sub_property", UNSET) - - another_sub_property = d.pop("another_sub_property", UNSET) - - model_from_all_of = cls( - a_sub_property=a_sub_property, - another_sub_property=another_sub_property, - ) + model_from_all_of = cls() model_from_all_of.additional_properties = d return model_from_all_of diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py index b265db582..1cfd6fdc7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py @@ -2,8 +2,6 @@ import attr -from ..models.an_enum import AnEnum - T = TypeVar("T", bound="ModelWithAdditionalPropertiesRefed") @@ -11,14 +9,12 @@ class ModelWithAdditionalPropertiesRefed: """ """ - additional_properties: Dict[str, AnEnum] = attr.ib(init=False, factory=dict) + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: field_dict: Dict[str, Any] = {} - for prop_name, prop in self.additional_properties.items(): - field_dict[prop_name] = prop.value - + field_dict.update(self.additional_properties) field_dict.update({}) return field_dict @@ -28,23 +24,17 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() model_with_additional_properties_refed = cls() - additional_properties = {} - for prop_name, prop_dict in d.items(): - additional_property = AnEnum(prop_dict) - - additional_properties[prop_name] = additional_property - - model_with_additional_properties_refed.additional_properties = additional_properties + model_with_additional_properties_refed.additional_properties = d return model_with_additional_properties_refed @property def additional_keys(self) -> List[str]: return list(self.additional_properties.keys()) - def __getitem__(self, key: str) -> AnEnum: + def __getitem__(self, key: str) -> Any: return self.additional_properties[key] - def __setitem__(self, key: str, value: AnEnum) -> None: + def __setitem__(self, key: str, value: Any) -> None: self.additional_properties[key] = value def __delitem__(self, key: str) -> None: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py index 1553914ba..cfb47ded3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py @@ -2,7 +2,6 @@ import attr -from ..models.model_name import ModelName from ..types import UNSET, Unset T = TypeVar("T", bound="ModelWithPropertyRef") @@ -12,13 +11,11 @@ class ModelWithPropertyRef: """ """ - inner: Union[Unset, ModelName] = UNSET + inner: Union[Unset, None] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: - inner: Union[Unset, Dict[str, Any]] = UNSET - if not isinstance(self.inner, Unset): - inner = self.inner.to_dict() + inner = None field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -31,10 +28,7 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - inner: Union[Unset, ModelName] = UNSET - _inner = d.pop("inner", UNSET) - if not isinstance(_inner, Unset): - inner = ModelName.from_dict(_inner) + inner = None model_with_property_ref = cls( inner=inner, diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py index a3f049533..c7a849cbf 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py @@ -2,8 +2,6 @@ import attr -from ..models.an_enum import AnEnum -from ..models.an_int_enum import AnIntEnum from ..types import UNSET, Unset T = TypeVar("T", bound="ModelWithUnionProperty") @@ -13,21 +11,17 @@ class ModelWithUnionProperty: """ """ - a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET + a_property: Union[None, Unset] = UNSET def to_dict(self) -> Dict[str, Any]: - a_property: Union[Unset, int, str] + a_property: Union[None, Unset] if isinstance(self.a_property, Unset): a_property = UNSET - elif isinstance(self.a_property, AnEnum): - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value + elif isinstance(self.a_property, None): + a_property = None else: - a_property = UNSET - if not isinstance(self.a_property, Unset): - a_property = self.a_property.value + a_property = None field_dict: Dict[str, Any] = {} field_dict.update({}) @@ -40,28 +34,24 @@ def to_dict(self) -> Dict[str, Any]: def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: + def _parse_a_property(data: object) -> Union[None, Unset]: + if data is None: + return data if isinstance(data, Unset): return data try: - a_property_type0: Union[Unset, AnEnum] - if not isinstance(data, str): + a_property_type0: Union[Unset, None] + if not data is None: raise TypeError() a_property_type0 = UNSET - _a_property_type0 = data - if not isinstance(_a_property_type0, Unset): - a_property_type0 = AnEnum(_a_property_type0) return a_property_type0 except: # noqa: E722 pass - if not isinstance(data, int): + if not data is None: raise TypeError() - a_property_type1: Union[Unset, AnIntEnum] + a_property_type1: Union[Unset, None] a_property_type1 = UNSET - _a_property_type1 = data - if not isinstance(_a_property_type1, Unset): - a_property_type1 = AnIntEnum(_a_property_type1) return a_property_type1 diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py index 5269f522d..350124c7e 100644 --- a/end_to_end_tests/regen_golden_record.py +++ b/end_to_end_tests/regen_golden_record.py @@ -18,7 +18,7 @@ shutil.rmtree(gr_path, ignore_errors=True) shutil.rmtree(output_path, ignore_errors=True) - result = runner.invoke(app, [f"--config={config_path}", "generate", f"--path={openapi_path}"]) + result = runner.invoke(app, ["generate", f"--config={config_path}", f"--path={openapi_path}"]) if result.stdout: print(result.stdout) diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py index 9201d9746..fa4d21598 100644 --- a/end_to_end_tests/test_end_to_end.py +++ b/end_to_end_tests/test_end_to_end.py @@ -53,7 +53,7 @@ def run_e2e_test(extra_args=None, expected_differences=None): output_path = Path.cwd() / "my-test-api-client" shutil.rmtree(output_path, ignore_errors=True) - args = [f"--config={config_path}", "generate", f"--path={openapi_path}"] + args = ["generate", f"--config={config_path}", f"--path={openapi_path}"] if extra_args: args.extend(extra_args) result = runner.invoke(app, args) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 46fa94a51..16cc07000 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -47,7 +47,7 @@ def __init__( *, openapi: GeneratorData, meta: MetaType, - config: Optional[Config], + config: Config, custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> None: @@ -259,14 +259,14 @@ def _get_project_for_url_or_path( url: Optional[str], path: Optional[Path], meta: MetaType, - config: Optional[Config], + config: Config, custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Union[Project, GeneratorError]: data_dict = _get_document(url=url, path=path) if isinstance(data_dict, GeneratorError): return data_dict - openapi = GeneratorData.from_dict(data_dict) + openapi = GeneratorData.from_dict(data_dict, config=config) if isinstance(openapi, GeneratorError): return openapi return Project( @@ -283,7 +283,7 @@ def create_new_client( url: Optional[str], path: Optional[Path], meta: MetaType, - config: Optional[Config], + config: Config, custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: @@ -311,7 +311,7 @@ def update_existing_client( url: Optional[str], path: Optional[Path], meta: MetaType, - config: Optional[Config], + config: Config, custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Sequence[GeneratorError]: diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index 9a60a302e..fea7cc34f 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -109,6 +109,8 @@ def handle_errors(errors: Sequence[GeneratorError]) -> None: help="The type of metadata you want to generate.", ) +CONFIG_OPTION = typer.Option(None, "--config", help="Path to the config file to use") + @app.command() def generate( @@ -117,7 +119,7 @@ def generate( custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore meta: MetaType = _meta_option, file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), - config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"), + config_path: Optional[pathlib.Path] = CONFIG_OPTION, ) -> None: """ Generate a new OpenAPI Client library """ from . import create_new_client @@ -135,8 +137,14 @@ def generate( typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED) raise typer.Exit(code=1) + config = Config.load_from_path(config_path) if config_path is not None else Config() errors = create_new_client( - url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding + url=url, + path=path, + meta=meta, + custom_template_path=custom_template_path, + file_encoding=file_encoding, + config=config, ) handle_errors(errors) @@ -148,7 +156,7 @@ def update( custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore meta: MetaType = _meta_option, file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"), - config: Optional[pathlib.Path] = typer.Option(None, help="Path to the config file to use"), + config_path: Optional[pathlib.Path] = CONFIG_OPTION, ) -> None: """ Update an existing OpenAPI Client library """ from . import update_existing_client @@ -166,7 +174,13 @@ def update( typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED) raise typer.Exit(code=1) + config = Config.load_from_path(config_path) if config_path is not None else Config() errors = update_existing_client( - url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding + url=url, + path=path, + meta=meta, + custom_template_path=custom_template_path, + file_encoding=file_encoding, + config=config, ) handle_errors(errors) diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py index 3deb79e71..273848e57 100644 --- a/openapi_python_client/config.py +++ b/openapi_python_client/config.py @@ -4,11 +4,14 @@ import yaml from pydantic import BaseModel -from openapi_python_client.parser.properties import Class + +class ClassOverride(BaseModel): + class_name: Optional[str] = None + module_name: Optional[str] = None class Config(BaseModel): - class_overrides: Dict[str, Class] = {} + class_overrides: Dict[str, ClassOverride] = {} project_name_override: Optional[str] package_name_override: Optional[str] package_version_override: Optional[str] diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index d40923ec1..9dd2106bc 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -5,9 +5,9 @@ from pydantic import ValidationError -from .. import Config from .. import schema as oai from .. import utils +from ..config import Config from .errors import GeneratorError, ParseError, PropertyError from .properties import Class, EnumProperty, ModelProperty, Property, Schemas, build_schemas, property_from_data from .responses import Response, response_from_data @@ -121,7 +121,7 @@ def parse_multipart_body(*, body: oai.RequestBody, config: Config) -> Optional[C @staticmethod def parse_request_json_body( - *, body: oai.RequestBody, schemas: Schemas, parent_name: str + *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[Property, PropertyError, None], Schemas]: """ Return json_body """ body_content = body.content @@ -133,12 +133,17 @@ def parse_request_json_body( data=json_body.media_type_schema, schemas=schemas, parent_name=parent_name, + config=config, ) return None, schemas @staticmethod def _add_body( - *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config + *, + endpoint: "Endpoint", + data: oai.Operation, + schemas: Schemas, + config: Config, ) -> Tuple[Union[ParseError, "Endpoint"], Schemas]: """ Adds form or JSON body to Endpoint if included in data """ endpoint = deepcopy(endpoint) @@ -147,7 +152,7 @@ def _add_body( endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, config=config) json_body, schemas = Endpoint.parse_request_json_body( - body=data.requestBody, schemas=schemas, parent_name=endpoint.name + body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config ) if isinstance(json_body, ParseError): return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas @@ -204,7 +209,7 @@ def _add_responses( @staticmethod def _add_parameters( - *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas + *, endpoint: "Endpoint", data: oai.Operation, schemas: Schemas, config: Config ) -> Tuple[Union["Endpoint", ParseError], Schemas]: endpoint = deepcopy(endpoint) if data.parameters is None: @@ -218,6 +223,7 @@ def _add_parameters( data=param.param_schema, schemas=schemas, parent_name=endpoint.name, + config=config, ) if isinstance(prop, ParseError): return ParseError(detail=f"cannot parse parameter of endpoint {endpoint.name}", data=prop.data), schemas @@ -255,7 +261,7 @@ def from_data( tag=tag, ) - result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas) + result, schemas = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=schemas, config=config) if isinstance(result, ParseError): return result, schemas result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config) @@ -295,7 +301,7 @@ def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", Ge ) schemas = Schemas() if openapi.components and openapi.components.schemas: - schemas = build_schemas(components=openapi.components.schemas, schemas=schemas) + schemas = build_schemas(components=openapi.components.schemas, schemas=schemas, config=config) endpoint_collections_by_tag, schemas = EndpointCollection.from_data( data=openapi.paths, schemas=schemas, config=config ) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 0cd100618..d9a6ddea0 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -14,6 +14,7 @@ import attr +from ... import Config from ... import schema as oai from ... import utils from ..errors import ParseError, PropertyError, ValidationError @@ -277,6 +278,7 @@ def build_enum_property( schemas: Schemas, enum: List[Union[str, int]], parent_name: Optional[str], + config: Config, ) -> Tuple[Union[EnumProperty, PropertyError], Schemas]: """ Create an EnumProperty from schema data. @@ -288,6 +290,7 @@ def build_enum_property( schemas: The Schemas which have been defined so far (used to prevent naming collisions) enum: The enum from the provided data. Required separately here to prevent extra type checking. parent_name: The context in which this EnumProperty is defined, used to create more specific class names. + config: The global config for this run of the generator Returns: A tuple containing either the created property or a PropertyError describing what went wrong AND update schemas. @@ -296,7 +299,7 @@ def build_enum_property( class_name = data.title or name if parent_name: class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}" - class_info = Class.from_string(string=class_name, schemas=schemas) + class_info = Class.from_string(string=class_name, config=config) values = EnumProperty.values_from_list(enum) if class_info.name in schemas.classes_by_name: @@ -340,12 +343,17 @@ def build_enum_property( def build_union_property( - *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str + *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: sub_properties: List[Property] = [] for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)): sub_prop, schemas = property_from_data( - name=f"{name}_type{i}", required=required, data=sub_prop_data, schemas=schemas, parent_name=parent_name + name=f"{name}_type{i}", + required=required, + data=sub_prop_data, + schemas=schemas, + parent_name=parent_name, + config=config, ) if isinstance(sub_prop, PropertyError): return PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data), schemas @@ -365,12 +373,12 @@ def build_union_property( def build_list_property( - *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str + *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[ListProperty[Any], PropertyError], Schemas]: if data.items is None: return PropertyError(data=data, detail="type array must have items defined"), schemas inner_prop, schemas = property_from_data( - name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name=parent_name + name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name=parent_name, config=config ) if isinstance(inner_prop, PropertyError): return PropertyError(data=inner_prop.data, detail=f"invalid data in items of array {name}"), schemas @@ -392,6 +400,7 @@ def _property_from_data( data: Union[oai.Reference, oai.Schema], schemas: Schemas, parent_name: str, + config: Config, ) -> Tuple[Union[Property, PropertyError], Schemas]: """ Generate a Property from the OpenAPI dictionary representation of it """ name = utils.remove_string_escapes(name) @@ -408,10 +417,18 @@ def _property_from_data( return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas if data.enum: return build_enum_property( - data=data, name=name, required=required, schemas=schemas, enum=data.enum, parent_name=parent_name + data=data, + name=name, + required=required, + schemas=schemas, + enum=data.enum, + parent_name=parent_name, + config=config, ) if data.anyOf or data.oneOf: - return build_union_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name) + return build_union_property( + data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config + ) if data.type == "string": return _string_based_property(name=name, required=required, data=data), schemas elif data.type == "number": @@ -445,9 +462,13 @@ def _property_from_data( schemas, ) elif data.type == "array": - return build_list_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name) + return build_list_property( + data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config + ) elif data.type == "object" or data.allOf: - return build_model_property(data=data, name=name, schemas=schemas, required=required, parent_name=parent_name) + return build_model_property( + data=data, name=name, schemas=schemas, required=required, parent_name=parent_name, config=config + ) elif not data.type: return NoneProperty(name=name, required=required, nullable=False, default=None), schemas return PropertyError(data=data, detail=f"unknown type {data.type}"), schemas @@ -460,14 +481,19 @@ def property_from_data( data: Union[oai.Reference, oai.Schema], schemas: Schemas, parent_name: str, + config: Config, ) -> Tuple[Union[Property, PropertyError], Schemas]: try: - return _property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name=parent_name) + return _property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name=parent_name, config=config + ) except ValidationError: return PropertyError(detail="Failed to validate default value", data=data), schemas -def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas) -> Schemas: +def build_schemas( + *, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config +) -> Schemas: """ Get a list of Schemas from an OpenAPI dict """ to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() processing = True @@ -488,7 +514,7 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]], sc next_round.append((name, data)) errors.append(PropertyError(detail=ref_path.detail, data=data)) continue - schemas_or_err = update_schemas_with_data(ref_path, data, schemas) + schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, PropertyError): next_round.append((name, data)) errors.append(schemas_or_err) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 2e400cb6d..83d824d8a 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -7,9 +7,8 @@ from ... import schema as oai from ... import utils from ..errors import ParseError, PropertyError -from . import parse_reference_path from .property import Property -from .schemas import Class, Schemas +from .schemas import Class, Schemas, parse_reference_path @attr.s(auto_attribs=True, frozen=True) @@ -68,7 +67,9 @@ class _PropertyData(NamedTuple): schemas: Schemas -def _process_properties(*, data: oai.Schema, schemas: Schemas, class_name: str) -> Union[_PropertyData, PropertyError]: +def _process_properties( + *, data: oai.Schema, schemas: Schemas, class_name: str, config: Config +) -> Union[_PropertyData, PropertyError]: from . import property_from_data properties: Dict[str, Property] = {} @@ -108,7 +109,7 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: for key, value in unprocessed_props.items(): prop_required = key in required_set prop_or_error, schemas = property_from_data( - name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name + name=key, required=prop_required, data=value, schemas=schemas, parent_name=class_name, config=config ) if isinstance(prop_or_error, Property): prop_or_error = _check_existing(prop_or_error) @@ -135,7 +136,11 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: def _get_additional_properties( - *, schema_additional: Union[None, bool, oai.Reference, oai.Schema], schemas: Schemas, class_name: str + *, + schema_additional: Union[None, bool, oai.Reference, oai.Schema], + schemas: Schemas, + class_name: str, + config: Config, ) -> Tuple[Union[bool, Property, PropertyError], Schemas]: from . import property_from_data @@ -155,6 +160,7 @@ def _get_additional_properties( data=schema_additional, schemas=schemas, parent_name=class_name, + config=config, ) return additional_properties, schemas @@ -179,13 +185,13 @@ def build_model_property( class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}" class_info = Class.from_string(string=class_name, config=config) - property_data = _process_properties(data=data, schemas=schemas, class_name=class_name) + property_data = _process_properties(data=data, schemas=schemas, class_name=class_name, config=config) if isinstance(property_data, PropertyError): return property_data, schemas schemas = property_data.schemas additional_properties, schemas = _get_additional_properties( - schema_additional=data.additionalProperties, schemas=schemas, class_name=class_name + schema_additional=data.additionalProperties, schemas=schemas, class_name=class_name, config=config ) if isinstance(additional_properties, Property): property_data.relative_imports.update(additional_properties.get_imports(prefix="..")) diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 19a794d8d..73adb6b57 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -1,4 +1,4 @@ -__all__ = ["Class", "Schemas", "parse_reference_path"] +__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with_data"] from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast from urllib.parse import urlparse @@ -25,8 +25,8 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError]: parsed = urlparse(ref_path_raw) - if parsed.scheme is not None or parsed.path is not None: - return ParseError(detail="Remote references are not supported yet.") + if parsed.scheme or parsed.path: + return ParseError(detail=f"Remote references such as {ref_path_raw} are not supported yet.") return cast(_ReferencePath, parsed.fragment) @@ -41,11 +41,13 @@ def from_string(*, string: str, config: Config) -> "Class": """ Get a Class from an arbitrary string """ class_name = string.split("/")[-1] # Get rid of ref path stuff class_name = utils.pascal_case(class_name) + module_name = utils.snake_case(class_name) + override = config.class_overrides.get(class_name) + if override is not None: + class_name = override.class_name or class_name + module_name = override.module_name or module_name - if class_name in config.class_overrides: - return config.class_overrides[class_name] - - return Class(name=cast(_ClassName, class_name), module_name=utils.snake_case(class_name)) + return Class(name=cast(_ClassName, class_name), module_name=module_name) @attr.s(auto_attribs=True, frozen=True) @@ -58,14 +60,14 @@ class Schemas: def update_schemas_with_data( - ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config + *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config ) -> Union[Schemas, PropertyError]: from . import build_enum_property, build_model_property prop: Union[PropertyError, ModelProperty, EnumProperty] if data.enum is not None: prop, schemas = build_enum_property( - data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None + data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None, config=config ) else: prop, schemas = build_model_property( diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py index c99a453d1..a7d5e89d7 100644 --- a/openapi_python_client/parser/responses.py +++ b/openapi_python_client/parser/responses.py @@ -74,6 +74,7 @@ def response_from_data( data=schema_data, schemas=schemas, parent_name=parent_name, + config=config, ) if isinstance(prop, PropertyError): diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index 705985aab..45fe6d7c3 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -99,12 +99,12 @@ client: Client, {{ parameter.to_string() }}, {% endfor %} {# Form data if any #} -{% if endpoint.form_body_reference %} -form_data: {{ endpoint.form_body_reference.class_name }}, +{% if endpoint.form_body_class %} +form_data: {{ endpoint.form_body_class.name }}, {% endif %} {# Multipart data if any #} -{% if endpoint.multipart_body_reference %} -multipart_data: {{ endpoint.multipart_body_reference.class_name }}, +{% if endpoint.multipart_body_class %} +multipart_data: {{ endpoint.multipart_body_class.class_name }}, {% endif %} {# JSON body if any #} {% if endpoint.json_body %} @@ -129,10 +129,10 @@ client=client, {% for parameter in endpoint.path_parameters %} {{ parameter.python_name }}={{ parameter.python_name }}, {% endfor %} -{% if endpoint.form_body_reference %} +{% if endpoint.form_body_class %} form_data=form_data, {% endif %} -{% if endpoint.multipart_body_reference %} +{% if endpoint.multipart_body_class %} multipart_data=multipart_data, {% endif %} {% if endpoint.json_body %} diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 5b1c434bc..a4e7d346b 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -4,7 +4,7 @@ import httpx from attr import asdict from ...client import AuthenticatedClient, Client -from ...types import Response, UNSET{% if endpoint.multipart_body_reference %}, File {% endif %} +from ...types import Response, UNSET{% if endpoint.multipart_body_class %}, File {% endif %} {% for relative in endpoint.relative_imports %} {{ relative }} @@ -36,7 +36,7 @@ def _get_kwargs( {{ json_body(endpoint) | indent(4) }} - {% if endpoint.multipart_body_reference %} + {% if endpoint.multipart_body_class %} files = {} data = {} for key, value in multipart_data.to_dict().items(): @@ -51,9 +51,9 @@ def _get_kwargs( "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), - {% if endpoint.form_body_reference %} + {% if endpoint.form_body_class %} "data": asdict(form_data), - {% elif endpoint.multipart_body_reference %} + {% elif endpoint.multipart_body_class %} "files": files, "data": data, {% elif endpoint.json_body %} diff --git a/openapi_python_client/templates/int_enum.py.jinja b/openapi_python_client/templates/int_enum.py.jinja index 18d6066ae..a508f1c8e 100644 --- a/openapi_python_client/templates/int_enum.py.jinja +++ b/openapi_python_client/templates/int_enum.py.jinja @@ -1,6 +1,6 @@ from enum import IntEnum -class {{ enum.reference.class_name }}(IntEnum): +class {{ enum.class_info.name }}(IntEnum): {% for key, value in enum.values.items() %} {{ key }} = {{ value }} {% endfor %} diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja index c286489e3..8541db32d 100644 --- a/openapi_python_client/templates/model.py.jinja +++ b/openapi_python_client/templates/model.py.jinja @@ -18,10 +18,13 @@ from ..types import UNSET, Unset {% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string() %} {% endif %} -T = TypeVar("T", bound="{{ model.reference.class_name }}") +{% set class_name = model.class_info.name %} +{% set module_name = model.class_info.module_name %} + +T = TypeVar("T", bound="{{ class_name }}") @attr.s(auto_attribs=True) -class {{ model.reference.class_name }}: +class {{ class_name }}: """ {{ model.description }} """ {% for property in model.required_properties + model.optional_properties %} {% if property.default is none and property.required %} @@ -91,7 +94,7 @@ class {{ model.reference.class_name }}: {% endif %} {% endfor %} - {{model.reference.module_name}} = cls( + {{ module_name }} = cls( {% for property in model.required_properties + model.optional_properties %} {{ property.python_name }}={{ property.python_name }}, {% endfor %} @@ -105,12 +108,12 @@ class {{ model.reference.class_name }}: {{ construct(model.additional_properties, "prop_dict") | indent(12) }} additional_properties[prop_name] = {{ model.additional_properties.python_name }} - {{model.reference.module_name}}.additional_properties = additional_properties + {{ module_name }}.additional_properties = additional_properties {% else %} - {{model.reference.module_name}}.additional_properties = d + {{ module_name }}.additional_properties = d {% endif %} {% endif %} - return {{model.reference.module_name}} + return {{ module_name }} {% if model.additional_properties %} @property diff --git a/openapi_python_client/templates/property_templates/enum_property.py.jinja b/openapi_python_client/templates/property_templates/enum_property.py.jinja index 4916927bd..9dd051b38 100644 --- a/openapi_python_client/templates/property_templates/enum_property.py.jinja +++ b/openapi_python_client/templates/property_templates/enum_property.py.jinja @@ -1,5 +1,5 @@ {% macro construct_function(property, source) %} -{{ property.reference.class_name }}({{ source }}) +{{ property.class_info.name }}({{ source }}) {% endmacro %} {% from "property_templates/property_macros.py.jinja" import construct_template %} diff --git a/openapi_python_client/templates/property_templates/model_property.py.jinja b/openapi_python_client/templates/property_templates/model_property.py.jinja index 4e394cc34..2772918cf 100644 --- a/openapi_python_client/templates/property_templates/model_property.py.jinja +++ b/openapi_python_client/templates/property_templates/model_property.py.jinja @@ -1,5 +1,5 @@ {% macro construct_function(property, source) %} -{{ property.reference.class_name }}.from_dict({{ source }}) +{{ property.class_info.name }}.from_dict({{ source }}) {% endmacro %} {% from "property_templates/property_macros.py.jinja" import construct_template %} diff --git a/openapi_python_client/templates/str_enum.py.jinja b/openapi_python_client/templates/str_enum.py.jinja index 74dcd1de2..4a9ab384a 100644 --- a/openapi_python_client/templates/str_enum.py.jinja +++ b/openapi_python_client/templates/str_enum.py.jinja @@ -1,6 +1,6 @@ from enum import Enum -class {{ enum.reference.class_name }}(str, Enum): +class {{ enum.class_info.name }}(str, Enum): {% for key, value in enum.values.items() %} {{ key }} = "{{ value }}" {% endfor %} diff --git a/tests/test___init__.py b/tests/test___init__.py index 3d2547d89..342928b94 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -5,7 +5,7 @@ import pytest import yaml -from openapi_python_client import GeneratorError +from openapi_python_client import Config, GeneratorError def test__get_project_for_url_or_path(mocker): @@ -16,15 +16,16 @@ def test__get_project_for_url_or_path(mocker): _Project = mocker.patch("openapi_python_client.Project") url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) _get_document.assert_called_once_with(url=url, path=path) - from_dict.assert_called_once_with(data_dict) + from_dict.assert_called_once_with(data_dict, config=config) _Project.assert_called_once_with( - openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config ) assert project == _Project.return_value @@ -37,13 +38,14 @@ def test__get_project_for_url_or_path_generator_error(mocker): _Project = mocker.patch("openapi_python_client.Project") url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) _get_document.assert_called_once_with(url=url, path=path) - from_dict.assert_called_once_with(data_dict) + from_dict.assert_called_once_with(data_dict, config=config) _Project.assert_not_called() assert project == error @@ -58,7 +60,7 @@ def test__get_project_for_url_or_path_document_error(mocker): from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=Config()) _get_document.assert_called_once_with(url=url, path=path) from_dict.assert_not_called() @@ -72,13 +74,14 @@ def test_create_new_client(mocker): ) url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, create_new_client - result = create_new_client(url=url, path=path, meta=MetaType.POETRY) + result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config) _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config ) project.build.assert_called_once() assert result == project.build.return_value @@ -91,13 +94,14 @@ def test_create_new_client_project_error(mocker): ) url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, create_new_client - result = create_new_client(url=url, path=path, meta=MetaType.POETRY) + result = create_new_client(url=url, path=path, meta=MetaType.POETRY, config=config) _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config ) assert result == [error] @@ -112,7 +116,7 @@ def test_update_existing_client(mocker): from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY) + result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config()) _get_project_for_url_or_path.assert_called_once_with( url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" @@ -131,7 +135,7 @@ def test_update_existing_client_project_error(mocker): from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY) + result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config()) _get_project_for_url_or_path.assert_called_once_with( url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" @@ -225,13 +229,21 @@ def test__get_document_bad_yaml(self, mocker): assert result == GeneratorError(header="Invalid YAML from provided source") +def make_project(): + from unittest.mock import MagicMock + + from openapi_python_client import MetaType, Project + + return Project(openapi=MagicMock(title="My Test API"), meta=MetaType.POETRY, config=Config()) + + class TestProject: def test___init__(self, mocker): openapi = mocker.MagicMock(title="My Test API") from openapi_python_client import MetaType, Project - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) assert project.openapi == openapi assert project.project_name == "my-test-api-client" @@ -246,7 +258,7 @@ def test___init___no_meta(self, mocker): from openapi_python_client import MetaType, Project - project = Project(openapi=openapi, meta=MetaType.NONE) + project = Project(openapi=openapi, meta=MetaType.NONE, config=Config()) assert project.openapi == openapi assert project.project_name == "my-test-api-client" @@ -261,22 +273,22 @@ def test_project_and_package_name_overrides(self, mocker): from openapi_python_client import MetaType, Project - Project.project_name_override = "my-special-project-name" - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = Project( + openapi=openapi, meta=MetaType.POETRY, config=Config(project_name_override="my-special-project-name") + ) assert project.project_name == "my-special-project-name" assert project.package_name == "my_special_project_name" - Project.package_name_override = "my_special_package_name" - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = Project( + openapi=openapi, meta=MetaType.POETRY, config=Config(package_name_override="my_special_package_name") + ) assert project.project_name == "my-special-project-name" assert project.package_name == "my_special_package_name" def test_build(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project.project_dir = mocker.MagicMock() project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() @@ -298,9 +310,7 @@ def test_build(self, mocker): assert result == project._get_errors.return_value def test_build_no_meta(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE) + project = make_project() project.project_dir = mocker.MagicMock() project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() @@ -315,9 +325,7 @@ def test_build_no_meta(self, mocker): project.project_dir.mkdir.assert_not_called() def test_build_file_exists(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project.project_dir = mocker.MagicMock() project.project_dir.mkdir.side_effect = FileExistsError result = project.build() @@ -327,10 +335,10 @@ def test_build_file_exists(self, mocker): assert result == [GeneratorError(detail="Directory already exists. Delete it or use the update command.")] def test_update(self, mocker): - from openapi_python_client import MetaType, Project, shutil + from openapi_python_client import shutil rmtree = mocker.patch.object(shutil, "rmtree") - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() project._build_models = mocker.MagicMock() @@ -350,9 +358,7 @@ def test_update(self, mocker): assert result == project._get_errors.return_value def test_update_missing_dir(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project.package_dir = mocker.MagicMock() project.package_dir.is_dir.return_value = False project._build_models = mocker.MagicMock() @@ -364,9 +370,7 @@ def test_update_missing_dir(self, mocker): project._build_models.assert_not_called() def test__build_metadata_poetry(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project._build_pyproject_toml = mocker.MagicMock() project.project_dir = mocker.MagicMock() readme_path = mocker.MagicMock(autospec=pathlib.Path) @@ -400,9 +404,7 @@ def test__build_metadata_poetry(self, mocker): project._build_pyproject_toml.assert_called_once_with(use_poetry=True) def test__build_metadata_setup(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP) + project = make_project() project._build_pyproject_toml = mocker.MagicMock() project._build_setup_py = mocker.MagicMock() project.project_dir = mocker.MagicMock() @@ -438,9 +440,7 @@ def test__build_metadata_setup(self, mocker): project._build_setup_py.assert_called_once() def test__build_metadata_none(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE) + project = make_project() project._build_pyproject_toml = mocker.MagicMock() project._build_metadata() @@ -449,9 +449,7 @@ def test__build_metadata_none(self, mocker): @pytest.mark.parametrize("use_poetry", [(True,), (False,)]) def test__build_pyproject_toml(self, mocker, use_poetry): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project = make_project() project.project_dir = mocker.MagicMock() pyproject_path = mocker.MagicMock(autospec=pathlib.Path) paths = { @@ -480,9 +478,7 @@ def test__build_pyproject_toml(self, mocker, use_poetry): pyproject_path.write_text.assert_called_once_with(pyproject_template.render(), encoding="utf-8") def test__build_setup_py(self, mocker): - from openapi_python_client import MetaType, Project - - project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP) + project = make_project() project.project_dir = mocker.MagicMock() setup_path = mocker.MagicMock(autospec=pathlib.Path) paths = { @@ -513,11 +509,8 @@ def test__build_setup_py(self, mocker): def test__reformat(mocker): import subprocess - from openapi_python_client import GeneratorData, MetaType, Project - sub_run = mocker.patch("subprocess.run") - openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = make_project() project.project_dir = mocker.MagicMock(autospec=pathlib.Path) project._reformat() @@ -556,30 +549,27 @@ def test__get_errors(mocker): }, errors=[3], ) - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) assert project._get_errors() == [1, 2, 3] def test__custom_templates(mocker): from openapi_python_client import GeneratorData, MetaType, Project - from openapi_python_client.parser.openapi import EndpointCollection, Schemas openapi = mocker.MagicMock( autospec=GeneratorData, title="My Test API", - endpoint_collections_by_tag={ - "default": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[1]), - "other": mocker.MagicMock(autospec=EndpointCollection, parse_errors=[2]), - }, - schemas=mocker.MagicMock(autospec=Schemas, errors=[3]), ) - project = Project(openapi=openapi, meta=MetaType.POETRY) + project = Project(openapi=openapi, meta=MetaType.POETRY, config=Config()) assert isinstance(project.env.loader, jinja2.PackageLoader) project = Project( - openapi=openapi, custom_template_path="../end_to_end_tests/test_custom_templates", meta=MetaType.POETRY + openapi=openapi, + custom_template_path="../end_to_end_tests/test_custom_templates", + meta=MetaType.POETRY, + config=Config(), ) assert isinstance(project.env.loader, jinja2.ChoiceLoader) assert len(project.env.loader.loaders) == 2 diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 9a856c190..ab35e514d 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -5,7 +5,12 @@ import openapi_python_client.schema as oai from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import DateTimeProperty, ModelProperty, StringProperty -from openapi_python_client.parser.reference import Reference + + +def get_class(): + from openapi_python_client.parser.properties import Class + + return Class(name="MyClass", module_name="my_module") @pytest.mark.parametrize( @@ -23,14 +28,14 @@ ], ) def test_get_type_string(no_optional, nullable, required, json, expected): - from openapi_python_client.parser.properties import ModelProperty, Reference + from openapi_python_client.parser.properties import ModelProperty prop = ModelProperty( name="prop", required=required, nullable=nullable, default=None, - reference=Reference(class_name="MyClass", module_name="my_module"), + class_info=get_class(), description="", optional_properties=[], required_properties=[], @@ -42,14 +47,14 @@ def test_get_type_string(no_optional, nullable, required, json, expected): def test_get_imports(): - from openapi_python_client.parser.properties import ModelProperty, Reference + from openapi_python_client.parser.properties import ModelProperty prop = ModelProperty( name="prop", required=False, nullable=True, default=None, - reference=Reference(class_name="MyClass", module_name="my_module"), + class_info=get_class(), description="", optional_properties=[], required_properties=[], @@ -99,7 +104,7 @@ def test_additional_schemas(self, additional_properties_schema, expected_additio assert model.additional_properties == expected_additional_properties def test_happy_path(self): - from openapi_python_client.parser.properties import Schemas, build_model_property + from openapi_python_client.parser.properties import Class, Schemas, build_model_property data = oai.Schema.construct( required=["req"], @@ -131,7 +136,7 @@ def test_happy_path(self): required=True, nullable=False, default=None, - reference=Reference(class_name="ParentMyModel", module_name="parent_my_model"), + class_info=Class(name="ParentMyModel", module_name="parent_my_model"), required_properties=[StringProperty(name="req", required=True, nullable=False, default=None)], optional_properties=[DateTimeProperty(name="opt", required=False, nullable=False, default=None)], description=data.description, @@ -209,7 +214,7 @@ def test_bad_additional_props_return_error(self): @pytest.fixture def model_property() -> Callable[..., ModelProperty]: - from openapi_python_client.parser.reference import Reference + from openapi_python_client.parser.properties import Class def _factory(**kwargs): kwargs = { @@ -218,7 +223,7 @@ def _factory(**kwargs): "required": True, "nullable": True, "default": None, - "reference": Reference(class_name="", module_name=""), + "reference": Class(name="", module_name=""), "required_properties": [], "optional_properties": [], "relative_imports": set(), From 2bbbed642f06923cd7befc7cdbe262e3cfa3e68d Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sat, 27 Mar 2021 17:04:36 -0600 Subject: [PATCH 04/11] test: Fix more broken tests --- .../parser/properties/schemas.py | 12 +- tests/conftest.py | 58 ++++ tests/test___init__.py | 58 ++-- tests/test_cli.py | 61 ++-- tests/test_config.py | 63 ++-- tests/test_parser/test_openapi.py | 300 ++++++++++++------ .../test_properties/test_model_property.py | 49 +-- .../test_properties/test_schemas.py | 34 ++ tests/test_parser/test_reference.py | 16 - tests/test_parser/test_responses.py | 16 +- 10 files changed, 425 insertions(+), 242 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_parser/test_properties/test_schemas.py delete mode 100644 tests/test_parser/test_reference.py diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 73adb6b57..8f4b43129 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -41,11 +41,15 @@ def from_string(*, string: str, config: Config) -> "Class": """ Get a Class from an arbitrary string """ class_name = string.split("/")[-1] # Get rid of ref path stuff class_name = utils.pascal_case(class_name) - module_name = utils.snake_case(class_name) override = config.class_overrides.get(class_name) - if override is not None: - class_name = override.class_name or class_name - module_name = override.module_name or module_name + + if override is not None and override.class_name is not None: + class_name = override.class_name + + if override is not None and override.module_name is not None: + module_name = override.module_name + else: + module_name = utils.snake_case(class_name) return Class(name=cast(_ClassName, class_name), module_name=module_name) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..d432982ae --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,58 @@ +from typing import Callable + +import pytest + +from openapi_python_client.parser.properties import EnumProperty, ModelProperty + + +@pytest.fixture +def model_property_factory() -> Callable[..., ModelProperty]: + """ + This fixture surfaces in the test as a function which manufactures ModelProperties with defaults. + + You can pass the same params into this as the ModelProperty constructor to override defaults. + """ + from openapi_python_client.parser.properties import Class + + def _factory(**kwargs): + kwargs = { + "name": "", + "description": "", + "required": True, + "nullable": True, + "default": None, + "class_info": Class(name="", module_name=""), + "required_properties": [], + "optional_properties": [], + "relative_imports": set(), + "additional_properties": False, + **kwargs, + } + return ModelProperty(**kwargs) + + return _factory + + +@pytest.fixture +def enum_property_factory() -> Callable[..., EnumProperty]: + """ + This fixture surfaces in the test as a function which manufactures EnumProperties with defaults. + + You can pass the same params into this as the EnumProerty constructor to override defaults. + """ + from openapi_python_client.parser.properties import Class + + def _factory(**kwargs): + kwargs = { + "name": "test", + "required": True, + "nullable": True, + "default": None, + "class_info": Class(name="", module_name=""), + "values": {}, + "value_type": str, + **kwargs, + } + return EnumProperty(**kwargs) + + return _factory diff --git a/tests/test___init__.py b/tests/test___init__.py index 342928b94..0579e83f0 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -113,13 +113,14 @@ def test_update_existing_client(mocker): ) url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config()) + result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config) _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config ) project.update.assert_called_once() assert result == project.update.return_value @@ -132,13 +133,14 @@ def test_update_existing_client_project_error(mocker): ) url = mocker.MagicMock() path = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=Config()) + result = update_existing_client(url=url, path=path, meta=MetaType.POETRY, config=config) _get_project_for_url_or_path.assert_called_once_with( - url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config ) assert result == [error] @@ -229,12 +231,14 @@ def test__get_document_bad_yaml(self, mocker): assert result == GeneratorError(header="Invalid YAML from provided source") -def make_project(): +def make_project(**kwargs): from unittest.mock import MagicMock from openapi_python_client import MetaType, Project - return Project(openapi=MagicMock(title="My Test API"), meta=MetaType.POETRY, config=Config()) + kwargs = {"openapi": MagicMock(title="My Test API"), "meta": MetaType.POETRY, "config": Config(), **kwargs} + + return Project(**kwargs) class TestProject: @@ -261,31 +265,35 @@ def test___init___no_meta(self, mocker): project = Project(openapi=openapi, meta=MetaType.NONE, config=Config()) assert project.openapi == openapi - assert project.project_name == "my-test-api-client" - assert project.package_name == "my_test_api_client" assert project.package_description == "A client library for accessing My Test API" assert project.meta == MetaType.NONE assert project.project_dir == pathlib.Path.cwd() assert project.package_dir == pathlib.Path.cwd() / project.package_name - def test_project_and_package_name_overrides(self, mocker): + @pytest.mark.parametrize( + "project_override, package_override, expected_project_name, expected_package_name", + ( + (None, None, "my-test-api-client", "my_test_api_client"), + ("custom-project", None, "custom-project", "custom_project"), + ("custom-project", "custom_package", "custom-project", "custom_package"), + (None, "custom_package", "my-test-api-client", "custom_package"), + ), + ) + def test_project_and_package_names( + self, mocker, project_override, package_override, expected_project_name, expected_package_name + ): openapi = mocker.MagicMock(title="My Test API") from openapi_python_client import MetaType, Project project = Project( - openapi=openapi, meta=MetaType.POETRY, config=Config(project_name_override="my-special-project-name") - ) - - assert project.project_name == "my-special-project-name" - assert project.package_name == "my_special_project_name" - - project = Project( - openapi=openapi, meta=MetaType.POETRY, config=Config(package_name_override="my_special_package_name") + openapi=openapi, + meta=MetaType.POETRY, + config=Config(project_name_override=project_override, package_name_override=package_override), ) - assert project.project_name == "my-special-project-name" - assert project.package_name == "my_special_package_name" + assert project.project_name == expected_project_name + assert project.package_name == expected_package_name def test_build(self, mocker): project = make_project() @@ -310,7 +318,9 @@ def test_build(self, mocker): assert result == project._get_errors.return_value def test_build_no_meta(self, mocker): - project = make_project() + from openapi_python_client import MetaType + + project = make_project(meta=MetaType.NONE) project.project_dir = mocker.MagicMock() project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() @@ -404,7 +414,9 @@ def test__build_metadata_poetry(self, mocker): project._build_pyproject_toml.assert_called_once_with(use_poetry=True) def test__build_metadata_setup(self, mocker): - project = make_project() + from openapi_python_client import MetaType + + project = make_project(meta=MetaType.SETUP) project._build_pyproject_toml = mocker.MagicMock() project._build_setup_py = mocker.MagicMock() project.project_dir = mocker.MagicMock() @@ -440,7 +452,9 @@ def test__build_metadata_setup(self, mocker): project._build_setup_py.assert_called_once() def test__build_metadata_none(self, mocker): - project = make_project() + from openapi_python_client import MetaType + + project = make_project(meta=MetaType.NONE) project._build_pyproject_toml = mocker.MagicMock() project._build_metadata() diff --git a/tests/test_cli.py b/tests/test_cli.py index d5bc2c69e..4954aea25 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -35,12 +35,12 @@ def test_config_arg(mocker, _create_new_client): result = runner.invoke( app, - [f"--config={config_path}", "generate", f"--path={path}", f"--file-encoding={file_encoding}"], + ["generate", f"--config={config_path}", f"--path={path}", f"--file-encoding={file_encoding}"], catch_exceptions=False, ) assert result.exit_code == 0 - load_config.assert_called_once_with(path=Path(config_path)) + load_config.assert_called_once_with(Path(config_path)) _create_new_client.assert_called_once_with( url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" ) @@ -55,11 +55,11 @@ def test_bad_config(mocker, _create_new_client): config_path = "config/path" path = "cool/path" - result = runner.invoke(app, [f"--config={config_path}", "generate", f"--path={path}"]) + result = runner.invoke(app, ["generate", f"--config={config_path}", f"--path={path}"]) assert result.exit_code == 2 assert "Unable to parse config" in result.stdout - load_config.assert_called_once_with(path=Path(config_path)) + load_config.assert_called_once_with(Path(config_path)) _create_new_client.assert_not_called() @@ -82,47 +82,62 @@ def test_generate_url_and_path(self, _create_new_client): def test_generate_url(self, _create_new_client): url = "cool.url" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["generate", f"--url={url}"]) assert result.exit_code == 0 _create_new_client.assert_called_once_with( - url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config() ) def test_generate_path(self, _create_new_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["generate", f"--path={path}"]) assert result.exit_code == 0 _create_new_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.POETRY, + file_encoding="utf-8", + config=Config(), ) def test_generate_meta(self, _create_new_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"]) assert result.exit_code == 0 _create_new_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.NONE, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.NONE, + file_encoding="utf-8", + config=Config(), ) def test_generate_encoding(self, _create_new_client): path = "cool/path" file_encoding = "utf-8" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["generate", f"--path={path}", f"--file-encoding={file_encoding}"]) assert result.exit_code == 0 _create_new_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.POETRY, + file_encoding="utf-8", + config=Config(), ) def test_generate_encoding_errors(self, _create_new_client): @@ -198,36 +213,46 @@ def test_update_url_and_path(self, _update_existing_client): def test_update_url(self, _update_existing_client): url = "cool.url" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["update", f"--url={url}"]) assert result.exit_code == 0 _update_existing_client.assert_called_once_with( - url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=url, path=None, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=Config() ) def test_update_path(self, _update_existing_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["update", f"--path={path}"]) assert result.exit_code == 0 _update_existing_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.POETRY, + file_encoding="utf-8", + config=Config(), ) def test_update_encoding(self, _update_existing_client): path = "cool/path" file_encoding = "utf-8" - from openapi_python_client.cli import MetaType, app + from openapi_python_client.cli import MetaType, app, Config result = runner.invoke(app, ["update", f"--path={path}", f"--file-encoding={file_encoding}"]) assert result.exit_code == 0 _update_existing_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.POETRY, + file_encoding="utf-8", + config=Config(), ) def test_update_encoding_errors(self, _update_existing_client): diff --git a/tests/test_config.py b/tests/test_config.py index 0ed3ace75..d0cdbcd2b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,46 +4,29 @@ def test_load_from_path(mocker): - safe_load = mocker.patch("yaml.safe_load", return_value={}) + from openapi_python_client import utils + + override1 = {"class_name": "ExampleClass", "module_name": "example_module"} + override2 = {"class_name": "DifferentClass", "module_name": "different_module"} + safe_load = mocker.patch( + "yaml.safe_load", + return_value={ + "field_prefix": "blah", + "class_overrides": {"Class1": override1, "Class2": override2}, + "project_name_override": "project-name", + "package_name_override": "package_name", + "package_version_override": "package_version", + }, + ) fake_path = mocker.MagicMock(autospec=pathlib.Path) - load_config = mocker.patch("openapi_python_client.config.Config.load_config") - Config.load_from_path(fake_path) + config = Config.load_from_path(fake_path) safe_load.assert_called() - load_config.assert_called() - - -class TestLoadConfig: - def test_class_overrides(self): - from openapi_python_client.parser import reference - - override1 = {"class_name": "ExampleClass", "module_name": "example_module"} - override2 = {"class_name": "DifferentClass", "module_name": "different_module"} - config = Config(class_overrides={"Class1": override1, "Class2": override2}) - config.load_config() - - assert reference.class_overrides["Class1"] == reference.Reference(**override1) - assert reference.class_overrides["Class2"] == reference.Reference(**override2) - - def test_project_and_package_name_and_package_version_overrides(self): - config = Config( - project_name_override="project-name", - package_name_override="package_name", - package_version_override="package_version", - ) - config.load_config() - - from openapi_python_client import Project - - assert Project.project_name_override == "project-name" - assert Project.package_name_override == "package_name" - assert Project.package_version_override == "package_version" - - def test_field_prefix(self): - Config(field_prefix="blah").load_config() - - from openapi_python_client import utils - - assert utils.FIELD_PREFIX == "blah" - - utils.FIELD_PREFIX = "field_" + assert utils.FIELD_PREFIX == "blah" + assert config.class_overrides["Class1"] == override1 + assert config.class_overrides["Class2"] == override2 + assert config.project_name_override == "project-name" + assert config.package_name_override == "package_name" + assert config.package_version_override == "package_version" + + utils.FIELD_PREFIX = "field_" diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index 1bbaa3e6e..b9124914e 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock + import openapi_python_client.schema as oai from openapi_python_client import GeneratorError from openapi_python_client.parser.errors import ParseError @@ -6,51 +8,58 @@ class TestGeneratorData: - def test_from_dict(self, mocker): + def test_from_dict(self, mocker, model_property_factory, enum_property_factory): + from openapi_python_client.parser.properties import Schemas + build_schemas = mocker.patch(f"{MODULE_NAME}.build_schemas") EndpointCollection = mocker.patch(f"{MODULE_NAME}.EndpointCollection") schemas = mocker.MagicMock() + schemas.classes_by_name = { + "Model": model_property_factory(), + "Enum": enum_property_factory(), + } endpoints_collections_by_tag = mocker.MagicMock() EndpointCollection.from_data.return_value = (endpoints_collections_by_tag, schemas) OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI") openapi = OpenAPI.parse_obj.return_value openapi.openapi = mocker.MagicMock(major=3) - + config = mocker.MagicMock() in_dict = mocker.MagicMock() from openapi_python_client.parser.openapi import GeneratorData - generator_data = GeneratorData.from_dict(in_dict) + generator_data = GeneratorData.from_dict(in_dict, config=config) OpenAPI.parse_obj.assert_called_once_with(in_dict) - build_schemas.assert_called_once_with(components=openapi.components.schemas) - EndpointCollection.from_data.assert_called_once_with(data=openapi.paths, schemas=build_schemas.return_value) - assert generator_data == GeneratorData( - title=openapi.info.title, - description=openapi.info.description, - version=openapi.info.version, - endpoint_collections_by_tag=endpoints_collections_by_tag, - errors=schemas.errors, - models=schemas.models, - enums=schemas.enums, - ) + build_schemas.assert_called_once_with(components=openapi.components.schemas, config=config, schemas=Schemas()) + EndpointCollection.from_data.assert_called_once_with( + data=openapi.paths, schemas=build_schemas.return_value, config=config + ) + assert generator_data.title == openapi.info.title + assert generator_data.description == openapi.info.description + assert generator_data.version == openapi.info.version + assert generator_data.endpoint_collections_by_tag == endpoints_collections_by_tag + assert generator_data.errors == schemas.errors + assert list(generator_data.models) == [schemas.classes_by_name["Model"]] + assert list(generator_data.enums) == [schemas.classes_by_name["Enum"]] # Test no components openapi.components = None build_schemas.reset_mock() - GeneratorData.from_dict(in_dict) + GeneratorData.from_dict(in_dict, config=config) build_schemas.assert_not_called() def test_from_dict_invalid_schema(self, mocker): Schemas = mocker.patch(f"{MODULE_NAME}.Schemas") + config = mocker.MagicMock() in_dict = {} from openapi_python_client.parser.openapi import GeneratorData - generator_data = GeneratorData.from_dict(in_dict) + generator_data = GeneratorData.from_dict(in_dict, config=config) assert generator_data == GeneratorError( header="Failed to parse OpenAPI document", @@ -69,12 +78,13 @@ def test_from_dict_invalid_schema(self, mocker): def test_swagger_document_invalid_schema(self, mocker): Schemas = mocker.patch(f"{MODULE_NAME}.Schemas") + config = mocker.MagicMock() in_dict = {"swagger": "2.0"} from openapi_python_client.parser.openapi import GeneratorData - generator_data = GeneratorData.from_dict(in_dict) + generator_data = GeneratorData.from_dict(in_dict, config=config) assert generator_data == GeneratorError( header="Failed to parse OpenAPI document", @@ -94,16 +104,15 @@ def test_swagger_document_invalid_schema(self, mocker): def test_from_dict_invalid_version(self, mocker): Schemas = mocker.patch(f"{MODULE_NAME}.Schemas") - OpenAPI = mocker.patch(f"{MODULE_NAME}.oai.OpenAPI") openapi = OpenAPI.parse_obj.return_value openapi.openapi = oai.SemVer("2.1.3") - in_dict = mocker.MagicMock() + config = mocker.MagicMock() from openapi_python_client.parser.openapi import GeneratorData - generator_data = GeneratorData.from_dict(in_dict) + generator_data = GeneratorData.from_dict(in_dict, config=config) assert generator_data == GeneratorError( header="openapi-python-client only supports OpenAPI 3.x", @@ -123,21 +132,23 @@ def test_parse_request_form_body(self, mocker): ) } ) - from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") + from_string = mocker.patch(f"{MODULE_NAME}.Class.from_string") + config = mocker.MagicMock() from openapi_python_client.parser.openapi import Endpoint - result = Endpoint.parse_request_form_body(body) + result = Endpoint.parse_request_form_body(body=body, config=config) - from_ref.assert_called_once_with(ref) - assert result == from_ref() + from_string.assert_called_once_with(string=ref, config=config) + assert result == from_string.return_value def test_parse_request_form_body_no_data(self): body = oai.RequestBody.construct(content={}) + config = MagicMock() from openapi_python_client.parser.openapi import Endpoint - result = Endpoint.parse_request_form_body(body) + result = Endpoint.parse_request_form_body(body=body, config=config) assert result is None @@ -146,21 +157,22 @@ def test_parse_multipart_body(self, mocker): body = oai.RequestBody.construct( content={"multipart/form-data": oai.MediaType.construct(media_type_schema=oai.Reference.construct(ref=ref))} ) - from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") + from_string = mocker.patch(f"{MODULE_NAME}.Class.from_string") + config = MagicMock() from openapi_python_client.parser.openapi import Endpoint - result = Endpoint.parse_multipart_body(body) + result = Endpoint.parse_multipart_body(body=body, config=config) - from_ref.assert_called_once_with(ref) - assert result == from_ref() + from_string.assert_called_once_with(string=ref, config=config) + assert result == from_string.return_value def test_parse_multipart_body_no_data(self): body = oai.RequestBody.construct(content={}) from openapi_python_client.parser.openapi import Endpoint - result = Endpoint.parse_multipart_body(body) + result = Endpoint.parse_multipart_body(body=body, config=MagicMock()) assert result is None @@ -173,11 +185,12 @@ def test_parse_request_json_body(self, mocker): ) property_from_data = mocker.patch(f"{MODULE_NAME}.property_from_data") schemas = Schemas() + config = MagicMock() - result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent") + result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=config) property_from_data.assert_called_once_with( - name="json_body", required=True, data=schema, schemas=schemas, parent_name="parent" + name="json_body", required=True, data=schema, schemas=schemas, parent_name="parent", config=config ) assert result == property_from_data.return_value @@ -187,7 +200,7 @@ def test_parse_request_json_body_no_data(self): body = oai.RequestBody.construct(content={}) schemas = Schemas() - result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent") + result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=MagicMock()) assert result == (None, schemas) @@ -206,7 +219,7 @@ def test_add_body_no_data(self, mocker): ) schemas = Schemas() - Endpoint._add_body(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas) + Endpoint._add_body(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas, config=MagicMock()) parse_request_form_body.assert_not_called() @@ -230,7 +243,10 @@ def test_add_body_bad_data(self, mocker): schemas = Schemas() result = Endpoint._add_body( - endpoint=endpoint, data=oai.Operation.construct(requestBody=request_body), schemas=schemas + endpoint=endpoint, + data=oai.Operation.construct(requestBody=request_body), + schemas=schemas, + config=MagicMock(), ) assert result == ( @@ -239,18 +255,15 @@ def test_add_body_bad_data(self, mocker): ) def test_add_body_happy(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Reference, Schemas + from openapi_python_client.parser.openapi import Endpoint, Class from openapi_python_client.parser.properties import Property request_body = mocker.MagicMock() - form_body_reference = Reference.from_ref(ref="a") - multipart_body_reference = Reference.from_ref(ref="b") - parse_request_form_body = mocker.patch.object( - Endpoint, "parse_request_form_body", return_value=form_body_reference - ) - parse_multipart_body = mocker.patch.object( - Endpoint, "parse_multipart_body", return_value=multipart_body_reference - ) + config = mocker.MagicMock() + form_body_class = Class(name="A", module_name="a") + multipart_body_class = Class(name="B", module_name="b") + parse_request_form_body = mocker.patch.object(Endpoint, "parse_request_form_body", return_value=form_body_class) + parse_multipart_body = mocker.patch.object(Endpoint, "parse_multipart_body", return_value=multipart_body_class) json_body = mocker.MagicMock(autospec=Property) json_body_imports = mocker.MagicMock() @@ -259,8 +272,8 @@ def test_add_body_happy(self, mocker): parse_request_json_body = mocker.patch.object( Endpoint, "parse_request_json_body", return_value=(json_body, parsed_schemas) ) - import_string_from_reference = mocker.patch( - f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"] + import_string_from_class = mocker.patch( + f"{MODULE_NAME}.import_string_from_class", side_effect=["import_1", "import_2"] ) endpoint = Endpoint( @@ -275,31 +288,35 @@ def test_add_body_happy(self, mocker): initial_schemas = mocker.MagicMock() (endpoint, response_schemas) = Endpoint._add_body( - endpoint=endpoint, data=oai.Operation.construct(requestBody=request_body), schemas=initial_schemas + endpoint=endpoint, + data=oai.Operation.construct(requestBody=request_body), + schemas=initial_schemas, + config=config, ) assert response_schemas == parsed_schemas - parse_request_form_body.assert_called_once_with(request_body) - parse_request_json_body.assert_called_once_with(body=request_body, schemas=initial_schemas, parent_name="name") - parse_multipart_body.assert_called_once_with(request_body) - import_string_from_reference.assert_has_calls( + parse_request_form_body.assert_called_once_with(body=request_body, config=config) + parse_request_json_body.assert_called_once_with( + body=request_body, schemas=initial_schemas, parent_name="name", config=config + ) + parse_multipart_body.assert_called_once_with(body=request_body, config=config) + import_string_from_class.assert_has_calls( [ - mocker.call(form_body_reference, prefix="...models"), - mocker.call(multipart_body_reference, prefix="...models"), + mocker.call(form_body_class, prefix="...models"), + mocker.call(multipart_body_class, prefix="...models"), ] ) json_body.get_imports.assert_called_once_with(prefix="...") assert endpoint.relative_imports == {"import_1", "import_2", "import_3", json_body_imports} assert endpoint.json_body == json_body - assert endpoint.form_body_reference == form_body_reference - assert endpoint.multipart_body_reference == multipart_body_reference + assert endpoint.form_body_class == form_body_class + assert endpoint.multipart_body_class == multipart_body_class def test__add_responses_status_code_error(self, mocker): from openapi_python_client.parser.openapi import Endpoint, Schemas schemas = Schemas() response_1_data = mocker.MagicMock() - response_2_data = mocker.MagicMock() data = { "not_a_number": response_1_data, } @@ -312,16 +329,17 @@ def test__add_responses_status_code_error(self, mocker): tag="tag", relative_imports={"import_3"}, ) - parse_error = ParseError(data=mocker.MagicMock()) - response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas)) + response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data") + config = MagicMock() - response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas) + response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas, config=config) assert response.errors == [ ParseError( detail=f"Invalid response status code not_a_number (not a number), response will be ommitted from generated client" ) ] + response_from_data.assert_not_called() def test__add_responses_error(self, mocker): from openapi_python_client.parser.openapi import Endpoint, Schemas @@ -344,13 +362,14 @@ def test__add_responses_error(self, mocker): ) parse_error = ParseError(data=mocker.MagicMock()) response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas)) + config = MagicMock() - response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas) + response, schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas, config=config) response_from_data.assert_has_calls( [ - mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name"), - mocker.call(status_code=404, data=response_2_data, schemas=schemas, parent_name="name"), + mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name", config=config), + mocker.call(status_code=404, data=response_2_data, schemas=schemas, parent_name="name", config=config), ] ) assert response.errors == [ @@ -399,13 +418,18 @@ def test__add_responses(self, mocker): response_from_data = mocker.patch( f"{MODULE_NAME}.response_from_data", side_effect=[(response_1, schemas_1), (response_2, schemas_2)] ) + config = MagicMock() - endpoint, response_schemas = Endpoint._add_responses(endpoint=endpoint, data=data, schemas=schemas) + endpoint, response_schemas = Endpoint._add_responses( + endpoint=endpoint, data=data, schemas=schemas, config=config + ) response_from_data.assert_has_calls( [ - mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name"), - mocker.call(status_code=404, data=response_2_data, schemas=schemas_1, parent_name="name"), + mocker.call(status_code=200, data=response_1_data, schemas=schemas, parent_name="name", config=config), + mocker.call( + status_code=404, data=response_2_data, schemas=schemas_1, parent_name="name", config=config + ), ] ) assert endpoint.responses == [response_1, response_2] @@ -429,8 +453,12 @@ def test__add_parameters_handles_no_params(self): tag="tag", ) schemas = Schemas() + config = MagicMock() + # Just checking there's no exception here - assert Endpoint._add_parameters(endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas) == ( + assert Endpoint._add_parameters( + endpoint=endpoint, data=oai.Operation.construct(), schemas=schemas, config=config + ) == ( endpoint, schemas, ) @@ -451,9 +479,10 @@ def test__add_parameters_parse_error(self, mocker): property_schemas = mocker.MagicMock() mocker.patch(f"{MODULE_NAME}.property_from_data", return_value=(parse_error, property_schemas)) param = oai.Parameter.construct(name="test", required=True, param_schema=mocker.MagicMock(), param_in="cookie") + config = MagicMock() result = Endpoint._add_parameters( - endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=initial_schemas + endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=initial_schemas, config=config ) assert result == ( ParseError(data=parse_error.data, detail=f"cannot parse parameter of endpoint {endpoint.name}"), @@ -477,9 +506,10 @@ def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker): name="test", required=True, param_schema=mocker.MagicMock(), param_in="error_location" ) schemas = Schemas() + config = MagicMock() result = Endpoint._add_parameters( - endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=schemas + endpoint=endpoint, data=oai.Operation.construct(parameters=[param]), schemas=schemas, config=config ) assert result == (ParseError(data=param, detail="Parameter must be declared in path or query"), parsed_schemas) @@ -531,19 +561,37 @@ def test__add_parameters_happy(self, mocker): ] ) initial_schemas = mocker.MagicMock() + config = MagicMock() - (endpoint, schemas) = Endpoint._add_parameters(endpoint=endpoint, data=data, schemas=initial_schemas) + (endpoint, schemas) = Endpoint._add_parameters( + endpoint=endpoint, data=data, schemas=initial_schemas, config=config + ) property_from_data.assert_has_calls( [ mocker.call( - name="path_prop_name", required=True, data=path_schema, schemas=initial_schemas, parent_name="name" + name="path_prop_name", + required=True, + data=path_schema, + schemas=initial_schemas, + parent_name="name", + config=config, ), mocker.call( - name="query_prop_name", required=False, data=query_schema, schemas=schemas_1, parent_name="name" + name="query_prop_name", + required=False, + data=query_schema, + schemas=schemas_1, + parent_name="name", + config=config, ), mocker.call( - name="header_prop_name", required=False, data=header_schema, schemas=schemas_2, parent_name="name" + name="header_prop_name", + required=False, + data=header_schema, + schemas=schemas_2, + parent_name="name", + config=config, ), ] ) @@ -571,8 +619,11 @@ def test_from_data_bad_params(self, mocker): responses=mocker.MagicMock(), ) inital_schemas = mocker.MagicMock() + config = MagicMock() - result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=inital_schemas) + result = Endpoint.from_data( + data=data, path=path, method=method, tag="default", schemas=inital_schemas, config=config + ) assert result == (parse_error, return_schemas) @@ -595,8 +646,11 @@ def test_from_data_bad_responses(self, mocker): responses=mocker.MagicMock(), ) initial_schemas = mocker.MagicMock() + config = MagicMock() - result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=initial_schemas) + result = Endpoint.from_data( + data=data, path=path, method=method, tag="default", schemas=initial_schemas, config=config + ) assert result == (parse_error, response_schemas) @@ -623,10 +677,13 @@ def test_from_data_standard(self, mocker): responses=mocker.MagicMock(), ) initial_schemas = mocker.MagicMock() + config = MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) - endpoint = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=initial_schemas) + endpoint = Endpoint.from_data( + data=data, path=path, method=method, tag="default", schemas=initial_schemas, config=config + ) assert endpoint == _add_body.return_value @@ -641,9 +698,14 @@ def test_from_data_standard(self, mocker): ), data=data, schemas=initial_schemas, + config=config, + ) + _add_responses.assert_called_once_with( + endpoint=param_endpoint, data=data.responses, schemas=param_schemas, config=config + ) + _add_body.assert_called_once_with( + endpoint=response_endpoint, data=data, schemas=response_schemas, config=config ) - _add_responses.assert_called_once_with(endpoint=param_endpoint, data=data.responses, schemas=param_schemas) - _add_body.assert_called_once_with(endpoint=response_endpoint, data=data, schemas=response_schemas) def test_from_data_no_operation_id(self, mocker): from openapi_python_client.parser.openapi import Endpoint @@ -665,8 +727,9 @@ def test_from_data_no_operation_id(self, mocker): ) schemas = mocker.MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) + config = MagicMock() - result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=schemas) + result = Endpoint.from_data(data=data, path=path, method=method, tag="default", schemas=schemas, config=config) assert result == _add_body.return_value @@ -681,12 +744,16 @@ def test_from_data_no_operation_id(self, mocker): ), data=data, schemas=schemas, + config=config, ) _add_responses.assert_called_once_with( - endpoint=_add_parameters.return_value[0], data=data.responses, schemas=_add_parameters.return_value[1] + endpoint=_add_parameters.return_value[0], + data=data.responses, + schemas=_add_parameters.return_value[1], + config=config, ) _add_body.assert_called_once_with( - endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1] + endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config ) def test_from_data_no_security(self, mocker): @@ -709,8 +776,9 @@ def test_from_data_no_security(self, mocker): method = mocker.MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) schemas = mocker.MagicMock() + config = MagicMock() - Endpoint.from_data(data=data, path=path, method=method, tag="a", schemas=schemas) + Endpoint.from_data(data=data, path=path, method=method, tag="a", schemas=schemas, config=config) _add_parameters.assert_called_once_with( endpoint=Endpoint( @@ -723,34 +791,38 @@ def test_from_data_no_security(self, mocker): ), data=data, schemas=schemas, + config=config, ) _add_responses.assert_called_once_with( - endpoint=_add_parameters.return_value[0], data=data.responses, schemas=_add_parameters.return_value[1] + endpoint=_add_parameters.return_value[0], + data=data.responses, + schemas=_add_parameters.return_value[1], + config=config, ) _add_body.assert_called_once_with( - endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1] + endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config ) class TestImportStringFromReference: def test_import_string_from_reference_no_prefix(self, mocker): - from openapi_python_client.parser.openapi import import_string_from_reference - from openapi_python_client.parser.reference import Reference + from openapi_python_client.parser.openapi import import_string_from_class + from openapi_python_client.parser.properties import Class - reference = mocker.MagicMock(autospec=Reference) - result = import_string_from_reference(reference) + class_ = mocker.MagicMock(autospec=Class) + result = import_string_from_class(class_) - assert result == f"from .{reference.module_name} import {reference.class_name}" + assert result == f"from .{class_.module_name} import {class_.name}" def test_import_string_from_reference_with_prefix(self, mocker): - from openapi_python_client.parser.openapi import import_string_from_reference - from openapi_python_client.parser.reference import Reference + from openapi_python_client.parser.openapi import import_string_from_class + from openapi_python_client.parser.properties import Class prefix = mocker.MagicMock(autospec=str) - reference = mocker.MagicMock(autospec=Reference) - result = import_string_from_reference(reference=reference, prefix=prefix) + class_ = mocker.MagicMock(autospec=Class) + result = import_string_from_class(class_=class_, prefix=prefix) - assert result == f"from {prefix}.{reference.module_name} import {reference.class_name}" + assert result == f"from {prefix}.{class_.module_name} import {class_.name}" class TestEndpointCollection: @@ -776,14 +848,21 @@ def test_from_data(self, mocker): side_effect=[(endpoint_1, schemas_1), (endpoint_2, schemas_2), (endpoint_3, schemas_3)], ) schemas = mocker.MagicMock() + config = MagicMock() - result = EndpointCollection.from_data(data=data, schemas=schemas) + result = EndpointCollection.from_data(data=data, schemas=schemas, config=config) endpoint_from_data.assert_has_calls( [ - mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas), - mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1), - mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2), + mocker.call( + data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config + ), + mocker.call( + data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1, config=config + ), + mocker.call( + data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config + ), ], ) assert result == ( @@ -817,14 +896,21 @@ def test_from_data_errors(self, mocker): ], ) schemas = mocker.MagicMock() + config = MagicMock() - result, result_schemas = EndpointCollection.from_data(data=data, schemas=schemas) + result, result_schemas = EndpointCollection.from_data(data=data, schemas=schemas, config=config) endpoint_from_data.assert_has_calls( [ - mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas), - mocker.call(data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1), - mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2), + mocker.call( + data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config + ), + mocker.call( + data=path_1_post, path="path_1", method="post", tag="tag_2", schemas=schemas_1, config=config + ), + mocker.call( + data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config + ), ], ) assert result["default"].parse_errors[0].data == "1" @@ -854,20 +940,26 @@ def test_from_data_tags_snake_case_sanitizer(self, mocker): side_effect=[(endpoint_1, schemas_1), (endpoint_2, schemas_2), (endpoint_3, schemas_3)], ) schemas = mocker.MagicMock() + config = MagicMock() - result = EndpointCollection.from_data(data=data, schemas=schemas) + result = EndpointCollection.from_data(data=data, schemas=schemas, config=config) endpoint_from_data.assert_has_calls( [ - mocker.call(data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas), + mocker.call( + data=path_1_put, path="path_1", method="put", tag="default", schemas=schemas, config=config + ), mocker.call( data=path_1_post, path="path_1", method="post", tag="amf_subscription_info_document", schemas=schemas_1, + config=config, + ), + mocker.call( + data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2, config=config ), - mocker.call(data=path_2_get, path="path_2", method="get", tag="default", schemas=schemas_2), ], ) assert result == ( diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index ab35e514d..2d6076be6 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -212,29 +212,6 @@ def test_bad_additional_props_return_error(self): assert err == PropertyError(detail="unknown type not_real", data=oai.Schema(type="not_real")) -@pytest.fixture -def model_property() -> Callable[..., ModelProperty]: - from openapi_python_client.parser.properties import Class - - def _factory(**kwargs): - kwargs = { - "name": "", - "description": "", - "required": True, - "nullable": True, - "default": None, - "reference": Class(name="", module_name=""), - "required_properties": [], - "optional_properties": [], - "relative_imports": set(), - "additional_properties": False, - **kwargs, - } - return ModelProperty(**kwargs) - - return _factory - - def string_property(**kwargs) -> StringProperty: kwargs = { "name": "", @@ -247,17 +224,17 @@ def string_property(**kwargs) -> StringProperty: class TestProcessProperties: - def test_conflicting_properties_different_types(self, model_property): + def test_conflicting_properties_different_types(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) schemas = Schemas( models={ - "First": model_property( + "First": model_property_factory( optional_properties=[StringProperty(name="prop", required=True, nullable=True, default=None)] ), - "Second": model_property( + "Second": model_property_factory( optional_properties=[DateTimeProperty(name="prop", required=True, nullable=True, default=None)] ), } @@ -267,15 +244,15 @@ def test_conflicting_properties_different_types(self, model_property): assert isinstance(result, PropertyError) - def test_conflicting_properties_same_types(self, model_property): + def test_conflicting_properties_same_types(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) schemas = Schemas( models={ - "First": model_property(optional_properties=[string_property(default="abc")]), - "Second": model_property(optional_properties=[string_property()]), + "First": model_property_factory(optional_properties=[string_property(default="abc")]), + "Second": model_property_factory(optional_properties=[string_property()]), } ) @@ -283,7 +260,7 @@ def test_conflicting_properties_same_types(self, model_property): assert isinstance(result, PropertyError) - def test_duplicate_properties(self, model_property): + def test_duplicate_properties(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties @@ -291,8 +268,8 @@ def test_duplicate_properties(self, model_property): prop = string_property() schemas = Schemas( models={ - "First": model_property(optional_properties=[prop]), - "Second": model_property(optional_properties=[prop]), + "First": model_property_factory(optional_properties=[prop]), + "Second": model_property_factory(optional_properties=[prop]), } ) @@ -304,17 +281,19 @@ def test_duplicate_properties(self, model_property): @pytest.mark.parametrize("second_nullable", [True, False]) @pytest.mark.parametrize("first_required", [True, False]) @pytest.mark.parametrize("second_required", [True, False]) - def test_mixed_requirements(self, model_property, first_nullable, second_nullable, first_required, second_required): + def test_mixed_requirements( + self, model_property_factory, first_nullable, second_nullable, first_required, second_required + ): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) schemas = Schemas( models={ - "First": model_property( + "First": model_property_factory( optional_properties=[string_property(required=first_required, nullable=first_nullable)] ), - "Second": model_property( + "Second": model_property_factory( optional_properties=[string_property(required=second_required, nullable=second_nullable)] ), } diff --git a/tests/test_parser/test_properties/test_schemas.py b/tests/test_parser/test_properties/test_schemas.py new file mode 100644 index 000000000..334ce0f5f --- /dev/null +++ b/tests/test_parser/test_properties/test_schemas.py @@ -0,0 +1,34 @@ +import pytest + + +def test_class_from_string_default_config(): + from openapi_python_client.parser.properties import Class + from openapi_python_client import Config + + class_ = Class.from_string(string="#/components/schemas/PingResponse", config=Config()) + + assert class_.name == "PingResponse" + assert class_.module_name == "ping_response" + + +@pytest.mark.parametrize( + "class_override, module_override, expected_class, expected_module", + ( + (None, None, "_MyResponse", "_my_response"), + ("MyClass", None, "MyClass", "my_class"), + ("MyClass", "some_module", "MyClass", "some_module"), + (None, "some_module", "_MyResponse", "some_module"), + ), +) +def test_class_from_string(class_override, module_override, expected_class, expected_module): + from openapi_python_client.parser.properties import Class + from openapi_python_client.config import Config, ClassOverride + + ref = "#/components/schemas/_MyResponse" + config = Config( + class_overrides={"_MyResponse": ClassOverride(class_name=class_override, module_name=module_override)} + ) + + result = Class.from_string(string=ref, config=config) + assert result.name == expected_class + assert result.module_name == expected_module diff --git a/tests/test_parser/test_reference.py b/tests/test_parser/test_reference.py deleted file mode 100644 index 3660a1c35..000000000 --- a/tests/test_parser/test_reference.py +++ /dev/null @@ -1,16 +0,0 @@ -def test_from_ref(): - from openapi_python_client.parser.reference import Reference - - r = Reference.from_ref("#/components/schemas/PingResponse") - - assert r.class_name == "PingResponse" - assert r.module_name == "ping_response" - - -def test_from_ref_class_overrides(): - from openapi_python_client.parser.reference import Reference, class_overrides - - ref = "#/components/schemas/_MyResponse" - class_overrides["_MyResponse"] = Reference(class_name="MyResponse", module_name="my_response") - - assert Reference.from_ref(ref) == class_overrides["_MyResponse"] diff --git a/tests/test_parser/test_responses.py b/tests/test_parser/test_responses.py index eb20fb338..50023ffef 100644 --- a/tests/test_parser/test_responses.py +++ b/tests/test_parser/test_responses.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock + import openapi_python_client.schema as oai from openapi_python_client.parser.errors import ParseError, PropertyError from openapi_python_client.parser.properties import NoneProperty, Schemas, StringProperty @@ -9,7 +11,11 @@ def test_response_from_data_no_content(): from openapi_python_client.parser.responses import Response, response_from_data response, schemas = response_from_data( - status_code=200, data=oai.Response.construct(description=""), schemas=Schemas(), parent_name="parent" + status_code=200, + data=oai.Response.construct(description=""), + schemas=Schemas(), + parent_name="parent", + config=MagicMock(), ) assert response == Response( @@ -23,7 +29,9 @@ def test_response_from_data_unsupported_content_type(): from openapi_python_client.parser.responses import response_from_data data = oai.Response.construct(description="", content={"blah": None}) - response, schemas = response_from_data(status_code=200, data=data, schemas=Schemas(), parent_name="parent") + response, schemas = response_from_data( + status_code=200, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) assert response == ParseError(data=data, detail="Unsupported content_type {'blah': None}") @@ -32,7 +40,9 @@ def test_response_from_data_no_content_schema(): from openapi_python_client.parser.responses import Response, response_from_data data = oai.Response.construct(description="", content={"application/json": oai.MediaType.construct()}) - response, schemas = response_from_data(status_code=200, data=data, schemas=Schemas(), parent_name="parent") + response, schemas = response_from_data( + status_code=200, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) assert response == Response( status_code=200, From 10163b2e8b7b944352b386fadfca2f387030a715 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sat, 27 Mar 2021 17:49:00 -0600 Subject: [PATCH 05/11] test: Fix more unit tests --- openapi_python_client/cli.py | 8 +- tests/conftest.py | 2 +- tests/test_cli.py | 12 +- .../test_parser/test_properties/test_init.py | 172 ++++++------------ tests/test_parser/test_responses.py | 12 +- 5 files changed, 73 insertions(+), 133 deletions(-) diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index fea7cc34f..953c92234 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -21,10 +21,10 @@ def _version_callback(value: bool) -> None: raise typer.Exit() -def _process_config(path: Optional[pathlib.Path]) -> Optional[Config]: +def _process_config(path: Optional[pathlib.Path]) -> Config: if not path: - return None + return Config() try: return Config.load_from_path(path=path) @@ -137,7 +137,7 @@ def generate( typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED) raise typer.Exit(code=1) - config = Config.load_from_path(config_path) if config_path is not None else Config() + config = _process_config(config_path) errors = create_new_client( url=url, path=path, @@ -174,7 +174,7 @@ def update( typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED) raise typer.Exit(code=1) - config = Config.load_from_path(config_path) if config_path is not None else Config() + config = _process_config(config_path) errors = update_existing_client( url=url, path=path, diff --git a/tests/conftest.py b/tests/conftest.py index d432982ae..967640ff2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,7 +46,7 @@ def _factory(**kwargs): kwargs = { "name": "test", "required": True, - "nullable": True, + "nullable": False, "default": None, "class_info": Class(name="", module_name=""), "values": {}, diff --git a/tests/test_cli.py b/tests/test_cli.py index 4954aea25..b6f893ee2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,6 +4,7 @@ import pytest from typer.testing import CliRunner +from openapi_python_client import Config from openapi_python_client.parser.errors import GeneratorError, ParseError runner = CliRunner() @@ -40,9 +41,14 @@ def test_config_arg(mocker, _create_new_client): ) assert result.exit_code == 0 - load_config.assert_called_once_with(Path(config_path)) + load_config.assert_called_once_with(path=Path(config_path)) _create_new_client.assert_called_once_with( - url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8" + url=None, + path=Path(path), + custom_template_path=None, + meta=MetaType.POETRY, + file_encoding="utf-8", + config=load_config.return_value, ) @@ -59,7 +65,7 @@ def test_bad_config(mocker, _create_new_client): assert result.exit_code == 2 assert "Unable to parse config" in result.stdout - load_config.assert_called_once_with(Path(config_path)) + load_config.assert_called_once_with(path=Path(config_path)) _create_new_client.assert_not_called() diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 69a1289d1..8dd5fcd9f 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -1,6 +1,7 @@ import pytest import openapi_python_client.schema as oai +from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError, ValidationError from openapi_python_client.parser.properties import BooleanProperty, FloatProperty, IntProperty @@ -398,110 +399,35 @@ def test_get_imports(self, mocker): class TestEnumProperty: - def test_get_type_string(self, mocker): - fake_reference = mocker.MagicMock(class_name="MyTestEnum") - - from openapi_python_client.parser import properties - - p = properties.EnumProperty( - name="test", - required=True, - default=None, - values={}, - nullable=False, - reference=fake_reference, - value_type=str, - ) + @pytest.mark.parametrize( + "required, nullable, expected", + ( + (False, False, "Union[Unset, {}]"), + (True, False, "{}"), + (False, True, "Union[Unset, None, {}]"), + (True, True, "Optional[{}]"), + ), + ) + def test_get_type_string(self, mocker, enum_property_factory, required, nullable, expected): + fake_class = mocker.MagicMock() + fake_class.name = "MyTestEnum" - base_type_string = f"MyTestEnum" + p = enum_property_factory(class_info=fake_class, required=required, nullable=nullable) - assert p.get_type_string() == base_type_string - assert p.get_type_string(json=True) == "str" + assert p.get_type_string() == expected.format(fake_class.name) + assert p.get_type_string(no_optional=True) == fake_class.name + assert p.get_type_string(json=True) == expected.format("str") - p = properties.EnumProperty( - name="test", - required=True, - default=None, - values={}, - nullable=True, - reference=fake_reference, - value_type=str, - ) - assert p.get_type_string() == f"Optional[{base_type_string}]" - assert p.get_type_string(no_optional=True) == base_type_string - - p = properties.EnumProperty( - name="test", - required=False, - default=None, - values={}, - nullable=True, - reference=fake_reference, - value_type=str, - ) - assert p.get_type_string() == f"Union[Unset, None, {base_type_string}]" - assert p.get_type_string(no_optional=True) == base_type_string - - p = properties.EnumProperty( - name="test", - required=False, - default=None, - values={}, - nullable=False, - reference=fake_reference, - value_type=str, - ) - assert p.get_type_string() == f"Union[Unset, {base_type_string}]" - assert p.get_type_string(no_optional=True) == base_type_string - - def test_get_imports(self, mocker): - fake_reference = mocker.MagicMock(class_name="MyTestEnum", module_name="my_test_enum") + def test_get_imports(self, mocker, enum_property_factory): + fake_class = mocker.MagicMock(module_name="my_test_enum") + fake_class.name = "MyTestEnum" prefix = "..." - from openapi_python_client.parser import properties - - enum_property = properties.EnumProperty( - name="test", - required=True, - default=None, - values={}, - nullable=False, - reference=fake_reference, - value_type=str, - ) - - assert enum_property.get_imports(prefix=prefix) == { - f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}", - } - - enum_property = properties.EnumProperty( - name="test", - required=False, - default=None, - values={}, - nullable=False, - reference=fake_reference, - value_type=str, - ) - assert enum_property.get_imports(prefix=prefix) == { - f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}", - "from typing import Union", - "from ...types import UNSET, Unset", - } + enum_property = enum_property_factory(class_info=fake_class, required=False) - enum_property = properties.EnumProperty( - name="test", - required=False, - default=None, - values={}, - nullable=True, - reference=fake_reference, - value_type=str, - ) assert enum_property.get_imports(prefix=prefix) == { - f"from {prefix}models.{fake_reference.module_name} import {fake_reference.class_name}", - "from typing import Union", - "from typing import Optional", + f"from {prefix}models.{fake_class.module_name} import {fake_class.name}", + "from typing import Union", # Makes sure unset is handled via base class "from ...types import UNSET, Unset", } @@ -534,7 +460,7 @@ def test_values_from_list_duplicate(self): class TestPropertyFromData: def test_property_from_data_str_enum(self, mocker): - from openapi_python_client.parser.properties import EnumProperty, Reference + from openapi_python_client.parser.properties import EnumProperty, Class from openapi_python_client.schema import Schema data = Schema(title="AnEnum", enum=["A", "B", "C"], nullable=False, default="B") @@ -543,10 +469,10 @@ def test_property_from_data_str_enum(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data - schemas = Schemas(enums={"AnEnum": mocker.MagicMock()}) + schemas = Schemas(classes_by_name={"AnEnum": mocker.MagicMock()}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() ) assert prop == EnumProperty( @@ -554,18 +480,18 @@ def test_property_from_data_str_enum(self, mocker): required=True, nullable=False, values={"A": "A", "B": "B", "C": "C"}, - reference=Reference(class_name="ParentAnEnum", module_name="parent_an_enum"), + class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), value_type=str, default="ParentAnEnum.B", ) assert schemas != new_schemas, "Provided Schemas was mutated" - assert new_schemas.enums == { - "AnEnum": schemas.enums["AnEnum"], + assert new_schemas.classes_by_name == { + "AnEnum": schemas.classes_by_name["AnEnum"], "ParentAnEnum": prop, } def test_property_from_data_int_enum(self, mocker): - from openapi_python_client.parser.properties import EnumProperty, Reference + from openapi_python_client.parser.properties import EnumProperty, Class from openapi_python_client.schema import Schema data = Schema.construct(title="anEnum", enum=[1, 2, 3], nullable=False, default=3) @@ -574,10 +500,10 @@ def test_property_from_data_int_enum(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data - schemas = Schemas(enums={"AnEnum": mocker.MagicMock()}) + schemas = Schemas(classes_by_name={"AnEnum": mocker.MagicMock()}) prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=Config() ) assert prop == EnumProperty( @@ -585,21 +511,21 @@ def test_property_from_data_int_enum(self, mocker): required=True, nullable=False, values={"VALUE_1": 1, "VALUE_2": 2, "VALUE_3": 3}, - reference=Reference(class_name="ParentAnEnum", module_name="parent_an_enum"), + class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), value_type=int, default="ParentAnEnum.VALUE_3", ) assert schemas != new_schemas, "Provided Schemas was mutated" - assert new_schemas.enums == { - "AnEnum": schemas.enums["AnEnum"], + assert new_schemas.classes_by_name == { + "AnEnum": schemas.classes_by_name["AnEnum"], "ParentAnEnum": prop, } def test_property_from_data_ref_enum(self): - from openapi_python_client.parser.properties import EnumProperty, Reference, Schemas, property_from_data + from openapi_python_client.parser.properties import EnumProperty, Class, Schemas, property_from_data name = "some_enum" - data = oai.Reference.construct(ref="MyEnum") + data = oai.Reference.construct(ref="#/components/schemas/MyEnum") existing_enum = EnumProperty( name="an_enum", required=True, @@ -607,11 +533,13 @@ def test_property_from_data_ref_enum(self): default=None, values={"A": "a"}, value_type=str, - reference=Reference(class_name="MyEnum", module_name="my_enum"), + class_info=Class(name="MyEnum", module_name="my_enum"), ) - schemas = Schemas(enums={"MyEnum": existing_enum}) + schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) - prop, new_schemas = property_from_data(name=name, required=False, data=data, schemas=schemas, parent_name="") + prop, new_schemas = property_from_data( + name=name, required=False, data=data, schemas=schemas, parent_name="", config=Config() + ) assert prop == EnumProperty( name="some_enum", @@ -620,39 +548,41 @@ def test_property_from_data_ref_enum(self): default=None, values={"A": "a"}, value_type=str, - reference=Reference(class_name="MyEnum", module_name="my_enum"), + class_info=Class(name="MyEnum", module_name="my_enum"), ) assert schemas == new_schemas def test_property_from_data_ref_model(self): - from openapi_python_client.parser.properties import ModelProperty, Reference, Schemas, property_from_data + from openapi_python_client.parser.properties import ModelProperty, Class, Schemas, property_from_data name = "new_name" required = False class_name = "MyModel" - data = oai.Reference.construct(ref=class_name) + data = oai.Reference.construct(ref=f"#/components/schemas/{class_name}") existing_model = ModelProperty( name="old_name", required=True, nullable=False, default=None, - reference=Reference(class_name=class_name, module_name="my_model"), + class_info=Class(name=class_name, module_name="my_model"), required_properties=[], optional_properties=[], description="", relative_imports=set(), additional_properties=False, ) - schemas = Schemas(models={class_name: existing_model}) + schemas = Schemas(classes_by_reference={f"/components/schemas/{class_name}": existing_model}) - prop, new_schemas = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="") + prop, new_schemas = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="", config=Config() + ) assert prop == ModelProperty( name=name, required=required, nullable=False, default=None, - reference=Reference(class_name=class_name, module_name="my_model"), + class_info=Class(name=class_name, module_name="my_model"), required_properties=[], optional_properties=[], description="", diff --git a/tests/test_parser/test_responses.py b/tests/test_parser/test_responses.py index 50023ffef..025172388 100644 --- a/tests/test_parser/test_responses.py +++ b/tests/test_parser/test_responses.py @@ -58,13 +58,15 @@ def test_response_from_data_property_error(mocker): data = oai.Response.construct( description="", content={"application/json": oai.MediaType.construct(media_type_schema="something")} ) + config = MagicMock() + response, schemas = responses.response_from_data( - status_code=400, data=data, schemas=Schemas(), parent_name="parent" + status_code=400, data=data, schemas=Schemas(), parent_name="parent", config=config ) assert response == PropertyError() property_from_data.assert_called_once_with( - name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent" + name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent", config=config ) @@ -76,8 +78,10 @@ def test_response_from_data_property(mocker): data = oai.Response.construct( description="", content={"application/json": oai.MediaType.construct(media_type_schema="something")} ) + config = MagicMock() + response, schemas = responses.response_from_data( - status_code=400, data=data, schemas=Schemas(), parent_name="parent" + status_code=400, data=data, schemas=Schemas(), parent_name="parent", config=config ) assert response == responses.Response( @@ -86,5 +90,5 @@ def test_response_from_data_property(mocker): source="response.json()", ) property_from_data.assert_called_once_with( - name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent" + name="response_400", required=True, data="something", schemas=Schemas(), parent_name="parent", config=config ) From af8d0ea732590bd6138435033573940e252557d9 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sun, 4 Apr 2021 10:36:57 -0600 Subject: [PATCH 06/11] test: Finish fixing broken unit tests --- .../parser/properties/__init__.py | 34 ++- .../parser/properties/schemas.py | 4 +- poetry.lock | 35 +-- tests/test_cli.py | 14 +- tests/test_parser/test_openapi.py | 2 +- .../test_parser/test_properties/test_init.py | 203 +++++++++++------- .../test_properties/test_model_property.py | 91 ++++---- .../test_properties/test_schemas.py | 4 +- 8 files changed, 203 insertions(+), 184 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index d9a6ddea0..505284f39 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -483,6 +483,29 @@ def property_from_data( parent_name: str, config: Config, ) -> Tuple[Union[Property, PropertyError], Schemas]: + """ + Build a Property from an OpenAPI schema or reference. This Property represents a single input or output for a + generated API operation. + + Args: + name: The name of the property, defined in OpenAPI as the key pointing at the schema. This is the parameter used + to send this data to an API or that the API will respond with. This will be used to generate a `python_name` + which is the name of the variable/attribute in generated Python. + required: Whether or not this property is required in whatever source is creating it. OpenAPI defines this by + including the property's name in the `required` list. If the property is required, `Unset` will not be + included in the generated code's available types. + data: The OpenAPI schema or reference that defines the details of this property (e.g. type, sub-properties). + schemas: A structure containing all of the parsed schemas so far that will become generated classes. This is + used to resolve references and to ensure that conflicting class names are not generated. + parent_name: The name of the thing above this property, prepended to generated class names to reduce the chance + of duplication. + config: Contains the parsed config that the user provided to tweak generation settings. Needed to apply class + name overrides for generated classes. + + Returns: + A tuple containing either the parsed Property or a PropertyError (if something went wrong) and the updated + Schemas (including any new classes that should be generated). + """ try: return _property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name=parent_name, config=config @@ -496,12 +519,12 @@ def build_schemas( ) -> Schemas: """ Get a list of Schemas from an OpenAPI dict """ to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() - processing = True + still_making_progress = True errors: List[PropertyError] = [] # References could have forward References so keep going as long as we are making progress - while processing: - processing = False + while still_making_progress: + still_making_progress = False errors = [] next_round = [] # Only accumulate errors from the last round, since we might fix some along the way @@ -511,8 +534,7 @@ def build_schemas( continue ref_path = parse_reference_path(f"#/components/schemas/{name}") if isinstance(ref_path, ParseError): - next_round.append((name, data)) - errors.append(PropertyError(detail=ref_path.detail, data=data)) + schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, PropertyError): @@ -520,7 +542,7 @@ def build_schemas( errors.append(schemas_or_err) continue schemas = schemas_or_err - processing = True # We made some progress this round, do another after it's done + still_making_progress = True to_process = next_round schemas.errors.extend(errors) diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 8f4b43129..315b9fb67 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -4,7 +4,6 @@ from urllib.parse import urlparse import attr -from pydantic import BaseModel from ... import Config from ... import schema as oai @@ -30,7 +29,8 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError] return cast(_ReferencePath, parsed.fragment) -class Class(BaseModel): +@attr.s(auto_attribs=True, frozen=True) +class Class: """ Info about a generated class which will be in models """ name: _ClassName diff --git a/poetry.lock b/poetry.lock index 041aa5585..4dac795bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -687,6 +687,7 @@ autoflake = [ {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, ] black = [ + {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"}, {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ @@ -821,39 +822,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -1028,12 +1010,6 @@ regex = [ {file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"}, {file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"}, {file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux1_i686.whl", hash = "sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad"}, - {file = "regex-2020.9.27-cp39-cp39-win32.whl", hash = "sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302"}, - {file = "regex-2020.9.27-cp39-cp39-win_amd64.whl", hash = "sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7"}, {file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"}, ] requests = [ @@ -1079,28 +1055,19 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, - {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, - {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] typer = [ diff --git a/tests/test_cli.py b/tests/test_cli.py index b6f893ee2..c551e80dd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -88,7 +88,7 @@ def test_generate_url_and_path(self, _create_new_client): def test_generate_url(self, _create_new_client): url = "cool.url" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["generate", f"--url={url}"]) @@ -99,7 +99,7 @@ def test_generate_url(self, _create_new_client): def test_generate_path(self, _create_new_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["generate", f"--path={path}"]) @@ -115,7 +115,7 @@ def test_generate_path(self, _create_new_client): def test_generate_meta(self, _create_new_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"]) @@ -132,7 +132,7 @@ def test_generate_meta(self, _create_new_client): def test_generate_encoding(self, _create_new_client): path = "cool/path" file_encoding = "utf-8" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["generate", f"--path={path}", f"--file-encoding={file_encoding}"]) @@ -219,7 +219,7 @@ def test_update_url_and_path(self, _update_existing_client): def test_update_url(self, _update_existing_client): url = "cool.url" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["update", f"--url={url}"]) @@ -230,7 +230,7 @@ def test_update_url(self, _update_existing_client): def test_update_path(self, _update_existing_client): path = "cool/path" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["update", f"--path={path}"]) @@ -247,7 +247,7 @@ def test_update_path(self, _update_existing_client): def test_update_encoding(self, _update_existing_client): path = "cool/path" file_encoding = "utf-8" - from openapi_python_client.cli import MetaType, app, Config + from openapi_python_client.cli import Config, MetaType, app result = runner.invoke(app, ["update", f"--path={path}", f"--file-encoding={file_encoding}"]) diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index b9124914e..e5a0e8e30 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -255,7 +255,7 @@ def test_add_body_bad_data(self, mocker): ) def test_add_body_happy(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Class + from openapi_python_client.parser.openapi import Class, Endpoint from openapi_python_client.parser.properties import Property request_body = mocker.MagicMock() diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 8dd5fcd9f..3e9c8cb54 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock, call + import pytest import openapi_python_client.schema as oai @@ -460,7 +462,7 @@ def test_values_from_list_duplicate(self): class TestPropertyFromData: def test_property_from_data_str_enum(self, mocker): - from openapi_python_client.parser.properties import EnumProperty, Class + from openapi_python_client.parser.properties import Class, EnumProperty from openapi_python_client.schema import Schema data = Schema(title="AnEnum", enum=["A", "B", "C"], nullable=False, default="B") @@ -491,7 +493,7 @@ def test_property_from_data_str_enum(self, mocker): } def test_property_from_data_int_enum(self, mocker): - from openapi_python_client.parser.properties import EnumProperty, Class + from openapi_python_client.parser.properties import Class, EnumProperty from openapi_python_client.schema import Schema data = Schema.construct(title="anEnum", enum=[1, 2, 3], nullable=False, default=3) @@ -522,7 +524,7 @@ def test_property_from_data_int_enum(self, mocker): } def test_property_from_data_ref_enum(self): - from openapi_python_client.parser.properties import EnumProperty, Class, Schemas, property_from_data + from openapi_python_client.parser.properties import Class, EnumProperty, Schemas, property_from_data name = "some_enum" data = oai.Reference.construct(ref="#/components/schemas/MyEnum") @@ -553,7 +555,7 @@ def test_property_from_data_ref_enum(self): assert schemas == new_schemas def test_property_from_data_ref_model(self): - from openapi_python_client.parser.properties import ModelProperty, Class, Schemas, property_from_data + from openapi_python_client.parser.properties import Class, ModelProperty, Schemas, property_from_data name = "new_name" required = False @@ -597,15 +599,15 @@ def test_property_from_data_ref_not_found(self, mocker): name = mocker.MagicMock() required = mocker.MagicMock() data = oai.Reference.construct(ref=mocker.MagicMock()) - from_ref = mocker.patch(f"{MODULE_NAME}.Reference.from_ref") + parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) schemas = Schemas() prop, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock() ) - from_ref.assert_called_once_with(data.ref) + parse_reference_path.assert_called_once_with(data.ref) assert prop == PropertyError(data=data, detail="Could not find reference in parsed models or enums") assert schemas == new_schemas @@ -620,7 +622,7 @@ def test_property_from_data_string(self, mocker): schemas = Schemas() p, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock() ) assert p == _string_based_property.return_value @@ -644,7 +646,7 @@ def test_property_from_data_simple_types(self, openapi_type, prop_type, python_t schemas = Schemas() p, new_schemas = property_from_data( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock() ) assert p == prop_type(name=name, required=required, default=python_type(data.default), nullable=False) @@ -654,12 +656,16 @@ def test_property_from_data_simple_types(self, openapi_type, prop_type, python_t data.default = 0 data.nullable = True - p, _ = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent") + p, _ = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock() + ) assert p == prop_type(name=name, required=required, default=python_type(data.default), nullable=True) # Test bad default value data.default = "a" - p, _ = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent") + p, _ = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock() + ) assert python_type is bool or isinstance(p, PropertyError) def test_property_from_data_array(self, mocker): @@ -674,12 +680,15 @@ def test_property_from_data_array(self, mocker): build_list_property = mocker.patch(f"{MODULE_NAME}.build_list_property") mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) schemas = Schemas() + config = MagicMock() - response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent") + response = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config + ) assert response == build_list_property.return_value build_list_property.assert_called_once_with( - data=data, name=name, required=required, schemas=schemas, parent_name="parent" + data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config ) def test_property_from_data_object(self, mocker): @@ -693,12 +702,15 @@ def test_property_from_data_object(self, mocker): build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property") mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) schemas = Schemas() + config = MagicMock() - response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent") + response = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config + ) assert response == build_model_property.return_value build_model_property.assert_called_once_with( - data=data, name=name, required=required, schemas=schemas, parent_name="parent" + data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config ) def test_property_from_data_union(self, mocker): @@ -715,12 +727,15 @@ def test_property_from_data_union(self, mocker): build_union_property = mocker.patch(f"{MODULE_NAME}.build_union_property") mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) schemas = Schemas() + config = MagicMock() - response = property_from_data(name=name, required=required, data=data, schemas=schemas, parent_name="parent") + response = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config + ) assert response == build_union_property.return_value build_union_property.assert_called_once_with( - data=data, name=name, required=required, schemas=schemas, parent_name="parent" + data=data, name=name, required=required, schemas=schemas, parent_name="parent", config=config ) def test_property_from_data_unsupported_type(self, mocker): @@ -731,7 +746,9 @@ def test_property_from_data_unsupported_type(self, mocker): from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import Schemas, property_from_data - assert property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent") == ( + assert property_from_data( + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) == ( PropertyError(data=data, detail=f"unknown type {data.type}"), Schemas(), ) @@ -743,7 +760,7 @@ def test_property_from_data_no_valid_props_in_data(self): data = oai.Schema() prop, new_schemas = property_from_data( - name="blah", required=True, data=data, schemas=schemas, parent_name="parent" + name="blah", required=True, data=data, schemas=schemas, parent_name="parent", config=MagicMock() ) assert prop == NoneProperty(name="blah", required=True, nullable=False, default=None) @@ -758,7 +775,7 @@ def test_property_from_data_validation_error(self, mocker): data = oai.Schema() err, new_schemas = property_from_data( - name="blah", required=True, data=data, schemas=schemas, parent_name="parent" + name="blah", required=True, data=data, schemas=schemas, parent_name="parent", config=MagicMock() ) assert err == PropertyError(detail="Failed to validate default value", data=data) assert new_schemas == schemas @@ -775,7 +792,7 @@ def test_build_list_property_no_items(self, mocker): schemas = properties.Schemas() p, new_schemas = properties.build_list_property( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=MagicMock() ) assert p == PropertyError(data=data, detail="type array must have items defined") @@ -796,16 +813,17 @@ def test_build_list_property_invalid_items(self, mocker): property_from_data = mocker.patch.object( properties, "property_from_data", return_value=(properties.PropertyError(data="blah"), second_schemas) ) + config = MagicMock() p, new_schemas = properties.build_list_property( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) assert p == PropertyError(data="blah", detail=f"invalid data in items of array {name}") assert new_schemas == second_schemas assert schemas != new_schemas, "Schema was mutated" property_from_data.assert_called_once_with( - name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent" + name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent", config=config ) def test_build_list_property(self, mocker): @@ -824,9 +842,10 @@ def test_build_list_property(self, mocker): ) mocker.patch("openapi_python_client.utils.snake_case", return_value=name) mocker.patch("openapi_python_client.utils.to_valid_python_identifier", return_value=name) + config = MagicMock() p, new_schemas = properties.build_list_property( - name=name, required=required, data=data, schemas=schemas, parent_name="parent" + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) assert isinstance(p, properties.ListProperty) @@ -834,7 +853,7 @@ def test_build_list_property(self, mocker): assert new_schemas == second_schemas assert schemas != new_schemas, "Schema was mutated" property_from_data.assert_called_once_with( - name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent" + name=f"{name}_item", required=True, data=data.items, schemas=schemas, parent_name="parent", config=config ) @@ -855,7 +874,9 @@ def test_property_from_data_union(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data - p, s = property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent") + p, s = property_from_data( + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=False) IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=False) @@ -877,7 +898,9 @@ def test_property_from_data_union_bad_type(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data - p, s = property_from_data(name=name, required=required, data=data, schemas=Schemas(), parent_name="parent") + p, s = property_from_data( + name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() + ) assert p == PropertyError(detail=f"Invalid property in union {name}", data=oai.Schema(type="garbage")) @@ -966,72 +989,86 @@ def test__string_based_property_unsupported_format(self, mocker): assert p == StringProperty(name=name, required=required, nullable=True, default=None) -def test_build_schemas(mocker): - build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property") - in_data = {"1": mocker.MagicMock(enum=None), "2": mocker.MagicMock(enum=None), "3": mocker.MagicMock(enum=None)} - model_1 = mocker.MagicMock() - schemas_1 = mocker.MagicMock() - model_2 = mocker.MagicMock() - schemas_2 = mocker.MagicMock(errors=[]) - error = PropertyError() - schemas_3 = mocker.MagicMock() - - # This loops through one for each, then again to retry the error - build_model_property.side_effect = [ - (model_1, schemas_1), - (model_2, schemas_2), - (error, schemas_3), - (error, schemas_3), - ] - - from openapi_python_client.parser.properties import Schemas, build_schemas - - result = build_schemas(components=in_data) - - build_model_property.assert_has_calls( - [ - mocker.call(data=in_data["1"], name="1", schemas=Schemas(), required=True, parent_name=None), - mocker.call(data=in_data["2"], name="2", schemas=schemas_1, required=True, parent_name=None), - mocker.call(data=in_data["3"], name="3", schemas=schemas_2, required=True, parent_name=None), - mocker.call(data=in_data["3"], name="3", schemas=schemas_2, required=True, parent_name=None), - ] - ) - # schemas_3 was the last to come back from build_model_property, but it should be ignored because it's an error - assert result == schemas_2 - assert result.errors == [error] - - -def test_build_parse_error_on_reference(): - from openapi_python_client.parser.openapi import build_schemas - - ref_schema = oai.Reference.construct() - in_data = {"1": ref_schema} - result = build_schemas(components=in_data) - assert result.errors[0] == PropertyError(data=ref_schema, detail="Reference schemas are not supported.") - +class TestBuildSchemas: + def test_skips_references_and_keeps_going(self, mocker): + from openapi_python_client.parser.properties import Schemas, build_schemas + from openapi_python_client.schema import Reference, Schema + + components = {"a_ref": Reference.construct(), "a_schema": Schema.construct()} + update_schemas_with_data = mocker.patch(f"{MODULE_NAME}.update_schemas_with_data") + parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") + config = Config() + + result = build_schemas(components=components, schemas=Schemas(), config=config) + # Should not even try to parse a path for the Reference + parse_reference_path.assert_called_once_with("#/components/schemas/a_schema") + update_schemas_with_data.assert_called_once_with( + ref_path=parse_reference_path.return_value, + config=config, + data=components["a_schema"], + schemas=Schemas( + errors=[PropertyError(detail="Reference schemas are not supported.", data=components["a_ref"])] + ), + ) + assert result == update_schemas_with_data.return_value -def test_build_enums(mocker): - from openapi_python_client.parser.openapi import build_schemas + def test_records_bad_uris_and_keeps_going(self, mocker): + from openapi_python_client.parser.properties import Schemas, build_schemas + from openapi_python_client.schema import Schema - build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property") - schemas = mocker.MagicMock() - build_enum_property = mocker.patch(f"{MODULE_NAME}.build_enum_property", return_value=(mocker.MagicMock(), schemas)) - in_data = {"1": mocker.MagicMock(enum=["val1", "val2", "val3"])} + components = {"first": Schema.construct(), "second": Schema.construct()} + update_schemas_with_data = mocker.patch(f"{MODULE_NAME}.update_schemas_with_data") + parse_reference_path = mocker.patch( + f"{MODULE_NAME}.parse_reference_path", side_effect=[PropertyError(detail="some details"), "a_path"] + ) + config = Config() + + result = build_schemas(components=components, schemas=Schemas(), config=config) + parse_reference_path.assert_has_calls( + [ + call("#/components/schemas/first"), + call("#/components/schemas/second"), + ] + ) + update_schemas_with_data.assert_called_once_with( + ref_path="a_path", + config=config, + data=components["second"], + schemas=Schemas(errors=[PropertyError(detail="some details", data=components["first"])]), + ) + assert result == update_schemas_with_data.return_value - build_schemas(components=in_data) + def test_retries_failing_properties_while_making_progress(self, mocker): + from openapi_python_client.parser.properties import Schemas, build_schemas + from openapi_python_client.schema import Schema - build_enum_property.assert_called() - build_model_property.assert_not_called() + components = {"first": Schema.construct(), "second": Schema.construct()} + update_schemas_with_data = mocker.patch( + f"{MODULE_NAME}.update_schemas_with_data", side_effect=[PropertyError(), Schemas(), PropertyError()] + ) + parse_reference_path = mocker.patch(f"{MODULE_NAME}.parse_reference_path") + config = Config() + + result = build_schemas(components=components, schemas=Schemas(), config=config) + parse_reference_path.assert_has_calls( + [ + call("#/components/schemas/first"), + call("#/components/schemas/second"), + call("#/components/schemas/first"), + ] + ) + assert update_schemas_with_data.call_count == 3 + assert result.errors == [PropertyError()] def test_build_enum_property_conflict(mocker): from openapi_python_client.parser.properties import Schemas, build_enum_property data = oai.Schema() - schemas = Schemas(enums={"Existing": mocker.MagicMock()}) + schemas = Schemas(classes_by_name={"Existing": mocker.MagicMock()}) err, schemas = build_enum_property( - data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None + data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None, config=Config() ) assert schemas == schemas @@ -1045,7 +1082,7 @@ def test_build_enum_property_no_values(): schemas = Schemas() err, schemas = build_enum_property( - data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None + data=data, name="Existing", required=True, schemas=schemas, enum=[], parent_name=None, config=Config() ) assert schemas == schemas @@ -1059,7 +1096,7 @@ def test_build_enum_property_bad_default(): schemas = Schemas() err, schemas = build_enum_property( - data=data, name="Existing", required=True, schemas=schemas, enum=["A"], parent_name=None + data=data, name="Existing", required=True, schemas=schemas, enum=["A"], parent_name=None, config=Config() ) assert schemas == schemas diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 2d6076be6..7db77309a 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -1,8 +1,10 @@ from typing import Callable +from unittest.mock import MagicMock import pytest import openapi_python_client.schema as oai +from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import DateTimeProperty, ModelProperty, StringProperty @@ -94,11 +96,7 @@ def test_additional_schemas(self, additional_properties_schema, expected_additio ) model, _ = build_model_property( - data=data, - name="prop", - schemas=Schemas(), - required=True, - parent_name="parent", + data=data, name="prop", schemas=Schemas(), required=True, parent_name="parent", config=MagicMock() ) assert model.additional_properties == expected_additional_properties @@ -116,21 +114,20 @@ def test_happy_path(self): description="A class called MyModel", nullable=False, ) - schemas = Schemas(models={"OtherModel": None}) + schemas = Schemas(classes_by_reference={"OtherModel": None}, classes_by_name={"OtherModel": None}) model, new_schemas = build_model_property( - data=data, - name="prop", - schemas=schemas, - required=True, - parent_name="parent", + data=data, name="prop", schemas=schemas, required=True, parent_name="parent", config=Config() ) assert new_schemas != schemas - assert new_schemas.models == { + assert new_schemas.classes_by_name == { "OtherModel": None, "ParentMyModel": model, } + assert new_schemas.classes_by_reference == { + "OtherModel": None, + } assert model == ModelProperty( name="prop", required=True, @@ -154,14 +151,10 @@ def test_model_name_conflict(self): from openapi_python_client.parser.properties import Schemas, build_model_property data = oai.Schema.construct() - schemas = Schemas(models={"OtherModel": None}) + schemas = Schemas(classes_by_name={"OtherModel": None}) err, new_schemas = build_model_property( - data=data, - name="OtherModel", - schemas=schemas, - required=True, - parent_name=None, + data=data, name="OtherModel", schemas=schemas, required=True, parent_name=None, config=Config() ) assert new_schemas == schemas @@ -178,11 +171,7 @@ def test_bad_props_return_error(self): schemas = Schemas() err, new_schemas = build_model_property( - data=data, - name="prop", - schemas=schemas, - required=True, - parent_name=None, + data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=MagicMock() ) assert new_schemas == schemas @@ -201,11 +190,7 @@ def test_bad_additional_props_return_error(self): schemas = Schemas() err, new_schemas = build_model_property( - data=data, - name="prop", - schemas=schemas, - required=True, - parent_name=None, + data=data, name="prop", schemas=schemas, required=True, parent_name=None, config=MagicMock() ) assert new_schemas == schemas @@ -228,19 +213,21 @@ def test_conflicting_properties_different_types(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties - data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) schemas = Schemas( - models={ - "First": model_property_factory( + classes_by_reference={ + "/First": model_property_factory( optional_properties=[StringProperty(name="prop", required=True, nullable=True, default=None)] ), - "Second": model_property_factory( + "/Second": model_property_factory( optional_properties=[DateTimeProperty(name="prop", required=True, nullable=True, default=None)] ), } ) - result = _process_properties(data=data, schemas=schemas, class_name="") + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) assert isinstance(result, PropertyError) @@ -248,15 +235,17 @@ def test_conflicting_properties_same_types(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties - data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) schemas = Schemas( - models={ - "First": model_property_factory(optional_properties=[string_property(default="abc")]), - "Second": model_property_factory(optional_properties=[string_property()]), + classes_by_reference={ + "/First": model_property_factory(optional_properties=[string_property(default="abc")]), + "/Second": model_property_factory(optional_properties=[string_property()]), } ) - result = _process_properties(data=data, schemas=schemas, class_name="") + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) assert isinstance(result, PropertyError) @@ -264,16 +253,18 @@ def test_duplicate_properties(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties - data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) prop = string_property() schemas = Schemas( - models={ - "First": model_property_factory(optional_properties=[prop]), - "Second": model_property_factory(optional_properties=[prop]), + classes_by_reference={ + "/First": model_property_factory(optional_properties=[prop]), + "/Second": model_property_factory(optional_properties=[prop]), } ) - result = _process_properties(data=data, schemas=schemas, class_name="") + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) assert result.optional_props == [prop], "There should only be one copy of duplicate properties" @@ -287,19 +278,21 @@ def test_mixed_requirements( from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties - data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="First"), oai.Reference.construct(ref="Second")]) + data = oai.Schema.construct( + allOf=[oai.Reference.construct(ref="#/First"), oai.Reference.construct(ref="#/Second")] + ) schemas = Schemas( - models={ - "First": model_property_factory( + classes_by_reference={ + "/First": model_property_factory( optional_properties=[string_property(required=first_required, nullable=first_nullable)] ), - "Second": model_property_factory( + "/Second": model_property_factory( optional_properties=[string_property(required=second_required, nullable=second_nullable)] ), } ) - result = _process_properties(data=data, schemas=schemas, class_name="") + result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock()) nullable = first_nullable and second_nullable required = first_required or second_required @@ -330,7 +323,7 @@ def test_direct_properties_non_ref(self): ) schemas = Schemas() - result = _process_properties(data=data, schemas=schemas, class_name="") + result = _process_properties(data=data, schemas=schemas, class_name="", config=MagicMock()) assert result.optional_props == [string_property(name="second", required=False, nullable=False)] assert result.required_props == [string_property(name="first", required=True, nullable=False)] diff --git a/tests/test_parser/test_properties/test_schemas.py b/tests/test_parser/test_properties/test_schemas.py index 334ce0f5f..7d961f802 100644 --- a/tests/test_parser/test_properties/test_schemas.py +++ b/tests/test_parser/test_properties/test_schemas.py @@ -2,8 +2,8 @@ def test_class_from_string_default_config(): - from openapi_python_client.parser.properties import Class from openapi_python_client import Config + from openapi_python_client.parser.properties import Class class_ = Class.from_string(string="#/components/schemas/PingResponse", config=Config()) @@ -21,8 +21,8 @@ def test_class_from_string_default_config(): ), ) def test_class_from_string(class_override, module_override, expected_class, expected_module): + from openapi_python_client.config import ClassOverride, Config from openapi_python_client.parser.properties import Class - from openapi_python_client.config import Config, ClassOverride ref = "#/components/schemas/_MyResponse" config = Config( From 503a7a68a4c0b89009517f9215e8c8b82852b5d5 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sun, 4 Apr 2021 10:52:25 -0600 Subject: [PATCH 07/11] test: Fix errors found by E2E test --- .../api/tests/defaults_tests_defaults_post.py | 124 +++++++++------- .../api/tests/get_user_list.py | 31 ++-- .../api/tests/int_enum_tests_int_enum_post.py | 28 ++-- .../tests/json_body_tests_json_body_post.py | 28 ++-- ...tional_value_tests_optional_query_param.py | 15 +- .../tests/upload_file_tests_upload_post.py | 37 ++++- .../my_test_api_client/models/a_model.py | 138 +++++++++++------- .../models/a_model_model.py | 53 ++++++- .../models/a_model_not_required_model.py | 53 ++++++- .../a_model_not_required_nullable_model.py | 53 ++++++- .../models/a_model_nullable_model.py | 53 ++++++- .../models/http_validation_error.py | 9 +- .../models/model_from_all_of.py | 21 ++- .../model_with_additional_properties_refed.py | 20 ++- .../models/model_with_property_ref.py | 12 +- .../models/model_with_union_property.py | 34 +++-- .../openapi_schema_pydantic/reference.py | 4 +- .../templates/endpoint_macros.py.jinja | 2 +- 18 files changed, 511 insertions(+), 204 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py index fc924f5f9..b6aaaea6e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py @@ -5,6 +5,9 @@ from dateutil.parser import isoparse from ...client import Client +from ...models.an_enum import AnEnum +from ...models.http_validation_error import HTTPValidationError +from ...models.model_with_union_property import ModelWithUnionProperty from ...types import UNSET, Response, Unset @@ -20,14 +23,14 @@ def _get_kwargs( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[None]] = UNSET, + list_prop: Union[Unset, List[AnEnum]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[None, Unset, float] = 0.6, - enum_prop: Union[Unset, None] = UNSET, - model_prop: Union[Unset, None] = UNSET, - required_model_prop: None, - nullable_model_prop: Union[None, Unset] = UNSET, - nullable_required_model_prop: None, + union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, + enum_prop: Union[Unset, AnEnum] = UNSET, + model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, + required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, + nullable_required_model_prop: Union[ModelWithUnionProperty, None], ) -> Dict[str, Any]: url = "{}/tests/defaults".format(client.base_url) @@ -54,11 +57,11 @@ def _get_kwargs( if not isinstance(date_prop, Unset): json_date_prop = date_prop.isoformat() - json_list_prop: Union[Unset, List[None]] = UNSET + json_list_prop: Union[Unset, List[str]] = UNSET if not isinstance(list_prop, Unset): json_list_prop = [] for list_prop_item_data in list_prop: - list_prop_item = None + list_prop_item = list_prop_item_data.value json_list_prop.append(list_prop_item) @@ -68,34 +71,42 @@ def _get_kwargs( else: json_union_prop = union_prop - json_union_prop_with_ref: Union[None, Unset, float] + json_union_prop_with_ref: Union[Unset, float, str] if isinstance(union_prop_with_ref, Unset): json_union_prop_with_ref = UNSET - elif isinstance(union_prop_with_ref, None): - json_union_prop_with_ref = None + elif isinstance(union_prop_with_ref, AnEnum): + json_union_prop_with_ref = UNSET + if not isinstance(union_prop_with_ref, Unset): + json_union_prop_with_ref = union_prop_with_ref.value else: json_union_prop_with_ref = union_prop_with_ref - json_enum_prop = None + json_enum_prop: Union[Unset, str] = UNSET + if not isinstance(enum_prop, Unset): + json_enum_prop = enum_prop.value - json_model_prop = None + json_model_prop: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(model_prop, Unset): + json_model_prop = model_prop.to_dict() - json_required_model_prop = None + json_required_model_prop = required_model_prop.to_dict() - json_nullable_model_prop: Union[None, Unset] + json_nullable_model_prop: Union[Dict[str, Any], None, Unset] if isinstance(nullable_model_prop, Unset): json_nullable_model_prop = UNSET elif nullable_model_prop is None: json_nullable_model_prop = None else: - json_nullable_model_prop = None + json_nullable_model_prop = UNSET + if not isinstance(nullable_model_prop, Unset): + json_nullable_model_prop = nullable_model_prop.to_dict() - json_nullable_required_model_prop: None + json_nullable_required_model_prop: Union[Dict[str, Any], None] if nullable_required_model_prop is None: json_nullable_required_model_prop = None else: - json_nullable_required_model_prop = None + json_nullable_required_model_prop = nullable_required_model_prop.to_dict() params: Dict[str, Any] = { "string_prop": string_prop, @@ -111,11 +122,12 @@ def _get_kwargs( "union_prop": json_union_prop, "union_prop_with_ref": json_union_prop_with_ref, "enum_prop": json_enum_prop, - "model_prop": json_model_prop, - "required_model_prop": json_required_model_prop, "nullable_model_prop": json_nullable_model_prop, "nullable_required_model_prop": json_nullable_required_model_prop, } + if not isinstance(json_model_prop, Unset): + params.update(json_model_prop) + params.update(json_required_model_prop) params = {k: v for k, v in params.items() if v is not UNSET and v is not None} return { @@ -127,19 +139,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -160,15 +172,15 @@ def sync_detailed( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[None]] = UNSET, + list_prop: Union[Unset, List[AnEnum]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[None, Unset, float] = 0.6, - enum_prop: Union[Unset, None] = UNSET, - model_prop: Union[Unset, None] = UNSET, - required_model_prop: None, - nullable_model_prop: Union[None, Unset] = UNSET, - nullable_required_model_prop: None, -) -> Response[Union[None, None]]: + union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, + enum_prop: Union[Unset, AnEnum] = UNSET, + model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, + required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, + nullable_required_model_prop: Union[ModelWithUnionProperty, None], +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, string_prop=string_prop, @@ -209,15 +221,15 @@ def sync( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[None]] = UNSET, + list_prop: Union[Unset, List[AnEnum]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[None, Unset, float] = 0.6, - enum_prop: Union[Unset, None] = UNSET, - model_prop: Union[Unset, None] = UNSET, - required_model_prop: None, - nullable_model_prop: Union[None, Unset] = UNSET, - nullable_required_model_prop: None, -) -> Optional[Union[None, None]]: + union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, + enum_prop: Union[Unset, AnEnum] = UNSET, + model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, + required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, + nullable_required_model_prop: Union[ModelWithUnionProperty, None], +) -> Optional[Union[None, HTTPValidationError]]: """ """ return sync_detailed( @@ -254,15 +266,15 @@ async def asyncio_detailed( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[None]] = UNSET, + list_prop: Union[Unset, List[AnEnum]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[None, Unset, float] = 0.6, - enum_prop: Union[Unset, None] = UNSET, - model_prop: Union[Unset, None] = UNSET, - required_model_prop: None, - nullable_model_prop: Union[None, Unset] = UNSET, - nullable_required_model_prop: None, -) -> Response[Union[None, None]]: + union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, + enum_prop: Union[Unset, AnEnum] = UNSET, + model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, + required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, + nullable_required_model_prop: Union[ModelWithUnionProperty, None], +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, string_prop=string_prop, @@ -302,15 +314,15 @@ async def asyncio( float_prop: Union[Unset, float] = 3.14, int_prop: Union[Unset, int] = 7, boolean_prop: Union[Unset, bool] = False, - list_prop: Union[Unset, List[None]] = UNSET, + list_prop: Union[Unset, List[AnEnum]] = UNSET, union_prop: Union[Unset, float, str] = "not a float", - union_prop_with_ref: Union[None, Unset, float] = 0.6, - enum_prop: Union[Unset, None] = UNSET, - model_prop: Union[Unset, None] = UNSET, - required_model_prop: None, - nullable_model_prop: Union[None, Unset] = UNSET, - nullable_required_model_prop: None, -) -> Optional[Union[None, None]]: + union_prop_with_ref: Union[AnEnum, Unset, float] = 0.6, + enum_prop: Union[Unset, AnEnum] = UNSET, + model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, + required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[ModelWithUnionProperty, None, Unset] = UNSET, + nullable_required_model_prop: Union[ModelWithUnionProperty, None], +) -> Optional[Union[None, HTTPValidationError]]: """ """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index 5cc5cde5c..ec2216810 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -4,13 +4,16 @@ import httpx from ...client import Client +from ...models.a_model import AModel +from ...models.an_enum import AnEnum +from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response def _get_kwargs( *, client: Client, - an_enum_value: List[None], + an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], ) -> Dict[str, Any]: url = "{}/tests/".format(client.base_url) @@ -20,7 +23,7 @@ def _get_kwargs( json_an_enum_value = [] for an_enum_value_item_data in an_enum_value: - an_enum_value_item = None + an_enum_value_item = an_enum_value_item_data.value json_an_enum_value.append(an_enum_value_item) @@ -44,24 +47,24 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[List[None], None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]: if response.status_code == 200: response_200 = [] _response_200 = response.json() for response_200_item_data in _response_200: - response_200_item = None + response_200_item = AModel.from_dict(response_200_item_data) response_200.append(response_200_item) return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[List[None], None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[List[AModel], HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -73,9 +76,9 @@ def _build_response(*, response: httpx.Response) -> Response[Union[List[None], N def sync_detailed( *, client: Client, - an_enum_value: List[None], + an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Response[Union[List[None], None]]: +) -> Response[Union[List[AModel], HTTPValidationError]]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -92,9 +95,9 @@ def sync_detailed( def sync( *, client: Client, - an_enum_value: List[None], + an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[Union[List[None], None]]: +) -> Optional[Union[List[AModel], HTTPValidationError]]: """ Get a list of things """ return sync_detailed( @@ -107,9 +110,9 @@ def sync( async def asyncio_detailed( *, client: Client, - an_enum_value: List[None], + an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Response[Union[List[None], None]]: +) -> Response[Union[List[AModel], HTTPValidationError]]: kwargs = _get_kwargs( client=client, an_enum_value=an_enum_value, @@ -125,9 +128,9 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - an_enum_value: List[None], + an_enum_value: List[AnEnum], some_date: Union[datetime.date, datetime.datetime], -) -> Optional[Union[List[None], None]]: +) -> Optional[Union[List[AModel], HTTPValidationError]]: """ Get a list of things """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py index 96aed7f17..7d14632c4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py @@ -3,20 +3,22 @@ import httpx from ...client import Client +from ...models.an_int_enum import AnIntEnum +from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response def _get_kwargs( *, client: Client, - int_enum: None, + int_enum: AnIntEnum, ) -> Dict[str, Any]: url = "{}/tests/int_enum".format(client.base_url) headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - json_int_enum = None + json_int_enum = int_enum.value params: Dict[str, Any] = { "int_enum": json_int_enum, @@ -32,19 +34,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -56,8 +58,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: def sync_detailed( *, client: Client, - int_enum: None, -) -> Response[Union[None, None]]: + int_enum: AnIntEnum, +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, int_enum=int_enum, @@ -73,8 +75,8 @@ def sync_detailed( def sync( *, client: Client, - int_enum: None, -) -> Optional[Union[None, None]]: + int_enum: AnIntEnum, +) -> Optional[Union[None, HTTPValidationError]]: """ """ return sync_detailed( @@ -86,8 +88,8 @@ def sync( async def asyncio_detailed( *, client: Client, - int_enum: None, -) -> Response[Union[None, None]]: + int_enum: AnIntEnum, +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, int_enum=int_enum, @@ -102,8 +104,8 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - int_enum: None, -) -> Optional[Union[None, None]]: + int_enum: AnIntEnum, +) -> Optional[Union[None, HTTPValidationError]]: """ """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py index a9a89a47c..074ab9d89 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -3,20 +3,22 @@ import httpx from ...client import Client +from ...models.a_model import AModel +from ...models.http_validation_error import HTTPValidationError from ...types import Response def _get_kwargs( *, client: Client, - json_body: None, + json_body: AModel, ) -> Dict[str, Any]: url = "{}/tests/json_body".format(client.base_url) headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - json_json_body = None + json_json_body = json_body.to_dict() return { "url": url, @@ -27,19 +29,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -51,8 +53,8 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: def sync_detailed( *, client: Client, - json_body: None, -) -> Response[Union[None, None]]: + json_body: AModel, +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, json_body=json_body, @@ -68,8 +70,8 @@ def sync_detailed( def sync( *, client: Client, - json_body: None, -) -> Optional[Union[None, None]]: + json_body: AModel, +) -> Optional[Union[None, HTTPValidationError]]: """ Try sending a JSON body """ return sync_detailed( @@ -81,8 +83,8 @@ def sync( async def asyncio_detailed( *, client: Client, - json_body: None, -) -> Response[Union[None, None]]: + json_body: AModel, +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, json_body=json_body, @@ -97,8 +99,8 @@ async def asyncio_detailed( async def asyncio( *, client: Client, - json_body: None, -) -> Optional[Union[None, None]]: + json_body: AModel, +) -> Optional[Union[None, HTTPValidationError]]: """ Try sending a JSON body """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py index 2f1f1d48b..64431ba2f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py @@ -3,6 +3,7 @@ import httpx from ...client import Client +from ...models.http_validation_error import HTTPValidationError from ...types import UNSET, Response, Unset @@ -34,19 +35,19 @@ def _get_kwargs( } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -59,7 +60,7 @@ def sync_detailed( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Response[Union[None, None]]: +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, query_param=query_param, @@ -76,7 +77,7 @@ def sync( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[None, None]]: +) -> Optional[Union[None, HTTPValidationError]]: """ Test optional query parameters """ return sync_detailed( @@ -89,7 +90,7 @@ async def asyncio_detailed( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Response[Union[None, None]]: +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, query_param=query_param, @@ -105,7 +106,7 @@ async def asyncio( *, client: Client, query_param: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[None, None]]: +) -> Optional[Union[None, HTTPValidationError]]: """ Test optional query parameters """ return ( diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index c06943593..705443d95 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -3,12 +3,15 @@ import httpx from ...client import Client -from ...types import UNSET, Response, Unset +from ...models.body_upload_file_tests_upload_post import BodyUploadFileTestsUploadPost +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, File, Response, Unset def _get_kwargs( *, client: Client, + multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, ) -> Dict[str, Any]: url = "{}/tests/upload".format(client.base_url) @@ -19,27 +22,37 @@ def _get_kwargs( if keep_alive is not UNSET: headers["keep-alive"] = keep_alive + files = {} + data = {} + for key, value in multipart_data.to_dict().items(): + if isinstance(value, File): + files[key] = value + else: + data[key] = value + return { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), + "files": files, + "data": data, } -def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: +def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: response_200 = None return response_200 if response.status_code == 422: - response_422 = None + response_422 = HTTPValidationError.from_dict(response.json()) return response_422 return None -def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: +def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]: return Response( status_code=response.status_code, content=response.content, @@ -51,10 +64,12 @@ def _build_response(*, response: httpx.Response) -> Response[Union[None, None]]: def sync_detailed( *, client: Client, + multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Response[Union[None, None]]: +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, + multipart_data=multipart_data, keep_alive=keep_alive, ) @@ -68,12 +83,14 @@ def sync_detailed( def sync( *, client: Client, + multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Optional[Union[None, None]]: +) -> Optional[Union[None, HTTPValidationError]]: """ Upload a file """ return sync_detailed( client=client, + multipart_data=multipart_data, keep_alive=keep_alive, ).parsed @@ -81,10 +98,12 @@ def sync( async def asyncio_detailed( *, client: Client, + multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Response[Union[None, None]]: +) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, + multipart_data=multipart_data, keep_alive=keep_alive, ) @@ -97,13 +116,15 @@ async def asyncio_detailed( async def asyncio( *, client: Client, + multipart_data: BodyUploadFileTestsUploadPost, keep_alive: Union[Unset, bool] = UNSET, -) -> Optional[Union[None, None]]: +) -> Optional[Union[None, HTTPValidationError]]: """ Upload a file """ return ( await asyncio_detailed( client=client, + multipart_data=multipart_data, keep_alive=keep_alive, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py index 92a9738d9..91b62c01b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -8,6 +8,10 @@ from ..models.a_model_not_required_model import AModelNotRequiredModel from ..models.a_model_not_required_nullable_model import AModelNotRequiredNullableModel from ..models.a_model_nullable_model import AModelNullableModel +from ..models.an_enum import AnEnum +from ..models.different_enum import DifferentEnum +from ..models.free_form_model import FreeFormModel +from ..models.model_with_union_property import ModelWithUnionProperty from ..types import UNSET, Unset T = TypeVar("T", bound="AModel") @@ -17,28 +21,28 @@ class AModel: """ A Model for testing all the ways custom objects can be used """ - an_enum_value: None + an_enum_value: AnEnum a_camel_date_time: Union[datetime.date, datetime.datetime] a_date: datetime.date required_not_nullable: str - one_of_models: None + one_of_models: Union[FreeFormModel, ModelWithUnionProperty] model: AModelModel a_nullable_date: Optional[datetime.date] required_nullable: Optional[str] - nullable_one_of_models: None + nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None] nullable_model: Optional[AModelNullableModel] - nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET + nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET a_not_required_date: Union[Unset, datetime.date] = UNSET attr_1_leading_digit: Union[Unset, str] = UNSET not_required_nullable: Union[Unset, None, str] = UNSET not_required_not_nullable: Union[Unset, str] = UNSET - not_required_one_of_models: Union[None, Unset] = UNSET - not_required_nullable_one_of_models: Union[None, Unset, str] = UNSET + not_required_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, Unset] = UNSET + not_required_nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str] = UNSET not_required_model: Union[Unset, AModelNotRequiredModel] = UNSET not_required_nullable_model: Union[Unset, None, AModelNotRequiredNullableModel] = UNSET def to_dict(self) -> Dict[str, Any]: - an_enum_value = None + an_enum_value = self.an_enum_value.value if isinstance(self.a_camel_date_time, datetime.datetime): a_camel_date_time = self.a_camel_date_time.isoformat() @@ -48,21 +52,21 @@ def to_dict(self) -> Dict[str, Any]: a_date = self.a_date.isoformat() required_not_nullable = self.required_not_nullable - if isinstance(self.one_of_models, None): - one_of_models = None + if isinstance(self.one_of_models, FreeFormModel): + one_of_models = self.one_of_models.to_dict() else: - one_of_models = None + one_of_models = self.one_of_models.to_dict() model = self.model.to_dict() - nested_list_of_enums: Union[Unset, List[List[None]]] = UNSET + nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET if not isinstance(self.nested_list_of_enums, Unset): nested_list_of_enums = [] for nested_list_of_enums_item_data in self.nested_list_of_enums: nested_list_of_enums_item = [] for nested_list_of_enums_item_item_data in nested_list_of_enums_item_data: - nested_list_of_enums_item_item = None + nested_list_of_enums_item_item = nested_list_of_enums_item_item_data.value nested_list_of_enums_item.append(nested_list_of_enums_item_item) @@ -77,34 +81,42 @@ def to_dict(self) -> Dict[str, Any]: required_nullable = self.required_nullable not_required_nullable = self.not_required_nullable not_required_not_nullable = self.not_required_not_nullable - nullable_one_of_models: None + nullable_one_of_models: Union[Dict[str, Any], None] if self.nullable_one_of_models is None: nullable_one_of_models = None - elif isinstance(self.nullable_one_of_models, None): - nullable_one_of_models = None + elif isinstance(self.nullable_one_of_models, FreeFormModel): + nullable_one_of_models = self.nullable_one_of_models.to_dict() else: - nullable_one_of_models = None + nullable_one_of_models = self.nullable_one_of_models.to_dict() - not_required_one_of_models: Union[None, Unset] + not_required_one_of_models: Union[Dict[str, Any], Unset] if isinstance(self.not_required_one_of_models, Unset): not_required_one_of_models = UNSET - elif isinstance(self.not_required_one_of_models, None): - not_required_one_of_models = None + elif isinstance(self.not_required_one_of_models, FreeFormModel): + not_required_one_of_models = UNSET + if not isinstance(self.not_required_one_of_models, Unset): + not_required_one_of_models = self.not_required_one_of_models.to_dict() else: - not_required_one_of_models = None + not_required_one_of_models = UNSET + if not isinstance(self.not_required_one_of_models, Unset): + not_required_one_of_models = self.not_required_one_of_models.to_dict() - not_required_nullable_one_of_models: Union[None, Unset, str] + not_required_nullable_one_of_models: Union[Dict[str, Any], None, Unset, str] if isinstance(self.not_required_nullable_one_of_models, Unset): not_required_nullable_one_of_models = UNSET elif self.not_required_nullable_one_of_models is None: not_required_nullable_one_of_models = None - elif isinstance(self.not_required_nullable_one_of_models, None): - not_required_nullable_one_of_models = None + elif isinstance(self.not_required_nullable_one_of_models, FreeFormModel): + not_required_nullable_one_of_models = UNSET + if not isinstance(self.not_required_nullable_one_of_models, Unset): + not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict() - elif isinstance(self.not_required_nullable_one_of_models, None): - not_required_nullable_one_of_models = None + elif isinstance(self.not_required_nullable_one_of_models, ModelWithUnionProperty): + not_required_nullable_one_of_models = UNSET + if not isinstance(self.not_required_nullable_one_of_models, Unset): + not_required_nullable_one_of_models = self.not_required_nullable_one_of_models.to_dict() else: not_required_nullable_one_of_models = self.not_required_nullable_one_of_models @@ -160,7 +172,7 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - an_enum_value = None + an_enum_value = AnEnum(d.pop("an_enum_value")) def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]: try: @@ -185,22 +197,20 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date required_not_nullable = d.pop("required_not_nullable") - def _parse_one_of_models(data: object) -> None: - if data is None: - return data + def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]: try: - one_of_models_type0: None - if not data is None: + one_of_models_type0: FreeFormModel + if not isinstance(data, dict): raise TypeError() - one_of_models_type0 = UNSET + one_of_models_type0 = FreeFormModel.from_dict(data) return one_of_models_type0 except: # noqa: E722 pass - if not data is None: + if not isinstance(data, dict): raise TypeError() - one_of_models_type1: None - one_of_models_type1 = UNSET + one_of_models_type1: ModelWithUnionProperty + one_of_models_type1 = ModelWithUnionProperty.from_dict(data) return one_of_models_type1 @@ -214,7 +224,7 @@ def _parse_one_of_models(data: object) -> None: nested_list_of_enums_item = [] _nested_list_of_enums_item = nested_list_of_enums_item_data for nested_list_of_enums_item_item_data in _nested_list_of_enums_item: - nested_list_of_enums_item_item = None + nested_list_of_enums_item_item = DifferentEnum(nested_list_of_enums_item_item_data) nested_list_of_enums_item.append(nested_list_of_enums_item_item) @@ -238,74 +248,90 @@ def _parse_one_of_models(data: object) -> None: not_required_not_nullable = d.pop("not_required_not_nullable", UNSET) - def _parse_nullable_one_of_models(data: object) -> None: + def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, None]: if data is None: return data try: - nullable_one_of_models_type0: None - if not data is None: + nullable_one_of_models_type0: FreeFormModel + if not isinstance(data, dict): raise TypeError() - nullable_one_of_models_type0 = UNSET + nullable_one_of_models_type0 = FreeFormModel.from_dict(data) return nullable_one_of_models_type0 except: # noqa: E722 pass - if not data is None: + if not isinstance(data, dict): raise TypeError() - nullable_one_of_models_type1: None - nullable_one_of_models_type1 = UNSET + nullable_one_of_models_type1: ModelWithUnionProperty + nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict(data) return nullable_one_of_models_type1 nullable_one_of_models = _parse_nullable_one_of_models(d.pop("nullable_one_of_models")) - def _parse_not_required_one_of_models(data: object) -> Union[None, Unset]: - if data is None: - return data + def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty, Unset]: if isinstance(data, Unset): return data try: - not_required_one_of_models_type0: Union[Unset, None] - if not data is None: + not_required_one_of_models_type0: Union[Unset, FreeFormModel] + if not isinstance(data, dict): raise TypeError() not_required_one_of_models_type0 = UNSET + _not_required_one_of_models_type0 = data + if not isinstance(_not_required_one_of_models_type0, Unset): + not_required_one_of_models_type0 = FreeFormModel.from_dict(_not_required_one_of_models_type0) return not_required_one_of_models_type0 except: # noqa: E722 pass - if not data is None: + if not isinstance(data, dict): raise TypeError() - not_required_one_of_models_type1: Union[Unset, None] + not_required_one_of_models_type1: Union[Unset, ModelWithUnionProperty] not_required_one_of_models_type1 = UNSET + _not_required_one_of_models_type1 = data + if not isinstance(_not_required_one_of_models_type1, Unset): + not_required_one_of_models_type1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type1) return not_required_one_of_models_type1 not_required_one_of_models = _parse_not_required_one_of_models(d.pop("not_required_one_of_models", UNSET)) - def _parse_not_required_nullable_one_of_models(data: object) -> Union[None, Unset, str]: + def _parse_not_required_nullable_one_of_models( + data: object, + ) -> Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str]: if data is None: return data if isinstance(data, Unset): return data try: - not_required_nullable_one_of_models_type0: Union[Unset, None] - if not data is None: + not_required_nullable_one_of_models_type0: Union[Unset, FreeFormModel] + if not isinstance(data, dict): raise TypeError() not_required_nullable_one_of_models_type0 = UNSET + _not_required_nullable_one_of_models_type0 = data + if not isinstance(_not_required_nullable_one_of_models_type0, Unset): + not_required_nullable_one_of_models_type0 = FreeFormModel.from_dict( + _not_required_nullable_one_of_models_type0 + ) return not_required_nullable_one_of_models_type0 except: # noqa: E722 pass try: - not_required_nullable_one_of_models_type1: Union[Unset, None] - if not data is None: + not_required_nullable_one_of_models_type1: Union[Unset, ModelWithUnionProperty] + if not isinstance(data, dict): raise TypeError() not_required_nullable_one_of_models_type1 = UNSET + _not_required_nullable_one_of_models_type1 = data + if not isinstance(_not_required_nullable_one_of_models_type1, Unset): + not_required_nullable_one_of_models_type1 = ModelWithUnionProperty.from_dict( + _not_required_nullable_one_of_models_type1 + ) return not_required_nullable_one_of_models_type1 except: # noqa: E722 pass - return cast(Union[None, Unset, str], data) + return cast(Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str], data) not_required_nullable_one_of_models = _parse_not_required_nullable_one_of_models( d.pop("not_required_nullable_one_of_models", UNSET) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py index 55ff303f9..0a7a54bd6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_model.py @@ -1,7 +1,11 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, Dict, List, Type, TypeVar, Union import attr +from ..models.an_enum import AnEnum +from ..models.an_int_enum import AnIntEnum +from ..types import UNSET, Unset + T = TypeVar("T", bound="AModelModel") @@ -9,20 +13,65 @@ class AModelModel: """ """ + a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: + a_property: Union[Unset, int, str] + if isinstance(self.a_property, Unset): + a_property = UNSET + elif isinstance(self.a_property, AnEnum): + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value + + else: + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if a_property is not UNSET: + field_dict["a_property"] = a_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_model_model = cls() + + def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: + if isinstance(data, Unset): + return data + try: + a_property_type0: Union[Unset, AnEnum] + if not isinstance(data, str): + raise TypeError() + a_property_type0 = UNSET + _a_property_type0 = data + if not isinstance(_a_property_type0, Unset): + a_property_type0 = AnEnum(_a_property_type0) + + return a_property_type0 + except: # noqa: E722 + pass + if not isinstance(data, int): + raise TypeError() + a_property_type1: Union[Unset, AnIntEnum] + a_property_type1 = UNSET + _a_property_type1 = data + if not isinstance(_a_property_type1, Unset): + a_property_type1 = AnIntEnum(_a_property_type1) + + return a_property_type1 + + a_property = _parse_a_property(d.pop("a_property", UNSET)) + + a_model_model = cls( + a_property=a_property, + ) a_model_model.additional_properties = d return a_model_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py index 4c86e1019..fd568db52 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_model.py @@ -1,7 +1,11 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, Dict, List, Type, TypeVar, Union import attr +from ..models.an_enum import AnEnum +from ..models.an_int_enum import AnIntEnum +from ..types import UNSET, Unset + T = TypeVar("T", bound="AModelNotRequiredModel") @@ -9,20 +13,65 @@ class AModelNotRequiredModel: """ """ + a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: + a_property: Union[Unset, int, str] + if isinstance(self.a_property, Unset): + a_property = UNSET + elif isinstance(self.a_property, AnEnum): + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value + + else: + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if a_property is not UNSET: + field_dict["a_property"] = a_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_model_not_required_model = cls() + + def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: + if isinstance(data, Unset): + return data + try: + a_property_type0: Union[Unset, AnEnum] + if not isinstance(data, str): + raise TypeError() + a_property_type0 = UNSET + _a_property_type0 = data + if not isinstance(_a_property_type0, Unset): + a_property_type0 = AnEnum(_a_property_type0) + + return a_property_type0 + except: # noqa: E722 + pass + if not isinstance(data, int): + raise TypeError() + a_property_type1: Union[Unset, AnIntEnum] + a_property_type1 = UNSET + _a_property_type1 = data + if not isinstance(_a_property_type1, Unset): + a_property_type1 = AnIntEnum(_a_property_type1) + + return a_property_type1 + + a_property = _parse_a_property(d.pop("a_property", UNSET)) + + a_model_not_required_model = cls( + a_property=a_property, + ) a_model_not_required_model.additional_properties = d return a_model_not_required_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py index c0bf0fafb..6413e7f9a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_not_required_nullable_model.py @@ -1,7 +1,11 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, Dict, List, Type, TypeVar, Union import attr +from ..models.an_enum import AnEnum +from ..models.an_int_enum import AnIntEnum +from ..types import UNSET, Unset + T = TypeVar("T", bound="AModelNotRequiredNullableModel") @@ -9,20 +13,65 @@ class AModelNotRequiredNullableModel: """ """ + a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: + a_property: Union[Unset, int, str] + if isinstance(self.a_property, Unset): + a_property = UNSET + elif isinstance(self.a_property, AnEnum): + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value + + else: + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if a_property is not UNSET: + field_dict["a_property"] = a_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_model_not_required_nullable_model = cls() + + def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: + if isinstance(data, Unset): + return data + try: + a_property_type0: Union[Unset, AnEnum] + if not isinstance(data, str): + raise TypeError() + a_property_type0 = UNSET + _a_property_type0 = data + if not isinstance(_a_property_type0, Unset): + a_property_type0 = AnEnum(_a_property_type0) + + return a_property_type0 + except: # noqa: E722 + pass + if not isinstance(data, int): + raise TypeError() + a_property_type1: Union[Unset, AnIntEnum] + a_property_type1 = UNSET + _a_property_type1 = data + if not isinstance(_a_property_type1, Unset): + a_property_type1 = AnIntEnum(_a_property_type1) + + return a_property_type1 + + a_property = _parse_a_property(d.pop("a_property", UNSET)) + + a_model_not_required_nullable_model = cls( + a_property=a_property, + ) a_model_not_required_nullable_model.additional_properties = d return a_model_not_required_nullable_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py index fe66227fb..cc6484d5f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_nullable_model.py @@ -1,7 +1,11 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, Dict, List, Type, TypeVar, Union import attr +from ..models.an_enum import AnEnum +from ..models.an_int_enum import AnIntEnum +from ..types import UNSET, Unset + T = TypeVar("T", bound="AModelNullableModel") @@ -9,20 +13,65 @@ class AModelNullableModel: """ """ + a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: + a_property: Union[Unset, int, str] + if isinstance(self.a_property, Unset): + a_property = UNSET + elif isinstance(self.a_property, AnEnum): + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value + + else: + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if a_property is not UNSET: + field_dict["a_property"] = a_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_model_nullable_model = cls() + + def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: + if isinstance(data, Unset): + return data + try: + a_property_type0: Union[Unset, AnEnum] + if not isinstance(data, str): + raise TypeError() + a_property_type0 = UNSET + _a_property_type0 = data + if not isinstance(_a_property_type0, Unset): + a_property_type0 = AnEnum(_a_property_type0) + + return a_property_type0 + except: # noqa: E722 + pass + if not isinstance(data, int): + raise TypeError() + a_property_type1: Union[Unset, AnIntEnum] + a_property_type1 = UNSET + _a_property_type1 = data + if not isinstance(_a_property_type1, Unset): + a_property_type1 = AnIntEnum(_a_property_type1) + + return a_property_type1 + + a_property = _parse_a_property(d.pop("a_property", UNSET)) + + a_model_nullable_model = cls( + a_property=a_property, + ) a_model_nullable_model.additional_properties = d return a_model_nullable_model diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py index 92ad83e50..feb2cdd6b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/http_validation_error.py @@ -2,6 +2,7 @@ import attr +from ..models.validation_error import ValidationError from ..types import UNSET, Unset T = TypeVar("T", bound="HTTPValidationError") @@ -11,14 +12,14 @@ class HTTPValidationError: """ """ - detail: Union[Unset, List[None]] = UNSET + detail: Union[Unset, List[ValidationError]] = UNSET def to_dict(self) -> Dict[str, Any]: - detail: Union[Unset, List[None]] = UNSET + detail: Union[Unset, List[Dict[str, Any]]] = UNSET if not isinstance(self.detail, Unset): detail = [] for detail_item_data in self.detail: - detail_item = None + detail_item = detail_item_data.to_dict() detail.append(detail_item) @@ -35,7 +36,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: detail = [] _detail = d.pop("detail", UNSET) for detail_item_data in _detail or []: - detail_item = None + detail_item = ValidationError.from_dict(detail_item_data) detail.append(detail_item) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py index 7f4ef8bbd..ce26a3bbb 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_from_all_of.py @@ -1,7 +1,9 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, Dict, List, Type, TypeVar, Union import attr +from ..types import UNSET, Unset + T = TypeVar("T", bound="ModelFromAllOf") @@ -9,20 +11,35 @@ class ModelFromAllOf: """ """ + a_sub_property: Union[Unset, str] = UNSET + another_sub_property: Union[Unset, str] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: + a_sub_property = self.a_sub_property + another_sub_property = self.another_sub_property field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if a_sub_property is not UNSET: + field_dict["a_sub_property"] = a_sub_property + if another_sub_property is not UNSET: + field_dict["another_sub_property"] = another_sub_property return field_dict @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - model_from_all_of = cls() + a_sub_property = d.pop("a_sub_property", UNSET) + + another_sub_property = d.pop("another_sub_property", UNSET) + + model_from_all_of = cls( + a_sub_property=a_sub_property, + another_sub_property=another_sub_property, + ) model_from_all_of.additional_properties = d return model_from_all_of diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py index 1cfd6fdc7..b265db582 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_additional_properties_refed.py @@ -2,6 +2,8 @@ import attr +from ..models.an_enum import AnEnum + T = TypeVar("T", bound="ModelWithAdditionalPropertiesRefed") @@ -9,12 +11,14 @@ class ModelWithAdditionalPropertiesRefed: """ """ - additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + additional_properties: Dict[str, AnEnum] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: field_dict: Dict[str, Any] = {} - field_dict.update(self.additional_properties) + for prop_name, prop in self.additional_properties.items(): + field_dict[prop_name] = prop.value + field_dict.update({}) return field_dict @@ -24,17 +28,23 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() model_with_additional_properties_refed = cls() - model_with_additional_properties_refed.additional_properties = d + additional_properties = {} + for prop_name, prop_dict in d.items(): + additional_property = AnEnum(prop_dict) + + additional_properties[prop_name] = additional_property + + model_with_additional_properties_refed.additional_properties = additional_properties return model_with_additional_properties_refed @property def additional_keys(self) -> List[str]: return list(self.additional_properties.keys()) - def __getitem__(self, key: str) -> Any: + def __getitem__(self, key: str) -> AnEnum: return self.additional_properties[key] - def __setitem__(self, key: str, value: Any) -> None: + def __setitem__(self, key: str, value: AnEnum) -> None: self.additional_properties[key] = value def __delitem__(self, key: str) -> None: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py index cfb47ded3..1553914ba 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py @@ -2,6 +2,7 @@ import attr +from ..models.model_name import ModelName from ..types import UNSET, Unset T = TypeVar("T", bound="ModelWithPropertyRef") @@ -11,11 +12,13 @@ class ModelWithPropertyRef: """ """ - inner: Union[Unset, None] = UNSET + inner: Union[Unset, ModelName] = UNSET additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) def to_dict(self) -> Dict[str, Any]: - inner = None + inner: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.inner, Unset): + inner = self.inner.to_dict() field_dict: Dict[str, Any] = {} field_dict.update(self.additional_properties) @@ -28,7 +31,10 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - inner = None + inner: Union[Unset, ModelName] = UNSET + _inner = d.pop("inner", UNSET) + if not isinstance(_inner, Unset): + inner = ModelName.from_dict(_inner) model_with_property_ref = cls( inner=inner, diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py index c7a849cbf..a3f049533 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py @@ -2,6 +2,8 @@ import attr +from ..models.an_enum import AnEnum +from ..models.an_int_enum import AnIntEnum from ..types import UNSET, Unset T = TypeVar("T", bound="ModelWithUnionProperty") @@ -11,17 +13,21 @@ class ModelWithUnionProperty: """ """ - a_property: Union[None, Unset] = UNSET + a_property: Union[AnEnum, AnIntEnum, Unset] = UNSET def to_dict(self) -> Dict[str, Any]: - a_property: Union[None, Unset] + a_property: Union[Unset, int, str] if isinstance(self.a_property, Unset): a_property = UNSET - elif isinstance(self.a_property, None): - a_property = None + elif isinstance(self.a_property, AnEnum): + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value else: - a_property = None + a_property = UNSET + if not isinstance(self.a_property, Unset): + a_property = self.a_property.value field_dict: Dict[str, Any] = {} field_dict.update({}) @@ -34,24 +40,28 @@ def to_dict(self) -> Dict[str, Any]: def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - def _parse_a_property(data: object) -> Union[None, Unset]: - if data is None: - return data + def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: if isinstance(data, Unset): return data try: - a_property_type0: Union[Unset, None] - if not data is None: + a_property_type0: Union[Unset, AnEnum] + if not isinstance(data, str): raise TypeError() a_property_type0 = UNSET + _a_property_type0 = data + if not isinstance(_a_property_type0, Unset): + a_property_type0 = AnEnum(_a_property_type0) return a_property_type0 except: # noqa: E722 pass - if not data is None: + if not isinstance(data, int): raise TypeError() - a_property_type1: Union[Unset, None] + a_property_type1: Union[Unset, AnIntEnum] a_property_type1 = UNSET + _a_property_type1 = data + if not isinstance(_a_property_type1, Unset): + a_property_type1 = AnIntEnum(_a_property_type1) return a_property_type1 diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py index 5a0bfc644..7803b3a54 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py @@ -1,4 +1,4 @@ -from pydantic import AnyUrl, BaseModel, Field +from pydantic import BaseModel, Field class Reference(BaseModel): @@ -12,7 +12,7 @@ class Reference(BaseModel): and not by the JSON Schema specification. """ - ref: AnyUrl = Field(alias="$ref") + ref: str = Field(alias="$ref") """**REQUIRED**. The reference string.""" class Config: diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index 45fe6d7c3..b226403ba 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -104,7 +104,7 @@ form_data: {{ endpoint.form_body_class.name }}, {% endif %} {# Multipart data if any #} {% if endpoint.multipart_body_class %} -multipart_data: {{ endpoint.multipart_body_class.class_name }}, +multipart_data: {{ endpoint.multipart_body_class.name }}, {% endif %} {# JSON body if any #} {% if endpoint.json_body %} From 0648aa03205d967188f395929f67bf37b2e84d93 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sun, 4 Apr 2021 10:58:13 -0600 Subject: [PATCH 08/11] docs: Update usage.md --- openapi_python_client/cli.py | 3 +-- usage.md | 44 +++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index 953c92234..d84062fd4 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -6,10 +6,9 @@ import typer from openapi_python_client import MetaType +from openapi_python_client.config import Config from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError -from .config import Config - app = typer.Typer() diff --git a/usage.md b/usage.md index 4890e0e37..709534d05 100644 --- a/usage.md +++ b/usage.md @@ -1,6 +1,6 @@ # `openapi-python-client` -Generate a Python client from an OpenAPI JSON document +Generate a Python client from an OpenAPI JSON document **Usage**: @@ -10,20 +10,19 @@ $ openapi-python-client [OPTIONS] COMMAND [ARGS]... **Options**: -* `--version`: Print the version and exit [default: False] -* `--config PATH`: Path to the config file to use -* `--install-completion`: Install completion for the current shell. -* `--show-completion`: Show completion for the current shell, to copy it or customize the installation. -* `--help`: Show this message and exit. +- `--version`: Print the version and exit [default: False] +- `--install-completion`: Install completion for the current shell. +- `--show-completion`: Show completion for the current shell, to copy it or customize the installation. +- `--help`: Show this message and exit. **Commands**: -* `generate`: Generate a new OpenAPI Client library -* `update`: Update an existing OpenAPI Client library +- `generate`: Generate a new OpenAPI Client library +- `update`: Update an existing OpenAPI Client library ## `openapi-python-client generate` -Generate a new OpenAPI Client library +Generate a new OpenAPI Client library **Usage**: @@ -33,15 +32,17 @@ $ openapi-python-client generate [OPTIONS] **Options**: -* `--url TEXT`: A URL to read the JSON from -* `--path PATH`: A path to the JSON file -* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) -* `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] -* `--help`: Show this message and exit. +- `--url TEXT`: A URL to read the JSON from +- `--path PATH`: A path to the JSON file +- `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) +- `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] +- `--file-encoding TEXT`: Encoding used when writing generated [default: utf-8] +- `--config PATH`: Path to the config file to use +- `--help`: Show this message and exit. ## `openapi-python-client update` -Update an existing OpenAPI Client library +Update an existing OpenAPI Client library **Usage**: @@ -51,9 +52,10 @@ $ openapi-python-client update [OPTIONS] **Options**: -* `--url TEXT`: A URL to read the JSON from -* `--path PATH`: A path to the JSON file -* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) -* `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] -* `--help`: Show this message and exit. - +- `--url TEXT`: A URL to read the JSON from +- `--path PATH`: A path to the JSON file +- `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) +- `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] +- `--file-encoding TEXT`: Encoding used when writing generated [default: utf-8] +- `--config PATH`: Path to the config file to use +- `--help`: Show this message and exit. From 5005d82e1766073f4f7c48a04a85e4be03281904 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sun, 4 Apr 2021 11:11:01 -0600 Subject: [PATCH 09/11] test: Add more unit test coverage --- .../test_parser/test_properties/test_init.py | 19 ++++++++++++++ .../test_properties/test_model_property.py | 26 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 3e9c8cb54..7d71efc86 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -611,6 +611,25 @@ def test_property_from_data_ref_not_found(self, mocker): assert prop == PropertyError(data=data, detail="Could not find reference in parsed models or enums") assert schemas == new_schemas + def test_property_from_data_invalid_ref(self, mocker): + from openapi_python_client.parser.properties import PropertyError, Schemas, property_from_data + + name = mocker.MagicMock() + required = mocker.MagicMock() + data = oai.Reference.construct(ref=mocker.MagicMock()) + parse_reference_path = mocker.patch( + f"{MODULE_NAME}.parse_reference_path", return_value=PropertyError(detail="bad stuff") + ) + schemas = Schemas() + + prop, new_schemas = property_from_data( + name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=mocker.MagicMock() + ) + + parse_reference_path.assert_called_once_with(data.ref) + assert prop == PropertyError(data=data, detail="bad stuff") + assert schemas == new_schemas + def test_property_from_data_string(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data diff --git a/tests/test_parser/test_properties/test_model_property.py b/tests/test_parser/test_properties/test_model_property.py index 7db77309a..b366c2333 100644 --- a/tests/test_parser/test_properties/test_model_property.py +++ b/tests/test_parser/test_properties/test_model_property.py @@ -231,6 +231,32 @@ def test_conflicting_properties_different_types(self, model_property_factory): assert isinstance(result, PropertyError) + def test_invalid_reference(self, model_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="ThisIsNotGood")]) + schemas = Schemas() + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + + assert isinstance(result, PropertyError) + + def test_non_model_reference(self, enum_property_factory): + from openapi_python_client.parser.properties import Schemas + from openapi_python_client.parser.properties.model_property import _process_properties + + data = oai.Schema.construct(allOf=[oai.Reference.construct(ref="#/First")]) + schemas = Schemas( + classes_by_reference={ + "/First": enum_property_factory(), + } + ) + + result = _process_properties(data=data, schemas=schemas, class_name="", config=Config()) + + assert isinstance(result, PropertyError) + def test_conflicting_properties_same_types(self, model_property_factory): from openapi_python_client.parser.properties import Schemas from openapi_python_client.parser.properties.model_property import _process_properties From fed62b053fada87d223578f78d7d46e47eeef005 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <contact@dylananthony.com> Date: Sun, 4 Apr 2021 11:24:08 -0600 Subject: [PATCH 10/11] fix: nullable passthrough in single union refs --- .../api/tests/defaults_tests_defaults_post.py | 31 ++++++++++--------- .../my_test_api_client/models/a_model.py | 29 ++++++++++------- .../parser/properties/__init__.py | 2 +- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py index 29d16b1d2..88e4421f4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py @@ -29,8 +29,8 @@ def _get_kwargs( enum_prop: Union[Unset, AnEnum] = UNSET, model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - nullable_required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET, + nullable_required_model_prop: Optional[ModelWithUnionProperty], ) -> Dict[str, Any]: url = "{}/tests/defaults".format(client.base_url) @@ -92,11 +92,11 @@ def _get_kwargs( json_required_model_prop = required_model_prop.to_dict() - json_nullable_model_prop: Union[Unset, Dict[str, Any]] = UNSET + json_nullable_model_prop: Union[Unset, None, Dict[str, Any]] = UNSET if not isinstance(nullable_model_prop, Unset): - json_nullable_model_prop = nullable_model_prop.to_dict() + json_nullable_model_prop = nullable_model_prop.to_dict() if nullable_model_prop else None - json_nullable_required_model_prop = nullable_required_model_prop.to_dict() + json_nullable_required_model_prop = nullable_required_model_prop.to_dict() if nullable_required_model_prop else None params: Dict[str, Any] = { "string_prop": string_prop, @@ -116,9 +116,10 @@ def _get_kwargs( if not isinstance(json_model_prop, Unset): params.update(json_model_prop) params.update(json_required_model_prop) - if not isinstance(json_nullable_model_prop, Unset): + if not isinstance(json_nullable_model_prop, Unset) and json_nullable_model_prop is not None: params.update(json_nullable_model_prop) - params.update(json_nullable_required_model_prop) + if json_nullable_required_model_prop is not None: + params.update(json_nullable_required_model_prop) params = {k: v for k, v in params.items() if v is not UNSET and v is not None} return { @@ -169,8 +170,8 @@ def sync_detailed( enum_prop: Union[Unset, AnEnum] = UNSET, model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - nullable_required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET, + nullable_required_model_prop: Optional[ModelWithUnionProperty], ) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, @@ -218,8 +219,8 @@ def sync( enum_prop: Union[Unset, AnEnum] = UNSET, model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - nullable_required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET, + nullable_required_model_prop: Optional[ModelWithUnionProperty], ) -> Optional[Union[None, HTTPValidationError]]: """ """ @@ -263,8 +264,8 @@ async def asyncio_detailed( enum_prop: Union[Unset, AnEnum] = UNSET, model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - nullable_required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET, + nullable_required_model_prop: Optional[ModelWithUnionProperty], ) -> Response[Union[None, HTTPValidationError]]: kwargs = _get_kwargs( client=client, @@ -311,8 +312,8 @@ async def asyncio( enum_prop: Union[Unset, AnEnum] = UNSET, model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, required_model_prop: ModelWithUnionProperty, - nullable_model_prop: Union[Unset, ModelWithUnionProperty] = UNSET, - nullable_required_model_prop: ModelWithUnionProperty, + nullable_model_prop: Union[Unset, None, ModelWithUnionProperty] = UNSET, + nullable_required_model_prop: Optional[ModelWithUnionProperty], ) -> Optional[Union[None, HTTPValidationError]]: """ """ diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py index 29a98a19f..b79f2e8d4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -23,10 +23,10 @@ class AModel: required_not_nullable: str one_of_models: Union[FreeFormModel, ModelWithUnionProperty] model: ModelWithUnionProperty - nullable_model: ModelWithUnionProperty a_nullable_date: Optional[datetime.date] required_nullable: Optional[str] nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None] + nullable_model: Optional[ModelWithUnionProperty] nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET a_not_required_date: Union[Unset, datetime.date] = UNSET attr_1_leading_digit: Union[Unset, str] = UNSET @@ -35,7 +35,7 @@ class AModel: not_required_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, Unset] = UNSET not_required_nullable_one_of_models: Union[FreeFormModel, ModelWithUnionProperty, None, Unset, str] = UNSET not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET - not_required_nullable_model: Union[Unset, ModelWithUnionProperty] = UNSET + not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty] = UNSET def to_dict(self) -> Dict[str, Any]: an_enum_value = self.an_enum_value.value @@ -56,8 +56,6 @@ def to_dict(self) -> Dict[str, Any]: model = self.model.to_dict() - nullable_model = self.nullable_model.to_dict() - nested_list_of_enums: Union[Unset, List[List[str]]] = UNSET if not isinstance(self.nested_list_of_enums, Unset): nested_list_of_enums = [] @@ -119,13 +117,17 @@ def to_dict(self) -> Dict[str, Any]: else: not_required_nullable_one_of_models = self.not_required_nullable_one_of_models + nullable_model = self.nullable_model.to_dict() if self.nullable_model else None + not_required_model: Union[Unset, Dict[str, Any]] = UNSET if not isinstance(self.not_required_model, Unset): not_required_model = self.not_required_model.to_dict() - not_required_nullable_model: Union[Unset, Dict[str, Any]] = UNSET + not_required_nullable_model: Union[Unset, None, Dict[str, Any]] = UNSET if not isinstance(self.not_required_nullable_model, Unset): - not_required_nullable_model = self.not_required_nullable_model.to_dict() + not_required_nullable_model = ( + self.not_required_nullable_model.to_dict() if self.not_required_nullable_model else None + ) field_dict: Dict[str, Any] = {} field_dict.update( @@ -136,10 +138,10 @@ def to_dict(self) -> Dict[str, Any]: "required_not_nullable": required_not_nullable, "one_of_models": one_of_models, "model": model, - "nullable_model": nullable_model, "a_nullable_date": a_nullable_date, "required_nullable": required_nullable, "nullable_one_of_models": nullable_one_of_models, + "nullable_model": nullable_model, } ) if nested_list_of_enums is not UNSET: @@ -212,8 +214,6 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro model = ModelWithUnionProperty.from_dict(d.pop("model")) - nullable_model = ModelWithUnionProperty.from_dict(d.pop("nullable_model")) - nested_list_of_enums = [] _nested_list_of_enums = d.pop("nested_list_of_enums", UNSET) for nested_list_of_enums_item_data in _nested_list_of_enums or []: @@ -333,14 +333,19 @@ def _parse_not_required_nullable_one_of_models( d.pop("not_required_nullable_one_of_models", UNSET) ) + nullable_model = None + _nullable_model = d.pop("nullable_model") + if _nullable_model is not None: + nullable_model = ModelWithUnionProperty.from_dict(_nullable_model) + not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET _not_required_model = d.pop("not_required_model", UNSET) if not isinstance(_not_required_model, Unset): not_required_model = ModelWithUnionProperty.from_dict(_not_required_model) - not_required_nullable_model: Union[Unset, ModelWithUnionProperty] = UNSET + not_required_nullable_model = None _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET) - if not isinstance(_not_required_nullable_model, Unset): + if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset): not_required_nullable_model = ModelWithUnionProperty.from_dict(_not_required_nullable_model) a_model = cls( @@ -350,7 +355,6 @@ def _parse_not_required_nullable_one_of_models( required_not_nullable=required_not_nullable, one_of_models=one_of_models, model=model, - nullable_model=nullable_model, nested_list_of_enums=nested_list_of_enums, a_nullable_date=a_nullable_date, a_not_required_date=a_not_required_date, @@ -361,6 +365,7 @@ def _parse_not_required_nullable_one_of_models( nullable_one_of_models=nullable_one_of_models, not_required_one_of_models=not_required_one_of_models, not_required_nullable_one_of_models=not_required_nullable_one_of_models, + nullable_model=nullable_model, not_required_model=not_required_model, not_required_nullable_model=not_required_nullable_model, ) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 448dcbc13..b9f29557c 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -407,7 +407,7 @@ def _property_from_ref( existing = schemas.classes_by_reference.get(ref_path) if existing: return ( - attr.evolve(existing, required=required, name=name), + attr.evolve(existing, required=required, name=name, nullable=nullable), schemas, ) return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas From d457227e50b4f5c0c5dc779c93dfc3700a31ccac Mon Sep 17 00:00:00 2001 From: Dylan Anthony <43723790+dbanty@users.noreply.github.com> Date: Mon, 5 Apr 2021 08:20:52 -0600 Subject: [PATCH 11/11] docs: Improve `properties.Class` docstring --- openapi_python_client/parser/properties/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 315b9fb67..b4e9140b4 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -31,7 +31,7 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError] @attr.s(auto_attribs=True, frozen=True) class Class: - """ Info about a generated class which will be in models """ + """ Represents Python class which will be generated from an OpenAPI schema """ name: _ClassName module_name: str