From e421b211e9be4365d2f0aaefcd0d3e0825b6d830 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 11:43:54 -0500 Subject: [PATCH 01/13] refactor: reduce code duplication --- openapi_python_client/parser/openapi.py | 83 ++++++++++--------------- tests/test_parser/test_openapi.py | 6 +- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index dc6e9f47b..38fb3d1b1 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -3,7 +3,7 @@ from copy import deepcopy from dataclasses import dataclass, field from http import HTTPStatus -from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union +from typing import Any, Dict, Iterator, List, Optional, Protocol, Set, Tuple, Union import attr from pydantic import ValidationError @@ -103,6 +103,15 @@ def generate_operation_id(*, path: str, method: str) -> str: models_relative_prefix: str = "..." +class RequestBodyParser(Protocol): + __name__: str = "RequestBodyParser" + + def __call__( + self, *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config + ) -> Tuple[Union[Property, PropertyError, None], Schemas]: + ... + + @dataclass class Endpoint: """ @@ -208,59 +217,33 @@ def _add_body( if data.requestBody is None or isinstance(data.requestBody, oai.Reference): return endpoint, schemas - form_body, schemas = Endpoint.parse_request_form_body( - body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config - ) + request_body_parsers: list[tuple[str, RequestBodyParser]] = [ + ("form_body", Endpoint.parse_request_form_body), + ("json_body", Endpoint.parse_request_json_body), + ("multipart_body", Endpoint.parse_multipart_body), + ] - if isinstance(form_body, ParseError): - return ( - ParseError( - header=f"Cannot parse form body of endpoint {endpoint.name}", - detail=form_body.detail, - data=form_body.data, - ), - schemas, - ) + for property_name, parser in request_body_parsers: + body, schemas = parser(body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config) - json_body, schemas = Endpoint.parse_request_json_body( - body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config - ) - if isinstance(json_body, ParseError): - return ( - ParseError( - header=f"Cannot parse JSON body of endpoint {endpoint.name}", - detail=json_body.detail, - data=json_body.data, - ), - schemas, - ) + if isinstance(body, ParseError): + return ( + ParseError( + header=( + f"Cannot parse {property_name.removesuffix('_body')} request body of endpoint" + f" {endpoint.name}" + ), + detail=body.detail, + data=body.data, + ), + schemas, + ) - multipart_body, schemas = Endpoint.parse_multipart_body( - body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config - ) - if isinstance(multipart_body, ParseError): - return ( - ParseError( - header=f"Cannot parse multipart body of endpoint {endpoint.name}", - detail=multipart_body.detail, - data=multipart_body.data, - ), - schemas, - ) + if body is not None: + setattr(endpoint, property_name, body) + endpoint.relative_imports.update(body.get_imports(prefix=models_relative_prefix)) + endpoint.relative_imports.update(body.get_lazy_imports(prefix=models_relative_prefix)) - # No reasons to use lazy imports in endpoints, so add lazy imports to relative here. - if form_body is not None: - endpoint.form_body = form_body - endpoint.relative_imports.update(endpoint.form_body.get_imports(prefix=models_relative_prefix)) - endpoint.relative_imports.update(endpoint.form_body.get_lazy_imports(prefix=models_relative_prefix)) - if multipart_body is not None: - endpoint.multipart_body = multipart_body - endpoint.relative_imports.update(endpoint.multipart_body.get_imports(prefix=models_relative_prefix)) - endpoint.relative_imports.update(endpoint.multipart_body.get_lazy_imports(prefix=models_relative_prefix)) - if json_body is not None: - endpoint.json_body = json_body - endpoint.relative_imports.update(endpoint.json_body.get_imports(prefix=models_relative_prefix)) - endpoint.relative_imports.update(endpoint.json_body.get_lazy_imports(prefix=models_relative_prefix)) return endpoint, schemas @staticmethod diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index 4a8594f73..145874e33 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -305,7 +305,7 @@ def test_add_body_bad_json_data(self, mocker): assert result == ( ParseError( - header=f"Cannot parse JSON body of endpoint {endpoint.name}", + header=f"Cannot parse json request body of endpoint {endpoint.name}", detail=parse_error.detail, data=parse_error.data, ), @@ -337,7 +337,7 @@ def test_add_body_bad_form_data(self, enum_property_factory): assert result == ( ParseError( detail="type array must have items defined", - header="Cannot parse form body of endpoint name", + header="Cannot parse form request body of endpoint name", data=bad_schema, ), schemas, @@ -364,7 +364,7 @@ def test_add_body_bad_multipart_data(self, mocker): assert result == ( ParseError( - header=f"Cannot parse multipart body of endpoint {endpoint.name}", + header=f"Cannot parse multipart request body of endpoint {endpoint.name}", detail=parse_error.detail, data=parse_error.data, ), From c258fabe43db436279c572ebf881e651ee8e6f50 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 12:36:41 -0500 Subject: [PATCH 02/13] Implement binary request bodies for endpoints --- .../my_test_api_client/api/tests/__init__.py | 8 + .../octet_stream_tests_octet_stream_post.py | 157 ++++++++++++++++++ end_to_end_tests/openapi.json | 40 +++++ openapi_python_client/parser/openapi.py | 28 ++++ .../templates/endpoint_macros.py.jinja | 13 +- .../templates/endpoint_module.py.jinja | 4 +- tests/test_parser/test_openapi.py | 51 +++++- 7 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py index 537c5c580..c678e9e96 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py @@ -15,6 +15,7 @@ json_body_tests_json_body_post, no_response_tests_no_response_get, octet_stream_tests_octet_stream_get, + octet_stream_tests_octet_stream_post, post_form_data, post_form_data_inline, post_tests_json_body_string, @@ -118,6 +119,13 @@ def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType: """ return octet_stream_tests_octet_stream_get + @classmethod + def octet_stream_tests_octet_stream_post(cls) -> types.ModuleType: + """ + Binary (octet stream) request body + """ + return octet_stream_tests_octet_stream_post + @classmethod def no_response_tests_no_response_get(cls) -> types.ModuleType: """ diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py new file mode 100644 index 000000000..e2d05856a --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -0,0 +1,157 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...types import File, Response + + +def _get_kwargs( + *, + binary_body: File, +) -> Dict[str, Any]: + headers = {} + headers["Content-Type"] = binary_body.mime_type if binary_body.mime_type else "application/octet-stream" + + return { + "method": "post", + "url": "/tests/octet_stream", + "content": binary_body, + "headers": headers, + } + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[HTTPValidationError, str]]: + if response.status_code == HTTPStatus.OK: + response_200 = cast(str, response.json()) + return response_200 + if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[HTTPValidationError, str]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: Union[AuthenticatedClient, Client], + binary_body: File, +) -> Response[Union[HTTPValidationError, str]]: + """Binary (octet stream) request body + + Args: + binary_body (File): A file to upload + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[HTTPValidationError, str]] + """ + + kwargs = _get_kwargs( + binary_body=binary_body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: Union[AuthenticatedClient, Client], + binary_body: File, +) -> Optional[Union[HTTPValidationError, str]]: + """Binary (octet stream) request body + + Args: + binary_body (File): A file to upload + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[HTTPValidationError, str] + """ + + return sync_detailed( + client=client, + binary_body=binary_body, + ).parsed + + +async def asyncio_detailed( + *, + client: Union[AuthenticatedClient, Client], + binary_body: File, +) -> Response[Union[HTTPValidationError, str]]: + """Binary (octet stream) request body + + Args: + binary_body (File): A file to upload + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[HTTPValidationError, str]] + """ + + kwargs = _get_kwargs( + binary_body=binary_body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: Union[AuthenticatedClient, Client], + binary_body: File, +) -> Optional[Union[HTTPValidationError, str]]: + """Binary (octet stream) request body + + Args: + binary_body (File): A file to upload + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[HTTPValidationError, str] + """ + + return ( + await asyncio_detailed( + client=client, + binary_body=binary_body, + ) + ).parsed diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 9c4334d46..9d02a1733 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -625,6 +625,46 @@ } } } + }, + "post": { + "tags": [ + "tests" + ], + "summary": "Binary (octet stream) request body", + "operationId": "octet_stream_tests_octet_stream_post", + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "description": "A file to upload", + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "success", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } } }, "/tests/no_response": { diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 38fb3d1b1..20e09da18 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -134,6 +134,7 @@ class Endpoint: form_body: Optional[Property] = None json_body: Optional[Property] = None multipart_body: Optional[Property] = None + binary_body: Optional[Property] = None errors: List[ParseError] = field(default_factory=list) used_python_identifiers: Set[PythonIdentifier] = field(default_factory=set) @@ -204,6 +205,30 @@ def parse_request_json_body( ) return None, schemas + @staticmethod + def parse_request_binary_body( + *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config + ) -> Tuple[Union[Property, PropertyError, None], Schemas]: + """Return binary_body""" + binary_body = None + for content_type, schema in body.content.items(): + content_type = get_content_type(content_type) # noqa: PLW2901 + + if content_type == "application/octet-stream": + binary_body = schema + break + + if binary_body is not None and binary_body.media_type_schema is not None: + return property_from_data( + name="binary_body", + required=True, + data=binary_body.media_type_schema, + schemas=schemas, + parent_name=parent_name, + config=config, + ) + return None, schemas + @staticmethod def _add_body( *, @@ -220,6 +245,7 @@ def _add_body( request_body_parsers: list[tuple[str, RequestBodyParser]] = [ ("form_body", Endpoint.parse_request_form_body), ("json_body", Endpoint.parse_request_json_body), + ("binary_body", Endpoint.parse_request_binary_body), ("multipart_body", Endpoint.parse_multipart_body), ] @@ -517,6 +543,8 @@ def iter_all_parameters(self) -> Iterator[Property]: yield self.multipart_body if self.json_body: yield self.json_body + if self.binary_body: + yield self.binary_body def list_all_parameters(self) -> List[Property]: """Return a List of all the parameters of this endpoint""" diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index 931554299..000f2c368 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -2,8 +2,9 @@ {% from "helpers.jinja" import safe_docstring %} {% macro header_params(endpoint) %} -{% if endpoint.header_parameters %} +{% if endpoint.header_parameters or endpoint.binary_body %} headers = {} +{% if endpoint.header_parameters %} {% for parameter in endpoint.header_parameters.values() %} {% import "property_templates/" + parameter.template as param_template %} {% if param_template.transform_header %} @@ -15,6 +16,10 @@ headers = {} {{ guarded_statement(parameter, parameter.python_name, statement) }} {% endfor %} {% endif %} +{% if endpoint.binary_body %} +headers['Content-Type'] = {{ endpoint.binary_body.python_name }}.mime_type if {{ endpoint.binary_body.python_name}}.mime_type else 'application/octet-stream' +{% endif %} +{% endif %} {% endmacro %} {% macro cookie_params(endpoint) %} @@ -108,6 +113,9 @@ multipart_data: {{ endpoint.multipart_body.get_type_string() }}, {% if endpoint.json_body %} json_body: {{ endpoint.json_body.get_type_string() }}, {% endif %} +{% if endpoint.binary_body %} +binary_body: {{ endpoint.binary_body.get_type_string() }}, +{% endif %} {# query parameters #} {% for parameter in endpoint.query_parameters.values() %} {{ parameter.to_string() }}, @@ -138,6 +146,9 @@ multipart_data=multipart_data, {% if endpoint.json_body %} json_body=json_body, {% endif %} +{% if endpoint.binary_body %} +binary_body=binary_body, +{% endif %} {% for parameter in endpoint.query_parameters.values() %} {{ parameter.python_name }}={{ parameter.python_name }}, {% endfor %} diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index c2b738ced..131226271 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -47,11 +47,13 @@ def _get_kwargs( "files": {{ "multipart_" + endpoint.multipart_body.python_name }}, {% elif endpoint.json_body %} "json": {{ "json_" + endpoint.json_body.python_name }}, + {% elif endpoint.binary_body %} + "content": {{ endpoint.binary_body.python_name }}, {% endif %} {% if endpoint.query_parameters %} "params": params, {% endif %} - {% if endpoint.header_parameters %} + {% if endpoint.header_parameters or endpoint.binary_body %} "headers": headers, {% endif %} {% if endpoint.cookie_parameters %} diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index 145874e33..8932b2f6f 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -312,6 +312,34 @@ def test_add_body_bad_json_data(self, mocker): other_schemas, ) + def test_add_body_bad_binary_data(self, mocker): + from openapi_python_client.parser.openapi import Endpoint, Schemas + + schemas = Schemas() + mocker.patch.object(Endpoint, "parse_request_form_body", return_value=(None, schemas)) + mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(mocker.MagicMock(), mocker.MagicMock())) + parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) + other_schemas = mocker.MagicMock() + mocker.patch.object(Endpoint, "parse_request_binary_body", return_value=(parse_error, other_schemas)) + endpoint = self.make_endpoint() + request_body = mocker.MagicMock() + + result = Endpoint._add_body( + endpoint=endpoint, + data=oai.Operation.model_construct(requestBody=request_body), + schemas=schemas, + config=MagicMock(), + ) + + assert result == ( + ParseError( + header=f"Cannot parse binary request body of endpoint {endpoint.name}", + detail=parse_error.detail, + data=parse_error.data, + ), + other_schemas, + ) + def test_add_body_bad_form_data(self, enum_property_factory): from openapi_python_client.parser.openapi import Endpoint, Schemas @@ -402,6 +430,14 @@ def test_add_body_happy(self, mocker): Endpoint, "parse_request_json_body", return_value=(json_body, json_schemas) ) + binary_body = mocker.MagicMock(autospec=Property) + binary_body_imports = mocker.MagicMock() + binary_body.get_imports.return_value = {binary_body_imports} + binary_schemas = mocker.MagicMock() + parse_request_binary_body = mocker.patch.object( + Endpoint, "parse_request_binary_body", return_value=(binary_body, binary_schemas) + ) + endpoint = self.make_endpoint() initial_schemas = mocker.MagicMock() @@ -419,16 +455,27 @@ def test_add_body_happy(self, mocker): parse_request_json_body.assert_called_once_with( body=request_body, schemas=form_schemas, parent_name="name", config=config ) - parse_multipart_body.assert_called_once_with( + parse_request_binary_body.assert_called_once_with( body=request_body, schemas=json_schemas, parent_name="name", config=config ) + parse_multipart_body.assert_called_once_with( + body=request_body, schemas=binary_schemas, parent_name="name", config=config + ) form_body.get_imports.assert_called_once_with(prefix="...") json_body.get_imports.assert_called_once_with(prefix="...") + binary_body.get_imports.assert_called_once_with(prefix="...") multipart_body.get_imports.assert_called_once_with(prefix="...") - assert endpoint.relative_imports == {"import_3", form_body_imports, json_body_imports, multipart_body_imports} + assert endpoint.relative_imports == { + "import_3", + form_body_imports, + json_body_imports, + binary_body_imports, + multipart_body_imports, + } assert endpoint.json_body == json_body assert endpoint.form_body == form_body assert endpoint.multipart_body == multipart_body + assert endpoint.binary_body == binary_body @pytest.mark.parametrize("response_status_code", ["not_a_number", 499]) def test__add_responses_status_code_error(self, response_status_code, mocker): From ab76ceecbd676c13d8e7490b8b3944b3efa853fd Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 12:44:13 -0500 Subject: [PATCH 03/13] Missed one crucial detail --- openapi_python_client/templates/endpoint_module.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 131226271..a31b66ff7 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -48,7 +48,7 @@ def _get_kwargs( {% elif endpoint.json_body %} "json": {{ "json_" + endpoint.json_body.python_name }}, {% elif endpoint.binary_body %} - "content": {{ endpoint.binary_body.python_name }}, + "content": {{ endpoint.binary_body.python_name }}.payload, {% endif %} {% if endpoint.query_parameters %} "params": params, From 518be9f21175f49432599277f539c50b0408cffc Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 15:08:51 -0500 Subject: [PATCH 04/13] Missed the corresponding regen --- .../api/tests/octet_stream_tests_octet_stream_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py index e2d05856a..e49e34d55 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -19,7 +19,7 @@ def _get_kwargs( return { "method": "post", "url": "/tests/octet_stream", - "content": binary_body, + "content": binary_body.payload, "headers": headers, } From 323e71a54368f408a0e26a62c2de876c10c70b1f Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 15:16:36 -0500 Subject: [PATCH 05/13] Resolve py3.8 incompatibility issues --- openapi_python_client/parser/openapi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 20e09da18..18012254a 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -242,7 +242,7 @@ def _add_body( if data.requestBody is None or isinstance(data.requestBody, oai.Reference): return endpoint, schemas - request_body_parsers: list[tuple[str, RequestBodyParser]] = [ + request_body_parsers: List[Tuple[str, RequestBodyParser]] = [ ("form_body", Endpoint.parse_request_form_body), ("json_body", Endpoint.parse_request_json_body), ("binary_body", Endpoint.parse_request_binary_body), @@ -253,12 +253,12 @@ def _add_body( body, schemas = parser(body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config) if isinstance(body, ParseError): + property_type = property_name + if property_type.endswith('_body'): + property_type = property_type[:-5] return ( ParseError( - header=( - f"Cannot parse {property_name.removesuffix('_body')} request body of endpoint" - f" {endpoint.name}" - ), + header=f"Cannot parse {property_type} request body of endpoint {endpoint.name}", detail=body.detail, data=body.data, ), From 5b7fa1456b1d3520f1f8eecc8763c236b4608b4c Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 15:22:12 -0500 Subject: [PATCH 06/13] missed a task check --- openapi_python_client/parser/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 18012254a..3dc5a9c9b 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -254,7 +254,7 @@ def _add_body( if isinstance(body, ParseError): property_type = property_name - if property_type.endswith('_body'): + if property_type.endswith("_body"): property_type = property_type[:-5] return ( ParseError( From c331a8b22753828ba8b58d11a1fcf5218dc6d694 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 6 Dec 2023 17:31:15 -0700 Subject: [PATCH 07/13] Support multiple bodies --- .../api/default/get_common_parameters.py | 4 +- .../api/default/post_common_parameters.py | 4 +- .../api/default/reserved_parameters.py | 4 +- .../api/location/get_location_header_types.py | 6 +- .../get_location_query_optionality.py | 4 +- ...st_naming_property_conflict_with_import.py | 41 +- .../get_parameter_references_path_param.py | 6 +- ...lete_common_parameters_overriding_param.py | 4 +- .../get_common_parameters_overriding_param.py | 4 +- .../get_same_name_multiple_locations_param.py | 6 +- .../parameters/multiple_path_parameters.py | 4 +- ..._responses_unions_simple_before_complex.py | 4 +- .../api/responses/text_response.py | 4 +- .../api/tag1/get_tag_with_number.py | 4 +- .../api/tests/callback_test.py | 39 +- .../api/tests/defaults_tests_defaults_post.py | 4 +- .../api/tests/description_with_backslash.py | 4 +- .../api/tests/get_basic_list_of_booleans.py | 4 +- .../api/tests/get_basic_list_of_floats.py | 4 +- .../api/tests/get_basic_list_of_integers.py | 4 +- .../api/tests/get_basic_list_of_strings.py | 4 +- .../api/tests/get_user_list.py | 4 +- .../api/tests/int_enum_tests_int_enum_post.py | 4 +- .../tests/json_body_tests_json_body_post.py | 39 +- .../no_response_tests_no_response_get.py | 4 +- .../octet_stream_tests_octet_stream_get.py | 4 +- .../octet_stream_tests_octet_stream_post.py | 39 +- .../api/tests/post_form_data.py | 30 +- .../api/tests/post_form_data_inline.py | 32 +- .../api/tests/post_tests_json_body_string.py | 39 +- .../api/tests/test_inline_objects.py | 41 +- ..._with_cookie_auth_token_with_cookie_get.py | 4 +- ...d_content_tests_unsupported_content_get.py | 4 +- .../tests/upload_file_tests_upload_post.py | 39 +- ...upload_multiple_files_tests_upload_post.py | 47 ++- .../my_test_api_client/api/true_/false_.py | 4 +- .../my_test_api_client/models/__init__.py | 12 +- ..._data.py => post_form_data_inline_body.py} | 10 +- ...ing_property_conflict_with_import_body.py} | 10 +- ...on_body.py => test_inline_objects_body.py} | 8 +- .../api/body/post_body_multipart.py | 41 +- .../api/parameters/post_parameters_header.py | 6 +- .../integration_tests/models/__init__.py | 4 +- ...rt_data.py => post_body_multipart_body.py} | 10 +- openapi_python_client/parser/bodies.py | 114 ++++++ openapi_python_client/parser/openapi.py | 197 ++-------- .../openapi_schema_pydantic/operation.py | 14 +- .../templates/endpoint_macros.py.jinja | 79 ++-- .../templates/endpoint_module.py.jinja | 58 +-- tests/test_parser/test_openapi.py | 371 +----------------- .../test_parser/test_properties/test_init.py | 1 + 51 files changed, 598 insertions(+), 833 deletions(-) rename end_to_end_tests/golden-record/my_test_api_client/models/{post_form_data_inline_data.py => post_form_data_inline_body.py} (88%) rename end_to_end_tests/golden-record/my_test_api_client/models/{post_naming_property_conflict_with_import_json_body.py => post_naming_property_conflict_with_import_body.py} (83%) rename end_to_end_tests/golden-record/my_test_api_client/models/{test_inline_objects_json_body.py => test_inline_objects_body.py} (80%) rename integration-tests/integration_tests/models/{post_body_multipart_multipart_data.py => post_body_multipart_body.py} (91%) create mode 100644 openapi_python_client/parser/bodies.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py index 3eb7cae13..c88a11050 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py @@ -17,12 +17,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/common_parameters", "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py index 4f82c6f90..5c36d0827 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py @@ -17,12 +17,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "post", "url": "/common_parameters", "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py index 7fb74c804..5f2c01914 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py @@ -20,12 +20,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/naming/reserved-parameters", "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py index b004866ce..9c501eb48 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py @@ -38,12 +38,14 @@ def _get_kwargs( if not isinstance(string_enum_header, Unset): headers["String-Enum-Header"] = str(string_enum_header) - return { + _kwargs = { "method": "get", "url": "/location/header/types", - "headers": headers, } + _kwargs["headers"] = headers + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py index 7daa34252..31e28e9f1 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py @@ -41,12 +41,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/location/query/optionality", "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py index 6174aea46..a69b7bc58 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py @@ -5,7 +5,7 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_naming_property_conflict_with_import_json_body import PostNamingPropertyConflictWithImportJsonBody +from ...models.post_naming_property_conflict_with_import_body import PostNamingPropertyConflictWithImportBody from ...models.post_naming_property_conflict_with_import_response_200 import ( PostNamingPropertyConflictWithImportResponse200, ) @@ -14,16 +14,23 @@ def _get_kwargs( *, - json_body: PostNamingPropertyConflictWithImportJsonBody, + body: PostNamingPropertyConflictWithImportBody, ) -> Dict[str, Any]: - json_json_body = json_body.to_dict() + headers = {} - return { + _kwargs = { "method": "post", "url": "/naming/property-conflict-with-import", - "json": json_json_body, } + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -52,11 +59,11 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: PostNamingPropertyConflictWithImportJsonBody, + body: PostNamingPropertyConflictWithImportBody, ) -> Response[PostNamingPropertyConflictWithImportResponse200]: """ Args: - json_body (PostNamingPropertyConflictWithImportJsonBody): + body (PostNamingPropertyConflictWithImportBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -67,7 +74,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = client.get_httpx_client().request( @@ -80,11 +87,11 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - json_body: PostNamingPropertyConflictWithImportJsonBody, + body: PostNamingPropertyConflictWithImportBody, ) -> Optional[PostNamingPropertyConflictWithImportResponse200]: """ Args: - json_body (PostNamingPropertyConflictWithImportJsonBody): + body (PostNamingPropertyConflictWithImportBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -96,18 +103,18 @@ def sync( return sync_detailed( client=client, - json_body=json_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: PostNamingPropertyConflictWithImportJsonBody, + body: PostNamingPropertyConflictWithImportBody, ) -> Response[PostNamingPropertyConflictWithImportResponse200]: """ Args: - json_body (PostNamingPropertyConflictWithImportJsonBody): + body (PostNamingPropertyConflictWithImportBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -118,7 +125,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -129,11 +136,11 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - json_body: PostNamingPropertyConflictWithImportJsonBody, + body: PostNamingPropertyConflictWithImportBody, ) -> Optional[PostNamingPropertyConflictWithImportResponse200]: """ Args: - json_body (PostNamingPropertyConflictWithImportJsonBody): + body (PostNamingPropertyConflictWithImportBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -146,6 +153,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - json_body=json_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py index 0f9c603f2..0b140bda7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py @@ -31,16 +31,18 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/parameter-references/{path_param}".format( path_param=path_param, ), "params": params, - "headers": headers, "cookies": cookies, } + _kwargs["headers"] = headers + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py index bf33c3206..2afb07966 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py @@ -18,7 +18,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "delete", "url": "/common_parameters_overriding/{param}".format( param=param_path, @@ -26,6 +26,8 @@ def _get_kwargs( "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py index 73d4dcfd7..3b102258d 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py @@ -18,7 +18,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/common_parameters_overriding/{param}".format( param=param_path, @@ -26,6 +26,8 @@ def _get_kwargs( "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py index 6aa991293..564545977 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py @@ -28,16 +28,18 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/same-name-multiple-locations/{param}".format( param=param_path, ), "params": params, - "headers": headers, "cookies": cookies, } + _kwargs["headers"] = headers + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py index 0e8ce7ec5..d9dad0f60 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py @@ -14,7 +14,7 @@ def _get_kwargs( param1: str, param3: int, ) -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}".format( param4=param4, @@ -24,6 +24,8 @@ def _get_kwargs( ), } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py index 3bb13d534..ba4b45210 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py @@ -12,11 +12,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "post", "url": "/responses/unions/simple_before_complex", } + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py index ce3f87e78..ef9b21ae5 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "post", "url": "/responses/text", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[str]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py index c2bacd545..d4e6738c5 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tag_with_number", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py index 643e9c0f6..49e17d19d 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py @@ -12,16 +12,23 @@ def _get_kwargs( *, - json_body: AModel, + body: AModel, ) -> Dict[str, Any]: - json_json_body = json_body.to_dict() + headers = {} - return { + _kwargs = { "method": "post", "url": "/tests/callback", - "json": json_json_body, } + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -53,14 +60,14 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Response[Union[Any, HTTPValidationError]]: """Path with callback Try sending a request related to a callback Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -71,7 +78,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = client.get_httpx_client().request( @@ -84,14 +91,14 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Optional[Union[Any, HTTPValidationError]]: """Path with callback Try sending a request related to a callback Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -103,21 +110,21 @@ def sync( return sync_detailed( client=client, - json_body=json_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Response[Union[Any, HTTPValidationError]]: """Path with callback Try sending a request related to a callback Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -128,7 +135,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -139,14 +146,14 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Optional[Union[Any, HTTPValidationError]]: """Path with callback Try sending a request related to a callback Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -159,6 +166,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - json_body=json_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py index 8d1702b71..ab5501691 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py @@ -83,12 +83,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "post", "url": "/tests/defaults", "params": params, } + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py index 20954b5d7..637acc14c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/description-with-backslash", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py index 73a08b31a..f02bde1fb 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/basic_lists/booleans", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[List[bool]]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py index f64ec5570..405612bed 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/basic_lists/floats", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[List[float]]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py index 859b09829..cf336d20b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/basic_lists/integers", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[List[int]]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py index 7d9261007..091912bcc 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/basic_lists/strings", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[List[str]]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index c03b67369..62024ec4d 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -54,12 +54,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/tests/", "params": params, } + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py index bbfa1b885..f7c9b497c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py @@ -21,12 +21,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "post", "url": "/tests/int_enum", "params": params, } + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py index e146b2ade..316a3d156 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -12,16 +12,23 @@ def _get_kwargs( *, - json_body: AModel, + body: AModel, ) -> Dict[str, Any]: - json_json_body = json_body.to_dict() + headers = {} - return { + _kwargs = { "method": "post", "url": "/tests/json_body", - "json": json_json_body, } + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -53,14 +60,14 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Response[Union[Any, HTTPValidationError]]: """Json Body Try sending a JSON body Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -71,7 +78,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = client.get_httpx_client().request( @@ -84,14 +91,14 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Optional[Union[Any, HTTPValidationError]]: """Json Body Try sending a JSON body Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -103,21 +110,21 @@ def sync( return sync_detailed( client=client, - json_body=json_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Response[Union[Any, HTTPValidationError]]: """Json Body Try sending a JSON body Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -128,7 +135,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -139,14 +146,14 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - json_body: AModel, + body: AModel, ) -> Optional[Union[Any, HTTPValidationError]]: """Json Body Try sending a JSON body Args: - json_body (AModel): A Model for testing all the ways custom objects can be used + body (AModel): A Model for testing all the ways custom objects can be used Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -159,6 +166,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - json_body=json_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py index 5cd61aa15..6afa9415a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/no_response", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py index 5cde2b110..b8eb2abb1 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py @@ -10,11 +10,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/octet_stream", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[File]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py index e49e34d55..10c0e1210 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -11,18 +11,23 @@ def _get_kwargs( *, - binary_body: File, + body: File, ) -> Dict[str, Any]: headers = {} - headers["Content-Type"] = binary_body.mime_type if binary_body.mime_type else "application/octet-stream" - return { + _kwargs = { "method": "post", "url": "/tests/octet_stream", - "content": binary_body.payload, - "headers": headers, } + _body = body.payload + + _kwargs["content"] = _body + headers["Content-Type"] = "application/octet-stream" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -54,12 +59,12 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - binary_body: File, + body: File, ) -> Response[Union[HTTPValidationError, str]]: """Binary (octet stream) request body Args: - binary_body (File): A file to upload + body (File): A file to upload Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -70,7 +75,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - binary_body=binary_body, + body=body, ) response = client.get_httpx_client().request( @@ -83,12 +88,12 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - binary_body: File, + body: File, ) -> Optional[Union[HTTPValidationError, str]]: """Binary (octet stream) request body Args: - binary_body (File): A file to upload + body (File): A file to upload Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -100,19 +105,19 @@ def sync( return sync_detailed( client=client, - binary_body=binary_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - binary_body: File, + body: File, ) -> Response[Union[HTTPValidationError, str]]: """Binary (octet stream) request body Args: - binary_body (File): A file to upload + body (File): A file to upload Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -123,7 +128,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - binary_body=binary_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -134,12 +139,12 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - binary_body: File, + body: File, ) -> Optional[Union[HTTPValidationError, str]]: """Binary (octet stream) request body Args: - binary_body (File): A file to upload + body (File): A file to upload Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -152,6 +157,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - binary_body=binary_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py index d64cae452..25c402cd1 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -10,14 +10,24 @@ def _get_kwargs( - form_data: AFormData, + *, + body: AFormData, ) -> Dict[str, Any]: - return { + headers = {} + + _kwargs = { "method": "post", "url": "/tests/post_form_data", - "data": form_data.to_dict(), } + _body = body.to_dict() + + _kwargs["data"] = _body + headers["Content-Type"] = "application/x-www-form-urlencoded" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: @@ -40,12 +50,15 @@ def _build_response(*, client: Union[AuthenticatedClient, Client], response: htt def sync_detailed( *, client: Union[AuthenticatedClient, Client], - form_data: AFormData, + body: AFormData, ) -> Response[Any]: """Post form data Post form data + Args: + body (AFormData): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -55,7 +68,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - form_data=form_data, + body=body, ) response = client.get_httpx_client().request( @@ -68,12 +81,15 @@ def sync_detailed( async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - form_data: AFormData, + body: AFormData, ) -> Response[Any]: """Post form data Post form data + Args: + body (AFormData): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -83,7 +99,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - form_data=form_data, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py index 2fde59a15..702aa6a55 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py @@ -5,19 +5,29 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_form_data_inline_data import PostFormDataInlineData +from ...models.post_form_data_inline_body import PostFormDataInlineBody from ...types import Response def _get_kwargs( - form_data: PostFormDataInlineData, + *, + body: PostFormDataInlineBody, ) -> Dict[str, Any]: - return { + headers = {} + + _kwargs = { "method": "post", "url": "/tests/post_form_data_inline", - "data": form_data.to_dict(), } + _body = body.to_dict() + + _kwargs["data"] = _body + headers["Content-Type"] = "application/x-www-form-urlencoded" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: @@ -40,12 +50,15 @@ def _build_response(*, client: Union[AuthenticatedClient, Client], response: htt def sync_detailed( *, client: Union[AuthenticatedClient, Client], - form_data: PostFormDataInlineData, + body: PostFormDataInlineBody, ) -> Response[Any]: """Post form data (inline schema) Post form data (inline schema) + Args: + body (PostFormDataInlineBody): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -55,7 +68,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - form_data=form_data, + body=body, ) response = client.get_httpx_client().request( @@ -68,12 +81,15 @@ def sync_detailed( async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - form_data: PostFormDataInlineData, + body: PostFormDataInlineBody, ) -> Response[Any]: """Post form data (inline schema) Post form data (inline schema) + Args: + body (PostFormDataInlineBody): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -83,7 +99,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - form_data=form_data, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py index c3614b375..e93a4b359 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py @@ -11,16 +11,23 @@ def _get_kwargs( *, - json_body: str, + body: str, ) -> Dict[str, Any]: - json_json_body = json_body + headers = {} - return { + _kwargs = { "method": "post", "url": "/tests/json_body/string", - "json": json_json_body, } + _body = body + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -52,12 +59,12 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: str, + body: str, ) -> Response[Union[HTTPValidationError, str]]: """Json Body Which is String Args: - json_body (str): + body (str): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -68,7 +75,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = client.get_httpx_client().request( @@ -81,12 +88,12 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - json_body: str, + body: str, ) -> Optional[Union[HTTPValidationError, str]]: """Json Body Which is String Args: - json_body (str): + body (str): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -98,19 +105,19 @@ def sync( return sync_detailed( client=client, - json_body=json_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: str, + body: str, ) -> Response[Union[HTTPValidationError, str]]: """Json Body Which is String Args: - json_body (str): + body (str): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -121,7 +128,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -132,12 +139,12 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - json_body: str, + body: str, ) -> Optional[Union[HTTPValidationError, str]]: """Json Body Which is String Args: - json_body (str): + body (str): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -150,6 +157,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - json_body=json_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py index 36f297c61..c796537c9 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py @@ -5,23 +5,30 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.test_inline_objects_json_body import TestInlineObjectsJsonBody +from ...models.test_inline_objects_body import TestInlineObjectsBody from ...models.test_inline_objects_response_200 import TestInlineObjectsResponse200 from ...types import Response def _get_kwargs( *, - json_body: TestInlineObjectsJsonBody, + body: TestInlineObjectsBody, ) -> Dict[str, Any]: - json_json_body = json_body.to_dict() + headers = {} - return { + _kwargs = { "method": "post", "url": "/tests/inline_objects", - "json": json_json_body, } + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -50,12 +57,12 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: TestInlineObjectsJsonBody, + body: TestInlineObjectsBody, ) -> Response[TestInlineObjectsResponse200]: """Test Inline Objects Args: - json_body (TestInlineObjectsJsonBody): + body (TestInlineObjectsBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -66,7 +73,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = client.get_httpx_client().request( @@ -79,12 +86,12 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - json_body: TestInlineObjectsJsonBody, + body: TestInlineObjectsBody, ) -> Optional[TestInlineObjectsResponse200]: """Test Inline Objects Args: - json_body (TestInlineObjectsJsonBody): + body (TestInlineObjectsBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -96,19 +103,19 @@ def sync( return sync_detailed( client=client, - json_body=json_body, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - json_body: TestInlineObjectsJsonBody, + body: TestInlineObjectsBody, ) -> Response[TestInlineObjectsResponse200]: """Test Inline Objects Args: - json_body (TestInlineObjectsJsonBody): + body (TestInlineObjectsBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -119,7 +126,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - json_body=json_body, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -130,12 +137,12 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - json_body: TestInlineObjectsJsonBody, + body: TestInlineObjectsBody, ) -> Optional[TestInlineObjectsResponse200]: """Test Inline Objects Args: - json_body (TestInlineObjectsJsonBody): + body (TestInlineObjectsBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -148,6 +155,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - json_body=json_body, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py index 5820cb934..552a7981b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py @@ -15,12 +15,14 @@ def _get_kwargs( cookies = {} cookies["MyToken"] = my_token - return { + _kwargs = { "method": "get", "url": "/auth/token_with_cookie", "cookies": cookies, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py index b3f5a95e6..99d841c57 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py @@ -9,11 +9,13 @@ def _get_kwargs() -> Dict[str, Any]: - return { + _kwargs = { "method": "get", "url": "/tests/unsupported_content", } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index 30a90393b..61f82b387 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -12,16 +12,23 @@ def _get_kwargs( *, - multipart_data: BodyUploadFileTestsUploadPost, + body: BodyUploadFileTestsUploadPost, ) -> Dict[str, Any]: - multipart_multipart_data = multipart_data.to_multipart() + headers = {} - return { + _kwargs = { "method": "post", "url": "/tests/upload", - "files": multipart_multipart_data, } + _body = body.to_multipart() + + _kwargs["files"] = _body + headers["Content-Type"] = "multipart/form-data" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -53,14 +60,14 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: BodyUploadFileTestsUploadPost, + body: BodyUploadFileTestsUploadPost, ) -> Response[Union[Any, HTTPValidationError]]: """Upload File Upload a file Args: - multipart_data (BodyUploadFileTestsUploadPost): + body (BodyUploadFileTestsUploadPost): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -71,7 +78,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = client.get_httpx_client().request( @@ -84,14 +91,14 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - multipart_data: BodyUploadFileTestsUploadPost, + body: BodyUploadFileTestsUploadPost, ) -> Optional[Union[Any, HTTPValidationError]]: """Upload File Upload a file Args: - multipart_data (BodyUploadFileTestsUploadPost): + body (BodyUploadFileTestsUploadPost): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -103,21 +110,21 @@ def sync( return sync_detailed( client=client, - multipart_data=multipart_data, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: BodyUploadFileTestsUploadPost, + body: BodyUploadFileTestsUploadPost, ) -> Response[Union[Any, HTTPValidationError]]: """Upload File Upload a file Args: - multipart_data (BodyUploadFileTestsUploadPost): + body (BodyUploadFileTestsUploadPost): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -128,7 +135,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -139,14 +146,14 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - multipart_data: BodyUploadFileTestsUploadPost, + body: BodyUploadFileTestsUploadPost, ) -> Optional[Union[Any, HTTPValidationError]]: """Upload File Upload a file Args: - multipart_data (BodyUploadFileTestsUploadPost): + body (BodyUploadFileTestsUploadPost): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -159,6 +166,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - multipart_data=multipart_data, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py index a5cb511d1..c91bc632b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py @@ -11,20 +11,27 @@ def _get_kwargs( *, - multipart_data: List[File], + body: List[File], ) -> Dict[str, Any]: - multipart_multipart_data = [] - for multipart_data_item_data in multipart_data: - multipart_data_item = multipart_data_item_data.to_tuple() + headers = {} - multipart_multipart_data.append(multipart_data_item) - - return { + _kwargs = { "method": "post", "url": "/tests/upload/multiple", - "files": multipart_multipart_data, } + _body = [] + for body_item_data in body: + body_item = body_item_data.to_tuple() + + _body.append(body_item) + + _kwargs["files"] = _body + headers["Content-Type"] = "multipart/form-data" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -56,14 +63,14 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: List[File], + body: List[File], ) -> Response[Union[Any, HTTPValidationError]]: """Upload multiple files Upload several files in the same request Args: - multipart_data (List[File]): + body (List[File]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -74,7 +81,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = client.get_httpx_client().request( @@ -87,14 +94,14 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - multipart_data: List[File], + body: List[File], ) -> Optional[Union[Any, HTTPValidationError]]: """Upload multiple files Upload several files in the same request Args: - multipart_data (List[File]): + body (List[File]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -106,21 +113,21 @@ def sync( return sync_detailed( client=client, - multipart_data=multipart_data, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: List[File], + body: List[File], ) -> Response[Union[Any, HTTPValidationError]]: """Upload multiple files Upload several files in the same request Args: - multipart_data (List[File]): + body (List[File]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -131,7 +138,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -142,14 +149,14 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - multipart_data: List[File], + body: List[File], ) -> Optional[Union[Any, HTTPValidationError]]: """Upload multiple files Upload several files in the same request Args: - multipart_data (List[File]): + body (List[File]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -162,6 +169,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - multipart_data=multipart_data, + body=body, ) ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py index 586a1ecdd..56b49358a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py @@ -17,12 +17,14 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + _kwargs = { "method": "get", "url": "/naming/keywords", "params": params, } + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == HTTPStatus.OK: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index d5be8c46c..f0b94bfac 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -63,14 +63,14 @@ from .model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0 from .model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1 from .none import None_ -from .post_form_data_inline_data import PostFormDataInlineData -from .post_naming_property_conflict_with_import_json_body import PostNamingPropertyConflictWithImportJsonBody +from .post_form_data_inline_body import PostFormDataInlineBody +from .post_naming_property_conflict_with_import_body import PostNamingPropertyConflictWithImportBody from .post_naming_property_conflict_with_import_response_200 import PostNamingPropertyConflictWithImportResponse200 from .post_responses_unions_simple_before_complex_response_200 import PostResponsesUnionsSimpleBeforeComplexResponse200 from .post_responses_unions_simple_before_complex_response_200a_type_1 import ( PostResponsesUnionsSimpleBeforeComplexResponse200AType1, ) -from .test_inline_objects_json_body import TestInlineObjectsJsonBody +from .test_inline_objects_body import TestInlineObjectsBody from .test_inline_objects_response_200 import TestInlineObjectsResponse200 from .validation_error import ValidationError @@ -130,12 +130,12 @@ "ModelWithUnionPropertyInlinedFruitType0", "ModelWithUnionPropertyInlinedFruitType1", "None_", - "PostFormDataInlineData", - "PostNamingPropertyConflictWithImportJsonBody", + "PostFormDataInlineBody", + "PostNamingPropertyConflictWithImportBody", "PostNamingPropertyConflictWithImportResponse200", "PostResponsesUnionsSimpleBeforeComplexResponse200", "PostResponsesUnionsSimpleBeforeComplexResponse200AType1", - "TestInlineObjectsJsonBody", + "TestInlineObjectsBody", "TestInlineObjectsResponse200", "ValidationError", ) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_data.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_body.py similarity index 88% rename from end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_data.py rename to end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_body.py index 6ecd3055b..31b4d8fdf 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_data.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_form_data_inline_body.py @@ -5,11 +5,11 @@ from ..types import UNSET, Unset -T = TypeVar("T", bound="PostFormDataInlineData") +T = TypeVar("T", bound="PostFormDataInlineBody") @_attrs_define -class PostFormDataInlineData: +class PostFormDataInlineBody: """ Attributes: a_required_field (str): @@ -43,13 +43,13 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: an_optional_field = d.pop("an_optional_field", UNSET) - post_form_data_inline_data = cls( + post_form_data_inline_body = cls( a_required_field=a_required_field, an_optional_field=an_optional_field, ) - post_form_data_inline_data.additional_properties = d - return post_form_data_inline_data + post_form_data_inline_body.additional_properties = d + return post_form_data_inline_body @property def additional_keys(self) -> List[str]: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_json_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_body.py similarity index 83% rename from end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_json_body.py rename to end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_body.py index fd4aa2de7..a6d959166 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_json_body.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_naming_property_conflict_with_import_body.py @@ -5,11 +5,11 @@ from ..types import UNSET, Unset -T = TypeVar("T", bound="PostNamingPropertyConflictWithImportJsonBody") +T = TypeVar("T", bound="PostNamingPropertyConflictWithImportBody") @_attrs_define -class PostNamingPropertyConflictWithImportJsonBody: +class PostNamingPropertyConflictWithImportBody: """ Attributes: field (Union[Unset, str]): A python_name of field should not interfere with attrs field @@ -41,13 +41,13 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: define = d.pop("Define", UNSET) - post_naming_property_conflict_with_import_json_body = cls( + post_naming_property_conflict_with_import_body = cls( field=field, define=define, ) - post_naming_property_conflict_with_import_json_body.additional_properties = d - return post_naming_property_conflict_with_import_json_body + post_naming_property_conflict_with_import_body.additional_properties = d + return post_naming_property_conflict_with_import_body @property def additional_keys(self) -> List[str]: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_json_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_body.py similarity index 80% rename from end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_json_body.py rename to end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_body.py index bf4c7d023..8c1843b41 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_json_body.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/test_inline_objects_body.py @@ -4,11 +4,11 @@ from ..types import UNSET, Unset -T = TypeVar("T", bound="TestInlineObjectsJsonBody") +T = TypeVar("T", bound="TestInlineObjectsBody") @_attrs_define -class TestInlineObjectsJsonBody: +class TestInlineObjectsBody: """ Attributes: a_property (Union[Unset, str]): @@ -31,8 +31,8 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() a_property = d.pop("a_property", UNSET) - test_inline_objects_json_body = cls( + test_inline_objects_body = cls( a_property=a_property, ) - return test_inline_objects_json_body + return test_inline_objects_body diff --git a/integration-tests/integration_tests/api/body/post_body_multipart.py b/integration-tests/integration_tests/api/body/post_body_multipart.py index 19d2b7d11..c3ed1dd9c 100644 --- a/integration-tests/integration_tests/api/body/post_body_multipart.py +++ b/integration-tests/integration_tests/api/body/post_body_multipart.py @@ -5,7 +5,7 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_body_multipart_multipart_data import PostBodyMultipartMultipartData +from ...models.post_body_multipart_body import PostBodyMultipartBody from ...models.post_body_multipart_response_200 import PostBodyMultipartResponse200 from ...models.public_error import PublicError from ...types import Response @@ -13,16 +13,23 @@ def _get_kwargs( *, - multipart_data: PostBodyMultipartMultipartData, + body: PostBodyMultipartBody, ) -> Dict[str, Any]: - multipart_multipart_data = multipart_data.to_multipart() + headers = {} - return { + _kwargs = { "method": "post", "url": "/body/multipart", - "files": multipart_multipart_data, } + _body = body.to_multipart() + + _kwargs["files"] = _body + headers["Content-Type"] = "multipart/form-data" + + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response @@ -55,11 +62,11 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: PostBodyMultipartMultipartData, + body: PostBodyMultipartBody, ) -> Response[Union[PostBodyMultipartResponse200, PublicError]]: """ Args: - multipart_data (PostBodyMultipartMultipartData): + body (PostBodyMultipartBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -70,7 +77,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = client.get_httpx_client().request( @@ -83,11 +90,11 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], - multipart_data: PostBodyMultipartMultipartData, + body: PostBodyMultipartBody, ) -> Optional[Union[PostBodyMultipartResponse200, PublicError]]: """ Args: - multipart_data (PostBodyMultipartMultipartData): + body (PostBodyMultipartBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -99,18 +106,18 @@ def sync( return sync_detailed( client=client, - multipart_data=multipart_data, + body=body, ).parsed async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], - multipart_data: PostBodyMultipartMultipartData, + body: PostBodyMultipartBody, ) -> Response[Union[PostBodyMultipartResponse200, PublicError]]: """ Args: - multipart_data (PostBodyMultipartMultipartData): + body (PostBodyMultipartBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -121,7 +128,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - multipart_data=multipart_data, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -132,11 +139,11 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], - multipart_data: PostBodyMultipartMultipartData, + body: PostBodyMultipartBody, ) -> Optional[Union[PostBodyMultipartResponse200, PublicError]]: """ Args: - multipart_data (PostBodyMultipartMultipartData): + body (PostBodyMultipartBody): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -149,6 +156,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - multipart_data=multipart_data, + body=body, ) ).parsed diff --git a/integration-tests/integration_tests/api/parameters/post_parameters_header.py b/integration-tests/integration_tests/api/parameters/post_parameters_header.py index a68dd841e..37e57accb 100644 --- a/integration-tests/integration_tests/api/parameters/post_parameters_header.py +++ b/integration-tests/integration_tests/api/parameters/post_parameters_header.py @@ -26,12 +26,14 @@ def _get_kwargs( headers["Integer-Header"] = str(integer_header) - return { + _kwargs = { "method": "post", "url": "/parameters/header", - "headers": headers, } + _kwargs["headers"] = headers + return _kwargs + def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response diff --git a/integration-tests/integration_tests/models/__init__.py b/integration-tests/integration_tests/models/__init__.py index 28d550bb2..0bc731d78 100644 --- a/integration-tests/integration_tests/models/__init__.py +++ b/integration-tests/integration_tests/models/__init__.py @@ -1,13 +1,13 @@ """ Contains all the data models used in inputs/outputs """ -from .post_body_multipart_multipart_data import PostBodyMultipartMultipartData +from .post_body_multipart_body import PostBodyMultipartBody from .post_body_multipart_response_200 import PostBodyMultipartResponse200 from .post_parameters_header_response_200 import PostParametersHeaderResponse200 from .problem import Problem from .public_error import PublicError __all__ = ( - "PostBodyMultipartMultipartData", + "PostBodyMultipartBody", "PostBodyMultipartResponse200", "PostParametersHeaderResponse200", "Problem", diff --git a/integration-tests/integration_tests/models/post_body_multipart_multipart_data.py b/integration-tests/integration_tests/models/post_body_multipart_body.py similarity index 91% rename from integration-tests/integration_tests/models/post_body_multipart_multipart_data.py rename to integration-tests/integration_tests/models/post_body_multipart_body.py index be94fc27e..1bbe788af 100644 --- a/integration-tests/integration_tests/models/post_body_multipart_multipart_data.py +++ b/integration-tests/integration_tests/models/post_body_multipart_body.py @@ -6,11 +6,11 @@ from ..types import UNSET, File, Unset -T = TypeVar("T", bound="PostBodyMultipartMultipartData") +T = TypeVar("T", bound="PostBodyMultipartBody") @_attrs_define -class PostBodyMultipartMultipartData: +class PostBodyMultipartBody: """ Attributes: a_string (str): @@ -79,14 +79,14 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: description = d.pop("description", UNSET) - post_body_multipart_multipart_data = cls( + post_body_multipart_body = cls( a_string=a_string, file=file, description=description, ) - post_body_multipart_multipart_data.additional_properties = d - return post_body_multipart_multipart_data + post_body_multipart_body.additional_properties = d + return post_body_multipart_body @property def additional_keys(self) -> List[str]: diff --git a/openapi_python_client/parser/bodies.py b/openapi_python_client/parser/bodies.py new file mode 100644 index 000000000..ebfdeb587 --- /dev/null +++ b/openapi_python_client/parser/bodies.py @@ -0,0 +1,114 @@ +from enum import Enum +from typing import List, Tuple, Union + +import attr + +from openapi_python_client.parser.properties import ( + Property, + Schemas, + property_from_data, ModelProperty, +) + +from .. import schema as oai +from ..config import Config +from ..utils import get_content_type +from .errors import ErrorLevel, ParseError + + +class BodyType(str, Enum): + JSON = "json" + DATA = "data" + FILES = "files" + CONTENT = "content" + + +@attr.define +class Body: + content_type: str + prop: Property + body_type: BodyType + + +def body_from_data( + *, + data: oai.Operation, + schemas: Schemas, + config: Config, + endpoint_name: str, +) -> Tuple[List[Union[Body, ParseError]], Schemas]: + """Adds form or JSON body to Endpoint if included in data""" + if data.request_body is None or isinstance(data.request_body, oai.Reference): + return [], schemas + + bodies: List[Union[Body, ParseError]] = [] + body_content = data.request_body.content + prefix_type_names = len(body_content) > 1 + + for content_type, media_type in body_content.items(): + simplified_content_type = get_content_type(content_type) + if simplified_content_type is None: + bodies.append( + ParseError( + detail="Invalid content type", + data=data.request_body, + level=ErrorLevel.WARNING, + ) + ) + continue + media_type_schema = media_type.media_type_schema + if media_type_schema is None: + bodies.append( + ParseError( + detail="Missing schema", + data=data.request_body, + level=ErrorLevel.WARNING, + ) + ) + continue + if simplified_content_type == "application/x-www-form-urlencoded": + body_type = BodyType.DATA + elif simplified_content_type == "multipart/form-data": + body_type = BodyType.FILES + elif simplified_content_type == "application/octet-stream": + body_type = BodyType.CONTENT + elif simplified_content_type == "application/json" or simplified_content_type.endswith("+json"): + body_type = BodyType.JSON + else: + bodies.append( + ParseError( + detail=f"Unsupported content type {simplified_content_type}", + data=data.request_body, + level=ErrorLevel.WARNING, + ) + ) + continue + prop, schemas = property_from_data( + name=f"{body_type}_body" if prefix_type_names else "body", + required=True, + data=media_type_schema, + schemas=schemas, + parent_name=endpoint_name, + config=config, + ) + if isinstance(prop, ParseError): + bodies.append(prop) + continue + if isinstance(prop, ModelProperty) and body_type == BodyType.FILES: + # Regardless of if we just made this property or found it, it now needs the `to_multipart` method + prop = attr.evolve(prop, is_multipart_body=True) + schemas = attr.evolve( + schemas, + classes_by_name={ + **schemas.classes_by_name, + prop.class_info.name: prop, + }, + ) + bodies.append( + Body( + content_type=content_type, + prop=prop, + body_type=body_type, + ) + ) + + return bodies, schemas diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 279597541..83528d5c4 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -11,7 +11,8 @@ from .. import schema as oai from .. import utils from ..config import Config -from ..utils import PythonIdentifier, get_content_type +from ..utils import PythonIdentifier +from .bodies import Body, body_from_data from .errors import GeneratorError, ParseError, PropertyError from .properties import ( AnyProperty, @@ -115,7 +116,12 @@ class RequestBodyParser(Protocol): __name__: str = "RequestBodyParser" def __call__( - self, *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config + self, + *, + body: oai.RequestBody, + schemas: Schemas, + parent_name: str, + config: Config, ) -> Tuple[Union[Property, PropertyError, None], Schemas]: ... @@ -139,161 +145,10 @@ class Endpoint: header_parameters: Dict[str, Property] = field(default_factory=dict) cookie_parameters: Dict[str, Property] = field(default_factory=dict) responses: List[Response] = field(default_factory=list) - form_body: Optional[Property] = None - json_body: Optional[Property] = None - multipart_body: Optional[Property] = None - binary_body: Optional[Property] = None + bodies: List[Body] = field(default_factory=list) errors: List[ParseError] = field(default_factory=list) used_python_identifiers: Set[PythonIdentifier] = field(default_factory=set) - @staticmethod - def parse_request_form_body( - *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config - ) -> Tuple[Union[Property, PropertyError, None], Schemas]: - """Return form_body and updated schemas""" - body_content = body.content - form_body = body_content.get("application/x-www-form-urlencoded") - if form_body is not None and form_body.media_type_schema is not None: - prop, schemas = property_from_data( - name="data", - required=True, - data=form_body.media_type_schema, - schemas=schemas, - parent_name=parent_name, - config=config, - ) - if isinstance(prop, ModelProperty): - schemas = attr.evolve( - schemas, - classes_by_name={ - **schemas.classes_by_name, - prop.class_info.name: prop, - }, - ) - return prop, schemas - return None, schemas - - @staticmethod - def parse_multipart_body( - *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config - ) -> Tuple[Union[Property, PropertyError, None], Schemas]: - """Return multipart_body""" - body_content = body.content - multipart_body = body_content.get("multipart/form-data") - if multipart_body is not None and multipart_body.media_type_schema is not None: - prop, schemas = property_from_data( - name="multipart_data", - required=True, - data=multipart_body.media_type_schema, - schemas=schemas, - parent_name=parent_name, - config=config, - ) - if isinstance(prop, ModelProperty): - prop = attr.evolve(prop, is_multipart_body=True) - schemas = attr.evolve( - schemas, - classes_by_name={ - **schemas.classes_by_name, - prop.class_info.name: prop, - }, - ) - return prop, schemas - return None, schemas - - @staticmethod - def parse_request_json_body( - *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config - ) -> Tuple[Union[Property, PropertyError, None], Schemas]: - """Return json_body""" - json_body = None - for content_type, schema in body.content.items(): - parsed_content_type = get_content_type(content_type) - - if parsed_content_type is not None and ( - parsed_content_type == "application/json" or parsed_content_type.endswith("+json") - ): - json_body = schema - break - - if json_body is not None and json_body.media_type_schema is not None: - return property_from_data( - name="json_body", - required=True, - data=json_body.media_type_schema, - schemas=schemas, - parent_name=parent_name, - config=config, - ) - return None, schemas - - @staticmethod - def parse_request_binary_body( - *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config - ) -> Tuple[Union[Property, PropertyError, None], Schemas]: - """Return binary_body""" - binary_body = None - for content_type, schema in body.content.items(): - content_type = get_content_type(content_type) # noqa: PLW2901 - - if content_type == "application/octet-stream": - binary_body = schema - break - - if binary_body is not None and binary_body.media_type_schema is not None: - return property_from_data( - name="binary_body", - required=True, - data=binary_body.media_type_schema, - schemas=schemas, - parent_name=parent_name, - config=config, - ) - return None, schemas - - @staticmethod - def _add_body( - *, - endpoint: "Endpoint", - data: oai.Operation, - schemas: Schemas, - config: Config, - ) -> Tuple[Union[ParseError, "Endpoint"], Schemas]: - """Adds form or JSON body to Endpoint if included in data""" - endpoint = deepcopy(endpoint) - if data.requestBody is None or isinstance(data.requestBody, oai.Reference): - return endpoint, schemas - - request_body_parsers: List[Tuple[str, RequestBodyParser]] = [ - ("form_body", Endpoint.parse_request_form_body), - ("json_body", Endpoint.parse_request_json_body), - ("binary_body", Endpoint.parse_request_binary_body), - ("multipart_body", Endpoint.parse_multipart_body), - ] - - for property_name, parser in request_body_parsers: - body, schemas = parser(body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config) - - if isinstance(body, ParseError): - property_type = property_name - if property_type.endswith("_body"): - property_type = property_type[:-5] - return ( - ParseError( - header=f"Cannot parse {property_type} request body of endpoint {endpoint.name}", - detail=body.detail, - data=body.data, - ), - schemas, - ) - - if body is not None: - setattr(endpoint, property_name, body) - endpoint.relative_imports.update(body.get_imports(prefix=models_relative_prefix)) - endpoint.relative_imports.update(body.get_lazy_imports(prefix=models_relative_prefix)) - - return endpoint, schemas - @staticmethod def _add_responses( *, endpoint: "Endpoint", data: oai.Responses, schemas: Schemas, config: Config @@ -543,7 +398,7 @@ def from_data( else: name = data.operationId - endpoint = Endpoint( + result: Union[Endpoint, ParseError] = Endpoint( path=path, method=method, summary=utils.remove_string_escapes(data.summary) if data.summary else "", @@ -554,7 +409,7 @@ def from_data( ) result, schemas, parameters = Endpoint.add_parameters( - endpoint=endpoint, + endpoint=result, data=data, schemas=schemas, parameters=parameters, @@ -563,7 +418,28 @@ def from_data( if isinstance(result, ParseError): return result, schemas, parameters result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config) - result, schemas = Endpoint._add_body(endpoint=result, data=data, schemas=schemas, config=config) + if isinstance(result, ParseError): + return result, schemas, parameters + bodies, schemas = body_from_data(data=data, schemas=schemas, config=config, endpoint_name=result.name) + body_errors = [] + for body in bodies: + if isinstance(body, ParseError): + body_errors.append(body) + continue + result.bodies.append(body) + result.relative_imports.update(body.prop.get_imports(prefix=models_relative_prefix)) + result.relative_imports.update(body.prop.get_lazy_imports(prefix=models_relative_prefix)) + if len(result.bodies) > 0: + result.errors.extend(body_errors) + elif len(body_errors) > 0: + return ( + ParseError( + header="Endpoint requires a body, but none were parseable.", + detail="\n".join(error.detail or "" for error in body_errors), + ), + schemas, + parameters, + ) return result, schemas, parameters @@ -582,12 +458,7 @@ def iter_all_parameters(self) -> Iterator[Property]: yield from self.query_parameters.values() yield from self.header_parameters.values() yield from self.cookie_parameters.values() - if self.multipart_body: - yield self.multipart_body - if self.json_body: - yield self.json_body - if self.binary_body: - yield self.binary_body + yield from (body.prop for body in self.bodies) def list_all_parameters(self) -> List[Property]: """Return a List of all the parameters of this endpoint""" diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 7dd7f8f15..41f5e7100 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -1,6 +1,6 @@ from typing import Dict, List, Optional, Union -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from .callback import Callback from .external_documentation import ExternalDocumentation @@ -30,7 +30,7 @@ class Operation(BaseModel): externalDocs: Optional[ExternalDocumentation] = None operationId: Optional[str] = None parameters: Optional[List[Union[Parameter, Reference]]] = None - requestBody: Optional[Union[RequestBody, Reference]] = None + request_body: Optional[Union[RequestBody, Reference]] = Field(None, alias="requestBody") responses: Responses callbacks: Optional[Dict[str, Callback]] = None @@ -60,8 +60,14 @@ class Operation(BaseModel): "schema": { "type": "object", "properties": { - "name": {"description": "Updated name of the pet", "type": "string"}, - "status": {"description": "Updated status of the pet", "type": "string"}, + "name": { + "description": "Updated name of the pet", + "type": "string", + }, + "status": { + "description": "Updated status of the pet", + "type": "string", + }, }, "required": ["status"], } diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index 000f2c368..a4826d066 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -2,7 +2,7 @@ {% from "helpers.jinja" import safe_docstring %} {% macro header_params(endpoint) %} -{% if endpoint.header_parameters or endpoint.binary_body %} +{% if endpoint.header_parameters or endpoint.bodies | length > 0 %} headers = {} {% if endpoint.header_parameters %} {% for parameter in endpoint.header_parameters.values() %} @@ -16,9 +16,6 @@ headers = {} {{ guarded_statement(parameter, parameter.python_name, statement) }} {% endfor %} {% endif %} -{% if endpoint.binary_body %} -headers['Content-Type'] = {{ endpoint.binary_body.python_name }}.mime_type if {{ endpoint.binary_body.python_name}}.mime_type else 'application/octet-stream' -{% endif %} {% endif %} {% endmacro %} @@ -60,27 +57,35 @@ params = {k: v for k, v in params.items() if v is not UNSET and v is not None} {% endif %} {% endmacro %} -{% macro json_body(endpoint) %} -{% if endpoint.json_body %} - {% set property = endpoint.json_body %} - {% set destination = "json_" + property.python_name %} - {% import "property_templates/" + property.template as prop_template %} - {% if prop_template.transform %} +{% macro body_to_kwarg(body) %} +{% if body.body_type == "data" %} +_body = body.to_dict() +{% elif body.body_type == "files"%} +{{ multipart_body(body) }} +{% elif body.body_type == "json" %} +{{ json_body(body) }} +{% elif body.body_type == "content" %} +_body = body.payload +{% endif %} +{% endmacro %} + +{% macro json_body(body) %} +{% set property = body.prop %} +{% set destination = "_body" %} +{% import "property_templates/" + property.template as prop_template %} +{% if prop_template.transform %} {{ prop_template.transform(property, property.python_name, destination) }} - {% else %} +{% else %} {{ destination }} = {{ property.python_name }} - {% endif %} {% endif %} {% endmacro %} -{% macro multipart_body(endpoint) %} -{% if endpoint.multipart_body %} - {% set property = endpoint.multipart_body %} - {% set destination = "multipart_" + property.python_name %} - {% import "property_templates/" + property.template as prop_template %} - {% if prop_template.transform_multipart %} +{% macro multipart_body(body) %} +{% set property = body.prop %} +{% set destination = "_body" %} +{% import "property_templates/" + property.template as prop_template %} +{% if prop_template.transform_multipart %} {{ prop_template.transform_multipart(property, property.python_name, destination) }} - {% endif %} {% endif %} {% endmacro %} @@ -101,20 +106,15 @@ client: AuthenticatedClient, client: Union[AuthenticatedClient, Client], {% endif %} {% endif %} -{# Form data if any #} -{% if endpoint.form_body %} -form_data: {{ endpoint.form_body.get_type_string() }}, -{% endif %} -{# Multipart data if any #} -{% if endpoint.multipart_body %} -multipart_data: {{ endpoint.multipart_body.get_type_string() }}, -{% endif %} -{# JSON body if any #} -{% if endpoint.json_body %} -json_body: {{ endpoint.json_body.get_type_string() }}, -{% endif %} -{% if endpoint.binary_body %} -binary_body: {{ endpoint.binary_body.get_type_string() }}, +{# Any allowed bodies #} +{% if endpoint.bodies | length == 1 %} +body: {{ endpoint.bodies[0].prop.get_type_string() }}, +{% elif endpoint.bodies | length > 1 %} +body: Union[ + {% for body in endpoint.bodies %} + {{ body.prop.get_type_string() }}, + {% endfor %} +], {% endif %} {# query parameters #} {% for parameter in endpoint.query_parameters.values() %} @@ -137,17 +137,8 @@ binary_body: {{ endpoint.binary_body.get_type_string() }}, {% if include_client %} client=client, {% endif %} -{% if endpoint.form_body %} -form_data=form_data, -{% endif %} -{% if endpoint.multipart_body %} -multipart_data=multipart_data, -{% endif %} -{% if endpoint.json_body %} -json_body=json_body, -{% endif %} -{% if endpoint.binary_body %} -binary_body=binary_body, +{% if endpoint.bodies | length > 0 %} +body=body, {% endif %} {% for parameter in endpoint.query_parameters.values() %} {{ parameter.python_name }}={{ parameter.python_name }}, diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 3aec2dcfc..0913a1609 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -11,8 +11,8 @@ from ... import errors {{ relative }} {% endfor %} -{% from "endpoint_macros.py.jinja" import header_params, cookie_params, query_params, json_body, multipart_body, - arguments, client, kwargs, parse_response, docstring %} +{% from "endpoint_macros.py.jinja" import header_params, cookie_params, query_params, + arguments, client, kwargs, parse_response, docstring, body_to_kwarg %} {% set return_string = endpoint.response_type() %} {% set parsed_responses = (endpoint.responses | length > 0) and return_string != "Any" %} @@ -26,41 +26,45 @@ def _get_kwargs( {{ query_params(endpoint) | indent(4) }} - {{ json_body(endpoint) | indent(4) }} - - {{ multipart_body(endpoint) | indent(4) }} - - return { + _kwargs = { "method": "{{ endpoint.method }}", - {% if endpoint.path_parameters %} + {% if endpoint.path_parameters %} "url": "{{ endpoint.path }}".format( {%- for parameter in endpoint.path_parameters.values() -%} - {{parameter.name}}={{parameter.python_name}}, + {{parameter.name}}={{parameter.python_name}}, {%- endfor -%} ), - {% else %} + {% else %} "url": "{{ endpoint.path }}", - {% endif %} - {% if endpoint.form_body %} - "data": form_data.to_dict(), - {% elif endpoint.multipart_body %} - "files": {{ "multipart_" + endpoint.multipart_body.python_name }}, - {% elif endpoint.json_body %} - "json": {{ "json_" + endpoint.json_body.python_name }}, - {% elif endpoint.binary_body %} - "content": {{ endpoint.binary_body.python_name }}.payload, - {% endif %} - {% if endpoint.query_parameters %} + {% endif %} + {% if endpoint.query_parameters %} "params": params, - {% endif %} - {% if endpoint.header_parameters or endpoint.binary_body %} - "headers": headers, - {% endif %} - {% if endpoint.cookie_parameters %} + {% endif %} + {% if endpoint.cookie_parameters %} "cookies": cookies, - {% endif %} + {% endif %} } +{% if endpoint.bodies | length > 1 %} + # {{ endpoint.bodies | length }} +{% for body in endpoint.bodies %} + if isinstance({{ body.prop.python_name }}, {{body.prop.get_type_string() }}): + {{ body_to_kwarg(body) | indent(8) }} + _kwargs["{{ body.body_type.value }}"] = _body + headers["Content-Type"] = "{{ body.content_type }}" +{% endfor %} +{% elif endpoint.bodies | length == 1 %} +{% set body = endpoint.bodies[0] %} + {{ body_to_kwarg(body) | indent(4) }} + _kwargs["{{ body.body_type.value }}"] = _body + headers["Content-Type"] = "{{ body.content_type }}" +{% endif %} + +{% if endpoint.header_parameters or endpoint.bodies | length > 0 %} + _kwargs["headers"] = headers +{% endif %} + return _kwargs + def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[{{ return_string }}]: {% for response in endpoint.responses %} diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index 8932b2f6f..8cd4d7cba 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -126,357 +126,6 @@ def make_endpoint(self): relative_imports={"import_3"}, ) - def test_parse_request_form_body(self, mocker, model_property_factory): - from openapi_python_client.parser.properties import Class - - schema = oai.Reference.model_construct(ref=mocker.MagicMock()) - body = oai.RequestBody.model_construct( - content={"application/x-www-form-urlencoded": oai.MediaType.model_construct(media_type_schema=schema)} - ) - class_info = Class(name="class_name", module_name="module_name") - prop_before = model_property_factory(class_info=class_info) - schemas_before = Schemas() - property_from_data = mocker.patch( - f"{MODULE_NAME}.property_from_data", return_value=(prop_before, schemas_before) - ) - config = mocker.MagicMock() - - from openapi_python_client.parser.openapi import Endpoint - - result = Endpoint.parse_request_form_body(body=body, schemas=schemas_before, parent_name="name", config=config) - - property_from_data.assert_called_once_with( - name="data", required=True, data=schema, schemas=schemas_before, parent_name="name", config=config - ) - prop_after = model_property_factory(class_info=class_info) - schemas_after = Schemas(classes_by_name={class_info.name: prop_after}) - assert result == (prop_after, schemas_after) - - def test_parse_request_form_body_no_data(self): - body = oai.RequestBody.model_construct(content={}) - config = MagicMock() - schemas = MagicMock() - - from openapi_python_client.parser.openapi import Endpoint - - result = Endpoint.parse_request_form_body(body=body, schemas=schemas, parent_name="name", config=config) - - assert result == (None, schemas) - - def test_parse_multipart_body(self, mocker, model_property_factory): - from openapi_python_client.parser.openapi import Endpoint, Schemas - from openapi_python_client.parser.properties import Class - - class_info = Class(name="class_name", module_name="module_name") - prop_before = model_property_factory(class_info=class_info, is_multipart_body=False) - - schema = mocker.MagicMock() - body = oai.RequestBody.model_construct( - content={"multipart/form-data": oai.MediaType.model_construct(media_type_schema=schema)} - ) - schemas_before = Schemas() - config = MagicMock() - property_from_data = mocker.patch( - f"{MODULE_NAME}.property_from_data", return_value=(prop_before, schemas_before) - ) - - result = Endpoint.parse_multipart_body(body=body, schemas=schemas_before, parent_name="parent", config=config) - - property_from_data.assert_called_once_with( - name="multipart_data", - required=True, - data=schema, - schemas=schemas_before, - parent_name="parent", - config=config, - ) - prop_after = model_property_factory(class_info=class_info, is_multipart_body=True) - schemas_after = Schemas(classes_by_name={class_info.name: prop_after}) - assert result == (prop_after, schemas_after) - - def test_parse_multipart_body_existing_schema(self, mocker, model_property_factory): - from openapi_python_client.parser.openapi import Endpoint, Schemas - from openapi_python_client.parser.properties import Class - - class_info = Class(name="class_name", module_name="module_name") - prop_before = model_property_factory(class_info=class_info, is_multipart_body=False) - schemas_before = Schemas(classes_by_name={class_info.name: prop_before}) - - schema = mocker.MagicMock() - body = oai.RequestBody.model_construct( - content={"multipart/form-data": oai.MediaType.model_construct(media_type_schema=schema)} - ) - config = MagicMock() - property_from_data = mocker.patch( - f"{MODULE_NAME}.property_from_data", return_value=(prop_before, schemas_before) - ) - - result = Endpoint.parse_multipart_body(body=body, schemas=schemas_before, parent_name="parent", config=config) - - property_from_data.assert_called_once_with( - name="multipart_data", - required=True, - data=schema, - schemas=schemas_before, - parent_name="parent", - config=config, - ) - prop_after = model_property_factory(class_info=class_info, is_multipart_body=True) - schemas_after = Schemas(classes_by_name={class_info.name: prop_after}) - assert result == (prop_after, schemas_after) - - def test_parse_multipart_body_no_data(self): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - body = oai.RequestBody.model_construct(content={}) - schemas = Schemas() - - prop, schemas = Endpoint.parse_multipart_body( - body=body, schemas=schemas, parent_name="parent", config=MagicMock() - ) - - assert prop is None - - @pytest.mark.parametrize( - "content_type", - ( - "application/json", - "application/vnd.api+json", - "application/yang-data+json", - "application/json;charset=utf-8", - ), - ) - def test_parse_request_json_body(self, mocker, content_type): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - schema = mocker.MagicMock() - body = oai.RequestBody.model_construct( - content={content_type: oai.MediaType.model_construct(media_type_schema=schema)} - ) - property_from_data = mocker.patch(f"{MODULE_NAME}.property_from_data") - schemas = Schemas() - config = MagicMock() - - result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=config) - - property_from_data.assert_called_once_with( - name="json_body", required=True, data=schema, schemas=schemas, parent_name="parent", config=config - ) - assert result == property_from_data.return_value - - def test_parse_request_json_body_no_data(self): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - body = oai.RequestBody.model_construct(content={}) - schemas = Schemas() - - result = Endpoint.parse_request_json_body(body=body, schemas=schemas, parent_name="parent", config=MagicMock()) - - assert result == (None, schemas) - - def test_add_body_no_data(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - parse_request_form_body = mocker.patch.object(Endpoint, "parse_request_form_body") - endpoint = self.make_endpoint() - schemas = Schemas() - - Endpoint._add_body(endpoint=endpoint, data=oai.Operation.model_construct(), schemas=schemas, config=MagicMock()) - - parse_request_form_body.assert_not_called() - - def test_add_body_bad_json_data(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - schemas = Schemas() - mocker.patch.object(Endpoint, "parse_request_form_body", return_value=(None, schemas)) - parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) - other_schemas = mocker.MagicMock() - mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(parse_error, other_schemas)) - endpoint = self.make_endpoint() - request_body = mocker.MagicMock() - - result = Endpoint._add_body( - endpoint=endpoint, - data=oai.Operation.model_construct(requestBody=request_body), - schemas=schemas, - config=MagicMock(), - ) - - assert result == ( - ParseError( - header=f"Cannot parse json request body of endpoint {endpoint.name}", - detail=parse_error.detail, - data=parse_error.data, - ), - other_schemas, - ) - - def test_add_body_bad_binary_data(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - schemas = Schemas() - mocker.patch.object(Endpoint, "parse_request_form_body", return_value=(None, schemas)) - mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(mocker.MagicMock(), mocker.MagicMock())) - parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) - other_schemas = mocker.MagicMock() - mocker.patch.object(Endpoint, "parse_request_binary_body", return_value=(parse_error, other_schemas)) - endpoint = self.make_endpoint() - request_body = mocker.MagicMock() - - result = Endpoint._add_body( - endpoint=endpoint, - data=oai.Operation.model_construct(requestBody=request_body), - schemas=schemas, - config=MagicMock(), - ) - - assert result == ( - ParseError( - header=f"Cannot parse binary request body of endpoint {endpoint.name}", - detail=parse_error.detail, - data=parse_error.data, - ), - other_schemas, - ) - - def test_add_body_bad_form_data(self, enum_property_factory): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - schemas = Schemas( - errors=[ParseError(detail="existing error")], - ) - endpoint = self.make_endpoint() - bad_schema = oai.Schema.model_construct(type=oai.DataType.ARRAY) - - result = Endpoint._add_body( - endpoint=endpoint, - data=oai.Operation.model_construct( - requestBody=oai.RequestBody.model_construct( - content={ - "application/x-www-form-urlencoded": oai.MediaType.model_construct(media_type_schema=bad_schema) - } - ) - ), - schemas=schemas, - config=Config(), - ) - - assert result == ( - ParseError( - detail="type array must have items defined", - header="Cannot parse form request body of endpoint name", - data=bad_schema, - ), - schemas, - ) - - def test_add_body_bad_multipart_data(self, mocker): - from openapi_python_client.parser.openapi import Endpoint, Schemas - - schemas = Schemas() - mocker.patch.object(Endpoint, "parse_request_form_body", return_value=(None, schemas)) - mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(mocker.MagicMock(), mocker.MagicMock())) - parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) - other_schemas = mocker.MagicMock() - mocker.patch.object(Endpoint, "parse_multipart_body", return_value=(parse_error, other_schemas)) - endpoint = self.make_endpoint() - request_body = mocker.MagicMock() - - result = Endpoint._add_body( - endpoint=endpoint, - data=oai.Operation.model_construct(requestBody=request_body), - schemas=schemas, - config=MagicMock(), - ) - - assert result == ( - ParseError( - header=f"Cannot parse multipart request body of endpoint {endpoint.name}", - detail=parse_error.detail, - data=parse_error.data, - ), - other_schemas, - ) - - def test_add_body_happy(self, mocker): - from openapi_python_client.parser.openapi import Endpoint - from openapi_python_client.parser.properties import Property - - request_body = mocker.MagicMock() - config = mocker.MagicMock() - - form_body = mocker.MagicMock(autospec=Property) - form_body_imports = mocker.MagicMock() - form_body.get_imports.return_value = {form_body_imports} - form_schemas = mocker.MagicMock() - parse_request_form_body = mocker.patch.object( - Endpoint, "parse_request_form_body", return_value=(form_body, form_schemas) - ) - - multipart_body = mocker.MagicMock(autospec=Property) - multipart_body_imports = mocker.MagicMock() - multipart_body.get_imports.return_value = {multipart_body_imports} - multipart_schemas = mocker.MagicMock() - parse_multipart_body = mocker.patch.object( - Endpoint, "parse_multipart_body", return_value=(multipart_body, multipart_schemas) - ) - - json_body = mocker.MagicMock(autospec=Property) - json_body_imports = mocker.MagicMock() - json_body.get_imports.return_value = {json_body_imports} - json_schemas = mocker.MagicMock() - parse_request_json_body = mocker.patch.object( - Endpoint, "parse_request_json_body", return_value=(json_body, json_schemas) - ) - - binary_body = mocker.MagicMock(autospec=Property) - binary_body_imports = mocker.MagicMock() - binary_body.get_imports.return_value = {binary_body_imports} - binary_schemas = mocker.MagicMock() - parse_request_binary_body = mocker.patch.object( - Endpoint, "parse_request_binary_body", return_value=(binary_body, binary_schemas) - ) - - endpoint = self.make_endpoint() - initial_schemas = mocker.MagicMock() - - (endpoint, response_schemas) = Endpoint._add_body( - endpoint=endpoint, - data=oai.Operation.model_construct(requestBody=request_body), - schemas=initial_schemas, - config=config, - ) - - assert response_schemas == multipart_schemas - parse_request_form_body.assert_called_once_with( - body=request_body, schemas=initial_schemas, parent_name="name", config=config - ) - parse_request_json_body.assert_called_once_with( - body=request_body, schemas=form_schemas, parent_name="name", config=config - ) - parse_request_binary_body.assert_called_once_with( - body=request_body, schemas=json_schemas, parent_name="name", config=config - ) - parse_multipart_body.assert_called_once_with( - body=request_body, schemas=binary_schemas, parent_name="name", config=config - ) - form_body.get_imports.assert_called_once_with(prefix="...") - json_body.get_imports.assert_called_once_with(prefix="...") - binary_body.get_imports.assert_called_once_with(prefix="...") - multipart_body.get_imports.assert_called_once_with(prefix="...") - assert endpoint.relative_imports == { - "import_3", - form_body_imports, - json_body_imports, - binary_body_imports, - multipart_body_imports, - } - assert endpoint.json_body == json_body - assert endpoint.form_body == form_body - assert endpoint.multipart_body == multipart_body - assert endpoint.binary_body == binary_body - @pytest.mark.parametrize("response_status_code", ["not_a_number", 499]) def test__add_responses_status_code_error(self, response_status_code, mocker): from openapi_python_client.parser.openapi import Endpoint, Schemas @@ -1099,9 +748,6 @@ def test_from_data_standard(self, mocker): _add_responses = mocker.patch.object( Endpoint, "_add_responses", return_value=(response_endpoint, response_schemas) ) - body_schemas = mocker.MagicMock() - body_endpoint = mocker.MagicMock() - _add_body = mocker.patch.object(Endpoint, "_add_body", return_value=(body_endpoint, body_schemas)) data = oai.Operation.model_construct( description=mocker.MagicMock(), operationId=mocker.MagicMock(), @@ -1114,7 +760,7 @@ def test_from_data_standard(self, mocker): mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) - endpoint = Endpoint.from_data( + Endpoint.from_data( data=data, path=path, method=method, @@ -1124,8 +770,6 @@ def test_from_data_standard(self, mocker): config=config, ) - assert (endpoint[0], endpoint[1]) == _add_body.return_value - add_parameters.assert_called_once_with( endpoint=Endpoint( path=path, @@ -1144,9 +788,6 @@ def test_from_data_standard(self, mocker): _add_responses.assert_called_once_with( endpoint=param_endpoint, data=data.responses, schemas=param_schemas, config=config ) - _add_body.assert_called_once_with( - endpoint=response_endpoint, data=data, schemas=response_schemas, config=config - ) def test_from_data_no_operation_id(self, mocker): from openapi_python_client.parser.openapi import Endpoint @@ -1159,7 +800,6 @@ def test_from_data_no_operation_id(self, mocker): _add_responses = mocker.patch.object( Endpoint, "_add_responses", return_value=(mocker.MagicMock(), mocker.MagicMock()) ) - _add_body = mocker.patch.object(Endpoint, "_add_body", return_value=(mocker.MagicMock(), mocker.MagicMock())) data = oai.Operation.model_construct( description=mocker.MagicMock(), operationId=None, @@ -1175,8 +815,6 @@ def test_from_data_no_operation_id(self, mocker): data=data, path=path, method=method, tag="default", schemas=schemas, parameters=parameters, config=config ) - assert (endpoint, return_schemas) == _add_body.return_value - add_parameters.assert_called_once_with( endpoint=Endpoint( path=path, @@ -1198,9 +836,6 @@ def test_from_data_no_operation_id(self, mocker): schemas=add_parameters.return_value[1], config=config, ) - _add_body.assert_called_once_with( - endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config - ) def test_from_data_no_security(self, mocker): from openapi_python_client.parser.openapi import Endpoint @@ -1217,7 +852,6 @@ def test_from_data_no_security(self, mocker): _add_responses = mocker.patch.object( Endpoint, "_add_responses", return_value=(mocker.MagicMock(), mocker.MagicMock()) ) - _add_body = mocker.patch.object(Endpoint, "_add_body", return_value=(mocker.MagicMock(), mocker.MagicMock())) path = mocker.MagicMock() method = mocker.MagicMock() mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=data.description) @@ -1250,9 +884,6 @@ def test_from_data_no_security(self, mocker): schemas=add_parameters.return_value[1], config=config, ) - _add_body.assert_called_once_with( - endpoint=_add_responses.return_value[0], data=data, schemas=_add_responses.return_value[1], config=config - ) @pytest.mark.parametrize( "response_types, expected", diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index ac1d65f91..ff5de2dab 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -803,6 +803,7 @@ def test_property_from_data_object(self, mocker): config=config, process_properties=process_properties, roots=roots, + is_multipart_body=False, ) def test_property_from_data_union(self, mocker): From ab2fc171df65767a9f56c8baf6322739bf5b1107 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Fri, 15 Dec 2023 17:20:32 -0700 Subject: [PATCH 08/13] chore: Regen, fix errors --- .../api/default/get_common_parameters.py | 2 +- .../api/default/post_common_parameters.py | 2 +- .../api/default/reserved_parameters.py | 2 +- .../api/location/get_location_header_types.py | 2 +- .../get_location_query_optionality.py | 2 +- ...st_naming_property_conflict_with_import.py | 2 +- .../get_parameter_references_path_param.py | 2 +- ...lete_common_parameters_overriding_param.py | 2 +- .../get_common_parameters_overriding_param.py | 2 +- .../get_same_name_multiple_locations_param.py | 2 +- .../parameters/multiple_path_parameters.py | 2 +- ..._responses_unions_simple_before_complex.py | 2 +- .../api/responses/text_response.py | 2 +- .../api/tag1/get_tag_with_number.py | 2 +- .../api/tests/callback_test.py | 2 +- .../api/tests/defaults_tests_defaults_post.py | 2 +- .../api/tests/description_with_backslash.py | 2 +- .../api/tests/get_basic_list_of_booleans.py | 2 +- .../api/tests/get_basic_list_of_floats.py | 2 +- .../api/tests/get_basic_list_of_integers.py | 2 +- .../api/tests/get_basic_list_of_strings.py | 2 +- .../api/tests/get_user_list.py | 2 +- .../api/tests/int_enum_tests_int_enum_post.py | 2 +- .../tests/json_body_tests_json_body_post.py | 2 +- .../no_response_tests_no_response_get.py | 2 +- .../octet_stream_tests_octet_stream_get.py | 2 +- .../octet_stream_tests_octet_stream_post.py | 2 +- .../api/tests/post_form_data.py | 2 +- .../api/tests/post_form_data_inline.py | 2 +- .../api/tests/post_tests_json_body_string.py | 2 +- .../api/tests/test_inline_objects.py | 2 +- ..._with_cookie_auth_token_with_cookie_get.py | 2 +- ...d_content_tests_unsupported_content_get.py | 2 +- .../tests/upload_file_tests_upload_post.py | 2 +- ...upload_multiple_files_tests_upload_post.py | 2 +- .../my_test_api_client/api/true_/false_.py | 2 +- .../api/body/post_body_multipart.py | 2 +- .../api/parameters/post_parameters_header.py | 2 +- .../test_body/test_post_body_multipart.py | 20 +++++----- openapi_python_client/parser/bodies.py | 3 +- openapi_python_client/parser/openapi.py | 3 ++ .../templates/endpoint_module.py.jinja | 2 +- .../test_parser/test_properties/test_init.py | 39 ------------------- 43 files changed, 54 insertions(+), 89 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py index c88a11050..02af937c4 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py @@ -17,7 +17,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/common_parameters", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py index 5c36d0827..594575d45 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py @@ -17,7 +17,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/common_parameters", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py index 5f2c01914..c836f1946 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py @@ -20,7 +20,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/naming/reserved-parameters", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py index 9c501eb48..973fd12c9 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py @@ -38,7 +38,7 @@ def _get_kwargs( if not isinstance(string_enum_header, Unset): headers["String-Enum-Header"] = str(string_enum_header) - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/location/header/types", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py index 31e28e9f1..0c7e4ecbb 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py @@ -41,7 +41,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/location/query/optionality", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py index a69b7bc58..9545f506c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py @@ -18,7 +18,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/naming/property-conflict-with-import", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py index 0b140bda7..f08f80b26 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py @@ -31,7 +31,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/parameter-references/{path_param}".format( path_param=path_param, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py index 2afb07966..262200511 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py @@ -18,7 +18,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "delete", "url": "/common_parameters_overriding/{param}".format( param=param_path, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py index 3b102258d..b9c1ccf74 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py @@ -18,7 +18,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/common_parameters_overriding/{param}".format( param=param_path, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py index 564545977..0def8acd3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py @@ -28,7 +28,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/same-name-multiple-locations/{param}".format( param=param_path, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py index d9dad0f60..6ffacceb7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py @@ -14,7 +14,7 @@ def _get_kwargs( param1: str, param3: int, ) -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/multiple-path-parameters/{param4}/something/{param2}/{param1}/{param3}".format( param4=param4, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py index ba4b45210..63f551edc 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py @@ -12,7 +12,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/responses/unions/simple_before_complex", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py index ef9b21ae5..c7d71a3f3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/responses/text", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py index d4e6738c5..eedbd5f7a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tag_with_number", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py index 49e17d19d..7fe233a90 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py @@ -16,7 +16,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/callback", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py index ab5501691..2f2ec5d36 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/defaults_tests_defaults_post.py @@ -83,7 +83,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/defaults", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py index 637acc14c..9ddd267d8 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/description-with-backslash", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py index f02bde1fb..8f90e7eb6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/basic_lists/booleans", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py index 405612bed..b76743cf6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/basic_lists/floats", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py index cf336d20b..346bcf99f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/basic_lists/integers", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py index 091912bcc..29606477e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/basic_lists/strings", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index 62024ec4d..406385e69 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -54,7 +54,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py index f7c9b497c..4ec544090 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/int_enum_tests_int_enum_post.py @@ -21,7 +21,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/int_enum", "params": params, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py index 316a3d156..d6557fb5d 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -16,7 +16,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/json_body", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py index 6afa9415a..670bb5663 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/no_response", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py index b8eb2abb1..231a7da74 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py @@ -10,7 +10,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/octet_stream", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py index 10c0e1210..a628a0899 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -15,7 +15,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/octet_stream", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py index 25c402cd1..fb08ec635 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -15,7 +15,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/post_form_data", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py index 702aa6a55..ecacddbef 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py @@ -15,7 +15,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/post_form_data_inline", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py index e93a4b359..facc40715 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py @@ -15,7 +15,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/json_body/string", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py index c796537c9..52e03e915 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py @@ -16,7 +16,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/inline_objects", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py index 552a7981b..0c68f4726 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py @@ -15,7 +15,7 @@ def _get_kwargs( cookies = {} cookies["MyToken"] = my_token - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/auth/token_with_cookie", "cookies": cookies, diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py index 99d841c57..a63b7b2a2 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py @@ -9,7 +9,7 @@ def _get_kwargs() -> Dict[str, Any]: - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/tests/unsupported_content", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index 61f82b387..71a283782 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -16,7 +16,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/upload", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py index c91bc632b..17cece060 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py @@ -15,7 +15,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/tests/upload/multiple", } diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py index 56b49358a..6d019fa42 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py @@ -17,7 +17,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "get", "url": "/naming/keywords", "params": params, diff --git a/integration-tests/integration_tests/api/body/post_body_multipart.py b/integration-tests/integration_tests/api/body/post_body_multipart.py index c3ed1dd9c..81f3481d3 100644 --- a/integration-tests/integration_tests/api/body/post_body_multipart.py +++ b/integration-tests/integration_tests/api/body/post_body_multipart.py @@ -17,7 +17,7 @@ def _get_kwargs( ) -> Dict[str, Any]: headers = {} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/body/multipart", } diff --git a/integration-tests/integration_tests/api/parameters/post_parameters_header.py b/integration-tests/integration_tests/api/parameters/post_parameters_header.py index 37e57accb..b01c56e9b 100644 --- a/integration-tests/integration_tests/api/parameters/post_parameters_header.py +++ b/integration-tests/integration_tests/api/parameters/post_parameters_header.py @@ -26,7 +26,7 @@ def _get_kwargs( headers["Integer-Header"] = str(integer_header) - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "post", "url": "/parameters/header", } diff --git a/integration-tests/tests/test_api/test_body/test_post_body_multipart.py b/integration-tests/tests/test_api/test_body/test_post_body_multipart.py index d81dddab6..54498ebc5 100644 --- a/integration-tests/tests/test_api/test_body/test_post_body_multipart.py +++ b/integration-tests/tests/test_api/test_body/test_post_body_multipart.py @@ -5,7 +5,7 @@ from integration_tests.api.body import post_body_multipart from integration_tests.client import Client -from integration_tests.models.post_body_multipart_multipart_data import PostBodyMultipartMultipartData +from integration_tests.models.post_body_multipart_body import PostBodyMultipartBody from integration_tests.models.post_body_multipart_response_200 import PostBodyMultipartResponse200 from integration_tests.types import File @@ -19,7 +19,7 @@ def test(client: Client) -> None: response = post_body_multipart.sync_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -65,7 +65,7 @@ def log_response(*_: Any, **__: Any) -> None: post_body_multipart.sync_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -90,7 +90,7 @@ def test_context_manager(client: Client) -> None: with client as client: post_body_multipart.sync_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -102,7 +102,7 @@ def test_context_manager(client: Client) -> None: ) response = post_body_multipart.sync_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -116,7 +116,7 @@ def test_context_manager(client: Client) -> None: with pytest.raises(RuntimeError): post_body_multipart.sync_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -148,7 +148,7 @@ async def test_async(client: Client) -> None: response = await post_body_multipart.asyncio_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -181,7 +181,7 @@ async def test_async_context_manager(client: Client) -> None: async with client as client: await post_body_multipart.asyncio_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -193,7 +193,7 @@ async def test_async_context_manager(client: Client) -> None: ) response = await post_body_multipart.asyncio_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), @@ -207,7 +207,7 @@ async def test_async_context_manager(client: Client) -> None: with pytest.raises(RuntimeError): await post_body_multipart.asyncio_detailed( client=client, - multipart_data=PostBodyMultipartMultipartData( + body=PostBodyMultipartBody( a_string=a_string, file=File( payload=BytesIO(payload), diff --git a/openapi_python_client/parser/bodies.py b/openapi_python_client/parser/bodies.py index ebfdeb587..ef1b44a71 100644 --- a/openapi_python_client/parser/bodies.py +++ b/openapi_python_client/parser/bodies.py @@ -4,9 +4,10 @@ import attr from openapi_python_client.parser.properties import ( + ModelProperty, Property, Schemas, - property_from_data, ModelProperty, + property_from_data, ) from .. import schema as oai diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index e6a90db03..0b1ddd1ce 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -403,6 +403,9 @@ def from_data( tag=tag, ) + if isinstance(result, ParseError): + return result, schemas, parameters + result, schemas, parameters = Endpoint.add_parameters( endpoint=result, data=data, diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 0913a1609..a4020947c 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -26,7 +26,7 @@ def _get_kwargs( {{ query_params(endpoint) | indent(4) }} - _kwargs = { + _kwargs: Dict[str, Any] = { "method": "{{ endpoint.method }}", {% if endpoint.path_parameters %} "url": "{{ endpoint.path }}".format( diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index ff5de2dab..cc106376d 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -767,45 +767,6 @@ def test_property_from_data_array(self, mocker): roots=roots, ) - def test_property_from_data_object(self, mocker): - from openapi_python_client.parser.properties import Schemas, property_from_data - - name = mocker.MagicMock() - required = mocker.MagicMock() - data = oai.Schema( - type="object", - ) - build_model_property = mocker.patch(f"{MODULE_NAME}.build_model_property") - mocker.patch("openapi_python_client.utils.remove_string_escapes", return_value=name) - schemas = Schemas() - config = MagicMock() - roots = {"root"} - process_properties = False - - response = property_from_data( - name=name, - required=required, - data=data, - schemas=schemas, - parent_name="parent", - config=config, - process_properties=process_properties, - roots=roots, - ) - - assert response == build_model_property.return_value - build_model_property.assert_called_once_with( - data=data, - name=name, - required=required, - schemas=schemas, - parent_name="parent", - config=config, - process_properties=process_properties, - roots=roots, - is_multipart_body=False, - ) - def test_property_from_data_union(self, mocker): from openapi_python_client.parser.properties import Schemas, property_from_data From 1ce3c7998a6a44db861aecada3b22033c33c7f71 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Fri, 15 Dec 2023 18:02:21 -0700 Subject: [PATCH 09/13] chore: Document & test changes --- ...s_use_correct_content_type_for_requests.md | 9 + .../renamed_body_types_and_parameters.md | 29 +++ .../support_multiple_possible_requestbody.md | 16 ++ .../my_test_api_client/api/__init__.py | 5 + .../my_test_api_client/api/bodies/__init__.py | 21 ++ .../my_test_api_client/api/bodies/__init__.py | 0 .../api/bodies/json_like.py | 103 ++++++++++ .../api/bodies/post_bodies_multiple.py | 142 ++++++++++++++ .../my_test_api_client/models/__init__.py | 8 + .../models/json_like_body.py | 58 ++++++ .../models/post_bodies_multiple_data_body.py | 58 ++++++ .../models/post_bodies_multiple_files_body.py | 71 +++++++ .../models/post_bodies_multiple_json_body.py | 58 ++++++ end_to_end_tests/openapi.json | 180 ++++++++++++++---- openapi_python_client/parser/bodies.py | 4 +- .../templates/endpoint_macros.py.jinja | 16 +- .../templates/endpoint_module.py.jinja | 10 +- 17 files changed, 740 insertions(+), 48 deletions(-) create mode 100644 .changeset/always_use_correct_content_type_for_requests.md create mode 100644 .changeset/renamed_body_types_and_parameters.md create mode 100644 .changeset/support_multiple_possible_requestbody.md create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/bodies/__init__.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/bodies/__init__.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/json_like_body.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_data_body.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_files_body.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_json_body.py diff --git a/.changeset/always_use_correct_content_type_for_requests.md b/.changeset/always_use_correct_content_type_for_requests.md new file mode 100644 index 000000000..bb4ff4f0b --- /dev/null +++ b/.changeset/always_use_correct_content_type_for_requests.md @@ -0,0 +1,9 @@ +--- +default: patch +--- + +# Always use correct content type for requests + +In previous versions, a request body that was similar to a known content type would use that content type in the request. For example `application/json` would be used for `application/vnd.api+json`. This was incorrect and could result in invalid requests being sent. + +Now, the content type defined in the OpenAPI document will always be used. diff --git a/.changeset/renamed_body_types_and_parameters.md b/.changeset/renamed_body_types_and_parameters.md new file mode 100644 index 000000000..7ab407cb7 --- /dev/null +++ b/.changeset/renamed_body_types_and_parameters.md @@ -0,0 +1,29 @@ +--- +default: major +--- + +# Renamed body types and parameters + +PR #900 addresses #822. + +Where previously there would be one body parameter per supported content type, now there is a single `body` parameter which takes a union of all the possible inputs. This correctly models the fact that only one body can be sent (and ever would be sent) in a request. + +For example, when calling a generated endpoint, code which used to look like this: + +```python +post_body_multipart.sync_detailed( + client=client, + multipart_data=PostBodyMultipartMultipartData(), +) +``` + +Will now look like this: + +```python +post_body_multipart.sync_detailed( + client=client, + body=PostBodyMultipartBody(), +) +``` + +Note that both the input parameter name _and_ the class name have changed. This should result in simpler code when there is only a single body type and now produces correct code when there are multiple body types. diff --git a/.changeset/support_multiple_possible_requestbody.md b/.changeset/support_multiple_possible_requestbody.md new file mode 100644 index 000000000..446374d16 --- /dev/null +++ b/.changeset/support_multiple_possible_requestbody.md @@ -0,0 +1,16 @@ +--- +default: minor +--- + +# Support multiple possible `requestBody` + +PR #900 addresses #822. + +It is now possible in some circumstances to generate valid code for OpenAPI documents which have multiple possible `requestBody` values. Previously, invalid code could have been generated with no warning (only one body could actually be sent). + +Only one content type per "category" is currently supported at a time. The categories are: + +- JSON, like `application/json` +- Binary data, like `application/octet-stream` +- Encoded form data, like `application/x-www-form-urlencoded` +- Files, like `multipart/form-data` diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py index 969248a72..50e3fb049 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py @@ -2,6 +2,7 @@ from typing import Type +from .bodies import BodiesEndpoints from .default import DefaultEndpoints from .location import LocationEndpoints from .naming import NamingEndpoints @@ -14,6 +15,10 @@ class MyTestApiClientApi: + @classmethod + def bodies(cls) -> Type[BodiesEndpoints]: + return BodiesEndpoints + @classmethod def tests(cls) -> Type[TestsEndpoints]: return TestsEndpoints diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/bodies/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/bodies/__init__.py new file mode 100644 index 000000000..ffbb41025 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/bodies/__init__.py @@ -0,0 +1,21 @@ +""" Contains methods for accessing the API Endpoints """ + +import types + +from . import json_like, post_bodies_multiple + + +class BodiesEndpoints: + @classmethod + def post_bodies_multiple(cls) -> types.ModuleType: + """ + Test multiple bodies + """ + return post_bodies_multiple + + @classmethod + def json_like(cls) -> types.ModuleType: + """ + A content type that works like json but isn't application/json + """ + return json_like diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py new file mode 100644 index 000000000..7231906f4 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py @@ -0,0 +1,103 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.json_like_body import JsonLikeBody +from ...types import Response + + +def _get_kwargs( + *, + body: JsonLikeBody, +) -> Dict[str, Any]: + headers = {} + + _kwargs: Dict[str, Any] = { + "method": "post", + "url": "/bodies/json-like", + } + + _body = body.to_dict() + + _kwargs["json"] = _body + headers["Content-Type"] = "application/vnd+json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == HTTPStatus.OK: + return None + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: Union[AuthenticatedClient, Client], + body: JsonLikeBody, +) -> Response[Any]: + """A content type that works like json but isn't application/json + + Args: + body (JsonLikeBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + *, + client: Union[AuthenticatedClient, Client], + body: JsonLikeBody, +) -> Response[Any]: + """A content type that works like json but isn't application/json + + Args: + body (JsonLikeBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py new file mode 100644 index 000000000..b6297598b --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py @@ -0,0 +1,142 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.post_bodies_multiple_data_body import PostBodiesMultipleDataBody +from ...models.post_bodies_multiple_files_body import PostBodiesMultipleFilesBody +from ...models.post_bodies_multiple_json_body import PostBodiesMultipleJsonBody +from ...types import File, Response + + +def _get_kwargs( + *, + body: Union[ + PostBodiesMultipleJsonBody, + File, + PostBodiesMultipleDataBody, + PostBodiesMultipleFilesBody, + ], +) -> Dict[str, Any]: + headers = {} + + _kwargs: Dict[str, Any] = { + "method": "post", + "url": "/bodies/multiple", + } + + if isinstance(body, PostBodiesMultipleJsonBody): + _json_body = body.to_dict() + + _kwargs["json"] = _json_body + headers["Content-Type"] = "application/json" + if isinstance(body, File): + _content_body = body.payload + + _kwargs["content"] = _content_body + headers["Content-Type"] = "application/octet-stream" + if isinstance(body, PostBodiesMultipleDataBody): + _data_body = body.to_dict() + + _kwargs["data"] = _data_body + headers["Content-Type"] = "application/x-www-form-urlencoded" + if isinstance(body, PostBodiesMultipleFilesBody): + _files_body = body.to_multipart() + + _kwargs["files"] = _files_body + headers["Content-Type"] = "multipart/form-data" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == HTTPStatus.OK: + return None + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: Union[AuthenticatedClient, Client], + body: Union[ + PostBodiesMultipleJsonBody, + File, + PostBodiesMultipleDataBody, + PostBodiesMultipleFilesBody, + ], +) -> Response[Any]: + """Test multiple bodies + + Args: + body (PostBodiesMultipleJsonBody): + body (File): + body (PostBodiesMultipleDataBody): + body (PostBodiesMultipleFilesBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +async def asyncio_detailed( + *, + client: Union[AuthenticatedClient, Client], + body: Union[ + PostBodiesMultipleJsonBody, + File, + PostBodiesMultipleDataBody, + PostBodiesMultipleFilesBody, + ], +) -> Response[Any]: + """Test multiple bodies + + Args: + body (PostBodiesMultipleJsonBody): + body (File): + body (PostBodiesMultipleDataBody): + body (PostBodiesMultipleFilesBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Any] + """ + + kwargs = _get_kwargs( + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index f0b94bfac..5166f321b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -37,6 +37,7 @@ from .get_location_header_types_string_enum_header import GetLocationHeaderTypesStringEnumHeader from .http_validation_error import HTTPValidationError from .import_ import Import +from .json_like_body import JsonLikeBody from .model_from_all_of import ModelFromAllOf from .model_name import ModelName from .model_reference_with_periods import ModelReferenceWithPeriods @@ -63,6 +64,9 @@ from .model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0 from .model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1 from .none import None_ +from .post_bodies_multiple_data_body import PostBodiesMultipleDataBody +from .post_bodies_multiple_files_body import PostBodiesMultipleFilesBody +from .post_bodies_multiple_json_body import PostBodiesMultipleJsonBody from .post_form_data_inline_body import PostFormDataInlineBody from .post_naming_property_conflict_with_import_body import PostNamingPropertyConflictWithImportBody from .post_naming_property_conflict_with_import_response_200 import PostNamingPropertyConflictWithImportResponse200 @@ -106,6 +110,7 @@ "GetLocationHeaderTypesStringEnumHeader", "HTTPValidationError", "Import", + "JsonLikeBody", "ModelFromAllOf", "ModelName", "ModelReferenceWithPeriods", @@ -130,6 +135,9 @@ "ModelWithUnionPropertyInlinedFruitType0", "ModelWithUnionPropertyInlinedFruitType1", "None_", + "PostBodiesMultipleDataBody", + "PostBodiesMultipleFilesBody", + "PostBodiesMultipleJsonBody", "PostFormDataInlineBody", "PostNamingPropertyConflictWithImportBody", "PostNamingPropertyConflictWithImportResponse200", diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/json_like_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/json_like_body.py new file mode 100644 index 000000000..623dcd848 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/json_like_body.py @@ -0,0 +1,58 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="JsonLikeBody") + + +@_attrs_define +class JsonLikeBody: + """ + Attributes: + a (Union[Unset, str]): + """ + + a: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + a = self.a + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if a is not UNSET: + field_dict["a"] = a + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a = d.pop("a", UNSET) + + json_like_body = cls( + a=a, + ) + + json_like_body.additional_properties = d + return json_like_body + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_data_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_data_body.py new file mode 100644 index 000000000..adc78cd6f --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_data_body.py @@ -0,0 +1,58 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="PostBodiesMultipleDataBody") + + +@_attrs_define +class PostBodiesMultipleDataBody: + """ + Attributes: + a (Union[Unset, str]): + """ + + a: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + a = self.a + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if a is not UNSET: + field_dict["a"] = a + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a = d.pop("a", UNSET) + + post_bodies_multiple_data_body = cls( + a=a, + ) + + post_bodies_multiple_data_body.additional_properties = d + return post_bodies_multiple_data_body + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_files_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_files_body.py new file mode 100644 index 000000000..1c61d3385 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_files_body.py @@ -0,0 +1,71 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="PostBodiesMultipleFilesBody") + + +@_attrs_define +class PostBodiesMultipleFilesBody: + """ + Attributes: + a (Union[Unset, str]): + """ + + a: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + a = self.a + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if a is not UNSET: + field_dict["a"] = a + + return field_dict + + def to_multipart(self) -> Dict[str, Any]: + a = self.a if isinstance(self.a, Unset) else (None, str(self.a).encode(), "text/plain") + + field_dict: Dict[str, Any] = {} + field_dict.update( + {key: (None, str(value).encode(), "text/plain") for key, value in self.additional_properties.items()} + ) + field_dict.update({}) + if a is not UNSET: + field_dict["a"] = a + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a = d.pop("a", UNSET) + + post_bodies_multiple_files_body = cls( + a=a, + ) + + post_bodies_multiple_files_body.additional_properties = d + return post_bodies_multiple_files_body + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_json_body.py b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_json_body.py new file mode 100644 index 000000000..88e5ec6f9 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/post_bodies_multiple_json_body.py @@ -0,0 +1,58 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="PostBodiesMultipleJsonBody") + + +@_attrs_define +class PostBodiesMultipleJsonBody: + """ + Attributes: + a (Union[Unset, str]): + """ + + a: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + a = self.a + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if a is not UNSET: + field_dict["a"] = a + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + a = d.pop("a", UNSET) + + post_bodies_multiple_json_body = cls( + a=a, + ) + + post_bodies_multiple_json_body.additional_properties = d + return post_bodies_multiple_json_body + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index fb98839b0..048e72cd8 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -6,6 +6,85 @@ "version": "0.1.0" }, "paths": { + "/bodies/multiple": { + "post": { + "description": "Test multiple bodies", + "tags": [ + "bodies" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + }, + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/bodies/json-like": { + "post": { + "tags": ["bodies"], + "description": "A content type that works like json but isn't application/json", + "operationId": "json-like", + "requestBody": { + "content": { + "application/vnd+json": { + "schema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/tests/": { "get": { "tags": [ @@ -796,7 +875,9 @@ }, "/responses/unions/simple_before_complex": { "post": { - "tags": ["responses"], + "tags": [ + "responses" + ], "description": "Regression test for #603", "responses": { "200": { @@ -805,12 +886,18 @@ "application/json": { "schema": { "type": "object", - "required": ["a"], + "required": [ + "a" + ], "properties": { "a": { "oneOf": [ - {"type": "string"}, - {"type": "object"} + { + "type": "string" + }, + { + "type": "object" + } ] } } @@ -938,20 +1025,20 @@ }, "parameters": [ { - "name": "param", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "param", - "in": "path", - "required": true, - "schema": { - "type": "string" - } + "name": "param", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "param", + "in": "path", + "required": true, + "schema": { + "type": "string" } + } ] }, "/same-name-multiple-locations/{param}": { @@ -1000,7 +1087,9 @@ }, "/tag_with_number": { "get": { - "tags": ["1"], + "tags": [ + "1" + ], "responses": { "200": { "description": "Success" @@ -1243,7 +1332,9 @@ "/naming/property-conflict-with-import": { "description": "Ensure that property names don't conflict with imports", "post": { - "tags": ["naming"], + "tags": [ + "naming" + ], "requestBody": { "content": { "application/json": { @@ -1300,9 +1391,15 @@ { "$ref": "#/components/parameters/integer-param" }, - {"$ref": "#/components/parameters/header-param"}, - {"$ref": "#/components/parameters/cookie-param"}, - {"$ref": "#/components/parameters/path-param"} + { + "$ref": "#/components/parameters/header-param" + }, + { + "$ref": "#/components/parameters/cookie-param" + }, + { + "$ref": "#/components/parameters/path-param" + } ], "responses": { "200": { @@ -1633,7 +1730,11 @@ }, "Body_upload_file_tests_upload_post": { "title": "Body_upload_file_tests_upload_post", - "required": ["some_file", "some_object", "some_nullable_object"], + "required": [ + "some_file", + "some_object", + "some_nullable_object" + ], "type": "object", "properties": { "some_file": { @@ -1675,7 +1776,10 @@ "some_object": { "title": "Some Object", "type": "object", - "required": ["num", "text"], + "required": [ + "num", + "text" + ], "properties": { "num": { "type": "number" @@ -1688,7 +1792,9 @@ "some_optional_object": { "title": "Some Optional Object", "type": "object", - "required": ["foo"], + "required": [ + "foo" + ], "properties": { "foo": { "type": "string" @@ -1911,7 +2017,10 @@ }, "type_enum": { "type": "integer", - "enum": [0, 1] + "enum": [ + 0, + 1 + ] } } }, @@ -1924,11 +2033,15 @@ }, "type": { "type": "string", - "enum": ["submodel"] + "enum": [ + "submodel" + ] }, "type_enum": { "type": "integer", - "enum": [0] + "enum": [ + 0 + ] } } }, @@ -1943,7 +2056,10 @@ }, "type_enum": { "type": "integer", - "enum": [0, 1] + "enum": [ + 0, + 1 + ] } } }, @@ -2086,7 +2202,7 @@ } } }, - "ModelWithDateTimeProperty" : { + "ModelWithDateTimeProperty": { "type": "object", "properties": { "datetime": { @@ -2264,10 +2380,10 @@ "type": "string", "format": "byte" }, - "import": { + "import": { "type": "object" }, - "None": { + "None": { "type": "object" }, "model.reference.with.Periods": { diff --git a/openapi_python_client/parser/bodies.py b/openapi_python_client/parser/bodies.py index ef1b44a71..d014dad36 100644 --- a/openapi_python_client/parser/bodies.py +++ b/openapi_python_client/parser/bodies.py @@ -84,11 +84,11 @@ def body_from_data( ) continue prop, schemas = property_from_data( - name=f"{body_type}_body" if prefix_type_names else "body", + name="body", required=True, data=media_type_schema, schemas=schemas, - parent_name=endpoint_name, + parent_name=f"{endpoint_name}_{body_type}" if prefix_type_names else endpoint_name, config=config, ) if isinstance(prop, ParseError): diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index a4826d066..ab22a2d98 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -57,21 +57,20 @@ params = {k: v for k, v in params.items() if v is not UNSET and v is not None} {% endif %} {% endmacro %} -{% macro body_to_kwarg(body) %} +{% macro body_to_kwarg(body, destination) %} {% if body.body_type == "data" %} -_body = body.to_dict() +{{ destination }} = body.to_dict() {% elif body.body_type == "files"%} -{{ multipart_body(body) }} +{{ multipart_body(body, destination) }} {% elif body.body_type == "json" %} -{{ json_body(body) }} +{{ json_body(body, destination) }} {% elif body.body_type == "content" %} -_body = body.payload +{{ destination }} = body.payload {% endif %} {% endmacro %} -{% macro json_body(body) %} +{% macro json_body(body, destination) %} {% set property = body.prop %} -{% set destination = "_body" %} {% import "property_templates/" + property.template as prop_template %} {% if prop_template.transform %} {{ prop_template.transform(property, property.python_name, destination) }} @@ -80,9 +79,8 @@ _body = body.payload {% endif %} {% endmacro %} -{% macro multipart_body(body) %} +{% macro multipart_body(body, destination) %} {% set property = body.prop %} -{% set destination = "_body" %} {% import "property_templates/" + property.template as prop_template %} {% if prop_template.transform_multipart %} {{ prop_template.transform_multipart(property, property.python_name, destination) }} diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index a4020947c..c1bcb6af1 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -46,16 +46,16 @@ def _get_kwargs( } {% if endpoint.bodies | length > 1 %} - # {{ endpoint.bodies | length }} {% for body in endpoint.bodies %} - if isinstance({{ body.prop.python_name }}, {{body.prop.get_type_string() }}): - {{ body_to_kwarg(body) | indent(8) }} - _kwargs["{{ body.body_type.value }}"] = _body + if isinstance(body, {{body.prop.get_type_string() }}): + {% set destination = "_" + body.body_type + "_body" %} + {{ body_to_kwarg(body, destination) | indent(8) }} + _kwargs["{{ body.body_type.value }}"] = {{ destination }} headers["Content-Type"] = "{{ body.content_type }}" {% endfor %} {% elif endpoint.bodies | length == 1 %} {% set body = endpoint.bodies[0] %} - {{ body_to_kwarg(body) | indent(4) }} + {{ body_to_kwarg(body, "_body") | indent(4) }} _kwargs["{{ body.body_type.value }}"] = _body headers["Content-Type"] = "{{ body.content_type }}" {% endif %} From 7903fcb700483024f69e28bfc4db2359475006ef Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Fri, 15 Dec 2023 18:18:38 -0700 Subject: [PATCH 10/13] fix: BodyType str enum for Py 3.11+ --- openapi_python_client/parser/bodies.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openapi_python_client/parser/bodies.py b/openapi_python_client/parser/bodies.py index d014dad36..9ab42cb4f 100644 --- a/openapi_python_client/parser/bodies.py +++ b/openapi_python_client/parser/bodies.py @@ -1,4 +1,4 @@ -from enum import Enum +import sys from typing import List, Tuple, Union import attr @@ -15,12 +15,22 @@ from ..utils import get_content_type from .errors import ErrorLevel, ParseError +if sys.version_info >= (3, 11): + from enum import StrEnum -class BodyType(str, Enum): - JSON = "json" - DATA = "data" - FILES = "files" - CONTENT = "content" + class BodyType(StrEnum): + JSON = "json" + DATA = "data" + FILES = "files" + CONTENT = "content" +else: + from enum import Enum + + class BodyType(str, Enum): + JSON = "json" + DATA = "data" + FILES = "files" + CONTENT = "content" @attr.define From d40491eb903be1267e5cec320f993875140e6d23 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Sun, 17 Dec 2023 11:33:17 -0700 Subject: [PATCH 11/13] fix: Don't manually set multipart/form-data content-type --- .../golden-record/my_test_api_client/api/bodies/json_like.py | 2 +- .../my_test_api_client/api/bodies/post_bodies_multiple.py | 2 +- .../api/location/get_location_header_types.py | 2 +- .../api/naming/post_naming_property_conflict_with_import.py | 2 +- .../get_parameter_references_path_param.py | 2 +- .../api/parameters/get_same_name_multiple_locations_param.py | 2 +- .../my_test_api_client/api/tests/callback_test.py | 2 +- .../api/tests/json_body_tests_json_body_post.py | 2 +- .../api/tests/octet_stream_tests_octet_stream_post.py | 2 +- .../my_test_api_client/api/tests/post_form_data.py | 2 +- .../my_test_api_client/api/tests/post_form_data_inline.py | 2 +- .../api/tests/post_tests_json_body_string.py | 2 +- .../my_test_api_client/api/tests/test_inline_objects.py | 2 +- .../api/tests/upload_file_tests_upload_post.py | 3 +-- .../api/tests/upload_multiple_files_tests_upload_post.py | 3 +-- .../integration_tests/api/body/post_body_multipart.py | 3 +-- .../integration_tests/api/parameters/post_parameters_header.py | 2 +- openapi_python_client/templates/endpoint_macros.py.jinja | 2 +- openapi_python_client/templates/endpoint_module.py.jinja | 2 ++ 19 files changed, 20 insertions(+), 21 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py index 7231906f4..8eb5c516a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: JsonLikeBody, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py index b6297598b..f71b1ef25 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py @@ -20,7 +20,7 @@ def _get_kwargs( PostBodiesMultipleFilesBody, ], ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py index 973fd12c9..904d26c72 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py @@ -19,7 +19,7 @@ def _get_kwargs( int_enum_header: Union[Unset, GetLocationHeaderTypesIntEnumHeader] = UNSET, string_enum_header: Union[Unset, GetLocationHeaderTypesStringEnumHeader] = UNSET, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} if not isinstance(boolean_header, Unset): headers["Boolean-Header"] = "true" if boolean_header else "false" diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py index 9545f506c..693eab608 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py @@ -16,7 +16,7 @@ def _get_kwargs( *, body: PostNamingPropertyConflictWithImportBody, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py index f08f80b26..b0ce20f3f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py @@ -16,7 +16,7 @@ def _get_kwargs( header_param: Union[Unset, str] = UNSET, cookie_param: Union[Unset, str] = UNSET, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} if not isinstance(header_param, Unset): headers["header param"] = header_param diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py index 0def8acd3..23842c87a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py @@ -15,7 +15,7 @@ def _get_kwargs( param_header: Union[Unset, str] = UNSET, param_cookie: Union[Unset, str] = UNSET, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} if not isinstance(param_header, Unset): headers["param"] = param_header diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py index 7fe233a90..925349cbd 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py @@ -14,7 +14,7 @@ def _get_kwargs( *, body: AModel, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py index d6557fb5d..c43a0ca7e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -14,7 +14,7 @@ def _get_kwargs( *, body: AModel, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py index a628a0899..cb72ba657 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: File, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py index fb08ec635..93954ace9 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: AFormData, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py index ecacddbef..b676061a3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: PostFormDataInlineBody, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py index facc40715..909c77e78 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: str, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py index 52e03e915..2a93ef5ad 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py @@ -14,7 +14,7 @@ def _get_kwargs( *, body: TestInlineObjectsBody, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index 71a283782..e36d4d92e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -14,7 +14,7 @@ def _get_kwargs( *, body: BodyUploadFileTestsUploadPost, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", @@ -24,7 +24,6 @@ def _get_kwargs( _body = body.to_multipart() _kwargs["files"] = _body - headers["Content-Type"] = "multipart/form-data" _kwargs["headers"] = headers return _kwargs diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py index 17cece060..6cfcfaa57 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_multiple_files_tests_upload_post.py @@ -13,7 +13,7 @@ def _get_kwargs( *, body: List[File], ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", @@ -27,7 +27,6 @@ def _get_kwargs( _body.append(body_item) _kwargs["files"] = _body - headers["Content-Type"] = "multipart/form-data" _kwargs["headers"] = headers return _kwargs diff --git a/integration-tests/integration_tests/api/body/post_body_multipart.py b/integration-tests/integration_tests/api/body/post_body_multipart.py index 81f3481d3..c64b4c4c2 100644 --- a/integration-tests/integration_tests/api/body/post_body_multipart.py +++ b/integration-tests/integration_tests/api/body/post_body_multipart.py @@ -15,7 +15,7 @@ def _get_kwargs( *, body: PostBodyMultipartBody, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} _kwargs: Dict[str, Any] = { "method": "post", @@ -25,7 +25,6 @@ def _get_kwargs( _body = body.to_multipart() _kwargs["files"] = _body - headers["Content-Type"] = "multipart/form-data" _kwargs["headers"] = headers return _kwargs diff --git a/integration-tests/integration_tests/api/parameters/post_parameters_header.py b/integration-tests/integration_tests/api/parameters/post_parameters_header.py index b01c56e9b..784eaf37f 100644 --- a/integration-tests/integration_tests/api/parameters/post_parameters_header.py +++ b/integration-tests/integration_tests/api/parameters/post_parameters_header.py @@ -17,7 +17,7 @@ def _get_kwargs( number_header: float, integer_header: int, ) -> Dict[str, Any]: - headers = {} + headers: Dict[str, Any] = {} headers["Boolean-Header"] = "true" if boolean_header else "false" headers["String-Header"] = string_header diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index ab22a2d98..7c1809929 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -3,7 +3,7 @@ {% macro header_params(endpoint) %} {% if endpoint.header_parameters or endpoint.bodies | length > 0 %} -headers = {} +headers: Dict[str, Any] = {} {% if endpoint.header_parameters %} {% for parameter in endpoint.header_parameters.values() %} {% import "property_templates/" + parameter.template as param_template %} diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index c1bcb6af1..5fb318535 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -57,7 +57,9 @@ def _get_kwargs( {% set body = endpoint.bodies[0] %} {{ body_to_kwarg(body, "_body") | indent(4) }} _kwargs["{{ body.body_type.value }}"] = _body + {% if body.content_type != "multipart/form-data" %} {# Need httpx to set the boundary automatically #} headers["Content-Type"] = "{{ body.content_type }}" + {% endif %} {% endif %} {% if endpoint.header_parameters or endpoint.bodies | length > 0 %} From 15b2c5df4971a38b2171ddb7ef595c0836ee3cfe Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Sun, 17 Dec 2023 11:36:39 -0700 Subject: [PATCH 12/13] fix: jinja comment breaking whitespace --- openapi_python_client/templates/endpoint_module.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 5fb318535..8bd4430d7 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -57,7 +57,7 @@ def _get_kwargs( {% set body = endpoint.bodies[0] %} {{ body_to_kwarg(body, "_body") | indent(4) }} _kwargs["{{ body.body_type.value }}"] = _body - {% if body.content_type != "multipart/form-data" %} {# Need httpx to set the boundary automatically #} + {% if body.content_type != "multipart/form-data" %}{# Need httpx to set the boundary automatically #} headers["Content-Type"] = "{{ body.content_type }}" {% endif %} {% endif %} From f17b699d4dbbcf44a18d604bea07dddfd825fc30 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Sun, 31 Dec 2023 16:13:04 -0700 Subject: [PATCH 13/13] Add tests --- openapi_python_client/parser/openapi.py | 7 ++-- tests/test_parser/test_bodies.py | 39 ++++++++++++++++++++++ tests/test_parser/test_openapi.py | 44 +++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 tests/test_parser/test_bodies.py diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index cd5ce29d4..37d50bca5 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -388,7 +388,7 @@ def from_data( else: name = data.operationId - result: Union[Endpoint, ParseError] = Endpoint( + endpoint = Endpoint( path=path, method=method, summary=utils.remove_string_escapes(data.summary) if data.summary else "", @@ -398,11 +398,8 @@ def from_data( tag=tag, ) - if isinstance(result, ParseError): - return result, schemas, parameters - result, schemas, parameters = Endpoint.add_parameters( - endpoint=result, + endpoint=endpoint, data=data, schemas=schemas, parameters=parameters, diff --git a/tests/test_parser/test_bodies.py b/tests/test_parser/test_bodies.py new file mode 100644 index 000000000..6641905f8 --- /dev/null +++ b/tests/test_parser/test_bodies.py @@ -0,0 +1,39 @@ +from openapi_python_client import Config +from openapi_python_client import schema as oai +from openapi_python_client.parser.bodies import body_from_data +from openapi_python_client.parser.errors import ParseError +from openapi_python_client.parser.properties import Schemas + + +def test_errors(): + operation = oai.Operation( + requestBody=oai.RequestBody( + content={ + "invalid content type": oai.MediaType( + media_type_schema=oai.Schema( + type=oai.DataType.STRING, + ) + ), + "application/json": oai.MediaType( + media_type_schema=None # Missing media type schema is an error + ), + "text/html": oai.MediaType( # content type not supported by the generator + media_type_schema=oai.Schema( + type=oai.DataType.STRING, + ) + ), + "application/sushi+json": oai.MediaType( + media_type_schema=oai.Schema( + type=oai.DataType.INTEGER, + default="make this an invalid property", + ) + ), + } + ), + responses={}, + ) + + errs, _ = body_from_data(data=operation, schemas=Schemas(), config=Config(), endpoint_name="this will not succeed") + + assert len(errs) == len(operation.request_body.content) + assert all(isinstance(err, ParseError) for err in errs) diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index dd39c15c0..a92cc7845 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -8,6 +8,7 @@ from openapi_python_client.parser.errors import ParseError from openapi_python_client.parser.openapi import Endpoint, EndpointCollection from openapi_python_client.parser.properties import IntProperty, Parameters, Schemas +from openapi_python_client.schema import DataType MODULE_NAME = "openapi_python_client.parser.openapi" @@ -874,6 +875,49 @@ def test_from_data_no_security(self, mocker): config=config, ) + def test_from_data_some_bad_bodies(self): + endpoint, _, _ = Endpoint.from_data( + data=oai.Operation( + responses={}, + requestBody=oai.RequestBody( + content={ + "application/json": oai.MediaType(media_type_schema=oai.Schema(type=DataType.STRING)), + "not a real media type": oai.MediaType(media_type_schema=oai.Schema(type=DataType.STRING)), + }, + ), + ), + schemas=Schemas(), + config=Config(), + parameters=Parameters(), + tag="tag", + path="/", + method="get", + ) + + assert isinstance(endpoint, Endpoint) + assert len(endpoint.bodies) == 1 + assert len(endpoint.errors) == 1 + + def test_from_data_all_bodies_bad(self): + endpoint, _, _ = Endpoint.from_data( + data=oai.Operation( + responses={}, + requestBody=oai.RequestBody( + content={ + "not a real media type": oai.MediaType(media_type_schema=oai.Schema(type=DataType.STRING)), + }, + ), + ), + schemas=Schemas(), + config=Config(), + parameters=Parameters(), + tag="tag", + path="/", + method="get", + ) + + assert isinstance(endpoint, ParseError) + @pytest.mark.parametrize( "response_types, expected", (([], "Any"), (["Something"], "Something"), (["First", "Second", "Second"], "Union[First, Second]")),