Skip to content

Commit 92ecee8

Browse files
authored
Don't cast list to tuple in python-prior binding (#14014)
* Add test for python-prior type conversion error In the spirit of test driven development, this test intentionally fails. A following commit will fix the code to comply with the test. See: #14012 * Don't cast list to tuple in python-prior binding Tweak the python-prior API bindings, so that they no longer cast lists to tuples when making a POST request with a multipart/form-data content-type. This fixes an interaction with `urllib3.request_encode_body`, whose `fields` parameter expects tuples, not lists. cc @spacether See: https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html Fix: #14012
1 parent 7722698 commit 92ecee8

6 files changed

Lines changed: 60 additions & 5 deletions

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,10 @@ class ApiClient(object):
298298
return obj.isoformat()
299299
elif isinstance(obj, ModelSimple):
300300
return cls.sanitize_for_serialization(obj.value)
301-
elif isinstance(obj, (list, tuple)):
301+
elif isinstance(obj, list):
302302
return [cls.sanitize_for_serialization(item) for item in obj]
303+
elif isinstance(obj, tuple):
304+
return tuple(cls.sanitize_for_serialization(item) for item in obj)
303305
if isinstance(obj, dict):
304306
return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()}
305307
raise ApiValueError(

samples/client/petstore/python-prior/petstore_api/api_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ def sanitize_for_serialization(cls, obj):
286286
return obj.isoformat()
287287
elif isinstance(obj, ModelSimple):
288288
return cls.sanitize_for_serialization(obj.value)
289-
elif isinstance(obj, (list, tuple)):
289+
elif isinstance(obj, list):
290290
return [cls.sanitize_for_serialization(item) for item in obj]
291+
elif isinstance(obj, tuple):
292+
return tuple(cls.sanitize_for_serialization(item) for item in obj)
291293
if isinstance(obj, dict):
292294
return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()}
293295
raise ApiValueError(

samples/client/petstore/python-prior_disallowAdditionalPropertiesIfNotPresent/petstore_api/api_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ def sanitize_for_serialization(cls, obj):
286286
return obj.isoformat()
287287
elif isinstance(obj, ModelSimple):
288288
return cls.sanitize_for_serialization(obj.value)
289-
elif isinstance(obj, (list, tuple)):
289+
elif isinstance(obj, list):
290290
return [cls.sanitize_for_serialization(item) for item in obj]
291+
elif isinstance(obj, tuple):
292+
return tuple(cls.sanitize_for_serialization(item) for item in obj)
291293
if isinstance(obj, dict):
292294
return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()}
293295
raise ApiValueError(

samples/openapi3/client/extensions/x-auth-id-alias/python-prior/x_auth_id_alias/api_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ def sanitize_for_serialization(cls, obj):
286286
return obj.isoformat()
287287
elif isinstance(obj, ModelSimple):
288288
return cls.sanitize_for_serialization(obj.value)
289-
elif isinstance(obj, (list, tuple)):
289+
elif isinstance(obj, list):
290290
return [cls.sanitize_for_serialization(item) for item in obj]
291+
elif isinstance(obj, tuple):
292+
return tuple(cls.sanitize_for_serialization(item) for item in obj)
291293
if isinstance(obj, dict):
292294
return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()}
293295
raise ApiValueError(

samples/openapi3/client/petstore/python-prior/petstore_api/api_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ def sanitize_for_serialization(cls, obj):
286286
return obj.isoformat()
287287
elif isinstance(obj, ModelSimple):
288288
return cls.sanitize_for_serialization(obj.value)
289-
elif isinstance(obj, (list, tuple)):
289+
elif isinstance(obj, list):
290290
return [cls.sanitize_for_serialization(item) for item in obj]
291+
elif isinstance(obj, tuple):
292+
return tuple(cls.sanitize_for_serialization(item) for item in obj)
291293
if isinstance(obj, dict):
292294
return {key: cls.sanitize_for_serialization(val) for key, val in obj.items()}
293295
raise ApiValueError(

samples/openapi3/client/petstore/python-prior/tests_manual/test_fake_api.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
import os
1515
import json
1616
import unittest
17+
from pathlib import Path
1718
from unittest.mock import patch
1819

20+
from urllib3.request import RequestMethods
21+
1922
import petstore_api
2023
from petstore_api.api.fake_api import FakeApi # noqa: E501
2124
from petstore_api.rest import RESTClientObject, RESTResponse
@@ -490,6 +493,48 @@ def get_header(name, default=None):
490493
finally:
491494
file.close()
492495

496+
def test_upload_with_mime_type(self):
497+
"""Upload a file, while setting a MIME type for that file.
498+
499+
Verify that:
500+
501+
* ``post_params`` may contain a three-tuple in the form ``(file_name, file_handle,
502+
file_mime_type)``
503+
* This three-tuple is passed to urllib3 as a tuple, without being converted to a list.
504+
505+
See:
506+
507+
* https://github.com/OpenAPITools/openapi-generator/issues/14012
508+
* https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html
509+
"""
510+
file_path = Path(__file__, "..", "..", "testfiles", "1px_pic1.png").resolve()
511+
file_mime_type = "image/png"
512+
with patch.object(RequestMethods, 'request') as request:
513+
with open(file_path, mode="rb") as file_handle:
514+
request.return_value.status = 200
515+
resp = self.api.api_client.call_api(
516+
resource_path="/fake/uploadFile",
517+
method="POST",
518+
header_params={"Content-Type": "multipart/form-data"},
519+
post_params={"file": (file_path.name, file_handle, file_mime_type)},
520+
)
521+
522+
# a single multipart/form-data POST call was made, with a single form field
523+
request.assert_called_once()
524+
fields = request.call_args.kwargs['fields']
525+
self.assertEqual(len(fields), 1, fields)
526+
field = fields[0]
527+
528+
# it is in the form (form_field_name, (filename, filedata, mimetype))
529+
self.assertEqual(field[0], "file")
530+
self.assertEqual(field[1][0], file_path.name)
531+
with open(file_path, mode="rb") as file_handle:
532+
self.assertEqual(field[1][1], file_handle.read())
533+
self.assertEqual(field[1][2], file_mime_type)
534+
535+
# the form field value wasn't cast to a list
536+
self.assertIsInstance(fields[0][1], tuple)
537+
493538
def test_download_attachment(self):
494539
"""Ensures that file deserialization works"""
495540

0 commit comments

Comments
 (0)