Skip to content

Commit 40d63f9

Browse files
authored
fix: import cast for required const properties, since it's used in the template (#1153)
The [template](https://github.com/openapi-generators/openapi-python-client/blob/main/openapi_python_client/templates/property_templates/const_property.py.jinja#L2) for `const` properties uses `cast` unconditionally, but the import for `cast` in `ConstProperty.get_imports` was conditioned on whether the property was required or not, leading to broken generated code for required const properties. I'd be happy to add a test for this if desired! A cursory glance suggests that maybe the end_to_end tests would be the right place? Resolves #1150
1 parent 4fbafa5 commit 40d63f9

File tree

8 files changed

+399
-2
lines changed

8 files changed

+399
-2
lines changed

end_to_end_tests/baseline_openapi_3.0.json

+41
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,47 @@
16661666
}
16671667
}
16681668
}
1669+
},
1670+
"/models/oneof-with-required-const": {
1671+
"get": {
1672+
"responses": {
1673+
"200": {
1674+
"description": "OK",
1675+
"content": {
1676+
"application/json": {
1677+
"schema": {
1678+
"oneOf": [
1679+
{
1680+
"type": "object",
1681+
"properties": {
1682+
"type": {
1683+
"const": "alpha"
1684+
},
1685+
"color": {
1686+
"type": "string"
1687+
}
1688+
},
1689+
"required": ["type"]
1690+
},
1691+
{
1692+
"type": "object",
1693+
"properties": {
1694+
"type": {
1695+
"const": "beta"
1696+
},
1697+
"texture": {
1698+
"type": "string"
1699+
}
1700+
},
1701+
"required": ["type"]
1702+
}
1703+
]
1704+
}
1705+
}
1706+
}
1707+
}
1708+
}
1709+
}
16691710
}
16701711
},
16711712
"components": {

end_to_end_tests/baseline_openapi_3.1.yaml

+41
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,47 @@ info:
16571657
}
16581658
}
16591659
},
1660+
"/models/oneof-with-required-const": {
1661+
"get": {
1662+
"responses": {
1663+
"200": {
1664+
"description": "OK",
1665+
"content": {
1666+
"application/json": {
1667+
"schema": {
1668+
"oneOf": [
1669+
{
1670+
"type": "object",
1671+
"properties": {
1672+
"type": {
1673+
"const": "alpha"
1674+
},
1675+
"color": {
1676+
"type": "string"
1677+
}
1678+
},
1679+
"required": ["type"]
1680+
},
1681+
{
1682+
"type": "object",
1683+
"properties": {
1684+
"type": {
1685+
"const": "beta"
1686+
},
1687+
"texture": {
1688+
"type": "string"
1689+
}
1690+
},
1691+
"required": ["type"]
1692+
}
1693+
]
1694+
}
1695+
}
1696+
}
1697+
}
1698+
}
1699+
}
1700+
}
16601701
}
16611702
"components":
16621703
"schemas": {

end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
import types
44

5-
from . import get_common_parameters, get_models_allof, post_common_parameters, reserved_parameters
5+
from . import (
6+
get_common_parameters,
7+
get_models_allof,
8+
get_models_oneof_with_required_const,
9+
post_common_parameters,
10+
reserved_parameters,
11+
)
612

713

814
class DefaultEndpoints:
@@ -21,3 +27,7 @@ def reserved_parameters(cls) -> types.ModuleType:
2127
@classmethod
2228
def get_models_allof(cls) -> types.ModuleType:
2329
return get_models_allof
30+
31+
@classmethod
32+
def get_models_oneof_with_required_const(cls) -> types.ModuleType:
33+
return get_models_oneof_with_required_const
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from http import HTTPStatus
2+
from typing import Any, Dict, Optional, Union
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.get_models_oneof_with_required_const_response_200_type_0 import (
9+
GetModelsOneofWithRequiredConstResponse200Type0,
10+
)
11+
from ...models.get_models_oneof_with_required_const_response_200_type_1 import (
12+
GetModelsOneofWithRequiredConstResponse200Type1,
13+
)
14+
from ...types import Response
15+
16+
17+
def _get_kwargs() -> Dict[str, Any]:
18+
_kwargs: Dict[str, Any] = {
19+
"method": "get",
20+
"url": "/models/oneof-with-required-const",
21+
}
22+
23+
return _kwargs
24+
25+
26+
def _parse_response(
27+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
28+
) -> Optional[
29+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
30+
]:
31+
if response.status_code == 200:
32+
33+
def _parse_response_200(
34+
data: object,
35+
) -> Union[
36+
"GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"
37+
]:
38+
try:
39+
if not isinstance(data, dict):
40+
raise TypeError()
41+
response_200_type_0 = GetModelsOneofWithRequiredConstResponse200Type0.from_dict(data)
42+
43+
return response_200_type_0
44+
except: # noqa: E722
45+
pass
46+
if not isinstance(data, dict):
47+
raise TypeError()
48+
response_200_type_1 = GetModelsOneofWithRequiredConstResponse200Type1.from_dict(data)
49+
50+
return response_200_type_1
51+
52+
response_200 = _parse_response_200(response.json())
53+
54+
return response_200
55+
if client.raise_on_unexpected_status:
56+
raise errors.UnexpectedStatus(response.status_code, response.content)
57+
else:
58+
return None
59+
60+
61+
def _build_response(
62+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
63+
) -> Response[
64+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
65+
]:
66+
return Response(
67+
status_code=HTTPStatus(response.status_code),
68+
content=response.content,
69+
headers=response.headers,
70+
parsed=_parse_response(client=client, response=response),
71+
)
72+
73+
74+
def sync_detailed(
75+
*,
76+
client: Union[AuthenticatedClient, Client],
77+
) -> Response[
78+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
79+
]:
80+
"""
81+
Raises:
82+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
83+
httpx.TimeoutException: If the request takes longer than Client.timeout.
84+
85+
Returns:
86+
Response[Union['GetModelsOneofWithRequiredConstResponse200Type0', 'GetModelsOneofWithRequiredConstResponse200Type1']]
87+
"""
88+
89+
kwargs = _get_kwargs()
90+
91+
response = client.get_httpx_client().request(
92+
**kwargs,
93+
)
94+
95+
return _build_response(client=client, response=response)
96+
97+
98+
def sync(
99+
*,
100+
client: Union[AuthenticatedClient, Client],
101+
) -> Optional[
102+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
103+
]:
104+
"""
105+
Raises:
106+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
107+
httpx.TimeoutException: If the request takes longer than Client.timeout.
108+
109+
Returns:
110+
Union['GetModelsOneofWithRequiredConstResponse200Type0', 'GetModelsOneofWithRequiredConstResponse200Type1']
111+
"""
112+
113+
return sync_detailed(
114+
client=client,
115+
).parsed
116+
117+
118+
async def asyncio_detailed(
119+
*,
120+
client: Union[AuthenticatedClient, Client],
121+
) -> Response[
122+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
123+
]:
124+
"""
125+
Raises:
126+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
127+
httpx.TimeoutException: If the request takes longer than Client.timeout.
128+
129+
Returns:
130+
Response[Union['GetModelsOneofWithRequiredConstResponse200Type0', 'GetModelsOneofWithRequiredConstResponse200Type1']]
131+
"""
132+
133+
kwargs = _get_kwargs()
134+
135+
response = await client.get_async_httpx_client().request(**kwargs)
136+
137+
return _build_response(client=client, response=response)
138+
139+
140+
async def asyncio(
141+
*,
142+
client: Union[AuthenticatedClient, Client],
143+
) -> Optional[
144+
Union["GetModelsOneofWithRequiredConstResponse200Type0", "GetModelsOneofWithRequiredConstResponse200Type1"]
145+
]:
146+
"""
147+
Raises:
148+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
149+
httpx.TimeoutException: If the request takes longer than Client.timeout.
150+
151+
Returns:
152+
Union['GetModelsOneofWithRequiredConstResponse200Type0', 'GetModelsOneofWithRequiredConstResponse200Type1']
153+
"""
154+
155+
return (
156+
await asyncio_detailed(
157+
client=client,
158+
)
159+
).parsed

end_to_end_tests/golden-record/my_test_api_client/models/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from .get_location_header_types_int_enum_header import GetLocationHeaderTypesIntEnumHeader
4040
from .get_location_header_types_string_enum_header import GetLocationHeaderTypesStringEnumHeader
4141
from .get_models_allof_response_200 import GetModelsAllofResponse200
42+
from .get_models_oneof_with_required_const_response_200_type_0 import GetModelsOneofWithRequiredConstResponse200Type0
43+
from .get_models_oneof_with_required_const_response_200_type_1 import GetModelsOneofWithRequiredConstResponse200Type1
4244
from .http_validation_error import HTTPValidationError
4345
from .import_ import Import
4446
from .json_like_body import JsonLikeBody
@@ -121,6 +123,8 @@
121123
"GetLocationHeaderTypesIntEnumHeader",
122124
"GetLocationHeaderTypesStringEnumHeader",
123125
"GetModelsAllofResponse200",
126+
"GetModelsOneofWithRequiredConstResponse200Type0",
127+
"GetModelsOneofWithRequiredConstResponse200Type1",
124128
"HTTPValidationError",
125129
"Import",
126130
"JsonLikeBody",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from typing import Any, Dict, List, Literal, Type, TypeVar, Union, cast
2+
3+
from attrs import define as _attrs_define
4+
from attrs import field as _attrs_field
5+
6+
from ..types import UNSET, Unset
7+
8+
T = TypeVar("T", bound="GetModelsOneofWithRequiredConstResponse200Type0")
9+
10+
11+
@_attrs_define
12+
class GetModelsOneofWithRequiredConstResponse200Type0:
13+
"""
14+
Attributes:
15+
type (Literal['alpha']):
16+
color (Union[Unset, str]):
17+
"""
18+
19+
type: Literal["alpha"]
20+
color: Union[Unset, str] = UNSET
21+
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)
22+
23+
def to_dict(self) -> Dict[str, Any]:
24+
type = self.type
25+
26+
color = self.color
27+
28+
field_dict: Dict[str, Any] = {}
29+
field_dict.update(self.additional_properties)
30+
field_dict.update(
31+
{
32+
"type": type,
33+
}
34+
)
35+
if color is not UNSET:
36+
field_dict["color"] = color
37+
38+
return field_dict
39+
40+
@classmethod
41+
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
42+
d = src_dict.copy()
43+
type = cast(Literal["alpha"], d.pop("type"))
44+
if type != "alpha":
45+
raise ValueError(f"type must match const 'alpha', got '{type}'")
46+
47+
color = d.pop("color", UNSET)
48+
49+
get_models_oneof_with_required_const_response_200_type_0 = cls(
50+
type=type,
51+
color=color,
52+
)
53+
54+
get_models_oneof_with_required_const_response_200_type_0.additional_properties = d
55+
return get_models_oneof_with_required_const_response_200_type_0
56+
57+
@property
58+
def additional_keys(self) -> List[str]:
59+
return list(self.additional_properties.keys())
60+
61+
def __getitem__(self, key: str) -> Any:
62+
return self.additional_properties[key]
63+
64+
def __setitem__(self, key: str, value: Any) -> None:
65+
self.additional_properties[key] = value
66+
67+
def __delitem__(self, key: str) -> None:
68+
del self.additional_properties[key]
69+
70+
def __contains__(self, key: str) -> bool:
71+
return key in self.additional_properties

0 commit comments

Comments
 (0)