Skip to content

Commit 2de3c57

Browse files
author
Adam Gray
committed
Type checkiing improvements
1 parent 5583d86 commit 2de3c57

File tree

18 files changed

+68
-48
lines changed

18 files changed

+68
-48
lines changed

openapi_python_client/openapi_parser/openapi.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,16 @@ def _iterate_properties() -> Generator[Property, None, None]:
236236

237237
@staticmethod
238238
def from_dict(d: Dict[str, Dict[str, Any]], /) -> OpenAPI:
239-
""" Create an OpenAPI from dict """
239+
""" Create an OpenAPI from dict
240+
:rtype: object
241+
"""
240242
schemas = Schema.dict(d["components"]["schemas"])
241243
endpoint_collections_by_tag = EndpointCollection.from_dict(d["paths"])
242244
enums = OpenAPI._check_enums(schemas.values(), endpoint_collections_by_tag.values())
243245

244246
return OpenAPI(
245247
title=d["info"]["title"],
246-
description=d["info"]["description"],
248+
description=d["info"].get("description"),
247249
version=d["info"]["version"],
248250
endpoint_collections_by_tag=endpoint_collections_by_tag,
249251
schemas=schemas,

openapi_python_client/openapi_parser/properties.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ def transform(self) -> str:
167167

168168
def constructor_from_dict(self, dict_name: str) -> str:
169169
""" How to load this property from a dict (used in generated model from_dict function """
170-
return f'{self.reference.class_name}({dict_name}["{self.name}"]) if "{self.name}" in {dict_name} else None'
170+
constructor = f'{self.reference.class_name}({dict_name}["{self.name}"])'
171+
if not self.required:
172+
constructor += f' if "{self.name}" in {dict_name} else None'
173+
return constructor
171174

172175
@staticmethod
173176
def values_from_list(l: List[str], /) -> Dict[str, str]:
@@ -208,15 +211,15 @@ def transform(self) -> str:
208211
class DictProperty(Property):
209212
""" Property that is a general Dict """
210213

211-
_type_string: ClassVar[str] = "Dict"
214+
_type_string: ClassVar[str] = "Dict[Any, Any]"
212215

213216

214217
_openapi_types_to_python_type_strings = {
215218
"string": "str",
216219
"number": "float",
217220
"integer": "int",
218221
"boolean": "bool",
219-
"object": "Dict",
222+
"object": "Dict[Any, Any]",
220223
}
221224

222225

openapi_python_client/openapi_parser/responses.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def return_string(self) -> str:
3333

3434
def constructor(self) -> str:
3535
""" How the return value of this response should be constructed """
36-
return f"[{self.reference.class_name}.from_dict(item) for item in response.json()]"
36+
return f"[{self.reference.class_name}.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]"
3737

3838

3939
@dataclass
@@ -48,7 +48,7 @@ def return_string(self) -> str:
4848

4949
def constructor(self) -> str:
5050
""" How the return value of this response should be constructed """
51-
return f"{self.reference.class_name}.from_dict(response.json())"
51+
return f"{self.reference.class_name}.from_dict(cast(Dict[str, Any], response.json()))"
5252

5353

5454
@dataclass

openapi_python_client/templates/async_endpoint_module.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Dict, List, Optional, Union, Any, cast
33

44
import httpx
55

@@ -60,8 +60,8 @@ async def {{ endpoint.name }}(
6060
{% endfor %}
6161
{% endif %}
6262

63-
with httpx.AsyncClient() as client:
64-
response = await client.{{ endpoint.method }}(
63+
async with httpx.AsyncClient() as _client:
64+
response = await _client.{{ endpoint.method }}(
6565
url=url,
6666
headers=client.get_headers(),
6767
{% if endpoint.form_body_reference %}

openapi_python_client/templates/endpoint_module.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Dict, List, Optional, Union, Any, cast
33

44
import httpx
55

openapi_python_client/templates/model.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from __future__ import annotations
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77
{% for relative in schema.relative_imports %}
88
{{ relative }}
@@ -16,7 +16,7 @@ class {{ schema.reference.class_name }}:
1616
{{ property.to_string() }}
1717
{% endfor %}
1818

19-
def to_dict(self) -> Dict:
19+
def to_dict(self) -> Dict[str, Any]:
2020
return {
2121
{% for property in schema.required_properties %}
2222
"{{ property.name }}": self.{{ property.transform() }},
@@ -27,7 +27,7 @@ class {{ schema.reference.class_name }}:
2727
}
2828

2929
@staticmethod
30-
def from_dict(d: Dict) -> {{ schema.reference.class_name }}:
30+
def from_dict(d: Dict[str, Any]) -> {{ schema.reference.class_name }}:
3131
{% for property in schema.required_properties + schema.optional_properties %}
3232

3333
{% if property.constructor_template %}

tests/test_end_to_end/golden-master/my_test_api_client/api/default.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union, cast
33

44
import httpx
55

@@ -19,6 +19,6 @@ def ping_ping_get(
1919
response = httpx.get(url=url, headers=client.get_headers(),)
2020

2121
if response.status_code == 200:
22-
return ABCResponse.from_dict(response.json())
22+
return ABCResponse.from_dict(cast(Dict[str, Any], response.json()))
2323
else:
2424
raise ApiResponseError(response=response)

tests/test_end_to_end/golden-master/my_test_api_client/api/users.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union, cast
33

44
import httpx
55

@@ -25,8 +25,8 @@ def get_list_tests__get(
2525
response = httpx.get(url=url, headers=client.get_headers(), params=params,)
2626

2727
if response.status_code == 200:
28-
return [AModel.from_dict(item) for item in response.json()]
28+
return [AModel.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]
2929
if response.status_code == 422:
30-
return HTTPValidationError.from_dict(response.json())
30+
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
3131
else:
3232
raise ApiResponseError(response=response)

tests/test_end_to_end/golden-master/my_test_api_client/async_api/default.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union, cast
33

44
import httpx
55

@@ -16,10 +16,10 @@ async def ping_ping_get(
1616
""" A quick check to see if the system is running """
1717
url = f"{client.base_url}/ping"
1818

19-
with httpx.AsyncClient() as client:
20-
response = await client.get(url=url, headers=client.get_headers(),)
19+
async with httpx.AsyncClient() as _client:
20+
response = await _client.get(url=url, headers=client.get_headers(),)
2121

2222
if response.status_code == 200:
23-
return ABCResponse.from_dict(response.json())
23+
return ABCResponse.from_dict(cast(Dict[str, Any], response.json()))
2424
else:
2525
raise ApiResponseError(response=response)

tests/test_end_to_end/golden-master/my_test_api_client/async_api/users.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import asdict
2-
from typing import Dict, List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union, cast
33

44
import httpx
55

@@ -22,12 +22,12 @@ async def get_list_tests__get(
2222
"statuses": statuses,
2323
}
2424

25-
with httpx.AsyncClient() as client:
26-
response = await client.get(url=url, headers=client.get_headers(), params=params,)
25+
async with httpx.AsyncClient() as _client:
26+
response = await _client.get(url=url, headers=client.get_headers(), params=params,)
2727

2828
if response.status_code == 200:
29-
return [AModel.from_dict(item) for item in response.json()]
29+
return [AModel.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]
3030
if response.status_code == 422:
31-
return HTTPValidationError.from_dict(response.json())
31+
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
3232
else:
3333
raise ApiResponseError(response=response)

tests/test_end_to_end/golden-master/my_test_api_client/models/a_model.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77
from .a_list_of_enums import AListOfEnums
88
from .an_enum_value import AnEnumValue
@@ -18,7 +18,7 @@ class AModel:
1818
a_list_of_strings: List[str]
1919
a_list_of_objects: List[OtherModel]
2020

21-
def to_dict(self) -> Dict:
21+
def to_dict(self) -> Dict[str, Any]:
2222
return {
2323
"an_enum_value": self.an_enum_value.value,
2424
"a_list_of_enums": self.a_list_of_enums,
@@ -27,9 +27,9 @@ def to_dict(self) -> Dict:
2727
}
2828

2929
@staticmethod
30-
def from_dict(d: Dict) -> AModel:
30+
def from_dict(d: Dict[str, Any]) -> AModel:
3131

32-
an_enum_value = AnEnumValue(d["an_enum_value"]) if "an_enum_value" in d else None
32+
an_enum_value = AnEnumValue(d["an_enum_value"])
3333

3434
a_list_of_enums = []
3535
for a_list_of_enums_item in d.get("a_list_of_enums", []):

tests/test_end_to_end/golden-master/my_test_api_client/models/abc_response.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77

88
@dataclass
@@ -11,13 +11,13 @@ class ABCResponse:
1111

1212
success: bool
1313

14-
def to_dict(self) -> Dict:
14+
def to_dict(self) -> Dict[str, Any]:
1515
return {
1616
"success": self.success,
1717
}
1818

1919
@staticmethod
20-
def from_dict(d: Dict) -> ABCResponse:
20+
def from_dict(d: Dict[str, Any]) -> ABCResponse:
2121

2222
success = d["success"]
2323

tests/test_end_to_end/golden-master/my_test_api_client/models/h_t_t_p_validation_error.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77
from .validation_error import ValidationError
88

@@ -13,13 +13,13 @@ class HTTPValidationError:
1313

1414
detail: Optional[List[ValidationError]] = None
1515

16-
def to_dict(self) -> Dict:
16+
def to_dict(self) -> Dict[str, Any]:
1717
return {
1818
"detail": self.detail if self.detail is not None else None,
1919
}
2020

2121
@staticmethod
22-
def from_dict(d: Dict) -> HTTPValidationError:
22+
def from_dict(d: Dict[str, Any]) -> HTTPValidationError:
2323

2424
detail = []
2525
for detail_item in d.get("detail", []):

tests/test_end_to_end/golden-master/my_test_api_client/models/other_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77

88
@dataclass
@@ -11,13 +11,13 @@ class OtherModel:
1111

1212
a_value: str
1313

14-
def to_dict(self) -> Dict:
14+
def to_dict(self) -> Dict[str, Any]:
1515
return {
1616
"a_value": self.a_value,
1717
}
1818

1919
@staticmethod
20-
def from_dict(d: Dict) -> OtherModel:
20+
def from_dict(d: Dict[str, Any]) -> OtherModel:
2121

2222
a_value = d["a_value"]
2323

tests/test_end_to_end/golden-master/my_test_api_client/models/validation_error.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from typing import Dict, List, Optional, cast
5+
from typing import Any, Dict, List, Optional, cast
66

77

88
@dataclass
@@ -13,15 +13,15 @@ class ValidationError:
1313
msg: str
1414
type: str
1515

16-
def to_dict(self) -> Dict:
16+
def to_dict(self) -> Dict[str, Any]:
1717
return {
1818
"loc": self.loc,
1919
"msg": self.msg,
2020
"type": self.type,
2121
}
2222

2323
@staticmethod
24-
def from_dict(d: Dict) -> ValidationError:
24+
def from_dict(d: Dict[str, Any]) -> ValidationError:
2525

2626
loc = d.get("loc", [])
2727

tests/test_end_to_end/test_end_to_end.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ def test_end_to_end(capsys):
3636
if result.exit_code != 0:
3737
raise result.exception
3838
_compare_directories(gm_path, output_path)
39+
40+
import mypy.api
41+
out, err, status = mypy.api.run([str(output_path), "--strict"])
42+
assert status == 0, f"Hello Type checking client failed: {err}"
43+
3944
shutil.rmtree(output_path)

tests/test_openapi_parser/test_properties.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,19 @@ def test_constructor_from_dict(self, mocker):
169169

170170
assert (
171171
enum_property.constructor_from_dict("my_dict")
172-
== 'MyTestEnum(my_dict["test_enum"]) if "test_enum" in my_dict else None'
172+
== 'MyTestEnum(my_dict["test_enum"])'
173173
)
174174

175+
enum_property = EnumProperty(name="test_enum", required=False,
176+
default=None, values={})
177+
178+
assert (
179+
enum_property.constructor_from_dict("my_dict")
180+
== 'MyTestEnum(my_dict["test_enum"]) if "test_enum" in my_dict else None'
181+
)
182+
183+
184+
175185
def test_values_from_list(self):
176186
from openapi_python_client.openapi_parser.properties import EnumProperty
177187

@@ -392,7 +402,7 @@ def test_property_from_dict_enum_array(self, mocker):
392402

393403
@pytest.mark.parametrize(
394404
"openapi_type,python_type",
395-
[("string", "str"), ("number", "float"), ("integer", "int"), ("boolean", "bool"), ("object", "Dict"),],
405+
[("string", "str"), ("number", "float"), ("integer", "int"), ("boolean", "bool"), ("object", "Dict[Any, Any]"),],
396406
)
397407
def test_property_from_dict_simple_array(self, mocker, openapi_type, python_type):
398408
name = mocker.MagicMock()

tests/test_openapi_parser/test_responses.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_constructor(self, mocker):
3232

3333
r = ListRefResponse(200, reference=mocker.MagicMock(class_name="SuperCoolClass"))
3434

35-
assert r.constructor() == "[SuperCoolClass.from_dict(item) for item in response.json()]"
35+
assert r.constructor() == "[SuperCoolClass.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]"
3636

3737

3838
class TestRefResponse:
@@ -48,7 +48,7 @@ def test_constructor(self, mocker):
4848

4949
r = RefResponse(200, reference=mocker.MagicMock(class_name="SuperCoolClass"))
5050

51-
assert r.constructor() == "SuperCoolClass.from_dict(response.json())"
51+
assert r.constructor() == "SuperCoolClass.from_dict(cast(Dict[str, Any], response.json()))"
5252

5353

5454
class TestStringResponse:

0 commit comments

Comments
 (0)