Skip to content

Commit 2247d2f

Browse files
authored
Handle bool enums better (#923)
TODO: - [x] Cover error cases with new unit tests --------- Co-authored-by: Dylan Anthony <[email protected]>
1 parent c10c1f2 commit 2247d2f

File tree

14 files changed

+282
-136
lines changed

14 files changed

+282
-136
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Do not stop generation for invalid enum values
6+
7+
This generator only supports `enum` values that are strings or integers.
8+
Previously, this was handled at the parsing level, which would cause the generator to fail if there were any unsupported values in the document.
9+
Now, the generator will correctly keep going, skipping only endpoints which contained unsupported values.
10+
11+
Thanks for reporting #922 @macmoritz!
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
default: minor
3+
---
4+
5+
# Generate properties for some boolean enums
6+
7+
If a schema has both `type = "boolean"` and `enum` defined, a normal boolean property will now be created.
8+
Previously, the generator would error.
9+
10+
Note that the generate code _will not_ correctly limit the values to the enum values. To work around this, use the
11+
OpenAPI 3.1 `const` instead of `enum` to generate Python `Literal` types.
12+
13+
Thanks for reporting #922 @macmoritz!

end_to_end_tests/baseline_openapi_3.0.json

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
},
6262
"/bodies/json-like": {
6363
"post": {
64-
"tags": ["bodies"],
64+
"tags": [
65+
"bodies"
66+
],
6567
"description": "A content type that works like json but isn't application/json",
6668
"operationId": "json-like",
6769
"requestBody": {
@@ -799,11 +801,11 @@
799801
}
800802
}
801803
}
802-
},
803-
"/tests/int_enum": {
804+
},
805+
"/enum/int": {
804806
"post": {
805807
"tags": [
806-
"tests"
808+
"enums"
807809
],
808810
"summary": "Int Enum",
809811
"operationId": "int_enum_tests_int_enum_post",
@@ -825,14 +827,37 @@
825827
"schema": {}
826828
}
827829
}
828-
},
829-
"422": {
830-
"description": "Validation Error",
830+
}
831+
}
832+
}
833+
},
834+
"/enum/bool": {
835+
"post": {
836+
"tags": [
837+
"enums"
838+
],
839+
"summary": "Bool Enum",
840+
"operationId": "bool_enum_tests_bool_enum_post",
841+
"parameters": [
842+
{
843+
"required": true,
844+
"schema": {
845+
"type": "boolean",
846+
"enum": [
847+
true,
848+
false
849+
]
850+
},
851+
"name": "bool_enum",
852+
"in": "query"
853+
}
854+
],
855+
"responses": {
856+
"200": {
857+
"description": "Successful Response",
831858
"content": {
832859
"application/json": {
833-
"schema": {
834-
"$ref": "#/components/schemas/HTTPValidationError"
835-
}
860+
"schema": {}
836861
}
837862
}
838863
}

end_to_end_tests/baseline_openapi_3.1.yaml

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -794,10 +794,10 @@ info:
794794
}
795795
}
796796
},
797-
"/tests/int_enum": {
797+
"/enum/int": {
798798
"post": {
799799
"tags": [
800-
"tests"
800+
"enums"
801801
],
802802
"summary": "Int Enum",
803803
"operationId": "int_enum_tests_int_enum_post",
@@ -819,14 +819,37 @@ info:
819819
"schema": { }
820820
}
821821
}
822-
},
823-
"422": {
824-
"description": "Validation Error",
822+
}
823+
}
824+
}
825+
},
826+
"/enum/bool": {
827+
"post": {
828+
"tags": [
829+
"enums"
830+
],
831+
"summary": "Bool Enum",
832+
"operationId": "bool_enum_tests_bool_enum_post",
833+
"parameters": [
834+
{
835+
"required": true,
836+
"schema": {
837+
"type": "boolean",
838+
"enum": [
839+
true,
840+
false
841+
]
842+
},
843+
"name": "bool_enum",
844+
"in": "query"
845+
}
846+
],
847+
"responses": {
848+
"200": {
849+
"description": "Successful Response",
825850
"content": {
826851
"application/json": {
827-
"schema": {
828-
"$ref": "#/components/schemas/HTTPValidationError"
829-
}
852+
"schema": { }
830853
}
831854
}
832855
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .bodies import BodiesEndpoints
66
from .default import DefaultEndpoints
77
from .defaults import DefaultsEndpoints
8+
from .enums import EnumsEndpoints
89
from .location import LocationEndpoints
910
from .naming import NamingEndpoints
1011
from .parameter_references import ParameterReferencesEndpoints
@@ -28,6 +29,10 @@ def tests(cls) -> Type[TestsEndpoints]:
2829
def defaults(cls) -> Type[DefaultsEndpoints]:
2930
return DefaultsEndpoints
3031

32+
@classmethod
33+
def enums(cls) -> Type[EnumsEndpoints]:
34+
return EnumsEndpoints
35+
3136
@classmethod
3237
def responses(cls) -> Type[ResponsesEndpoints]:
3338
return ResponsesEndpoints
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
""" Contains methods for accessing the API Endpoints """
2+
3+
import types
4+
5+
from . import bool_enum_tests_bool_enum_post, int_enum_tests_int_enum_post
6+
7+
8+
class EnumsEndpoints:
9+
@classmethod
10+
def int_enum_tests_int_enum_post(cls) -> types.ModuleType:
11+
"""
12+
Int Enum
13+
"""
14+
return int_enum_tests_int_enum_post
15+
16+
@classmethod
17+
def bool_enum_tests_bool_enum_post(cls) -> types.ModuleType:
18+
"""
19+
Bool Enum
20+
"""
21+
return bool_enum_tests_bool_enum_post

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
get_basic_list_of_integers,
1111
get_basic_list_of_strings,
1212
get_user_list,
13-
int_enum_tests_int_enum_post,
1413
json_body_tests_json_body_post,
1514
no_response_tests_no_response_get,
1615
octet_stream_tests_octet_stream_get,
@@ -132,13 +131,6 @@ def unsupported_content_tests_unsupported_content_get(cls) -> types.ModuleType:
132131
"""
133132
return unsupported_content_tests_unsupported_content_get
134133

135-
@classmethod
136-
def int_enum_tests_int_enum_post(cls) -> types.ModuleType:
137-
"""
138-
Int Enum
139-
"""
140-
return int_enum_tests_int_enum_post
141-
142134
@classmethod
143135
def test_inline_objects(cls) -> types.ModuleType:
144136
"""

end_to_end_tests/golden-record/my_test_api_client/api/enums/__init__.py

Whitespace-only changes.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 ...types import UNSET, Response
9+
10+
11+
def _get_kwargs(
12+
*,
13+
bool_enum: bool,
14+
) -> Dict[str, Any]:
15+
params: Dict[str, Any] = {}
16+
17+
params["bool_enum"] = bool_enum
18+
19+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
20+
21+
_kwargs: Dict[str, Any] = {
22+
"method": "post",
23+
"url": "/enum/bool",
24+
"params": params,
25+
}
26+
27+
return _kwargs
28+
29+
30+
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]:
31+
if response.status_code == HTTPStatus.OK:
32+
return None
33+
if client.raise_on_unexpected_status:
34+
raise errors.UnexpectedStatus(response.status_code, response.content)
35+
else:
36+
return None
37+
38+
39+
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]:
40+
return Response(
41+
status_code=HTTPStatus(response.status_code),
42+
content=response.content,
43+
headers=response.headers,
44+
parsed=_parse_response(client=client, response=response),
45+
)
46+
47+
48+
def sync_detailed(
49+
*,
50+
client: Union[AuthenticatedClient, Client],
51+
bool_enum: bool,
52+
) -> Response[Any]:
53+
"""Bool Enum
54+
55+
Args:
56+
bool_enum (bool):
57+
58+
Raises:
59+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
60+
httpx.TimeoutException: If the request takes longer than Client.timeout.
61+
62+
Returns:
63+
Response[Any]
64+
"""
65+
66+
kwargs = _get_kwargs(
67+
bool_enum=bool_enum,
68+
)
69+
70+
response = client.get_httpx_client().request(
71+
**kwargs,
72+
)
73+
74+
return _build_response(client=client, response=response)
75+
76+
77+
async def asyncio_detailed(
78+
*,
79+
client: Union[AuthenticatedClient, Client],
80+
bool_enum: bool,
81+
) -> Response[Any]:
82+
"""Bool Enum
83+
84+
Args:
85+
bool_enum (bool):
86+
87+
Raises:
88+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
89+
httpx.TimeoutException: If the request takes longer than Client.timeout.
90+
91+
Returns:
92+
Response[Any]
93+
"""
94+
95+
kwargs = _get_kwargs(
96+
bool_enum=bool_enum,
97+
)
98+
99+
response = await client.get_async_httpx_client().request(**kwargs)
100+
101+
return _build_response(client=client, response=response)

0 commit comments

Comments
 (0)