Skip to content

Commit 0b1c3c2

Browse files
committed
Fix optional bodies
1 parent 3464f80 commit 0b1c3c2

File tree

28 files changed

+391
-119
lines changed

28 files changed

+391
-119
lines changed

.changeset/fix_optional_bodies.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Fix optional bodies
6+
7+
If a body is not required (the default), it will now:
8+
9+
1. Have `Unset` as part of its type annotation.
10+
2. Default to a value of `UNSET`
11+
3. Not be included in the request if it is `UNSET`
12+
13+
Thanks @orelmaliach for the report! Fixes #1354

end_to_end_tests/baseline_openapi_3.0.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@
104104
}
105105
}
106106
},
107+
"/bodies/optional": {
108+
"post": {
109+
"tags": [
110+
"bodies"
111+
],
112+
"description": "Test optional request body",
113+
"operationId": "optional-body",
114+
"requestBody": {
115+
"required": false,
116+
"content": {
117+
"application/json": {
118+
"schema": {
119+
"type": "object"
120+
}
121+
}
122+
}
123+
},
124+
"responses": {
125+
"200": {
126+
"description": "OK"
127+
}
128+
}
129+
}
130+
},
107131
"/tests/": {
108132
"get": {
109133
"tags": [

end_to_end_tests/baseline_openapi_3.1.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,30 @@ info:
100100
}
101101
}
102102
},
103+
"/bodies/optional": {
104+
"post": {
105+
"tags": [
106+
"bodies"
107+
],
108+
"description": "Test optional request body",
109+
"operationId": "optional-body",
110+
"requestBody": {
111+
"required": false,
112+
"content": {
113+
"application/json": {
114+
"schema": {
115+
"type": "object"
116+
}
117+
}
118+
}
119+
},
120+
"responses": {
121+
"200": {
122+
"description": "OK"
123+
}
124+
}
125+
}
126+
},
103127
"/tests/": {
104128
"get": {
105129
"tags": [

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import types
44

5-
from . import json_like, post_bodies_multiple, refs
5+
from . import json_like, optional_body, post_bodies_multiple, refs
66

77

88
class BodiesEndpoints:
@@ -26,3 +26,10 @@ def refs(cls) -> types.ModuleType:
2626
Test request body defined via ref
2727
"""
2828
return refs
29+
30+
@classmethod
31+
def optional_body(cls) -> types.ModuleType:
32+
"""
33+
Test optional request body
34+
"""
35+
return optional_body

end_to_end_tests/functional_tests/generated_code_execution/test_docstrings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test_response_union_type(self, post_simple_thing_sync):
152152

153153
def test_request_body(self, post_simple_thing_sync):
154154
assert DocstringParser(post_simple_thing_sync).get_section("Args:") == [
155-
"body (Thing): The thing."
155+
"body (Thing | Unset): The thing."
156156
]
157157

158158
def test_params(self, get_attribute_by_index_sync):

end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
from ... import errors
77
from ...client import AuthenticatedClient, Client
88
from ...models.json_like_body import JsonLikeBody
9-
from ...types import Response
9+
from ...types import UNSET, Response, Unset
1010

1111

1212
def _get_kwargs(
1313
*,
14-
body: JsonLikeBody,
14+
body: JsonLikeBody | Unset = UNSET,
1515
) -> dict[str, Any]:
1616
headers: dict[str, Any] = {}
1717

@@ -20,7 +20,8 @@ def _get_kwargs(
2020
"url": "/bodies/json-like",
2121
}
2222

23-
_kwargs["json"] = body.to_dict()
23+
if not isinstance(body, Unset):
24+
_kwargs["json"] = body.to_dict()
2425

2526
headers["Content-Type"] = "application/vnd+json"
2627

@@ -50,12 +51,12 @@ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Res
5051
def sync_detailed(
5152
*,
5253
client: AuthenticatedClient | Client,
53-
body: JsonLikeBody,
54+
body: JsonLikeBody | Unset = UNSET,
5455
) -> Response[Any]:
5556
"""A content type that works like json but isn't application/json
5657
5758
Args:
58-
body (JsonLikeBody):
59+
body (JsonLikeBody | Unset):
5960
6061
Raises:
6162
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -79,12 +80,12 @@ def sync_detailed(
7980
async def asyncio_detailed(
8081
*,
8182
client: AuthenticatedClient | Client,
82-
body: JsonLikeBody,
83+
body: JsonLikeBody | Unset = UNSET,
8384
) -> Response[Any]:
8485
"""A content type that works like json but isn't application/json
8586
8687
Args:
87-
body (JsonLikeBody):
88+
body (JsonLikeBody | Unset):
8889
8990
Raises:
9091
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from http import HTTPStatus
2+
from typing import Any
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.optional_body_body import OptionalBodyBody
9+
from ...types import UNSET, Response, Unset
10+
11+
12+
def _get_kwargs(
13+
*,
14+
body: OptionalBodyBody | Unset = UNSET,
15+
) -> dict[str, Any]:
16+
headers: dict[str, Any] = {}
17+
18+
_kwargs: dict[str, Any] = {
19+
"method": "post",
20+
"url": "/bodies/optional",
21+
}
22+
23+
if not isinstance(body, Unset):
24+
_kwargs["json"] = body.to_dict()
25+
26+
headers["Content-Type"] = "application/json"
27+
28+
_kwargs["headers"] = headers
29+
return _kwargs
30+
31+
32+
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | None:
33+
if response.status_code == 200:
34+
return None
35+
36+
if client.raise_on_unexpected_status:
37+
raise errors.UnexpectedStatus(response.status_code, response.content)
38+
else:
39+
return None
40+
41+
42+
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
43+
return Response(
44+
status_code=HTTPStatus(response.status_code),
45+
content=response.content,
46+
headers=response.headers,
47+
parsed=_parse_response(client=client, response=response),
48+
)
49+
50+
51+
def sync_detailed(
52+
*,
53+
client: AuthenticatedClient | Client,
54+
body: OptionalBodyBody | Unset = UNSET,
55+
) -> Response[Any]:
56+
"""Test optional request body
57+
58+
Args:
59+
body (OptionalBodyBody | Unset):
60+
61+
Raises:
62+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
63+
httpx.TimeoutException: If the request takes longer than Client.timeout.
64+
65+
Returns:
66+
Response[Any]
67+
"""
68+
69+
kwargs = _get_kwargs(
70+
body=body,
71+
)
72+
73+
response = client.get_httpx_client().request(
74+
**kwargs,
75+
)
76+
77+
return _build_response(client=client, response=response)
78+
79+
80+
async def asyncio_detailed(
81+
*,
82+
client: AuthenticatedClient | Client,
83+
body: OptionalBodyBody | Unset = UNSET,
84+
) -> Response[Any]:
85+
"""Test optional request body
86+
87+
Args:
88+
body (OptionalBodyBody | Unset):
89+
90+
Raises:
91+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
92+
httpx.TimeoutException: If the request takes longer than Client.timeout.
93+
94+
Returns:
95+
Response[Any]
96+
"""
97+
98+
kwargs = _get_kwargs(
99+
body=body,
100+
)
101+
102+
response = await client.get_async_httpx_client().request(**kwargs)
103+
104+
return _build_response(client=client, response=response)

end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@
88
from ...models.post_bodies_multiple_data_body import PostBodiesMultipleDataBody
99
from ...models.post_bodies_multiple_files_body import PostBodiesMultipleFilesBody
1010
from ...models.post_bodies_multiple_json_body import PostBodiesMultipleJsonBody
11-
from ...types import File, Response
11+
from ...types import UNSET, File, Response, Unset
1212

1313

1414
def _get_kwargs(
1515
*,
16-
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody,
16+
body: PostBodiesMultipleJsonBody
17+
| Unset
18+
| File
19+
| Unset
20+
| PostBodiesMultipleDataBody
21+
| Unset
22+
| PostBodiesMultipleFilesBody
23+
| Unset = UNSET,
1724
) -> dict[str, Any]:
1825
headers: dict[str, Any] = {}
1926

@@ -23,19 +30,23 @@ def _get_kwargs(
2330
}
2431

2532
if isinstance(body, PostBodiesMultipleJsonBody):
26-
_kwargs["json"] = body.to_dict()
33+
if not isinstance(body, Unset):
34+
_kwargs["json"] = body.to_dict()
2735

2836
headers["Content-Type"] = "application/json"
2937
if isinstance(body, File):
30-
_kwargs["content"] = body.payload
38+
if not isinstance(body, Unset):
39+
_kwargs["content"] = body.payload
3140

3241
headers["Content-Type"] = "application/octet-stream"
3342
if isinstance(body, PostBodiesMultipleDataBody):
34-
_kwargs["data"] = body.to_dict()
43+
if not isinstance(body, Unset):
44+
_kwargs["data"] = body.to_dict()
3545

3646
headers["Content-Type"] = "application/x-www-form-urlencoded"
3747
if isinstance(body, PostBodiesMultipleFilesBody):
38-
_kwargs["files"] = body.to_multipart()
48+
if not isinstance(body, Unset):
49+
_kwargs["files"] = body.to_multipart()
3950

4051
headers["Content-Type"] = "multipart/form-data"
4152

@@ -65,15 +76,22 @@ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Res
6576
def sync_detailed(
6677
*,
6778
client: AuthenticatedClient | Client,
68-
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody,
79+
body: PostBodiesMultipleJsonBody
80+
| Unset
81+
| File
82+
| Unset
83+
| PostBodiesMultipleDataBody
84+
| Unset
85+
| PostBodiesMultipleFilesBody
86+
| Unset = UNSET,
6987
) -> Response[Any]:
7088
"""Test multiple bodies
7189
7290
Args:
73-
body (PostBodiesMultipleJsonBody):
74-
body (File):
75-
body (PostBodiesMultipleDataBody):
76-
body (PostBodiesMultipleFilesBody):
91+
body (PostBodiesMultipleJsonBody | Unset):
92+
body (File | Unset):
93+
body (PostBodiesMultipleDataBody | Unset):
94+
body (PostBodiesMultipleFilesBody | Unset):
7795
7896
Raises:
7997
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -97,15 +115,22 @@ def sync_detailed(
97115
async def asyncio_detailed(
98116
*,
99117
client: AuthenticatedClient | Client,
100-
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody,
118+
body: PostBodiesMultipleJsonBody
119+
| Unset
120+
| File
121+
| Unset
122+
| PostBodiesMultipleDataBody
123+
| Unset
124+
| PostBodiesMultipleFilesBody
125+
| Unset = UNSET,
101126
) -> Response[Any]:
102127
"""Test multiple bodies
103128
104129
Args:
105-
body (PostBodiesMultipleJsonBody):
106-
body (File):
107-
body (PostBodiesMultipleDataBody):
108-
body (PostBodiesMultipleFilesBody):
130+
body (PostBodiesMultipleJsonBody | Unset):
131+
body (File | Unset):
132+
body (PostBodiesMultipleDataBody | Unset):
133+
body (PostBodiesMultipleFilesBody | Unset):
109134
110135
Raises:
111136
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.

0 commit comments

Comments
 (0)