From 85990b699688179da1da39466bf366cfe39ec595 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 4 Nov 2022 04:25:18 -0700 Subject: [PATCH 1/5] Updates api_client template to set content disposition header with make_multipart --- .../resources/python/api_client.handlebars | 23 ++++++++++--------- .../python/.openapi-generator/VERSION | 2 +- .../python/petstore_api/api_client.py | 15 ++++++++---- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars index 81426853056..ca443ddf4f7 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars +++ b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars @@ -1399,24 +1399,25 @@ class RequestBody(StyleFormSerializer, JSONDetector): def __multipart_json_item(self, key: str, value: Schema) -> RequestField: json_value = self.__json_encoder.default(value) - return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) + request_field = RequestField(name=key, data=json.dumps(json_value)) + request_field.make_multipart(content_type='application/json') + return request_field def __multipart_form_item(self, key: str, value: Schema) -> RequestField: + content_type = None if isinstance(value, str): - return RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) + request_field = RequestField(name=key, data=str(value)) + request_field.make_multipart(content_type='text/plain') elif isinstance(value, bytes): - return RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) + request_field = RequestField(name=key, data=value) + request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): - request_field = RequestField( - name=key, - data=value.read(), - filename=os.path.basename(value.name), - headers={'Content-Type': 'application/octet-stream'} - ) + # TODO use content.encoding to limit allowed content types if they are present + request_field = RequestField.from_tuple(key, (os.path.basename(value.name), value.read())) value.close() - return request_field else: - return self.__multipart_json_item(key=key, value=value) + request_field = self.__multipart_json_item(key=key, value=value) + return request_field def __serialize_multipart_form_data( self, in_data: Schema diff --git a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION index e6d5cb833c6..717311e32e3 100644 --- a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION +++ b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION @@ -1 +1 @@ -1.0.2 \ No newline at end of file +unset \ No newline at end of file diff --git a/samples/openapi3/client/petstore/python/petstore_api/api_client.py b/samples/openapi3/client/petstore/python/petstore_api/api_client.py index 9ee9ff13f68..294a6df44e2 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api_client.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api_client.py @@ -1401,11 +1401,16 @@ def __multipart_json_item(self, key: str, value: Schema) -> RequestField: return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) def __multipart_form_item(self, key: str, value: Schema) -> RequestField: + content_type = None if isinstance(value, str): - return RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) + content_type = 'text/plain' + request_field = RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) elif isinstance(value, bytes): - return RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) + content_type = 'application/octet-stream' + request_field = RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) elif isinstance(value, FileIO): + # TODO get content type using urllib3 + content_type = 'application/octet-stream' request_field = RequestField( name=key, data=value.read(), @@ -1413,9 +1418,11 @@ def __multipart_form_item(self, key: str, value: Schema) -> RequestField: headers={'Content-Type': 'application/octet-stream'} ) value.close() - return request_field else: - return self.__multipart_json_item(key=key, value=value) + content_type = 'application/json' + request_field = self.__multipart_json_item(key=key, value=value) + request_field.make_multipart(content_type=content_type) + return request_field def __serialize_multipart_form_data( self, in_data: Schema From 6359a5c2c34f9919c53d3cafef9f93f086daafd0 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 4 Nov 2022 09:30:50 -0700 Subject: [PATCH 2/5] Sample regenerated --- .../resources/python/api_client.handlebars | 1 - .../python/petstore_api/api_client.py | 25 +++++++------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars index ca443ddf4f7..9fd66787d84 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars +++ b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars @@ -1404,7 +1404,6 @@ class RequestBody(StyleFormSerializer, JSONDetector): return request_field def __multipart_form_item(self, key: str, value: Schema) -> RequestField: - content_type = None if isinstance(value, str): request_field = RequestField(name=key, data=str(value)) request_field.make_multipart(content_type='text/plain') diff --git a/samples/openapi3/client/petstore/python/petstore_api/api_client.py b/samples/openapi3/client/petstore/python/petstore_api/api_client.py index 294a6df44e2..92dde3b52db 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api_client.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api_client.py @@ -1398,30 +1398,23 @@ def __serialize_text_plain(in_data: typing.Any) -> typing.Dict[str, str]: def __multipart_json_item(self, key: str, value: Schema) -> RequestField: json_value = self.__json_encoder.default(value) - return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) + request_field = RequestField(name=key, data=json.dumps(json_value)) + request_field.make_multipart(content_type='application/json') + return request_field def __multipart_form_item(self, key: str, value: Schema) -> RequestField: - content_type = None if isinstance(value, str): - content_type = 'text/plain' - request_field = RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) + request_field = RequestField(name=key, data=str(value)) + request_field.make_multipart(content_type='text/plain') elif isinstance(value, bytes): - content_type = 'application/octet-stream' - request_field = RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) + request_field = RequestField(name=key, data=value) + request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): - # TODO get content type using urllib3 - content_type = 'application/octet-stream' - request_field = RequestField( - name=key, - data=value.read(), - filename=os.path.basename(value.name), - headers={'Content-Type': 'application/octet-stream'} - ) + # TODO use content.encoding to limit allowed content types if they are present + request_field = RequestField.from_tuple(key, (os.path.basename(value.name), value.read())) value.close() else: - content_type = 'application/json' request_field = self.__multipart_json_item(key=key, value=value) - request_field.make_multipart(content_type=content_type) return request_field def __serialize_multipart_form_data( From 82a8f659bba7410d20b4c58565ca7551351cc686 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 4 Nov 2022 15:18:14 -0700 Subject: [PATCH 3/5] Fixes all but one test --- .../resources/python/api_client.handlebars | 2 +- .../python/petstore_api/api_client.py | 10 +++- .../python/tests_manual/test_fake_api.py | 52 +++++++++++++++---- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars index 9fd66787d84..f1514924c48 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars +++ b/modules/openapi-json-schema-generator/src/main/resources/python/api_client.handlebars @@ -1412,7 +1412,7 @@ class RequestBody(StyleFormSerializer, JSONDetector): request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): # TODO use content.encoding to limit allowed content types if they are present - request_field = RequestField.from_tuple(key, (os.path.basename(value.name), value.read())) + request_field = RequestField.from_tuples(key, (os.path.basename(value.name), value.read())) value.close() else: request_field = self.__multipart_json_item(key=key, value=value) diff --git a/samples/openapi3/client/petstore/python/petstore_api/api_client.py b/samples/openapi3/client/petstore/python/petstore_api/api_client.py index 92dde3b52db..f2f83781dd5 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api_client.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api_client.py @@ -51,6 +51,14 @@ def __eq__(self, other): return False return self.__dict__ == other.__dict__ + def __repr__(self): + self_dict = { + 'name': self._name, + 'filename': self._filename, + 'headers': self.headers, + } + return json.dumps(self_dict) + class JSONEncoder(json.JSONEncoder): compact_separators = (',', ':') @@ -1411,7 +1419,7 @@ def __multipart_form_item(self, key: str, value: Schema) -> RequestField: request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): # TODO use content.encoding to limit allowed content types if they are present - request_field = RequestField.from_tuple(key, (os.path.basename(value.name), value.read())) + request_field = RequestField.from_tuples(key, (os.path.basename(value.name), value.read())) value.close() else: request_field = self.__multipart_json_item(key=key, value=value) diff --git a/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py b/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py index 7ce4a60e8f7..2807020497a 100644 --- a/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py +++ b/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py @@ -468,7 +468,11 @@ def test_upload_file(self): name='file', data=file_bytes, filename=file_name, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Location': None, + 'Content-Type': 'image/png', + "Content-Disposition": "form-data; name=\"file\"; filename=\"1px_pic1.png\"" + } ), ), content_type='multipart/form-data' @@ -492,7 +496,11 @@ def test_upload_file(self): api_client.RequestField( name='file', data=file_bytes, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Type': 'application/octet-stream', + "Content-Disposition": "form-data; name=\"file\"", + "Content-Location": None + } ), ), content_type='multipart/form-data' @@ -547,13 +555,22 @@ def test_upload_files(self): name='files', data=file_bytes, filename=file_name, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Type': 'image/png', + "Content-Disposition": "form-data; name=\"files\"; filename=\"1px_pic1.png\"", + "Content-Location": None + } ), api_client.RequestField( name='files', data=file_bytes, filename=file_name, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Type': 'image/png', + "Content-Disposition": "form-data; name=\"files\"; filename=\"1px_pic1.png\"", + "Content-Location": None + + } ), ), content_type='multipart/form-data' @@ -578,12 +595,20 @@ def test_upload_files(self): api_client.RequestField( name='files', data=file_bytes, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Type': 'application/octet-stream', + "Content-Disposition": "form-data; name=\"files\"", + "Content-Location": None + } ), api_client.RequestField( name='files', data=file_bytes, - headers={'Content-Type': 'application/octet-stream'} + headers={ + 'Content-Type': 'application/octet-stream', + "Content-Disposition": "form-data; name=\"files\"", + "Content-Location": None + } ), ), content_type='multipart/form-data' @@ -650,17 +675,22 @@ def test_inline_composition(self, mock_request): content_type=content_type, accept_content_types=(content_type,) ) + self.maxDiff = None self.assert_request_called_with( mock_request, 'http://petstore.swagger.io:80/v2/fake/inlineComposition/?compositionAtRoot=a&someProp=a', accept_content_type=content_type, content_type=content_type, fields=( - api_client.RequestField( - name='someProp', - data=single_char_str, - headers={'Content-Type': 'text/plain'} - ), + api_client.RequestField( + name='someProp', + data=single_char_str, + headers={ + 'Content-Type': 'text/plain', + "Content-Disposition": "form-data; name=\"someProp\"", + "Content-Location": None + } + ), ), ) self.assertEqual(api_response.body, {'someProp': single_char_str}) From c78ab8a46de7610e2f3faeb29aab3e8024aa1583 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 4 Nov 2022 15:46:55 -0700 Subject: [PATCH 4/5] Fixes tests --- .../python/tests_manual/test_fake_api.py | 1 - .../python/tests_manual/test_request_body.py | 48 +++++++++++++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py b/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py index 2807020497a..cd990c9bc39 100644 --- a/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py +++ b/samples/openapi3/client/petstore/python/tests_manual/test_fake_api.py @@ -675,7 +675,6 @@ def test_inline_composition(self, mock_request): content_type=content_type, accept_content_types=(content_type,) ) - self.maxDiff = None self.assert_request_called_with( mock_request, 'http://petstore.swagger.io:80/v2/fake/inlineComposition/?compositionAtRoot=a&someProp=a', diff --git a/samples/openapi3/client/petstore/python/tests_manual/test_request_body.py b/samples/openapi3/client/petstore/python/tests_manual/test_request_body.py index 000e04f90a1..14339c2e230 100644 --- a/samples/openapi3/client/petstore/python/tests_manual/test_request_body.py +++ b/samples/openapi3/client/petstore/python/tests_manual/test_request_body.py @@ -71,21 +71,53 @@ def test_content_multipart_form_data_serialization(self): dict( fields=( api_client.RequestField( - name='some_null', data='null', headers={'Content-Type': 'application/json'}), + name='some_null', data='null', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_null\"", + "Content-Location": None + }), api_client.RequestField( - name='some_bool', data='true', headers={'Content-Type': 'application/json'}), + name='some_bool', data='true', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_bool\"", + "Content-Location": None + }), api_client.RequestField( - name='some_str', data='a', headers={'Content-Type': 'text/plain'}), + name='some_str', data='a', headers={ + 'Content-Type': 'text/plain', + "Content-Disposition": "form-data; name=\"some_str\"", + "Content-Location": None + }), api_client.RequestField( - name='some_int', data='1', headers={'Content-Type': 'application/json'}), + name='some_int', data='1', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_int\"", + "Content-Location": None + }), api_client.RequestField( - name='some_float', data='3.14', headers={'Content-Type': 'application/json'}), + name='some_float', data='3.14', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_float\"", + "Content-Location": None + }), api_client.RequestField( - name='some_list', data='[]', headers={'Content-Type': 'application/json'}), + name='some_list', data='[]', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_list\"", + "Content-Location": None + }), api_client.RequestField( - name='some_dict', data='{}', headers={'Content-Type': 'application/json'}), + name='some_dict', data='{}', headers={ + 'Content-Type': 'application/json', + "Content-Disposition": "form-data; name=\"some_dict\"", + "Content-Location": None + }), api_client.RequestField( - name='some_bytes', data=b'abc', headers={'Content-Type': 'application/octet-stream'}) + name='some_bytes', data=b'abc', headers={ + 'Content-Type': 'application/octet-stream', + "Content-Disposition": "form-data; name=\"some_bytes\"", + "Content-Location": None + }) ) ) ) From c8daa101f63e50f97236217ed89a3484e87b883f Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 4 Nov 2022 15:54:35 -0700 Subject: [PATCH 5/5] Samples have been regenerated --- .../python/unit_test_api/api_client.py | 22 +++++++++---------- .../python/this_package/api_client.py | 22 +++++++++---------- .../python/.openapi-generator/VERSION | 2 +- .../python/petstore_api/api_client.py | 8 ------- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/api_client.py b/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/api_client.py index 6fa884fc7d8..3985bb0565b 100644 --- a/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/api_client.py +++ b/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/api_client.py @@ -1389,24 +1389,24 @@ def __serialize_text_plain(in_data: typing.Any) -> typing.Dict[str, str]: def __multipart_json_item(self, key: str, value: Schema) -> RequestField: json_value = self.__json_encoder.default(value) - return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) + request_field = RequestField(name=key, data=json.dumps(json_value)) + request_field.make_multipart(content_type='application/json') + return request_field def __multipart_form_item(self, key: str, value: Schema) -> RequestField: if isinstance(value, str): - return RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) + request_field = RequestField(name=key, data=str(value)) + request_field.make_multipart(content_type='text/plain') elif isinstance(value, bytes): - return RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) + request_field = RequestField(name=key, data=value) + request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): - request_field = RequestField( - name=key, - data=value.read(), - filename=os.path.basename(value.name), - headers={'Content-Type': 'application/octet-stream'} - ) + # TODO use content.encoding to limit allowed content types if they are present + request_field = RequestField.from_tuples(key, (os.path.basename(value.name), value.read())) value.close() - return request_field else: - return self.__multipart_json_item(key=key, value=value) + request_field = self.__multipart_json_item(key=key, value=value) + return request_field def __serialize_multipart_form_data( self, in_data: Schema diff --git a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/api_client.py b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/api_client.py index 048ae4ed1ba..bdab1907d48 100644 --- a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/api_client.py +++ b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/api_client.py @@ -1389,24 +1389,24 @@ def __serialize_text_plain(in_data: typing.Any) -> typing.Dict[str, str]: def __multipart_json_item(self, key: str, value: Schema) -> RequestField: json_value = self.__json_encoder.default(value) - return RequestField(name=key, data=json.dumps(json_value), headers={'Content-Type': 'application/json'}) + request_field = RequestField(name=key, data=json.dumps(json_value)) + request_field.make_multipart(content_type='application/json') + return request_field def __multipart_form_item(self, key: str, value: Schema) -> RequestField: if isinstance(value, str): - return RequestField(name=key, data=str(value), headers={'Content-Type': 'text/plain'}) + request_field = RequestField(name=key, data=str(value)) + request_field.make_multipart(content_type='text/plain') elif isinstance(value, bytes): - return RequestField(name=key, data=value, headers={'Content-Type': 'application/octet-stream'}) + request_field = RequestField(name=key, data=value) + request_field.make_multipart(content_type='application/octet-stream') elif isinstance(value, FileIO): - request_field = RequestField( - name=key, - data=value.read(), - filename=os.path.basename(value.name), - headers={'Content-Type': 'application/octet-stream'} - ) + # TODO use content.encoding to limit allowed content types if they are present + request_field = RequestField.from_tuples(key, (os.path.basename(value.name), value.read())) value.close() - return request_field else: - return self.__multipart_json_item(key=key, value=value) + request_field = self.__multipart_json_item(key=key, value=value) + return request_field def __serialize_multipart_form_data( self, in_data: Schema diff --git a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION index 717311e32e3..e6d5cb833c6 100644 --- a/samples/openapi3/client/petstore/python/.openapi-generator/VERSION +++ b/samples/openapi3/client/petstore/python/.openapi-generator/VERSION @@ -1 +1 @@ -unset \ No newline at end of file +1.0.2 \ No newline at end of file diff --git a/samples/openapi3/client/petstore/python/petstore_api/api_client.py b/samples/openapi3/client/petstore/python/petstore_api/api_client.py index f2f83781dd5..b4a583829a3 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api_client.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api_client.py @@ -51,14 +51,6 @@ def __eq__(self, other): return False return self.__dict__ == other.__dict__ - def __repr__(self): - self_dict = { - 'name': self._name, - 'filename': self._filename, - 'headers': self.headers, - } - return json.dumps(self_dict) - class JSONEncoder(json.JSONEncoder): compact_separators = (',', ':')