Skip to content

Commit cc98333

Browse files
authored
feat(python): handle multiple file parameters (#19329)
* test: fix broken python test * fix: handle multiple file parameters Previously an array of files was not handled correctly, despite the type annotation implying it was. * feat: handle filename,filedata tuples in file param This allows for users to pass filenames with their data in file params, which is useful for multipart formdata requests. Without this, the list of files added in the previous commit would have the same filename for all files (the parameter name).
1 parent c2472b0 commit cc98333

38 files changed

+673
-235
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java

+24
Original file line numberDiff line numberDiff line change
@@ -1951,9 +1951,22 @@ private PythonType binaryType(IJsonSchemaValidationProperties cp) {
19511951
}
19521952

19531953
moduleImports.add("typing", "Union");
1954+
19541955
PythonType pt = new PythonType("Union");
19551956
pt.addTypeParam(bytest);
19561957
pt.addTypeParam(strt);
1958+
1959+
if (cp.getIsBinary()) {
1960+
moduleImports.add("typing", "Tuple");
1961+
1962+
PythonType tt = new PythonType("Tuple");
1963+
// this string is a filename, not a validated value
1964+
tt.addTypeParam(new PythonType("str"));
1965+
tt.addTypeParam(bytest);
1966+
1967+
pt.addTypeParam(tt);
1968+
}
1969+
19571970
return pt;
19581971
} else {
19591972
// same as above which has validation
@@ -1964,6 +1977,17 @@ private PythonType binaryType(IJsonSchemaValidationProperties cp) {
19641977
PythonType pt = new PythonType("Union");
19651978
pt.addTypeParam(new PythonType("StrictBytes"));
19661979
pt.addTypeParam(new PythonType("StrictStr"));
1980+
1981+
if (cp.getIsBinary()) {
1982+
moduleImports.add("typing", "Tuple");
1983+
1984+
PythonType tt = new PythonType("Tuple");
1985+
tt.addTypeParam(new PythonType("StrictStr"));
1986+
tt.addTypeParam(new PythonType("StrictBytes"));
1987+
1988+
pt.addTypeParam(tt);
1989+
}
1990+
19671991
return pt;
19681992
}
19691993
}

modules/openapi-generator/src/main/resources/python/api.mustache

+6-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ class {{classname}}:
101101
_query_params: List[Tuple[str, str]] = []
102102
_header_params: Dict[str, Optional[str]] = _headers or {}
103103
_form_params: List[Tuple[str, str]] = []
104-
_files: Dict[str, Union[str, bytes]] = {}
104+
_files: Dict[
105+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
106+
] = {}
105107
_body_params: Optional[bytes] = None
106108

107109
# process the path parameters
@@ -165,6 +167,9 @@ class {{classname}}:
165167
if isinstance({{paramName}}, str):
166168
with open({{paramName}}, "rb") as _fp:
167169
_body_params = _fp.read()
170+
elif isinstance({{paramName}}, tuple):
171+
# drop the filename from the tuple
172+
_body_params = {{paramName}}[1]
168173
else:
169174
_body_params = {{paramName}}
170175
{{/isBinary}}

modules/openapi-generator/src/main/resources/python/api_client.mustache

+10-1
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,10 @@ class ApiClient:
543543

544544
return "&".join(["=".join(map(str, item)) for item in new_params])
545545

