From 8fc85530e85eb802f8ad3def6d36584733f27b83 Mon Sep 17 00:00:00 2001 From: Justin Black Date: Mon, 2 May 2022 18:42:13 -0700 Subject: [PATCH] [python-experimental] fixes delete endpoint with no body use case (#12287) * Generates delete_coffee endpoint * Adds test of delete_coffee * Removes .run files * Fixes bug and adds test * Reverts version file * Reverts function sig * Removes comma --- .../python-experimental/rest.handlebars | 14 +- ...odels-for-testing-with-http-signature.yaml | 19 ++ .../petstore/python-experimental/README.md | 1 + .../python-experimental/docs/FakeApi.md | 90 +++++++++ .../petstore_api/api/fake_api.py | 2 + .../api/fake_api_endpoints/delete_coffee.py | 180 ++++++++++++++++++ .../python-experimental/petstore_api/rest.py | 14 +- .../tests_manual/test_fake_api.py | 62 +++++- 8 files changed, 372 insertions(+), 10 deletions(-) create mode 100644 samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/delete_coffee.py diff --git a/modules/openapi-generator/src/main/resources/python-experimental/rest.handlebars b/modules/openapi-generator/src/main/resources/python-experimental/rest.handlebars index e30831938f1..1fa9f555edc 100644 --- a/modules/openapi-generator/src/main/resources/python-experimental/rest.handlebars +++ b/modules/openapi-generator/src/main/resources/python-experimental/rest.handlebars @@ -127,15 +127,21 @@ class RESTClientObject(object): len(timeout) == 2): timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1]) - if 'Content-Type' not in headers: - headers['Content-Type'] = 'application/json' - try: # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: if query_params: url += '?' + urlencode(query_params) - if headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + if 'Content-Type' not in headers and body is None: + r = self.pool_manager.request( + method, + url, + fields=query_params, + preload_content=not stream, + timeout=timeout, + headers=headers + ) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 r = self.pool_manager.request( method, url, fields=fields, diff --git a/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml b/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml index 5b31f8d14f1..9a63e801a63 100644 --- a/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml @@ -1558,6 +1558,25 @@ paths: content: application/json: {} application/xml: {} + /fake/deleteCoffee/{id}: + delete: + operationId: deleteCoffee + summary: Delete coffee + description: Delete the coffee identified by the given id, (delete without request body) + tags: + - fake + parameters: + - name: id + in: path + description: The internal object id + required: true + schema: + type: string + responses: + '200': + description: OK + default: + description: Unexpected error servers: - url: 'http://{server}.swagger.io:{port}/v2' description: petstore server diff --git a/samples/openapi3/client/petstore/python-experimental/README.md b/samples/openapi3/client/petstore/python-experimental/README.md index 19c6f9a6933..9f5d5132d20 100644 --- a/samples/openapi3/client/petstore/python-experimental/README.md +++ b/samples/openapi3/client/petstore/python-experimental/README.md @@ -93,6 +93,7 @@ Class | Method | HTTP request | Description *FakeApi* | [**case_sensitive_params**](docs/FakeApi.md#case_sensitive_params) | **PUT** /fake/case-sensitive-params | *FakeApi* | [**client_model**](docs/FakeApi.md#client_model) | **PATCH** /fake | To test \"client\" model *FakeApi* | [**composed_one_of_different_types**](docs/FakeApi.md#composed_one_of_different_types) | **POST** /fake/refs/composed_one_of_number_with_validations | +*FakeApi* | [**delete_coffee**](docs/FakeApi.md#delete_coffee) | **DELETE** /fake/deleteCoffee/{id} | Delete coffee *FakeApi* | [**endpoint_parameters**](docs/FakeApi.md#endpoint_parameters) | **POST** /fake | Fake endpoint for testing various parameters 假端點 偽のエンドポイント 가짜 엔드 포인트 *FakeApi* | [**enum_parameters**](docs/FakeApi.md#enum_parameters) | **GET** /fake | To test enum parameters *FakeApi* | [**fake_health_get**](docs/FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint diff --git a/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md b/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md index 961e2a4cf30..cd82dc35870 100644 --- a/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md +++ b/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md @@ -13,6 +13,7 @@ Method | HTTP request | Description [**case_sensitive_params**](FakeApi.md#case_sensitive_params) | **PUT** /fake/case-sensitive-params | [**client_model**](FakeApi.md#client_model) | **PATCH** /fake | To test \"client\" model [**composed_one_of_different_types**](FakeApi.md#composed_one_of_different_types) | **POST** /fake/refs/composed_one_of_number_with_validations | +[**delete_coffee**](FakeApi.md#delete_coffee) | **DELETE** /fake/deleteCoffee/{id} | Delete coffee [**endpoint_parameters**](FakeApi.md#endpoint_parameters) | **POST** /fake | Fake endpoint for testing various parameters 假端點 偽のエンドポイント 가짜 엔드 포인트 [**enum_parameters**](FakeApi.md#enum_parameters) | **GET** /fake | To test enum parameters [**fake_health_get**](FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint @@ -826,6 +827,95 @@ 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) +# **delete_coffee** +> delete_coffee(id) + +Delete coffee + +Delete the coffee identified by the given id, (delete without request body) + +### Example + +```python +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(configuration) as api_client: + # Create an instance of the API class + api_instance = fake_api.FakeApi(api_client) + + # example passing only required values which don't have defaults set + path_params = { + 'id': "id_example", + } + try: + # Delete coffee + api_response = api_instance.delete_coffee( + path_params=path_params, + ) + except petstore_api.ApiException as e: + print("Exception when calling FakeApi->delete_coffee: %s\n" % e) +``` +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +path_params | RequestPathParams | | +stream | bool | default is False | if True then the response.content will be streamed and loaded from a file like object. When downloading a file, set this to True to force the code to deserialize the content to a FileSchema file +timeout | typing.Optional[typing.Union[int, typing.Tuple]] | default is None | the timeout used by the rest client +skip_deserialization | bool | default is False | when True, headers and body will be unset and an instance of api_client.ApiResponseWithoutDeserialization will be returned + +### path_params +#### RequestPathParams + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +id | IdSchema | | + +#### IdSchema + +Type | Description | Notes +------------- | ------------- | ------------- +**str** | | + +### Return Types, Responses + +Code | Class | Description +------------- | ------------- | ------------- +n/a | api_client.ApiResponseWithoutDeserialization | When skip_deserialization is True this response is returned +200 | ApiResponseFor200 | OK +default | ApiResponseForDefault | Unexpected error + +#### ApiResponseFor200 +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +response | urllib3.HTTPResponse | Raw response | +body | Unset | body was not defined | +headers | Unset | headers were not defined | + +#### ApiResponseForDefault +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +response | urllib3.HTTPResponse | Raw response | +body | Unset | body was not defined | +headers | Unset | headers were not defined | + + +void (empty response body) + +### Authorization + +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) + # **endpoint_parameters** > endpoint_parameters() diff --git a/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api.py b/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api.py index 1e135fc899b..5e2fc9ec136 100644 --- a/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api.py +++ b/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api.py @@ -19,6 +19,7 @@ from petstore_api.api.fake_api_endpoints.boolean import Boolean from petstore_api.api.fake_api_endpoints.case_sensitive_params import CaseSensitiveParams from petstore_api.api.fake_api_endpoints.client_model import ClientModel from petstore_api.api.fake_api_endpoints.composed_one_of_different_types import ComposedOneOfDifferentTypes +from petstore_api.api.fake_api_endpoints.delete_coffee import DeleteCoffee from petstore_api.api.fake_api_endpoints.endpoint_parameters import EndpointParameters from petstore_api.api.fake_api_endpoints.enum_parameters import EnumParameters from petstore_api.api.fake_api_endpoints.fake_health_get import FakeHealthGet @@ -52,6 +53,7 @@ class FakeApi( CaseSensitiveParams, ClientModel, ComposedOneOfDifferentTypes, + DeleteCoffee, EndpointParameters, EnumParameters, FakeHealthGet, diff --git a/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/delete_coffee.py b/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/delete_coffee.py new file mode 100644 index 00000000000..f16ebaf6b31 --- /dev/null +++ b/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/delete_coffee.py @@ -0,0 +1,180 @@ +# coding: utf-8 + +""" + + + Generated by: https://openapi-generator.tech +""" + +from dataclasses import dataclass +import re # noqa: F401 +import sys # noqa: F401 +import typing +import urllib3 +import functools # noqa: F401 + +from petstore_api import api_client, exceptions +import decimal # noqa: F401 +from datetime import date, datetime # noqa: F401 +from frozendict import frozendict # noqa: F401 + +from petstore_api.schemas import ( # noqa: F401 + AnyTypeSchema, + ComposedSchema, + DictSchema, + ListSchema, + StrSchema, + IntSchema, + Int32Schema, + Int64Schema, + Float32Schema, + Float64Schema, + NumberSchema, + UUIDSchema, + DateSchema, + DateTimeSchema, + DecimalSchema, + BoolSchema, + BinarySchema, + NoneSchema, + none_type, + Configuration, + Unset, + unset, + ComposedBase, + ListBase, + DictBase, + NoneBase, + StrBase, + IntBase, + Int32Base, + Int64Base, + Float32Base, + Float64Base, + NumberBase, + UUIDBase, + DateBase, + DateTimeBase, + BoolBase, + BinaryBase, + Schema, + _SchemaValidator, + _SchemaTypeChecker, + _SchemaEnumMaker +) + +# path params +IdSchema = StrSchema +RequestRequiredPathParams = typing.TypedDict( + 'RequestRequiredPathParams', + { + 'id': IdSchema, + } +) +RequestOptionalPathParams = typing.TypedDict( + 'RequestOptionalPathParams', + { + }, + total=False +) + + +class RequestPathParams(RequestRequiredPathParams, RequestOptionalPathParams): + pass + + +request_path_id = api_client.PathParameter( + name="id", + style=api_client.ParameterStyle.SIMPLE, + schema=IdSchema, + required=True, +) +_path = '/fake/deleteCoffee/{id}' +_method = 'DELETE' + + +@dataclass +class ApiResponseFor200(api_client.ApiResponse): + response: urllib3.HTTPResponse + body: Unset = unset + headers: Unset = unset + + +_response_for_200 = api_client.OpenApiResponse( + response_cls=ApiResponseFor200, +) + + +@dataclass +class ApiResponseForDefault(api_client.ApiResponse): + response: urllib3.HTTPResponse + body: Unset = unset + headers: Unset = unset + + +_response_for_default = api_client.OpenApiResponse( + response_cls=ApiResponseForDefault, +) +_status_code_to_response = { + '200': _response_for_200, + 'default': _response_for_default, +} + + +class DeleteCoffee(api_client.Api): + + def delete_coffee( + self: api_client.Api, + path_params: RequestPathParams = frozendict(), + stream: bool = False, + timeout: typing.Optional[typing.Union[int, typing.Tuple]] = None, + skip_deserialization: bool = False, + ) -> typing.Union[ + ApiResponseFor200, + ApiResponseForDefault, + api_client.ApiResponseWithoutDeserialization + ]: + """ + Delete coffee + :param skip_deserialization: If true then api_response.response will be set but + api_response.body and api_response.headers will not be deserialized into schema + class instances + """ + self._verify_typed_dict_inputs(RequestPathParams, path_params) + + _path_params = {} + for parameter in ( + request_path_id, + ): + parameter_data = path_params.get(parameter.name, unset) + if parameter_data is unset: + continue + serialized_data = parameter.serialize(parameter_data) + _path_params.update(serialized_data) + # TODO add cookie handling + + response = self.api_client.call_api( + resource_path=_path, + method=_method, + path_params=_path_params, + stream=stream, + timeout=timeout, + ) + + if skip_deserialization: + api_response = api_client.ApiResponseWithoutDeserialization(response=response) + else: + response_for_status = _status_code_to_response.get(str(response.status)) + if response_for_status: + api_response = response_for_status.deserialize(response, self.api_client.configuration) + else: + default_response = _status_code_to_response.get('default') + if default_response: + api_response = default_response.deserialize(response, self.api_client.configuration) + else: + api_response = api_client.ApiResponseWithoutDeserialization(response=response) + + if not 200 <= response.status <= 299: + raise exceptions.ApiException(api_response=api_response) + + return api_response diff --git a/samples/openapi3/client/petstore/python-experimental/petstore_api/rest.py b/samples/openapi3/client/petstore/python-experimental/petstore_api/rest.py index 7724b98a71c..20f6077cbcf 100644 --- a/samples/openapi3/client/petstore/python-experimental/petstore_api/rest.py +++ b/samples/openapi3/client/petstore/python-experimental/petstore_api/rest.py @@ -134,15 +134,21 @@ class RESTClientObject(object): len(timeout) == 2): timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1]) - if 'Content-Type' not in headers: - headers['Content-Type'] = 'application/json' - try: # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: if query_params: url += '?' + urlencode(query_params) - if headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + if 'Content-Type' not in headers and body is None: + r = self.pool_manager.request( + method, + url, + fields=query_params, + preload_content=not stream, + timeout=timeout, + headers=headers + ) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 r = self.pool_manager.request( method, url, fields=fields, diff --git a/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fake_api.py b/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fake_api.py index a5d0c021f3a..2b50e28fa09 100644 --- a/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fake_api.py +++ b/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fake_api.py @@ -38,6 +38,7 @@ class TestFakeApi(unittest.TestCase): json_content_type = 'application/json' configuration = petstore_api.Configuration() api = FakeApi(api_client=api_client.ApiClient(configuration=configuration)) + user_agent = 'OpenAPI-Generator/1.0.0/python' @staticmethod def headers_for_content_type(content_type: str) -> dict[str, str]: @@ -66,8 +67,9 @@ class TestFakeApi(unittest.TestCase): def __json_bytes(in_data: typing.Any) -> bytes: return json.dumps(in_data, separators=(",", ":"), ensure_ascii=False).encode('utf-8') - @staticmethod + @classmethod def __assert_request_called_with( + cls, mock_request, url: str, method: str = 'POST', @@ -80,8 +82,10 @@ class TestFakeApi(unittest.TestCase): ): headers = { 'Accept': accept_content_type, - 'User-Agent': 'OpenAPI-Generator/1.0.0/python' + 'User-Agent': cls.user_agent } + if accept_content_type: + headers['Accept'] = accept_content_type if content_type: headers['Content-Type'] = content_type kwargs = dict( @@ -99,6 +103,39 @@ class TestFakeApi(unittest.TestCase): **kwargs ) + @classmethod + def __assert_pool_manager_request_called_with( + cls, + mock_request, + url: str, + method: str = 'POST', + body: typing.Optional[bytes] = None, + content_type: typing.Optional[str] = 'application/json', + accept_content_type: typing.Optional[str] = 'application/json', + stream: bool = False, + query_params: typing.Optional[typing.Tuple[typing.Tuple[str, str], ...]] = None, + ): + headers = { + 'User-Agent': cls.user_agent + } + if accept_content_type: + headers['Accept'] = accept_content_type + if content_type: + headers['Content-Type'] = content_type + kwargs = dict( + headers=HTTPHeaderDict(headers), + fields=query_params, + preload_content=not stream, + timeout=None, + ) + if content_type and method != 'GET': + kwargs['body'] = body + mock_request.assert_called_with( + method, + url, + **kwargs + ) + def test_array_model(self): from petstore_api.model import animal_farm, animal @@ -738,6 +775,27 @@ class TestFakeApi(unittest.TestCase): with self.assertRaises(exceptions.ApiValueError): self.api.response_without_schema() + def test_delete_endpoint_without_request_body(self): + with patch.object(urllib3.PoolManager, 'request') as mock_request: + + body = None + mock_request.return_value = self.__response( + self.__json_bytes(body), + ) + + api_response = self.api.delete_coffee(path_params=dict(id='1')) + self.__assert_pool_manager_request_called_with( + mock_request, + 'http://petstore.swagger.io:80/v2/fake/deleteCoffee/1', + method='DELETE', + content_type=None, + accept_content_type=None, + ) + + assert isinstance(api_response.response, urllib3.HTTPResponse) + assert isinstance(api_response.body, schemas.Unset) + assert isinstance(api_response.headers, schemas.Unset) + if __name__ == '__main__': unittest.main()