diff --git a/openapi_python_client/openapi_parser/openapi.py b/openapi_python_client/openapi_parser/openapi.py index 23ffb0c5a..6fb87d4cb 100644 --- a/openapi_python_client/openapi_parser/openapi.py +++ b/openapi_python_client/openapi_parser/openapi.py @@ -236,14 +236,16 @@ def _iterate_properties() -> Generator[Property, None, None]: @staticmethod def from_dict(d: Dict[str, Dict[str, Any]], /) -> OpenAPI: - """ Create an OpenAPI from dict """ + """ Create an OpenAPI from dict + :rtype: object + """ schemas = Schema.dict(d["components"]["schemas"]) endpoint_collections_by_tag = EndpointCollection.from_dict(d["paths"]) enums = OpenAPI._check_enums(schemas.values(), endpoint_collections_by_tag.values()) return OpenAPI( title=d["info"]["title"], - description=d["info"]["description"], + description=d["info"].get("description"), version=d["info"]["version"], endpoint_collections_by_tag=endpoint_collections_by_tag, schemas=schemas, diff --git a/openapi_python_client/openapi_parser/properties.py b/openapi_python_client/openapi_parser/properties.py index fc092b1a6..0d0826064 100644 --- a/openapi_python_client/openapi_parser/properties.py +++ b/openapi_python_client/openapi_parser/properties.py @@ -167,7 +167,10 @@ def transform(self) -> str: def constructor_from_dict(self, dict_name: str) -> str: """ How to load this property from a dict (used in generated model from_dict function """ - return f'{self.reference.class_name}({dict_name}["{self.name}"]) if "{self.name}" in {dict_name} else None' + constructor = f'{self.reference.class_name}({dict_name}["{self.name}"])' + if not self.required: + constructor += f' if "{self.name}" in {dict_name} else None' + return constructor @staticmethod def values_from_list(l: List[str], /) -> Dict[str, str]: @@ -208,7 +211,7 @@ def transform(self) -> str: class DictProperty(Property): """ Property that is a general Dict """ - _type_string: ClassVar[str] = "Dict" + _type_string: ClassVar[str] = "Dict[Any, Any]" _openapi_types_to_python_type_strings = { @@ -216,7 +219,7 @@ class DictProperty(Property): "number": "float", "integer": "int", "boolean": "bool", - "object": "Dict", + "object": "Dict[Any, Any]", } diff --git a/openapi_python_client/openapi_parser/responses.py b/openapi_python_client/openapi_parser/responses.py index 34e31552d..75490c7bc 100644 --- a/openapi_python_client/openapi_parser/responses.py +++ b/openapi_python_client/openapi_parser/responses.py @@ -33,7 +33,7 @@ def return_string(self) -> str: def constructor(self) -> str: """ How the return value of this response should be constructed """ - return f"[{self.reference.class_name}.from_dict(item) for item in response.json()]" + return f"[{self.reference.class_name}.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]" @dataclass @@ -48,7 +48,7 @@ def return_string(self) -> str: def constructor(self) -> str: """ How the return value of this response should be constructed """ - return f"{self.reference.class_name}.from_dict(response.json())" + return f"{self.reference.class_name}.from_dict(cast(Dict[str, Any], response.json()))" @dataclass diff --git a/openapi_python_client/templates/async_endpoint_module.pyi b/openapi_python_client/templates/async_endpoint_module.pyi index 8025cb2b0..cfd46481d 100644 --- a/openapi_python_client/templates/async_endpoint_module.pyi +++ b/openapi_python_client/templates/async_endpoint_module.pyi @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, Any, cast import httpx @@ -60,8 +60,8 @@ async def {{ endpoint.name }}( {% endfor %} {% endif %} - with httpx.AsyncClient() as client: - response = await client.{{ endpoint.method }}( + async with httpx.AsyncClient() as _client: + response = await _client.{{ endpoint.method }}( url=url, headers=client.get_headers(), {% if endpoint.form_body_reference %} diff --git a/openapi_python_client/templates/endpoint_module.pyi b/openapi_python_client/templates/endpoint_module.pyi index 5383da950..610b687e9 100644 --- a/openapi_python_client/templates/endpoint_module.pyi +++ b/openapi_python_client/templates/endpoint_module.pyi @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, Any, cast import httpx diff --git a/openapi_python_client/templates/model.pyi b/openapi_python_client/templates/model.pyi index 25d0648e6..7d5a0423e 100644 --- a/openapi_python_client/templates/model.pyi +++ b/openapi_python_client/templates/model.pyi @@ -2,7 +2,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast {% for relative in schema.relative_imports %} {{ relative }} @@ -16,7 +16,7 @@ class {{ schema.reference.class_name }}: {{ property.to_string() }} {% endfor %} - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { {% for property in schema.required_properties %} "{{ property.name }}": self.{{ property.transform() }}, @@ -27,7 +27,7 @@ class {{ schema.reference.class_name }}: } @staticmethod - def from_dict(d: Dict) -> {{ schema.reference.class_name }}: + def from_dict(d: Dict[str, Any]) -> {{ schema.reference.class_name }}: {% for property in schema.required_properties + schema.optional_properties %} {% if property.constructor_template %} diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/api/default.py b/tests/test_end_to_end/golden-master/my_test_api_client/api/default.py index b3d18f879..58af0a0e8 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/api/default.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/api/default.py @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast import httpx @@ -19,6 +19,6 @@ def ping_ping_get( response = httpx.get(url=url, headers=client.get_headers(),) if response.status_code == 200: - return ABCResponse.from_dict(response.json()) + return ABCResponse.from_dict(cast(Dict[str, Any], response.json())) else: raise ApiResponseError(response=response) diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/api/users.py b/tests/test_end_to_end/golden-master/my_test_api_client/api/users.py index 9695442a5..501cb2b50 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/api/users.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/api/users.py @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast import httpx @@ -25,8 +25,8 @@ def get_list_tests__get( response = httpx.get(url=url, headers=client.get_headers(), params=params,) if response.status_code == 200: - return [AModel.from_dict(item) for item in response.json()] + return [AModel.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())] if response.status_code == 422: - return HTTPValidationError.from_dict(response.json()) + return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json())) else: raise ApiResponseError(response=response) diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/async_api/default.py b/tests/test_end_to_end/golden-master/my_test_api_client/async_api/default.py index a2753f489..59c54c3be 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/async_api/default.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/async_api/default.py @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast import httpx @@ -16,10 +16,10 @@ async def ping_ping_get( """ A quick check to see if the system is running """ url = f"{client.base_url}/ping" - with httpx.AsyncClient() as client: - response = await client.get(url=url, headers=client.get_headers(),) + async with httpx.AsyncClient() as _client: + response = await _client.get(url=url, headers=client.get_headers(),) if response.status_code == 200: - return ABCResponse.from_dict(response.json()) + return ABCResponse.from_dict(cast(Dict[str, Any], response.json())) else: raise ApiResponseError(response=response) diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py b/tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py index 32ceb45b6..ce0c9eb6c 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py @@ -1,5 +1,5 @@ from dataclasses import asdict -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast import httpx @@ -22,12 +22,12 @@ async def get_list_tests__get( "statuses": statuses, } - with httpx.AsyncClient() as client: - response = await client.get(url=url, headers=client.get_headers(), params=params,) + async with httpx.AsyncClient() as _client: + response = await _client.get(url=url, headers=client.get_headers(), params=params,) if response.status_code == 200: - return [AModel.from_dict(item) for item in response.json()] + return [AModel.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())] if response.status_code == 422: - return HTTPValidationError.from_dict(response.json()) + return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json())) else: raise ApiResponseError(response=response) diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py b/tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py index b8d25dac2..026d63a25 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast from .a_list_of_enums import AListOfEnums from .an_enum_value import AnEnumValue @@ -18,7 +18,7 @@ class AModel: a_list_of_strings: List[str] a_list_of_objects: List[OtherModel] - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { "an_enum_value": self.an_enum_value.value, "a_list_of_enums": self.a_list_of_enums, @@ -27,9 +27,9 @@ def to_dict(self) -> Dict: } @staticmethod - def from_dict(d: Dict) -> AModel: + def from_dict(d: Dict[str, Any]) -> AModel: - an_enum_value = AnEnumValue(d["an_enum_value"]) if "an_enum_value" in d else None + an_enum_value = AnEnumValue(d["an_enum_value"]) a_list_of_enums = [] for a_list_of_enums_item in d.get("a_list_of_enums", []): diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py b/tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py index ce48a7cb9..d8562b64d 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast @dataclass @@ -11,13 +11,13 @@ class ABCResponse: success: bool - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { "success": self.success, } @staticmethod - def from_dict(d: Dict) -> ABCResponse: + def from_dict(d: Dict[str, Any]) -> ABCResponse: success = d["success"] diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/models/h_t_t_p_validation_error.py b/tests/test_end_to_end/golden-master/my_test_api_client/models/h_t_t_p_validation_error.py index 575453355..738096e39 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/models/h_t_t_p_validation_error.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/models/h_t_t_p_validation_error.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast from .validation_error import ValidationError @@ -13,13 +13,13 @@ class HTTPValidationError: detail: Optional[List[ValidationError]] = None - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { "detail": self.detail if self.detail is not None else None, } @staticmethod - def from_dict(d: Dict) -> HTTPValidationError: + def from_dict(d: Dict[str, Any]) -> HTTPValidationError: detail = [] for detail_item in d.get("detail", []): diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py b/tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py index 5bb19ce17..c2e7a90e9 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast @dataclass @@ -11,13 +11,13 @@ class OtherModel: a_value: str - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { "a_value": self.a_value, } @staticmethod - def from_dict(d: Dict) -> OtherModel: + def from_dict(d: Dict[str, Any]) -> OtherModel: a_value = d["a_value"] diff --git a/tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py b/tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py index fc462229f..c3b91642f 100644 --- a/tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py +++ b/tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import datetime -from typing import Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, cast @dataclass @@ -13,7 +13,7 @@ class ValidationError: msg: str type: str - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, Any]: return { "loc": self.loc, "msg": self.msg, @@ -21,7 +21,7 @@ def to_dict(self) -> Dict: } @staticmethod - def from_dict(d: Dict) -> ValidationError: + def from_dict(d: Dict[str, Any]) -> ValidationError: loc = d.get("loc", []) diff --git a/tests/test_end_to_end/test_end_to_end.py b/tests/test_end_to_end/test_end_to_end.py index adca12695..5397ae719 100644 --- a/tests/test_end_to_end/test_end_to_end.py +++ b/tests/test_end_to_end/test_end_to_end.py @@ -36,4 +36,9 @@ def test_end_to_end(capsys): if result.exit_code != 0: raise result.exception _compare_directories(gm_path, output_path) + + import mypy.api + out, err, status = mypy.api.run([str(output_path), "--strict"]) + assert status == 0, f"Hello Type checking client failed: {err}" + shutil.rmtree(output_path) diff --git a/tests/test_openapi_parser/test_properties.py b/tests/test_openapi_parser/test_properties.py index ead21aed0..0e038f974 100644 --- a/tests/test_openapi_parser/test_properties.py +++ b/tests/test_openapi_parser/test_properties.py @@ -169,9 +169,19 @@ def test_constructor_from_dict(self, mocker): assert ( enum_property.constructor_from_dict("my_dict") - == 'MyTestEnum(my_dict["test_enum"]) if "test_enum" in my_dict else None' + == 'MyTestEnum(my_dict["test_enum"])' ) + enum_property = EnumProperty(name="test_enum", required=False, + default=None, values={}) + + assert ( + enum_property.constructor_from_dict("my_dict") + == 'MyTestEnum(my_dict["test_enum"]) if "test_enum" in my_dict else None' + ) + + + def test_values_from_list(self): from openapi_python_client.openapi_parser.properties import EnumProperty @@ -392,7 +402,7 @@ def test_property_from_dict_enum_array(self, mocker): @pytest.mark.parametrize( "openapi_type,python_type", - [("string", "str"), ("number", "float"), ("integer", "int"), ("boolean", "bool"), ("object", "Dict"),], + [("string", "str"), ("number", "float"), ("integer", "int"), ("boolean", "bool"), ("object", "Dict[Any, Any]"),], ) def test_property_from_dict_simple_array(self, mocker, openapi_type, python_type): name = mocker.MagicMock() diff --git a/tests/test_openapi_parser/test_responses.py b/tests/test_openapi_parser/test_responses.py index dfe6cefd9..f3078fe89 100644 --- a/tests/test_openapi_parser/test_responses.py +++ b/tests/test_openapi_parser/test_responses.py @@ -32,7 +32,7 @@ def test_constructor(self, mocker): r = ListRefResponse(200, reference=mocker.MagicMock(class_name="SuperCoolClass")) - assert r.constructor() == "[SuperCoolClass.from_dict(item) for item in response.json()]" + assert r.constructor() == "[SuperCoolClass.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]" class TestRefResponse: @@ -48,7 +48,7 @@ def test_constructor(self, mocker): r = RefResponse(200, reference=mocker.MagicMock(class_name="SuperCoolClass")) - assert r.constructor() == "SuperCoolClass.from_dict(response.json())" + assert r.constructor() == "SuperCoolClass.from_dict(cast(Dict[str, Any], response.json()))" class TestStringResponse: