Skip to content

Commit 2cc22a7

Browse files
fix: Support multipart requests with type: array [#452 & #451]. Thanks @csymeonides-mf @slamora and @dpursehouse
1 parent 289787a commit 2cc22a7

File tree

4 files changed

+200
-7
lines changed

4 files changed

+200
-7
lines changed

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
@@ -19,6 +19,7 @@
1919
token_with_cookie_auth_token_with_cookie_get,
2020
unsupported_content_tests_unsupported_content_get,
2121
upload_file_tests_upload_post,
22+
upload_multiple_files_tests_upload_post,
2223
)
2324

2425

@@ -72,6 +73,13 @@ def upload_file_tests_upload_post(cls) -> types.ModuleType:
7273
"""
7374
return upload_file_tests_upload_post
7475

76+
@classmethod
77+
def upload_multiple_files_tests_upload_post(cls) -> types.ModuleType:
78+
"""
79+
Upload several files in the same request
80+
"""
81+
return upload_multiple_files_tests_upload_post
82+
7583
@classmethod
7684
def json_body_tests_json_body_post(cls) -> types.ModuleType:
7785
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from typing import Any, Dict, List, Optional, Union
2+
3+
import httpx
4+
5+
from ...client import Client
6+
from ...models.http_validation_error import HTTPValidationError
7+
from ...types import UNSET, File, Response, Unset
8+
9+
10+
def _get_kwargs(
11+
*,
12+
client: Client,
13+
multipart_data: List[File],
14+
keep_alive: Union[Unset, bool] = UNSET,
15+
) -> Dict[str, Any]:
16+
url = "{}/tests/upload/multiple".format(client.base_url)
17+
18+
headers: Dict[str, Any] = client.get_headers()
19+
cookies: Dict[str, Any] = client.get_cookies()
20+
21+
if keep_alive is not UNSET:
22+
headers["keep-alive"] = keep_alive
23+
24+
multipart_multipart_data = []
25+
for multipart_data_item_data in multipart_data:
26+
multipart_data_item = multipart_data_item_data.to_tuple()
27+
28+
multipart_multipart_data.append(multipart_data_item)
29+
30+
return {
31+
"url": url,
32+
"headers": headers,
33+
"cookies": cookies,
34+
"timeout": client.get_timeout(),
35+
"files": multipart_multipart_data,
36+
}
37+
38+
39+
def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, HTTPValidationError]]:
40+
if response.status_code == 200:
41+
response_200 = response.json()
42+
43+
return response_200
44+
if response.status_code == 422:
45+
response_422 = HTTPValidationError.from_dict(response.json())
46+
47+
return response_422
48+
return None
49+
50+
51+
def _build_response(*, response: httpx.Response) -> Response[Union[Any, HTTPValidationError]]:
52+
return Response(
53+
status_code=response.status_code,
54+
content=response.content,
55+
headers=response.headers,
56+
parsed=_parse_response(response=response),
57+
)
58+
59+
60+
def sync_detailed(
61+
*,
62+
client: Client,
63+
multipart_data: List[File],
64+
keep_alive: Union[Unset, bool] = UNSET,
65+
) -> Response[Union[Any, HTTPValidationError]]:
66+
kwargs = _get_kwargs(
67+
client=client,
68+
multipart_data=multipart_data,
69+
keep_alive=keep_alive,
70+
)
71+
72+
response = httpx.post(
73+
**kwargs,
74+
)
75+
76+
return _build_response(response=response)
77+
78+
79+
def sync(
80+
*,
81+
client: Client,
82+
multipart_data: List[File],
83+
keep_alive: Union[Unset, bool] = UNSET,
84+
) -> Optional[Union[Any, HTTPValidationError]]:
85+
"""Upload several files in the same request"""
86+
87+
return sync_detailed(
88+
client=client,
89+
multipart_data=multipart_data,
90+
keep_alive=keep_alive,
91+
).parsed
92+
93+
94+
async def asyncio_detailed(
95+
*,
96+
client: Client,
97+
multipart_data: List[File],
98+
keep_alive: Union[Unset, bool] = UNSET,
99+
) -> Response[Union[Any, HTTPValidationError]]:
100+
kwargs = _get_kwargs(
101+
client=client,
102+
multipart_data=multipart_data,
103+
keep_alive=keep_alive,
104+
)
105+
106+
async with httpx.AsyncClient() as _client:
107+
response = await _client.post(**kwargs)
108+
109+
return _build_response(response=response)
110+
111+
112+
async def asyncio(
113+
*,
114+
client: Client,
115+
multipart_data: List[File],
116+
keep_alive: Union[Unset, bool] = UNSET,
117+
) -> Optional[Union[Any, HTTPValidationError]]:
118+
"""Upload several files in the same request"""
119+
120+
return (
121+
await asyncio_detailed(
122+
client=client,
123+
multipart_data=multipart_data,
124+
keep_alive=keep_alive,
125+
)
126+
).parsed

end_to_end_tests/openapi.json

+55
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,61 @@
269269
}
270270
}
271271
},
272+
"/tests/upload/multiple": {
273+
"post": {
274+
"tags": [
275+
"tests"
276+
],
277+
"summary": "Upload multiple files",
278+
"description": "Upload several files in the same request",
279+
"operationId": "upload_multiple_files_tests_upload_post",
280+
"parameters": [
281+
{
282+
"required": false,
283+
"schema": {
284+
"title": "Keep-Alive",
285+
"type": "boolean"
286+
},
287+
"name": "keep-alive",
288+
"in": "header"
289+
}
290+
],
291+
"requestBody": {
292+
"content": {
293+
"multipart/form-data": {
294+
"schema": {
295+
"type": "array",
296+
"items": {
297+
"type": "string",
298+
"format": "binary"
299+
}
300+
}
301+
}
302+
},
303+
"required": true
304+
},
305+
"responses": {
306+
"200": {
307+
"description": "Successful Response",
308+
"content": {
309+
"application/json": {
310+
"schema": {}
311+
}
312+
}
313+
},
314+
"422": {
315+
"description": "Validation Error",
316+
"content": {
317+
"application/json": {
318+
"schema": {
319+
"$ref": "#/components/schemas/HTTPValidationError"
320+
}
321+
}
322+
}
323+
}
324+
}
325+
}
326+
},
272327
"/tests/json_body": {
273328
"post": {
274329
"tags": [

openapi_python_client/templates/property_templates/list_property.py.jinja

+11-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ for {{ inner_source }} in (_{{ property.python_name }} or []):
1717
{% endif %}
1818
{% endmacro %}
1919

20-
{% macro _transform(property, source, destination, stringify) %}
20+
{% macro _transform(property, source, destination, stringify, transform_method) %}
2121
{% set inner_property = property.inner_property %}
2222
{% if stringify %}
2323
{% set stringified_destination = destination %}
@@ -28,7 +28,7 @@ for {{ inner_source }} in (_{{ property.python_name }} or []):
2828
{{ destination }} = []
2929
for {{ inner_source }} in {{ source }}:
3030
{% from "property_templates/" + inner_property.template import transform %}
31-
{{ transform(inner_property, inner_source, inner_property.python_name) | indent(4) }}
31+
{{ transform(inner_property, inner_source, inner_property.python_name, transform_method) | indent(4) }}
3232
{{ destination }}.append({{ inner_property.python_name }})
3333
{% else %}
3434
{{ destination }} = {{ source }}
@@ -40,7 +40,7 @@ for {{ inner_source }} in {{ source }}:
4040

4141
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, list){% endmacro %}
4242

43-
{% macro transform(property, source, destination, declare_type=True, stringify=False) %}
43+
{% macro transform(property, source, destination, declare_type=True, stringify=False, transform_method="to_dict") %}
4444
{% set inner_property = property.inner_property %}
4545
{% if stringify %}
4646
{% set type_string = "Union[Unset, Tuple[None, str, str]]" %}
@@ -52,9 +52,9 @@ for {{ inner_source }} in {{ source }}:
5252
if {{ source }} is None:
5353
{{ destination }} = None
5454
else:
55-
{{ _transform(property, source, destination, stringify) | indent(4) }}
55+
{{ _transform(property, source, destination, stringify, transform_method) | indent(4) }}
5656
{% else %}
57-
{{ _transform(property, source, destination, stringify) }}
57+
{{ _transform(property, source, destination, stringify, transform_method) }}
5858
{% endif %}
5959
{% else %}
6060
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = UNSET
@@ -63,11 +63,15 @@ if not isinstance({{ source }}, Unset):
6363
if {{ source }} is None:
6464
{{ destination }} = None
6565
else:
66-
{{ _transform(property, source, destination, stringify) | indent(8)}}
66+
{{ _transform(property, source, destination, stringify, transform_method) | indent(8)}}
6767
{% else %}
68-
{{ _transform(property, source, destination, stringify) | indent(4)}}
68+
{{ _transform(property, source, destination, stringify, transform_method) | indent(4)}}
6969
{% endif %}
7070
{% endif %}
7171

7272

7373
{% endmacro %}
74+
75+
{% macro transform_multipart(property, source, destination) %}
76+
{{ transform(property, source, destination, transform_method="to_multipart") }}
77+
{% endmacro %}

0 commit comments

Comments
 (0)