Skip to content

Commit c39737d

Browse files
committed
Fix optional bodies
1 parent 3464f80 commit c39737d

File tree

24 files changed

+374
-97
lines changed

24 files changed

+374
-97
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: 10 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,10 @@ def _get_kwargs(
2020
"url": "/bodies/json-like",
2121
}
2222

23-
_kwargs["json"] = body.to_dict()
23+
_json: dict[str, Any] | Unset = UNSET
24+
if not isinstance(body, Unset):
25+
_json = body.to_dict()
26+
_kwargs["json"] = _json
2427

2528
headers["Content-Type"] = "application/vnd+json"
2629

@@ -50,12 +53,12 @@ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Res
5053
def sync_detailed(
5154
*,
5255
client: AuthenticatedClient | Client,
53-
body: JsonLikeBody,
56+
body: JsonLikeBody | Unset = UNSET,
5457
) -> Response[Any]:
5558
"""A content type that works like json but isn't application/json
5659
5760
Args:
58-
body (JsonLikeBody):
61+
body (JsonLikeBody | Unset):
5962
6063
Raises:
6164
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -79,12 +82,12 @@ def sync_detailed(
7982
async def asyncio_detailed(
8083
*,
8184
client: AuthenticatedClient | Client,
82-
body: JsonLikeBody,
85+
body: JsonLikeBody | Unset = UNSET,
8386
) -> Response[Any]:
8487
"""A content type that works like json but isn't application/json
8588
8689
Args:
87-
body (JsonLikeBody):
90+
body (JsonLikeBody | Unset):
8891
8992
Raises:
9093
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
_json: dict[str, Any] | Unset = UNSET
24+
if not isinstance(body, Unset):
25+
_json = body.to_dict()
26+
_kwargs["json"] = _json
27+
28+
headers["Content-Type"] = "application/json"
29+
30+
_kwargs["headers"] = headers
31+
return _kwargs
32+
33+
34+
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | None:
35+
if response.status_code == 200:
36+
return None
37+
38+
if client.raise_on_unexpected_status:
39+
raise errors.UnexpectedStatus(response.status_code, response.content)
40+
else:
41+
return None
42+
43+
44+
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
45+
return Response(
46+
status_code=HTTPStatus(response.status_code),
47+
content=response.content,
48+
headers=response.headers,
49+
parsed=_parse_response(client=client, response=response),
50+
)
51+
52+
53+
def sync_detailed(
54+
*,
55+
client: AuthenticatedClient | Client,
56+
body: OptionalBodyBody | Unset = UNSET,
57+
) -> Response[Any]:
58+
"""Test optional request body
59+
60+
Args:
61+
body (OptionalBodyBody | Unset):
62+
63+
Raises:
64+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
65+
httpx.TimeoutException: If the request takes longer than Client.timeout.
66+
67+
Returns:
68+
Response[Any]
69+
"""
70+
71+
kwargs = _get_kwargs(
72+
body=body,
73+
)
74+
75+
response = client.get_httpx_client().request(
76+
**kwargs,
77+
)
78+
79+
return _build_response(client=client, response=response)
80+
81+
82+
async def asyncio_detailed(
83+
*,
84+
client: AuthenticatedClient | Client,
85+
body: OptionalBodyBody | Unset = UNSET,
86+
) -> Response[Any]:
87+
"""Test optional request body
88+
89+
Args:
90+
body (OptionalBodyBody | Unset):
91+
92+
Raises:
93+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
94+
httpx.TimeoutException: If the request takes longer than Client.timeout.
95+
96+
Returns:
97+
Response[Any]
98+
"""
99+
100+
kwargs = _get_kwargs(
101+
body=body,
102+
)
103+
104+
response = await client.get_async_httpx_client().request(**kwargs)
105+
106+
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: 43 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,25 @@ def _get_kwargs(
2330
}
2431

2532
if isinstance(body, PostBodiesMultipleJsonBody):
26-
_kwargs["json"] = body.to_dict()
33+
_json: dict[str, Any] | Unset = UNSET
34+
if not isinstance(body, Unset):
35+
_json = body.to_dict()
36+
_kwargs["json"] = _json
2737

2838
headers["Content-Type"] = "application/json"
2939
if isinstance(body, File):
30-
_kwargs["content"] = body.payload
40+
if body:
41+
_kwargs["content"] = body.payload
3142

3243
headers["Content-Type"] = "application/octet-stream"
3344
if isinstance(body, PostBodiesMultipleDataBody):
34-
_kwargs["data"] = body.to_dict()
45+
if body:
46+
_kwargs["data"] = body.to_dict()
3547

3648
headers["Content-Type"] = "application/x-www-form-urlencoded"
3749
if isinstance(body, PostBodiesMultipleFilesBody):
38-
_kwargs["files"] = body.to_multipart()
50+
if not isinstance(body, Unset):
51+
_kwargs["files"] = body.to_multipart()
3952

4053
headers["Content-Type"] = "multipart/form-data"
4154

@@ -65,15 +78,22 @@ def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Res
6578
def sync_detailed(
6679
*,
6780
client: AuthenticatedClient | Client,
68-
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody,
81+
body: PostBodiesMultipleJsonBody
82+
| Unset
83+
| File
84+
| Unset
85+
| PostBodiesMultipleDataBody
86+
| Unset
87+
| PostBodiesMultipleFilesBody
88+
| Unset = UNSET,
6989
) -> Response[Any]:
7090
"""Test multiple bodies
7191
7292
Args:
73-
body (PostBodiesMultipleJsonBody):
74-
body (File):
75-
body (PostBodiesMultipleDataBody):
76-
body (PostBodiesMultipleFilesBody):
93+
body (PostBodiesMultipleJsonBody | Unset):
94+
body (File | Unset):
95+
body (PostBodiesMultipleDataBody | Unset):
96+
body (PostBodiesMultipleFilesBody | Unset):
7797
7898
Raises:
7999
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -97,15 +117,22 @@ def sync_detailed(
97117
async def asyncio_detailed(
98118
*,
99119
client: AuthenticatedClient | Client,
100-
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody,
120+
body: PostBodiesMultipleJsonBody
121+
| Unset
122+
| File
123+
| Unset
124+
| PostBodiesMultipleDataBody
125+
| Unset
126+
| PostBodiesMultipleFilesBody
127+
| Unset = UNSET,
101128
) -> Response[Any]:
102129
"""Test multiple bodies
103130
104131
Args:
105-
body (PostBodiesMultipleJsonBody):
106-
body (File):
107-
body (PostBodiesMultipleDataBody):
108-
body (PostBodiesMultipleFilesBody):
132+
body (PostBodiesMultipleJsonBody | Unset):
133+
body (File | Unset):
134+
body (PostBodiesMultipleDataBody | Unset):
135+
body (PostBodiesMultipleFilesBody | Unset):
109136
110137
Raises:
111138
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.

0 commit comments

Comments
 (0)