546-
def files_parameters(self, files: Dict[str, Union[str, bytes]]):
546+
def files_parameters(
547+
self,
548+
files: Dict[str, Union[str, bytes, List[str], List[bytes], Tuple[str, bytes]]],
549+
):
547550
"""Builds form parameters.
548551

549552
:param files: File parameters.
@@ -558,6 +561,12 @@ class ApiClient:
558561
elif isinstance(v, bytes):
559562
filename = k
560563
filedata = v
564+
elif isinstance(v, tuple):
565+
filename, filedata = v
566+
elif isinstance(v, list):
567+
for file_param in v:
568+
params.extend(self.files_parameters({k: file_param}))
569+
continue
561570
else:
562571
raise ValueError("Unsupported file value")
563572
mimetype = (

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api/auth_api.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ def _test_auth_http_basic_serialize(
239239
_query_params: List[Tuple[str, str]] = []
240240
_header_params: Dict[str, Optional[str]] = _headers or {}
241241
_form_params: List[Tuple[str, str]] = []
242-
_files: Dict[str, Union[str, bytes]] = {}
242+
_files: Dict[
243+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
244+
] = {}
243245
_body_params: Optional[bytes] = None
244246

245247
# process the path parameters
@@ -483,7 +485,9 @@ def _test_auth_http_bearer_serialize(
483485
_query_params: List[Tuple[str, str]] = []
484486
_header_params: Dict[str, Optional[str]] = _headers or {}
485487
_form_params: List[Tuple[str, str]] = []
486-
_files: Dict[str, Union[str, bytes]] = {}
488+
_files: Dict[
489+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
490+
] = {}
487491
_body_params: Optional[bytes] = None
488492

489493
# process the path parameters

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api/body_api.py

+43-20
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from typing_extensions import Annotated
1919

2020
from pydantic import Field, StrictBytes, StrictStr
21-
from typing import Any, Dict, List, Optional, Union
21+
from typing import Any, Dict, List, Optional, Tuple, Union
2222
from typing_extensions import Annotated
2323
from openapi_client.models.pet import Pet
2424
from openapi_client.models.string_enum_ref import StringEnumRef
@@ -244,7 +244,9 @@ def _test_binary_gif_serialize(
244244
_query_params: List[Tuple[str, str]] = []
245245
_header_params: Dict[str, Optional[str]] = _headers or {}
246246
_form_params: List[Tuple[str, str]] = []
247-
_files: Dict[str, Union[str, bytes]] = {}
247+
_files: Dict[
248+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
249+
] = {}
248250
_body_params: Optional[bytes] = None
249251

250252
# process the path parameters
@@ -288,7 +290,7 @@ def _test_binary_gif_serialize(
288290
@validate_call
289291
def test_body_application_octetstream_binary(
290292
self,
291-
body: Optional[Union[StrictBytes, StrictStr]] = None,
293+
body: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
292294
_request_timeout: Union[
293295
None,
294296
Annotated[StrictFloat, Field(gt=0)],
@@ -355,7 +357,7 @@ def test_body_application_octetstream_binary(
355357
@validate_call
356358
def test_body_application_octetstream_binary_with_http_info(
357359
self,
358-
body: Optional[Union[StrictBytes, StrictStr]] = None,
360+
body: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
359361
_request_timeout: Union[
360362
None,
361363
Annotated[StrictFloat, Field(gt=0)],
@@ -422,7 +424,7 @@ def test_body_application_octetstream_binary_with_http_info(
422424
@validate_call
423425
def test_body_application_octetstream_binary_without_preload_content(
424426
self,
425-
body: Optional[Union[StrictBytes, StrictStr]] = None,
427+
body: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
426428
_request_timeout: Union[
427429
None,
428430
Annotated[StrictFloat, Field(gt=0)],
@@ -500,7 +502,9 @@ def _test_body_application_octetstream_binary_serialize(
500502
_query_params: List[Tuple[str, str]] = []
501503
_header_params: Dict[str, Optional[str]] = _headers or {}
502504
_form_params: List[Tuple[str, str]] = []
503-
_files: Dict[str, Union[str, bytes]] = {}
505+
_files: Dict[
506+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
507+
] = {}
504508
_body_params: Optional[bytes] = None
505509

506510
# process the path parameters
@@ -513,6 +517,9 @@ def _test_body_application_octetstream_binary_serialize(
513517
if isinstance(body, str):
514518
with open(body, "rb") as _fp:
515519
_body_params = _fp.read()
520+
elif isinstance(body, tuple):
521+
# drop the filename from the tuple
522+
_body_params = body[1]
516523
else:
517524
_body_params = body
518525

@@ -564,7 +571,7 @@ def _test_body_application_octetstream_binary_serialize(
564571
@validate_call
565572
def test_body_multipart_formdata_array_of_binary(
566573
self,
567-
files: List[Union[StrictBytes, StrictStr]],
574+
files: List[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]],
568575
_request_timeout: Union[
569576
None,
570577
Annotated[StrictFloat, Field(gt=0)],
@@ -631,7 +638,7 @@ def test_body_multipart_formdata_array_of_binary(
631638
@validate_call
632639
def test_body_multipart_formdata_array_of_binary_with_http_info(
633640
self,
634-
files: List[Union[StrictBytes, StrictStr]],
641+
files: List[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]],
635642
_request_timeout: Union[
636643
None,
637644
Annotated[StrictFloat, Field(gt=0)],
@@ -698,7 +705,7 @@ def test_body_multipart_formdata_array_of_binary_with_http_info(
698705
@validate_call
699706
def test_body_multipart_formdata_array_of_binary_without_preload_content(
700707
self,
701-
files: List[Union[StrictBytes, StrictStr]],
708+
files: List[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]],
702709
_request_timeout: Union[
703710
None,
704711
Annotated[StrictFloat, Field(gt=0)],
@@ -777,7 +784,9 @@ def _test_body_multipart_formdata_array_of_binary_serialize(
777784
_query_params: List[Tuple[str, str]] = []
778785
_header_params: Dict[str, Optional[str]] = _headers or {}
779786
_form_params: List[Tuple[str, str]] = []
780-
_files: Dict[str, Union[str, bytes]] = {}
787+
_files: Dict[
788+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
789+
] = {}
781790
_body_params: Optional[bytes] = None
782791

783792
# process the path parameters
@@ -836,7 +845,7 @@ def _test_body_multipart_formdata_array_of_binary_serialize(
836845
@validate_call
837846
def test_body_multipart_formdata_single_binary(
838847
self,
839-
my_file: Optional[Union[StrictBytes, StrictStr]] = None,
848+
my_file: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
840849
_request_timeout: Union[
841850
None,
842851
Annotated[StrictFloat, Field(gt=0)],
@@ -903,7 +912,7 @@ def test_body_multipart_formdata_single_binary(
903912
@validate_call
904913
def test_body_multipart_formdata_single_binary_with_http_info(
905914
self,
906-
my_file: Optional[Union[StrictBytes, StrictStr]] = None,
915+
my_file: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
907916
_request_timeout: Union[
908917
None,
909918
Annotated[StrictFloat, Field(gt=0)],
@@ -970,7 +979,7 @@ def test_body_multipart_formdata_single_binary_with_http_info(
970979
@validate_call
971980
def test_body_multipart_formdata_single_binary_without_preload_content(
972981
self,
973-
my_file: Optional[Union[StrictBytes, StrictStr]] = None,
982+
my_file: Optional[Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]]] = None,
974983
_request_timeout: Union[
975984
None,
976985
Annotated[StrictFloat, Field(gt=0)],
@@ -1048,7 +1057,9 @@ def _test_body_multipart_formdata_single_binary_serialize(
10481057
_query_params: List[Tuple[str, str]] = []
10491058
_header_params: Dict[str, Optional[str]] = _headers or {}
10501059
_form_params: List[Tuple[str, str]] = []
1051-
_files: Dict[str, Union[str, bytes]] = {}
1060+
_files: Dict[
1061+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
1062+
] = {}
10521063
_body_params: Optional[bytes] = None
10531064

10541065
# process the path parameters
@@ -1319,7 +1330,9 @@ def _test_echo_body_all_of_pet_serialize(
13191330
_query_params: List[Tuple[str, str]] = []
13201331
_header_params: Dict[str, Optional[str]] = _headers or {}
13211332
_form_params: List[Tuple[str, str]] = []
1322-
_files: Dict[str, Union[str, bytes]] = {}
1333+
_files: Dict[
1334+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
1335+
] = {}
13231336
_body_params: Optional[bytes] = None
13241337

13251338
# process the path parameters
@@ -1590,7 +1603,9 @@ def _test_echo_body_free_form_object_response_string_serialize(
15901603
_query_params: List[Tuple[str, str]] = []
15911604
_header_params: Dict[str, Optional[str]] = _headers or {}
15921605
_form_params: List[Tuple[str, str]] = []
1593-
_files: Dict[str, Union[str, bytes]] = {}
1606+
_files: Dict[
1607+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
1608+
] = {}
15941609
_body_params: Optional[bytes] = None
15951610

15961611
# process the path parameters
@@ -1861,7 +1876,9 @@ def _test_echo_body_pet_serialize(
18611876
_query_params: List[Tuple[str, str]] = []
18621877
_header_params: Dict[str, Optional[str]] = _headers or {}
18631878
_form_params: List[Tuple[str, str]] = []
1864-
_files: Dict[str, Union[str, bytes]] = {}
1879+
_files: Dict[
1880+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
1881+
] = {}
18651882
_body_params: Optional[bytes] = None
18661883

18671884
# process the path parameters
@@ -2132,7 +2149,9 @@ def _test_echo_body_pet_response_string_serialize(
21322149
_query_params: List[Tuple[str, str]] = []
21332150
_header_params: Dict[str, Optional[str]] = _headers or {}
21342151
_form_params: List[Tuple[str, str]] = []
2135-
_files: Dict[str, Union[str, bytes]] = {}
2152+
_files: Dict[
2153+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
2154+
] = {}
21362155
_body_params: Optional[bytes] = None
21372156

21382157
# process the path parameters
@@ -2403,7 +2422,9 @@ def _test_echo_body_string_enum_serialize(
24032422
_query_params: List[Tuple[str, str]] = []
24042423
_header_params: Dict[str, Optional[str]] = _headers or {}
24052424
_form_params: List[Tuple[str, str]] = []
2406-
_files: Dict[str, Union[str, bytes]] = {}
2425+
_files: Dict[
2426+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
2427+
] = {}
24072428
_body_params: Optional[bytes] = None
24082429

24092430
# process the path parameters
@@ -2674,7 +2695,9 @@ def _test_echo_body_tag_response_string_serialize(
26742695
_query_params: List[Tuple[str, str]] = []
26752696
_header_params: Dict[str, Optional[str]] = _headers or {}
26762697
_form_params: List[Tuple[str, str]] = []
2677-
_files: Dict[str, Union[str, bytes]] = {}
2698+
_files: Dict[
2699+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
2700+
] = {}
26782701
_body_params: Optional[bytes] = None
26792702

26802703
# process the path parameters

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api/form_api.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ def _test_form_integer_boolean_string_serialize(
280280
_query_params: List[Tuple[str, str]] = []
281281
_header_params: Dict[str, Optional[str]] = _headers or {}
282282
_form_params: List[Tuple[str, str]] = []
283-
_files: Dict[str, Union[str, bytes]] = {}
283+
_files: Dict[
284+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
285+
] = {}
284286
_body_params: Optional[bytes] = None
285287

286288
# process the path parameters
@@ -555,7 +557,9 @@ def _test_form_object_multipart_serialize(
555557
_query_params: List[Tuple[str, str]] = []
556558
_header_params: Dict[str, Optional[str]] = _headers or {}
557559
_form_params: List[Tuple[str, str]] = []
558-
_files: Dict[str, Union[str, bytes]] = {}
560+
_files: Dict[
561+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
562+
] = {}
559563
_body_params: Optional[bytes] = None
560564

561565
# process the path parameters
@@ -891,7 +895,9 @@ def _test_form_oneof_serialize(
891895
_query_params: List[Tuple[str, str]] = []
892896
_header_params: Dict[str, Optional[str]] = _headers or {}
893897
_form_params: List[Tuple[str, str]] = []
894-
_files: Dict[str, Union[str, bytes]] = {}
898+
_files: Dict[
899+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
900+
] = {}
895901
_body_params: Optional[bytes] = None
896902

897903
# process the path parameters

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api/header_api.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,9 @@ def _test_header_integer_boolean_string_enums_serialize(
306306
_query_params: List[Tuple[str, str]] = []
307307
_header_params: Dict[str, Optional[str]] = _headers or {}
308308
_form_params: List[Tuple[str, str]] = []
309-
_files: Dict[str, Union[str, bytes]] = {}
309+
_files: Dict[
310+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
311+
] = {}
310312
_body_params: Optional[bytes] = None
311313

312314
# process the path parameters

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api/path_api.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ def _tests_path_string_path_string_integer_path_integer_enum_nonref_string_path_
292292
_query_params: List[Tuple[str, str]] = []
293293
_header_params: Dict[str, Optional[str]] = _headers or {}
294294
_form_params: List[Tuple[str, str]] = []
295-
_files: Dict[str, Union[str, bytes]] = {}
295+
_files: Dict[
296+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
297+
] = {}
296298
_body_params: Optional[bytes] = None
297299

298300
# process the path parameters

0 commit comments

Comments
 (0)