From c4dbd2cfa3abb85a23ee78c6c7951877a2c9e287 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Fri, 15 Jan 2021 13:27:33 -0800 Subject: [PATCH] [python] Fixes file upload + download, adds tests (#8437) * Adds tests for file upload and files upload * Adds test_download_attachment * Fixes test_upload_file * Adds download_attachment endpoint * Adds test_download_attachment * Updates assert_request_called_with signature * Samples regen * Adds upload download file spec route and sample gen * Fixes file upload for application/octet-stream, writes test_upload_download_file * Changes if into elif * Improves python code in api_client --- .../main/resources/python/api_client.mustache | 32 +- ...odels-for-testing-with-http-signature.yaml | 138 +++-- .../python/petstore_api/api_client.py | 32 +- .../python/x_auth_id_alias/api_client.py | 32 +- .../python/dynamic_servers/api_client.py | 32 +- .../openapi3/client/petstore/python/README.md | 6 +- .../client/petstore/python/docs/FakeApi.md | 258 ++++++++++ .../client/petstore/python/docs/PetApi.md | 172 ------- .../python/petstore_api/api/fake_api.py | 476 ++++++++++++++++++ .../python/petstore_api/api/pet_api.py | 269 ---------- .../python/petstore_api/api_client.py | 32 +- .../petstore/python/testfiles/1px_pic1.png | Bin 0 -> 67 bytes .../petstore/python/testfiles/1px_pic2.png | Bin 0 -> 67 bytes .../python/tests_manual/test_fake_api.py | 255 +++++++++- 14 files changed, 1154 insertions(+), 580 deletions(-) create mode 100644 samples/openapi3/client/petstore/python/testfiles/1px_pic1.png create mode 100644 samples/openapi3/client/petstore/python/testfiles/1px_pic2.png diff --git a/modules/openapi-generator/src/main/resources/python/api_client.mustache b/modules/openapi-generator/src/main/resources/python/api_client.mustache index aaf14c4af366..9b9a04fefd91 100644 --- a/modules/openapi-generator/src/main/resources/python/api_client.mustache +++ b/modules/openapi-generator/src/main/resources/python/api_client.mustache @@ -199,8 +199,6 @@ class ApiClient(object): e.body = e.body.decode('utf-8') raise e - content_type = response_data.getheader('content-type') - self.last_response = response_data return_data = response_data @@ -214,15 +212,17 @@ class ApiClient(object): {{/tornado}} return return_data - if response_type not in ["file", "bytes"]: - match = None - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - response_data.data = response_data.data.decode(encoding) - # deserialize response data if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + return_data = self.deserialize( response_data, response_type, @@ -268,7 +268,7 @@ class ApiClient(object): @classmethod def sanitize_for_serialization(cls, obj): - """Builds a JSON POST object. + """Prepares data for transmission before it is sent with the rest client If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date @@ -276,6 +276,7 @@ class ApiClient(object): If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes :param obj: The data to serialize. :return: The serialized form of data. """ @@ -283,6 +284,8 @@ class ApiClient(object): return { key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): return obj elif isinstance(obj, (datetime, date)): @@ -526,6 +529,12 @@ class ApiClient(object): new_params.append((k, v)) return new_params + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): """Builds form parameters. @@ -551,12 +560,11 @@ class ApiClient(object): "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) - filedata = file_instance.read() + filedata = self.get_file_data_and_close_file(file_instance) mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream') params.append( tuple([param_name, tuple([filename, filedata, mimetype])])) - file_instance.close() return params diff --git a/modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml b/modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml index 41a16b1011bd..809235694835 100644 --- a/modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml @@ -16,6 +16,8 @@ tags: description: Access to Petstore orders - name: user description: Operations about user + - name: fake + description: Fake api used for feature testing paths: /foo: get: @@ -246,45 +248,6 @@ paths: - petstore_auth: - 'write:pets' - 'read:pets' - '/pet/{petId}/uploadImage': - post: - tags: - - pet - summary: uploads an image - description: '' - operationId: uploadFile - parameters: - - name: petId - in: path - description: ID of pet to update - required: true - schema: - type: integer - format: int64 - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - petstore_auth: - - 'write:pets' - - 'read:pets' - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - additionalMetadata: - description: Additional data to pass to server - type: string - file: - description: file to upload - type: string - format: binary /store/inventory: get: tags: @@ -1196,21 +1159,41 @@ paths: responses: "200": description: Success - '/fake/{petId}/uploadImageWithRequiredFile': - post: + /{fileName}: + get: + servers: + - url: http://www.jtricks.com tags: - - pet - summary: uploads an image (required) - description: '' - operationId: uploadFileWithRequiredFile + - fake + summary: downloads a file using Content-Disposition + operationId: downloadAttachment parameters: - - name: petId + - name: fileName in: path - description: ID of pet to update + description: file name required: true schema: - type: integer - format: int64 + type: string + responses: + 200: + description: successful operation + content: + 'text/plain': + schema: + type: string + format: binary + headers: + Content-Disposition: + schema: + type: string + description: "describes the received file. Looks like: 'attachment; filename=fileName.txt'" + /fake/uploadFile: + post: + tags: + - fake + summary: uploads a file using multipart/form-data + description: '' + operationId: uploadFile responses: '200': description: successful operation @@ -1218,10 +1201,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ApiResponse' - security: - - petstore_auth: - - 'write:pets' - - 'read:pets' requestBody: content: multipart/form-data: @@ -1231,12 +1210,61 @@ paths: additionalMetadata: description: Additional data to pass to server type: string - requiredFile: + file: description: file to upload type: string format: binary required: - - requiredFile + - file + /fake/uploadFiles: + post: + tags: + - fake + summary: uploads files using multipart/form-data + description: '' + operationId: uploadFiles + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: string + format: binary + /fake/uploadDownloadFile: + post: + tags: + - fake + summary: uploads a file and downloads a file using application/octet-stream + description: '' + operationId: uploadDownloadFile + responses: + '200': + description: successful operation + content: + application/octet-stream: + schema: + type: string + format: binary + description: file to download + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + description: file to upload /fake/health: get: tags: diff --git a/samples/client/petstore/python/petstore_api/api_client.py b/samples/client/petstore/python/petstore_api/api_client.py index a6472b9579d5..626409660045 100644 --- a/samples/client/petstore/python/petstore_api/api_client.py +++ b/samples/client/petstore/python/petstore_api/api_client.py @@ -201,8 +201,6 @@ def __call_api( e.body = e.body.decode('utf-8') raise e - content_type = response_data.getheader('content-type') - self.last_response = response_data return_data = response_data @@ -211,15 +209,17 @@ def __call_api( return (return_data) return return_data - if response_type not in ["file", "bytes"]: - match = None - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - response_data.data = response_data.data.decode(encoding) - # deserialize response data if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + return_data = self.deserialize( response_data, response_type, @@ -256,7 +256,7 @@ def parameters_to_multipart(self, params, collection_types): @classmethod def sanitize_for_serialization(cls, obj): - """Builds a JSON POST object. + """Prepares data for transmission before it is sent with the rest client If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date @@ -264,6 +264,7 @@ def sanitize_for_serialization(cls, obj): If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes :param obj: The data to serialize. :return: The serialized form of data. """ @@ -271,6 +272,8 @@ def sanitize_for_serialization(cls, obj): return { key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): return obj elif isinstance(obj, (datetime, date)): @@ -514,6 +517,12 @@ def parameters_to_tuples(self, params, collection_formats): new_params.append((k, v)) return new_params + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): """Builds form parameters. @@ -539,12 +548,11 @@ def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[i "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) - filedata = file_instance.read() + filedata = self.get_file_data_and_close_file(file_instance) mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream') params.append( tuple([param_name, tuple([filename, filedata, mimetype])])) - file_instance.close() return params diff --git a/samples/openapi3/client/extensions/x-auth-id-alias/python/x_auth_id_alias/api_client.py b/samples/openapi3/client/extensions/x-auth-id-alias/python/x_auth_id_alias/api_client.py index 698a75ec9769..ff79b8cbba4d 100644 --- a/samples/openapi3/client/extensions/x-auth-id-alias/python/x_auth_id_alias/api_client.py +++ b/samples/openapi3/client/extensions/x-auth-id-alias/python/x_auth_id_alias/api_client.py @@ -201,8 +201,6 @@ def __call_api( e.body = e.body.decode('utf-8') raise e - content_type = response_data.getheader('content-type') - self.last_response = response_data return_data = response_data @@ -211,15 +209,17 @@ def __call_api( return (return_data) return return_data - if response_type not in ["file", "bytes"]: - match = None - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - response_data.data = response_data.data.decode(encoding) - # deserialize response data if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + return_data = self.deserialize( response_data, response_type, @@ -256,7 +256,7 @@ def parameters_to_multipart(self, params, collection_types): @classmethod def sanitize_for_serialization(cls, obj): - """Builds a JSON POST object. + """Prepares data for transmission before it is sent with the rest client If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date @@ -264,6 +264,7 @@ def sanitize_for_serialization(cls, obj): If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes :param obj: The data to serialize. :return: The serialized form of data. """ @@ -271,6 +272,8 @@ def sanitize_for_serialization(cls, obj): return { key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): return obj elif isinstance(obj, (datetime, date)): @@ -514,6 +517,12 @@ def parameters_to_tuples(self, params, collection_formats): new_params.append((k, v)) return new_params + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): """Builds form parameters. @@ -539,12 +548,11 @@ def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[i "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) - filedata = file_instance.read() + filedata = self.get_file_data_and_close_file(file_instance) mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream') params.append( tuple([param_name, tuple([filename, filedata, mimetype])])) - file_instance.close() return params diff --git a/samples/openapi3/client/features/dynamic-servers/python/dynamic_servers/api_client.py b/samples/openapi3/client/features/dynamic-servers/python/dynamic_servers/api_client.py index 736510bd34d3..54e8236e10d9 100644 --- a/samples/openapi3/client/features/dynamic-servers/python/dynamic_servers/api_client.py +++ b/samples/openapi3/client/features/dynamic-servers/python/dynamic_servers/api_client.py @@ -201,8 +201,6 @@ def __call_api( e.body = e.body.decode('utf-8') raise e - content_type = response_data.getheader('content-type') - self.last_response = response_data return_data = response_data @@ -211,15 +209,17 @@ def __call_api( return (return_data) return return_data - if response_type not in ["file", "bytes"]: - match = None - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - response_data.data = response_data.data.decode(encoding) - # deserialize response data if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + return_data = self.deserialize( response_data, response_type, @@ -256,7 +256,7 @@ def parameters_to_multipart(self, params, collection_types): @classmethod def sanitize_for_serialization(cls, obj): - """Builds a JSON POST object. + """Prepares data for transmission before it is sent with the rest client If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date @@ -264,6 +264,7 @@ def sanitize_for_serialization(cls, obj): If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes :param obj: The data to serialize. :return: The serialized form of data. """ @@ -271,6 +272,8 @@ def sanitize_for_serialization(cls, obj): return { key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): return obj elif isinstance(obj, (datetime, date)): @@ -514,6 +517,12 @@ def parameters_to_tuples(self, params, collection_formats): new_params.append((k, v)) return new_params + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): """Builds form parameters. @@ -539,12 +548,11 @@ def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[i "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) - filedata = file_instance.read() + filedata = self.get_file_data_and_close_file(file_instance) mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream') params.append( tuple([param_name, tuple([filename, filedata, mimetype])])) - file_instance.close() return params diff --git a/samples/openapi3/client/petstore/python/README.md b/samples/openapi3/client/petstore/python/README.md index b7e1090f0344..8939aa5a1318 100644 --- a/samples/openapi3/client/petstore/python/README.md +++ b/samples/openapi3/client/petstore/python/README.md @@ -88,6 +88,7 @@ Class | Method | HTTP request | Description *FakeApi* | [**array_of_enums**](docs/FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums *FakeApi* | [**boolean**](docs/FakeApi.md#boolean) | **POST** /fake/refs/boolean | *FakeApi* | [**composed_one_of_number_with_validations**](docs/FakeApi.md#composed_one_of_number_with_validations) | **POST** /fake/refs/composed_one_of_number_with_validations | +*FakeApi* | [**download_attachment**](docs/FakeApi.md#download_attachment) | **GET** /{fileName} | downloads a file using Content-Disposition *FakeApi* | [**enum_test**](docs/FakeApi.md#enum_test) | **POST** /fake/refs/enum-test | Object contains enum properties and array properties containing enums *FakeApi* | [**fake_health_get**](docs/FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint *FakeApi* | [**mammal**](docs/FakeApi.md#mammal) | **POST** /fake/refs/mammal | @@ -104,6 +105,9 @@ Class | Method | HTTP request | Description *FakeApi* | [**test_inline_additional_properties**](docs/FakeApi.md#test_inline_additional_properties) | **POST** /fake/inline-additionalProperties | test inline additionalProperties *FakeApi* | [**test_json_form_data**](docs/FakeApi.md#test_json_form_data) | **GET** /fake/jsonFormData | test json serialization of form data *FakeApi* | [**test_query_parameter_collection_format**](docs/FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-paramters | +*FakeApi* | [**upload_download_file**](docs/FakeApi.md#upload_download_file) | **POST** /fake/uploadDownloadFile | uploads a file and downloads a file using application/octet-stream +*FakeApi* | [**upload_file**](docs/FakeApi.md#upload_file) | **POST** /fake/uploadFile | uploads a file using multipart/form-data +*FakeApi* | [**upload_files**](docs/FakeApi.md#upload_files) | **POST** /fake/uploadFiles | uploads files using multipart/form-data *FakeClassnameTags123Api* | [**test_classname**](docs/FakeClassnameTags123Api.md#test_classname) | **PATCH** /fake_classname_test | To test class name in snake case *PetApi* | [**add_pet**](docs/PetApi.md#add_pet) | **POST** /pet | Add a new pet to the store *PetApi* | [**delete_pet**](docs/PetApi.md#delete_pet) | **DELETE** /pet/{petId} | Deletes a pet @@ -112,8 +116,6 @@ Class | Method | HTTP request | Description *PetApi* | [**get_pet_by_id**](docs/PetApi.md#get_pet_by_id) | **GET** /pet/{petId} | Find pet by ID *PetApi* | [**update_pet**](docs/PetApi.md#update_pet) | **PUT** /pet | Update an existing pet *PetApi* | [**update_pet_with_form**](docs/PetApi.md#update_pet_with_form) | **POST** /pet/{petId} | Updates a pet in the store with form data -*PetApi* | [**upload_file**](docs/PetApi.md#upload_file) | **POST** /pet/{petId}/uploadImage | uploads an image -*PetApi* | [**upload_file_with_required_file**](docs/PetApi.md#upload_file_with_required_file) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) *StoreApi* | [**delete_order**](docs/StoreApi.md#delete_order) | **DELETE** /store/order/{order_id} | Delete purchase order by ID *StoreApi* | [**get_inventory**](docs/StoreApi.md#get_inventory) | **GET** /store/inventory | Returns pet inventories by status *StoreApi* | [**get_order_by_id**](docs/StoreApi.md#get_order_by_id) | **GET** /store/order/{order_id} | Find purchase order by ID diff --git a/samples/openapi3/client/petstore/python/docs/FakeApi.md b/samples/openapi3/client/petstore/python/docs/FakeApi.md index ae713e6ee67a..a8efca889708 100644 --- a/samples/openapi3/client/petstore/python/docs/FakeApi.md +++ b/samples/openapi3/client/petstore/python/docs/FakeApi.md @@ -9,6 +9,7 @@ Method | HTTP request | Description [**array_of_enums**](FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums [**boolean**](FakeApi.md#boolean) | **POST** /fake/refs/boolean | [**composed_one_of_number_with_validations**](FakeApi.md#composed_one_of_number_with_validations) | **POST** /fake/refs/composed_one_of_number_with_validations | +[**download_attachment**](FakeApi.md#download_attachment) | **GET** /{fileName} | downloads a file using Content-Disposition [**enum_test**](FakeApi.md#enum_test) | **POST** /fake/refs/enum-test | Object contains enum properties and array properties containing enums [**fake_health_get**](FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint [**mammal**](FakeApi.md#mammal) | **POST** /fake/refs/mammal | @@ -25,6 +26,9 @@ Method | HTTP request | Description [**test_inline_additional_properties**](FakeApi.md#test_inline_additional_properties) | **POST** /fake/inline-additionalProperties | test inline additionalProperties [**test_json_form_data**](FakeApi.md#test_json_form_data) | **GET** /fake/jsonFormData | test json serialization of form data [**test_query_parameter_collection_format**](FakeApi.md#test_query_parameter_collection_format) | **PUT** /fake/test-query-paramters | +[**upload_download_file**](FakeApi.md#upload_download_file) | **POST** /fake/uploadDownloadFile | uploads a file and downloads a file using application/octet-stream +[**upload_file**](FakeApi.md#upload_file) | **POST** /fake/uploadFile | uploads a file using multipart/form-data +[**upload_files**](FakeApi.md#upload_files) | **POST** /fake/uploadFiles | uploads files using multipart/form-data # **additional_properties_with_array_of_enums** @@ -347,6 +351,66 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **download_attachment** +> file_type download_attachment(file_name) + +downloads a file using Content-Disposition + +### Example + +```python +import time +import petstore_api +from petstore_api.api import fake_api +from pprint import pprint +# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 +# See configuration.py for a list of all supported configuration parameters. +configuration = petstore_api.Configuration( + host = "http://petstore.swagger.io:80/v2" +) + + +# Enter a context with an instance of the API client +with petstore_api.ApiClient() as api_client: + # Create an instance of the API class + api_instance = fake_api.FakeApi(api_client) + file_name = "fileName_example" # str | file name + + # example passing only required values which don't have defaults set + try: + # downloads a file using Content-Disposition + api_response = api_instance.download_attachment(file_name) + pprint(api_response) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->download_attachment: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_name** | **str**| file name | + +### Return type + +**file_type** + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: text/plain + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | successful operation | * Content-Disposition - describes the received file. Looks like: 'attachment; filename=fileName.txt'
| + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **enum_test** > EnumTest enum_test() @@ -1489,3 +1553,197 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **upload_download_file** +> file_type upload_download_file(body) + +uploads a file and downloads a file using application/octet-stream + +### Example + +```python +import time +import petstore_api +from petstore_api.api import fake_api +from pprint import pprint +# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 +# See configuration.py for a list of all supported configuration parameters. +configuration = petstore_api.Configuration( + host = "http://petstore.swagger.io:80/v2" +) + + +# Enter a context with an instance of the API client +with petstore_api.ApiClient() as api_client: + # Create an instance of the API class + api_instance = fake_api.FakeApi(api_client) + body = open('/path/to/file', 'rb') # file_type | + + # example passing only required values which don't have defaults set + try: + # uploads a file and downloads a file using application/octet-stream + api_response = api_instance.upload_download_file(body) + pprint(api_response) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->upload_download_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **body** | **file_type**| | + +### Return type + +**file_type** + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/octet-stream + - **Accept**: application/octet-stream + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | successful operation | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **upload_file** +> ApiResponse upload_file(file) + +uploads a file using multipart/form-data + +### Example + +```python +import time +import petstore_api +from petstore_api.api import fake_api +from petstore_api.model.api_response import ApiResponse +from pprint import pprint +# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 +# See configuration.py for a list of all supported configuration parameters. +configuration = petstore_api.Configuration( + host = "http://petstore.swagger.io:80/v2" +) + + +# Enter a context with an instance of the API client +with petstore_api.ApiClient() as api_client: + # Create an instance of the API class + api_instance = fake_api.FakeApi(api_client) + file = open('/path/to/file', 'rb') # file_type | file to upload + additional_metadata = "additional_metadata_example" # str | Additional data to pass to server (optional) + + # example passing only required values which don't have defaults set + try: + # uploads a file using multipart/form-data + api_response = api_instance.upload_file(file) + pprint(api_response) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->upload_file: %s\n" % e) + + # example passing only required values which don't have defaults set + # and optional values + try: + # uploads a file using multipart/form-data + api_response = api_instance.upload_file(file, additional_metadata=additional_metadata) + pprint(api_response) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->upload_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file** | **file_type**| file to upload | + **additional_metadata** | **str**| Additional data to pass to server | [optional] + +### Return type + +[**ApiResponse**](ApiResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: multipart/form-data + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | successful operation | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **upload_files** +> ApiResponse upload_files() + +uploads files using multipart/form-data + +### Example + +```python +import time +import petstore_api +from petstore_api.api import fake_api +from petstore_api.model.api_response import ApiResponse +from pprint import pprint +# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 +# See configuration.py for a list of all supported configuration parameters. +configuration = petstore_api.Configuration( + host = "http://petstore.swagger.io:80/v2" +) + + +# Enter a context with an instance of the API client +with petstore_api.ApiClient() as api_client: + # Create an instance of the API class + api_instance = fake_api.FakeApi(api_client) + files = open('/path/to/file', 'rb') # [file_type] | (optional) + + # example passing only required values which don't have defaults set + # and optional values + try: + # uploads files using multipart/form-data + api_response = api_instance.upload_files(files=files) + pprint(api_response) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->upload_files: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **files** | **[file_type]**| | [optional] + +### Return type + +[**ApiResponse**](ApiResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: multipart/form-data + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | successful operation | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/samples/openapi3/client/petstore/python/docs/PetApi.md b/samples/openapi3/client/petstore/python/docs/PetApi.md index 3ed215ecf615..87aa32e25a88 100644 --- a/samples/openapi3/client/petstore/python/docs/PetApi.md +++ b/samples/openapi3/client/petstore/python/docs/PetApi.md @@ -11,8 +11,6 @@ Method | HTTP request | Description [**get_pet_by_id**](PetApi.md#get_pet_by_id) | **GET** /pet/{petId} | Find pet by ID [**update_pet**](PetApi.md#update_pet) | **PUT** /pet | Update an existing pet [**update_pet_with_form**](PetApi.md#update_pet_with_form) | **POST** /pet/{petId} | Updates a pet in the store with form data -[**upload_file**](PetApi.md#upload_file) | **POST** /pet/{petId}/uploadImage | uploads an image -[**upload_file_with_required_file**](PetApi.md#upload_file_with_required_file) | **POST** /fake/{petId}/uploadImageWithRequiredFile | uploads an image (required) # **add_pet** @@ -823,173 +821,3 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) -# **upload_file** -> ApiResponse upload_file(pet_id) - -uploads an image - -### Example - -* OAuth Authentication (petstore_auth): -```python -import time -import petstore_api -from petstore_api.api import pet_api -from petstore_api.model.api_response import ApiResponse -from pprint import pprint -# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 -# See configuration.py for a list of all supported configuration parameters. -configuration = petstore_api.Configuration( - host = "http://petstore.swagger.io:80/v2" -) - -# The client must configure the authentication and authorization parameters -# in accordance with the API server security policy. -# Examples for each auth method are provided below, use the example that -# satisfies your auth use case. - -# Configure OAuth2 access token for authorization: petstore_auth -configuration = petstore_api.Configuration( - host = "http://petstore.swagger.io:80/v2" -) -configuration.access_token = 'YOUR_ACCESS_TOKEN' - -# Enter a context with an instance of the API client -with petstore_api.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = pet_api.PetApi(api_client) - pet_id = 1 # int | ID of pet to update - additional_metadata = "additional_metadata_example" # str | Additional data to pass to server (optional) - file = open('/path/to/file', 'rb') # file_type | file to upload (optional) - - # example passing only required values which don't have defaults set - try: - # uploads an image - api_response = api_instance.upload_file(pet_id) - pprint(api_response) - except petstore_api.ApiException as e: - print("Exception when calling PetApi->upload_file: %s\n" % e) - - # example passing only required values which don't have defaults set - # and optional values - try: - # uploads an image - api_response = api_instance.upload_file(pet_id, additional_metadata=additional_metadata, file=file) - pprint(api_response) - except petstore_api.ApiException as e: - print("Exception when calling PetApi->upload_file: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **pet_id** | **int**| ID of pet to update | - **additional_metadata** | **str**| Additional data to pass to server | [optional] - **file** | **file_type**| file to upload | [optional] - -### Return type - -[**ApiResponse**](ApiResponse.md) - -### Authorization - -[petstore_auth](../README.md#petstore_auth) - -### HTTP request headers - - - **Content-Type**: multipart/form-data - - **Accept**: application/json - -### HTTP response details -| Status code | Description | Response headers | -|-------------|-------------|------------------| -**200** | successful operation | - | - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **upload_file_with_required_file** -> ApiResponse upload_file_with_required_file(pet_id, required_file) - -uploads an image (required) - -### Example - -* OAuth Authentication (petstore_auth): -```python -import time -import petstore_api -from petstore_api.api import pet_api -from petstore_api.model.api_response import ApiResponse -from pprint import pprint -# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2 -# See configuration.py for a list of all supported configuration parameters. -configuration = petstore_api.Configuration( - host = "http://petstore.swagger.io:80/v2" -) - -# The client must configure the authentication and authorization parameters -# in accordance with the API server security policy. -# Examples for each auth method are provided below, use the example that -# satisfies your auth use case. - -# Configure OAuth2 access token for authorization: petstore_auth -configuration = petstore_api.Configuration( - host = "http://petstore.swagger.io:80/v2" -) -configuration.access_token = 'YOUR_ACCESS_TOKEN' - -# Enter a context with an instance of the API client -with petstore_api.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = pet_api.PetApi(api_client) - pet_id = 1 # int | ID of pet to update - required_file = open('/path/to/file', 'rb') # file_type | file to upload - additional_metadata = "additional_metadata_example" # str | Additional data to pass to server (optional) - - # example passing only required values which don't have defaults set - try: - # uploads an image (required) - api_response = api_instance.upload_file_with_required_file(pet_id, required_file) - pprint(api_response) - except petstore_api.ApiException as e: - print("Exception when calling PetApi->upload_file_with_required_file: %s\n" % e) - - # example passing only required values which don't have defaults set - # and optional values - try: - # uploads an image (required) - api_response = api_instance.upload_file_with_required_file(pet_id, required_file, additional_metadata=additional_metadata) - pprint(api_response) - except petstore_api.ApiException as e: - print("Exception when calling PetApi->upload_file_with_required_file: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **pet_id** | **int**| ID of pet to update | - **required_file** | **file_type**| file to upload | - **additional_metadata** | **str**| Additional data to pass to server | [optional] - -### Return type - -[**ApiResponse**](ApiResponse.md) - -### Authorization - -[petstore_auth](../README.md#petstore_auth) - -### HTTP request headers - - - **Content-Type**: multipart/form-data - - **Accept**: application/json - -### HTTP response details -| Status code | Description | Response headers | -|-------------|-------------|------------------| -**200** | successful operation | - | - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - diff --git a/samples/openapi3/client/petstore/python/petstore_api/api/fake_api.py b/samples/openapi3/client/petstore/python/petstore_api/api/fake_api.py index 13460a26e1d8..6baed5ea63d1 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api/fake_api.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api/fake_api.py @@ -23,6 +23,7 @@ ) from petstore_api.model.additional_properties_with_array_of_enums import AdditionalPropertiesWithArrayOfEnums from petstore_api.model.animal_farm import AnimalFarm +from petstore_api.model.api_response import ApiResponse from petstore_api.model.array_of_enums import ArrayOfEnums from petstore_api.model.client import Client from petstore_api.model.composed_one_of_number_with_validations import ComposedOneOfNumberWithValidations @@ -606,6 +607,127 @@ def __composed_one_of_number_with_validations( callable=__composed_one_of_number_with_validations ) + def __download_attachment( + self, + file_name, + **kwargs + ): + """downloads a file using Content-Disposition # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.download_attachment(file_name, async_req=True) + >>> result = thread.get() + + Args: + file_name (str): file name + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): timeout setting for this request. If one + number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + file_type + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['file_name'] = \ + file_name + return self.call_with_http_info(**kwargs) + + self.download_attachment = _Endpoint( + settings={ + 'response_type': (file_type,), + 'auth': [], + 'endpoint_path': '/{fileName}', + 'operation_id': 'download_attachment', + 'http_method': 'GET', + 'servers': [ + { + 'url': "http://www.jtricks.com", + 'description': "No description provided", + }, + ] + }, + params_map={ + 'all': [ + 'file_name', + ], + 'required': [ + 'file_name', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'file_name': + (str,), + }, + 'attribute_map': { + 'file_name': 'fileName', + }, + 'location_map': { + 'file_name': 'path', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'text/plain' + ], + 'content_type': [], + }, + api_client=api_client, + callable=__download_attachment + ) + def __enum_test( self, **kwargs @@ -2767,3 +2889,357 @@ def __test_query_parameter_collection_format( api_client=api_client, callable=__test_query_parameter_collection_format ) + + def __upload_download_file( + self, + body, + **kwargs + ): + """uploads a file and downloads a file using application/octet-stream # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.upload_download_file(body, async_req=True) + >>> result = thread.get() + + Args: + body (file_type): + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): timeout setting for this request. If one + number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + file_type + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['body'] = \ + body + return self.call_with_http_info(**kwargs) + + self.upload_download_file = _Endpoint( + settings={ + 'response_type': (file_type,), + 'auth': [], + 'endpoint_path': '/fake/uploadDownloadFile', + 'operation_id': 'upload_download_file', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'body', + ], + 'required': [ + 'body', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'body': + (file_type,), + }, + 'attribute_map': { + }, + 'location_map': { + 'body': 'body', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/octet-stream' + ], + 'content_type': [ + 'application/octet-stream' + ] + }, + api_client=api_client, + callable=__upload_download_file + ) + + def __upload_file( + self, + file, + **kwargs + ): + """uploads a file using multipart/form-data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.upload_file(file, async_req=True) + >>> result = thread.get() + + Args: + file (file_type): file to upload + + Keyword Args: + additional_metadata (str): Additional data to pass to server. [optional] + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): timeout setting for this request. If one + number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + ApiResponse + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['file'] = \ + file + return self.call_with_http_info(**kwargs) + + self.upload_file = _Endpoint( + settings={ + 'response_type': (ApiResponse,), + 'auth': [], + 'endpoint_path': '/fake/uploadFile', + 'operation_id': 'upload_file', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'file', + 'additional_metadata', + ], + 'required': [ + 'file', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'file': + (file_type,), + 'additional_metadata': + (str,), + }, + 'attribute_map': { + 'file': 'file', + 'additional_metadata': 'additionalMetadata', + }, + 'location_map': { + 'file': 'form', + 'additional_metadata': 'form', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [ + 'multipart/form-data' + ] + }, + api_client=api_client, + callable=__upload_file + ) + + def __upload_files( + self, + **kwargs + ): + """uploads files using multipart/form-data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.upload_files(async_req=True) + >>> result = thread.get() + + + Keyword Args: + files ([file_type]): [optional] + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (float/tuple): timeout setting for this request. If one + number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + ApiResponse + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + return self.call_with_http_info(**kwargs) + + self.upload_files = _Endpoint( + settings={ + 'response_type': (ApiResponse,), + 'auth': [], + 'endpoint_path': '/fake/uploadFiles', + 'operation_id': 'upload_files', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'files', + ], + 'required': [], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'files': + ([file_type],), + }, + 'attribute_map': { + 'files': 'files', + }, + 'location_map': { + 'files': 'form', + }, + 'collection_format_map': { + 'files': 'csv', + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [ + 'multipart/form-data' + ] + }, + api_client=api_client, + callable=__upload_files + ) diff --git a/samples/openapi3/client/petstore/python/petstore_api/api/pet_api.py b/samples/openapi3/client/petstore/python/petstore_api/api/pet_api.py index 49e6e0694cdc..7b637be215bb 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api/pet_api.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api/pet_api.py @@ -21,7 +21,6 @@ none_type, validate_and_convert_types ) -from petstore_api.model.api_response import ApiResponse from petstore_api.model.pet import Pet @@ -915,271 +914,3 @@ def __update_pet_with_form( api_client=api_client, callable=__update_pet_with_form ) - - def __upload_file( - self, - pet_id, - **kwargs - ): - """uploads an image # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.upload_file(pet_id, async_req=True) - >>> result = thread.get() - - Args: - pet_id (int): ID of pet to update - - Keyword Args: - additional_metadata (str): Additional data to pass to server. [optional] - file (file_type): file to upload. [optional] - _return_http_data_only (bool): response data without head status - code and headers. Default is True. - _preload_content (bool): if False, the urllib3.HTTPResponse object - will be returned without reading/decoding response data. - Default is True. - _request_timeout (float/tuple): timeout setting for this request. If one - number provided, it will be total request timeout. It can also - be a pair (tuple) of (connection, read) timeouts. - Default is None. - _check_input_type (bool): specifies if type checking - should be done one the data sent to the server. - Default is True. - _check_return_type (bool): specifies if type checking - should be done one the data received from the server. - Default is True. - _host_index (int/None): specifies the index of the server - that we want to use. - Default is read from the configuration. - async_req (bool): execute request asynchronously - - Returns: - ApiResponse - If the method is called asynchronously, returns the request - thread. - """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['pet_id'] = \ - pet_id - return self.call_with_http_info(**kwargs) - - self.upload_file = _Endpoint( - settings={ - 'response_type': (ApiResponse,), - 'auth': [ - 'petstore_auth' - ], - 'endpoint_path': '/pet/{petId}/uploadImage', - 'operation_id': 'upload_file', - 'http_method': 'POST', - 'servers': None, - }, - params_map={ - 'all': [ - 'pet_id', - 'additional_metadata', - 'file', - ], - 'required': [ - 'pet_id', - ], - 'nullable': [ - ], - 'enum': [ - ], - 'validation': [ - ] - }, - root_map={ - 'validations': { - }, - 'allowed_values': { - }, - 'openapi_types': { - 'pet_id': - (int,), - 'additional_metadata': - (str,), - 'file': - (file_type,), - }, - 'attribute_map': { - 'pet_id': 'petId', - 'additional_metadata': 'additionalMetadata', - 'file': 'file', - }, - 'location_map': { - 'pet_id': 'path', - 'additional_metadata': 'form', - 'file': 'form', - }, - 'collection_format_map': { - } - }, - headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'multipart/form-data' - ] - }, - api_client=api_client, - callable=__upload_file - ) - - def __upload_file_with_required_file( - self, - pet_id, - required_file, - **kwargs - ): - """uploads an image (required) # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.upload_file_with_required_file(pet_id, required_file, async_req=True) - >>> result = thread.get() - - Args: - pet_id (int): ID of pet to update - required_file (file_type): file to upload - - Keyword Args: - additional_metadata (str): Additional data to pass to server. [optional] - _return_http_data_only (bool): response data without head status - code and headers. Default is True. - _preload_content (bool): if False, the urllib3.HTTPResponse object - will be returned without reading/decoding response data. - Default is True. - _request_timeout (float/tuple): timeout setting for this request. If one - number provided, it will be total request timeout. It can also - be a pair (tuple) of (connection, read) timeouts. - Default is None. - _check_input_type (bool): specifies if type checking - should be done one the data sent to the server. - Default is True. - _check_return_type (bool): specifies if type checking - should be done one the data received from the server. - Default is True. - _host_index (int/None): specifies the index of the server - that we want to use. - Default is read from the configuration. - async_req (bool): execute request asynchronously - - Returns: - ApiResponse - If the method is called asynchronously, returns the request - thread. - """ - kwargs['async_req'] = kwargs.get( - 'async_req', False - ) - kwargs['_return_http_data_only'] = kwargs.get( - '_return_http_data_only', True - ) - kwargs['_preload_content'] = kwargs.get( - '_preload_content', True - ) - kwargs['_request_timeout'] = kwargs.get( - '_request_timeout', None - ) - kwargs['_check_input_type'] = kwargs.get( - '_check_input_type', True - ) - kwargs['_check_return_type'] = kwargs.get( - '_check_return_type', True - ) - kwargs['_host_index'] = kwargs.get('_host_index') - kwargs['pet_id'] = \ - pet_id - kwargs['required_file'] = \ - required_file - return self.call_with_http_info(**kwargs) - - self.upload_file_with_required_file = _Endpoint( - settings={ - 'response_type': (ApiResponse,), - 'auth': [ - 'petstore_auth' - ], - 'endpoint_path': '/fake/{petId}/uploadImageWithRequiredFile', - 'operation_id': 'upload_file_with_required_file', - 'http_method': 'POST', - 'servers': None, - }, - params_map={ - 'all': [ - 'pet_id', - 'required_file', - 'additional_metadata', - ], - 'required': [ - 'pet_id', - 'required_file', - ], - 'nullable': [ - ], - 'enum': [ - ], - 'validation': [ - ] - }, - root_map={ - 'validations': { - }, - 'allowed_values': { - }, - 'openapi_types': { - 'pet_id': - (int,), - 'required_file': - (file_type,), - 'additional_metadata': - (str,), - }, - 'attribute_map': { - 'pet_id': 'petId', - 'required_file': 'requiredFile', - 'additional_metadata': 'additionalMetadata', - }, - 'location_map': { - 'pet_id': 'path', - 'required_file': 'form', - 'additional_metadata': 'form', - }, - 'collection_format_map': { - } - }, - headers_map={ - 'accept': [ - 'application/json' - ], - 'content_type': [ - 'multipart/form-data' - ] - }, - api_client=api_client, - callable=__upload_file_with_required_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 022d808ce681..3b0b3e4ea7fd 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/api_client.py +++ b/samples/openapi3/client/petstore/python/petstore_api/api_client.py @@ -201,8 +201,6 @@ def __call_api( e.body = e.body.decode('utf-8') raise e - content_type = response_data.getheader('content-type') - self.last_response = response_data return_data = response_data @@ -211,15 +209,17 @@ def __call_api( return (return_data) return return_data - if response_type not in ["file", "bytes"]: - match = None - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - response_data.data = response_data.data.decode(encoding) - # deserialize response data if response_type: + if response_type != (file_type,): + encoding = "utf-8" + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data.data = response_data.data.decode(encoding) + return_data = self.deserialize( response_data, response_type, @@ -256,7 +256,7 @@ def parameters_to_multipart(self, params, collection_types): @classmethod def sanitize_for_serialization(cls, obj): - """Builds a JSON POST object. + """Prepares data for transmission before it is sent with the rest client If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date @@ -264,6 +264,7 @@ def sanitize_for_serialization(cls, obj): If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes :param obj: The data to serialize. :return: The serialized form of data. """ @@ -271,6 +272,8 @@ def sanitize_for_serialization(cls, obj): return { key: cls.sanitize_for_serialization(val) for key, val in model_to_dict(obj, serialize=True).items() } + elif isinstance(obj, io.IOBase): + return cls.get_file_data_and_close_file(obj) elif isinstance(obj, (str, int, float, none_type, bool)): return obj elif isinstance(obj, (datetime, date)): @@ -514,6 +517,12 @@ def parameters_to_tuples(self, params, collection_formats): new_params.append((k, v)) return new_params + @staticmethod + def get_file_data_and_close_file(file_instance: io.IOBase) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data + def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None): """Builds form parameters. @@ -539,12 +548,11 @@ def files_parameters(self, files: typing.Optional[typing.Dict[str, typing.List[i "for %s must be open." % param_name ) filename = os.path.basename(file_instance.name) - filedata = file_instance.read() + filedata = self.get_file_data_and_close_file(file_instance) mimetype = (mimetypes.guess_type(filename)[0] or 'application/octet-stream') params.append( tuple([param_name, tuple([filename, filedata, mimetype])])) - file_instance.close() return params diff --git a/samples/openapi3/client/petstore/python/testfiles/1px_pic1.png b/samples/openapi3/client/petstore/python/testfiles/1px_pic1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3a386a21026f9b15b2c303a81f5c69333e9056 GIT binary patch literal 67 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`_bPE>9Q7kcv6UzxWv#Ss9tmFVH&+ OlJ#`;b6Mw<&;$T@!wsPT literal 0 HcmV?d00001 diff --git a/samples/openapi3/client/petstore/python/testfiles/1px_pic2.png b/samples/openapi3/client/petstore/python/testfiles/1px_pic2.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3a386a21026f9b15b2c303a81f5c69333e9056 GIT binary patch literal 67 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbBp9sfW`_bPE>9Q7kcv6UzxWv#Ss9tmFVH&+ OlJ#`;b6Mw<&;$T@!wsPT literal 0 HcmV?d00001 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 52e39fb2fbc0..12fb6163684a 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 @@ -11,13 +11,15 @@ import sys from collections import namedtuple -import unittest +import os import json +import unittest from unittest.mock import patch import petstore_api from petstore_api.api.fake_api import FakeApi # noqa: E501 from petstore_api.rest import RESTClientObject, RESTResponse +from petstore_api.model_utils import file_type HTTPResponse = namedtuple( 'urllib3_response_HTTPResponse', @@ -52,22 +54,35 @@ def mock_response(body_value): return RESTResponse(http_response) @staticmethod - def assert_request_called_with(mock_method, url, value): - mock_method.assert_called_with( - 'POST', - url, + def assert_request_called_with( + mock_method, + url, + accept='application/json', + http_method='POST', + **kwargs + ): + headers = { + 'Accept': accept, + 'User-Agent': 'OpenAPI-Generator/1.0.0/python', + } + if 'content_type' in kwargs: + headers['Content-Type'] = kwargs['content_type'] + used_kwargs = dict( _preload_content=True, _request_timeout=None, - body=value, - headers={ - 'Accept': 'application/json', - 'User-Agent': 'OpenAPI-Generator/1.0.0/python', - 'Content-Type': 'application/json' - }, - post_params=[], + headers=headers, query_params=[] ) - + if 'post_params' in kwargs: + used_kwargs['post_params'] = kwargs['post_params'] + if 'body' in kwargs: + used_kwargs['body'] = kwargs['body'] + else: + mock_method.assert_called_with( + http_method, + url, + **used_kwargs + ) def test_array_model(self): """Test case for array_model @@ -86,7 +101,7 @@ def test_array_model(self): mock_method.return_value = self.mock_response(json_data) response = endpoint(body=body) - self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/arraymodel', json_data) + self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/arraymodel', body=json_data) assert isinstance(response, animal_farm.AnimalFarm) assert response == body @@ -143,7 +158,7 @@ def test_enum_test(self): response = endpoint(enum_test=body) self.assert_request_called_with( - mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum-test', json_value) + mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum-test', body=json_value) assert isinstance(response, EnumTest) assert response == body @@ -159,7 +174,7 @@ def test_enum_test(self): response = endpoint(enum_test=body) self.assert_request_called_with( - mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum-test', json_value) + mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum-test', body=json_value) assert isinstance(response, EnumTest) assert response == body @@ -183,7 +198,7 @@ def test_array_of_enums(self): mock_method.return_value = self.mock_response(value_simple) response = endpoint(array_of_enums=body) - self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/array-of-enums', value_simple) + self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/array-of-enums', body=value_simple) assert isinstance(response, array_of_enums.ArrayOfEnums) assert response.value == value @@ -204,7 +219,7 @@ def test_number_with_validations(self): mock_method.return_value = self.mock_response(value) response = endpoint(body=body) - self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/number', value) + self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/number', body=value) assert isinstance(response, number_with_validations.NumberWithValidations) assert response.value == value @@ -247,7 +262,7 @@ def test_object_model_with_ref_props(self): self.assert_request_called_with( mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/object_model_with_ref_props', - json_payload + body=json_payload ) assert isinstance(response, expected_model.__class__) @@ -285,7 +300,7 @@ def test_composed_one_of_number_with_validations(self): self.assert_request_called_with( mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/composed_one_of_number_with_validations', - value_simple + body=value_simple ) assert isinstance(response, body.__class__) @@ -306,7 +321,7 @@ def test_string(self): mock_method.return_value = self.mock_response(value_simple) response = endpoint(body=body) - self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/string', value_simple) + self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/string', body=value_simple) assert isinstance(response, str) assert response == value_simple @@ -328,11 +343,207 @@ def test_string_enum(self): mock_method.return_value = self.mock_response(value) response = endpoint(body=body) - self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum', value) + self.assert_request_called_with(mock_method, 'http://petstore.swagger.io:80/v2/fake/refs/enum', body=value) assert isinstance(response, string_enum.StringEnum) assert response.value == value + def test_upload_file(self): + # uploads a file + test_file_dir = os.path.realpath( + os.path.join(os.path.dirname(__file__), "..", "testfiles")) + file_path1 = os.path.join(test_file_dir, "1px_pic1.png") + + headers = {} + def get_headers(): + return headers + def get_header(name, default=None): + return headers.get(name, default) + api_respponse = { + 'code': 200, + 'type': 'blah', + 'message': 'file upload succeeded' + } + http_response = HTTPResponse( + status=200, + reason='OK', + data=json.dumps(api_respponse).encode('utf-8'), + getheaders=get_headers, + getheader=get_header + ) + mock_response = RESTResponse(http_response) + file1 = open(file_path1, "rb") + try: + with patch.object(RESTClientObject, 'request') as mock_method: + mock_method.return_value = mock_response + res = self.api.upload_file( + file=file1) + body = None + post_params=[ + ('file', ('1px_pic1.png', b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00:~\x9bU\x00\x00\x00\nIDATx\x9cc\xfa\x0f\x00\x01\x05\x01\x02\xcf\xa0.\xcd\x00\x00\x00\x00IEND\xaeB`\x82', 'image/png')), + ] + self.assert_request_called_with( + mock_method, + 'http://petstore.swagger.io:80/v2/fake/uploadFile', + body=body, post_params=post_params, content_type='multipart/form-data' + ) + except petstore_api.ApiException as e: + self.fail("upload_file() raised {0} unexpectedly".format(type(e))) + finally: + file1.close() + + # passing in an array of files to when file only allows one + # raises an exceptions + try: + file = open(file_path1, "rb") + with self.assertRaises(petstore_api.ApiTypeError) as exc: + self.api.upload_file(file=[file]) + finally: + file.close() + + # passing in a closed file raises an exception + with self.assertRaises(petstore_api.ApiValueError) as exc: + file = open(file_path1, "rb") + file.close() + self.api.upload_file(file=file) + + def test_upload_files(self): + test_file_dir = os.path.realpath( + os.path.join(os.path.dirname(__file__), "..", "testfiles")) + file_path1 = os.path.join(test_file_dir, "1px_pic1.png") + file_path2 = os.path.join(test_file_dir, "1px_pic2.png") + + headers = {} + def get_headers(): + return headers + def get_header(name, default=None): + return headers.get(name, default) + api_respponse = { + 'code': 200, + 'type': 'blah', + 'message': 'file upload succeeded' + } + http_response = HTTPResponse( + status=200, + reason='OK', + data=json.dumps(api_respponse).encode('utf-8'), + getheaders=get_headers, + getheader=get_header + ) + mock_response = RESTResponse(http_response) + file1 = open(file_path1, "rb") + file2 = open(file_path2, "rb") + try: + with patch.object(RESTClientObject, 'request') as mock_method: + mock_method.return_value = mock_response + res = self.api.upload_files( + files=[file1, file2]) + post_params=[ + ('files', ('1px_pic1.png', b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00:~\x9bU\x00\x00\x00\nIDATx\x9cc\xfa\x0f\x00\x01\x05\x01\x02\xcf\xa0.\xcd\x00\x00\x00\x00IEND\xaeB`\x82', 'image/png')), + ('files', ('1px_pic2.png', b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00:~\x9bU\x00\x00\x00\nIDATx\x9cc\xfa\x0f\x00\x01\x05\x01\x02\xcf\xa0.\xcd\x00\x00\x00\x00IEND\xaeB`\x82', 'image/png')) + ] + self.assert_request_called_with( + mock_method, + 'http://petstore.swagger.io:80/v2/fake/uploadFiles', + body=None, post_params=post_params, content_type='multipart/form-data' + ) + except petstore_api.ApiException as e: + self.fail("upload_file() raised {0} unexpectedly".format(type(e))) + finally: + file1.close() + file2.close() + + # passing in a single file when an array of file is required + # raises an exception + try: + file = open(file_path1, "rb") + with self.assertRaises(petstore_api.ApiTypeError) as exc: + self.api.upload_files(files=file) + finally: + file.close() + + def test_download_attachment(self): + """Ensures that file deserialization works""" + + # sample from http://www.jtricks.com/download-text + file_name = 'content.txt' + headers = {'Content-Disposition': 'attachment; filename={}'.format(file_name), 'Content-Type': 'text/plain'} + def get_headers(): + return headers + def get_header(name, default=None): + return headers.get(name, default) + file_data = ( + "You are reading text file that was supposed to be downloaded\r\n" + "to your hard disk. If your browser offered to save you the file," + "\r\nthen it handled the Content-Disposition header correctly." + ) + http_response = HTTPResponse( + status=200, + reason='OK', + data=file_data, + getheaders=get_headers, + getheader=get_header + ) + # deserialize response to a file + mock_response = RESTResponse(http_response) + with patch.object(RESTClientObject, 'request') as mock_method: + mock_method.return_value = mock_response + try: + file_object = self.api.download_attachment(file_name='download-text') + self.assert_request_called_with( + mock_method, + 'http://www.jtricks.com/download-text', + http_method='GET', + accept='text/plain', + ) + self.assertTrue(isinstance(file_object, file_type)) + self.assertFalse(file_object.closed) + self.assertEqual(file_object.read(), file_data.encode('utf-8')) + finally: + file_object.close() + os.unlink(file_object.name) + + def test_upload_download_file(self): + test_file_dir = os.path.realpath( + os.path.join(os.path.dirname(__file__), "..", "testfiles")) + file_path1 = os.path.join(test_file_dir, "1px_pic1.png") + + with open(file_path1, "rb") as f: + expected_file_data = f.read() + + headers = {'Content-Type': 'application/octet-stream'} + def get_headers(): + return headers + def get_header(name, default=None): + return headers.get(name, default) + http_response = HTTPResponse( + status=200, + reason='OK', + data=expected_file_data, + getheaders=get_headers, + getheader=get_header + ) + mock_response = RESTResponse(http_response) + file1 = open(file_path1, "rb") + try: + with patch.object(RESTClientObject, 'request') as mock_method: + mock_method.return_value = mock_response + downloaded_file = self.api.upload_download_file(body=file1) + self.assert_request_called_with( + mock_method, + 'http://petstore.swagger.io:80/v2/fake/uploadDownloadFile', + body=expected_file_data, content_type='application/octet-stream' + ) + self.assertTrue(isinstance(downloaded_file, file_type)) + self.assertFalse(downloaded_file.closed) + self.assertEqual(downloaded_file.read(), expected_file_data) + except petstore_api.ApiException as e: + self.fail("upload_download_file() raised {0} unexpectedly".format(type(e))) + finally: + file1.close() + downloaded_file.close() + os.unlink(downloaded_file.name) + def test_test_body_with_file_schema(self): """Test case for test_body_with_file_schema