Skip to content

Commit 3e0f835

Browse files
kgutwindbanty
andauthored
Support binary request bodies for endpoints (#899)
Resolves #588. Thanks for a great tool! I hope this is compatible with the active work going on in #897. --------- Co-authored-by: Dylan Anthony <[email protected]>
1 parent 87b969c commit 3e0f835

File tree

8 files changed

+344
-66
lines changed

8 files changed

+344
-66
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
default: minor
3+
---
4+
5+
# Support `application/octet-stream` request bodies
6+
7+
Endpoints that accept `application/octet-stream` request bodies are now supported using the same `File` type as octet-stream responses.
8+
9+
Thanks to @kgutwin for the implementation and @rtaycher for the discussion!
10+
11+
PR #899 closes #588

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

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
json_body_tests_json_body_post,
1616
no_response_tests_no_response_get,
1717
octet_stream_tests_octet_stream_get,
18+
octet_stream_tests_octet_stream_post,
1819
post_form_data,
1920
post_form_data_inline,
2021
post_tests_json_body_string,
@@ -118,6 +119,13 @@ def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType:
118119
"""
119120
return octet_stream_tests_octet_stream_get
120121

122+
@classmethod
123+
def octet_stream_tests_octet_stream_post(cls) -> types.ModuleType:
124+
"""
125+
Binary (octet stream) request body
126+
"""
127+
return octet_stream_tests_octet_stream_post
128+
121129
@classmethod
122130
def no_response_tests_no_response_get(cls) -> types.ModuleType:
123131
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from http import HTTPStatus
2+
from typing import Any, Dict, Optional, Union, cast
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.http_validation_error import HTTPValidationError
9+
from ...types import File, Response
10+
11+
12+
def _get_kwargs(
13+
*,
14+
binary_body: File,
15+
) -> Dict[str, Any]:
16+
headers = {}
17+
headers["Content-Type"] = binary_body.mime_type if binary_body.mime_type else "application/octet-stream"
18+
19+
return {
20+
"method": "post",
21+
"url": "/tests/octet_stream",
22+
"content": binary_body.payload,
23+
"headers": headers,
24+
}
25+
26+
27+
def _parse_response(
28+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
29+
) -> Optional[Union[HTTPValidationError, str]]:
30+
if response.status_code == HTTPStatus.OK:
31+
response_200 = cast(str, response.json())
32+
return response_200
33+
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
34+
response_422 = HTTPValidationError.from_dict(response.json())
35+
36+
return response_422
37+
if client.raise_on_unexpected_status:
38+
raise errors.UnexpectedStatus(response.status_code, response.content)
39+
else:
40+
return None
41+
42+
43+
def _build_response(
44+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
45+
) -> Response[Union[HTTPValidationError, str]]:
46+
return Response(
47+
status_code=HTTPStatus(response.status_code),
48+
content=response.content,
49+
headers=response.headers,
50+
parsed=_parse_response(client=client, response=response),
51+
)
52+
53+
54+
def sync_detailed(
55+
*,
56+
client: Union[AuthenticatedClient, Client],
57+
binary_body: File,
58+
) -> Response[Union[HTTPValidationError, str]]:
59+
"""Binary (octet stream) request body
60+
61+
Args:
62+
binary_body (File): A file to upload
63+
64+
Raises:
65+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
66+
httpx.TimeoutException: If the request takes longer than Client.timeout.
67+
68+
Returns:
69+
Response[Union[HTTPValidationError, str]]
70+
"""
71+
72+
kwargs = _get_kwargs(
73+
binary_body=binary_body,
74+
)
75+
76+
response = client.get_httpx_client().request(
77+
**kwargs,
78+
)
79+
80+
return _build_response(client=client, response=response)
81+
82+
83+
def sync(
84+
*,
85+
client: Union[AuthenticatedClient, Client],
86+
binary_body: File,
87+
) -> Optional[Union[HTTPValidationError, str]]:
88+
"""Binary (octet stream) request body
89+
90+
Args:
91+
binary_body (File): A file to upload
92+
93+
Raises:
94+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
95+
httpx.TimeoutException: If the request takes longer than Client.timeout.
96+
97+
Returns:
98+
Union[HTTPValidationError, str]
99+
"""
100+
101+
return sync_detailed(
102+
client=client,
103+
binary_body=binary_body,
104+
).parsed
105+
106+
107+
async def asyncio_detailed(
108+
*,
109+
client: Union[AuthenticatedClient, Client],
110+
binary_body: File,
111+
) -> Response[Union[HTTPValidationError, str]]:
112+
"""Binary (octet stream) request body
113+
114+
Args:
115+
binary_body (File): A file to upload
116+
117+
Raises:
118+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
119+
httpx.TimeoutException: If the request takes longer than Client.timeout.
120+
121+
Returns:
122+
Response[Union[HTTPValidationError, str]]
123+
"""
124+
125+
kwargs = _get_kwargs(
126+
binary_body=binary_body,
127+
)
128+
129+
response = await client.get_async_httpx_client().request(**kwargs)
130+
131+
return _build_response(client=client, response=response)
132+
133+
134+
async def asyncio(
135+
*,
136+
client: Union[AuthenticatedClient, Client],
137+
binary_body: File,
138+
) -> Optional[Union[HTTPValidationError, str]]:
139+
"""Binary (octet stream) request body
140+
141+
Args:
142+
binary_body (File): A file to upload
143+
144+
Raises:
145+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
146+
httpx.TimeoutException: If the request takes longer than Client.timeout.
147+
148+
Returns:
149+
Union[HTTPValidationError, str]
150+
"""
151+
152+
return (
153+
await asyncio_detailed(
154+
client=client,
155+
binary_body=binary_body,
156+
)
157+
).parsed

end_to_end_tests/openapi.json

+40
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,46 @@
625625
}
626626
}
627627
}
628+
},
629+
"post": {
630+
"tags": [
631+
"tests"
632+
],
633+
"summary": "Binary (octet stream) request body",
634+
"operationId": "octet_stream_tests_octet_stream_post",
635+
"requestBody": {
636+
"content": {
637+
"application/octet-stream": {
638+
"schema": {
639+
"description": "A file to upload",
640+
"type": "string",
641+
"format": "binary"
642+
}
643+
}
644+
}
645+
},
646+
"responses": {
647+
"200": {
648+
"description": "success",
649+
"content": {
650+
"application/json": {
651+
"schema": {
652+
"type": "string"
653+
}
654+
}
655+
}
656+
},
657+
"422": {
658+
"description": "Validation Error",
659+
"content": {
660+
"application/json": {
661+
"schema": {
662+
"$ref": "#/components/schemas/HTTPValidationError"
663+
}
664+
}
665+
}
666+
}
667+
}
628668
}
629669
},
630670
"/tests/no_response": {

0 commit comments

Comments
 (0)