From e421b211e9be4365d2f0aaefcd0d3e0825b6d830 Mon Sep 17 00:00:00 2001 From: Karl Gutwin Date: Wed, 6 Dec 2023 11:43:54 -0500 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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 8b88539fdfb16066dfb1ba7df2df34441245a56a Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 6 Dec 2023 17:36:04 -0700 Subject: [PATCH 07/10] chore: fix coverage --- 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 279597541..229617559 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -117,7 +117,7 @@ class RequestBodyParser(Protocol): def __call__( self, *, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[Property, PropertyError, None], Schemas]: - ... + ... # pragma: no cover @dataclass From 7a66c38b743a0bc1bd609f21b572a1e7cbd5ec10 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 6 Dec 2023 17:43:02 -0700 Subject: [PATCH 08/10] docs: changeset --- .../support_applicationoctet_stream_request_bodies.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/support_applicationoctet_stream_request_bodies.md diff --git a/.changeset/support_applicationoctet_stream_request_bodies.md b/.changeset/support_applicationoctet_stream_request_bodies.md new file mode 100644 index 000000000..8fd9df8f7 --- /dev/null +++ b/.changeset/support_applicationoctet_stream_request_bodies.md @@ -0,0 +1,11 @@ +--- +default: minor +--- + +# Support `application/octet-stream` request bodies + +Endpoints that accept `application/octet-stream` request bodies are now supported using the same `File` type as octet-stream responses. + +Thanks to @kgutwin for the implementation and @rtaycher for the discussion! + +PR #899 closes #588 From c7d11bebb1d3c58547dae6e3c50a2cc69f1332d7 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 6 Dec 2023 17:45:31 -0700 Subject: [PATCH 09/10] redo merge patch --- openapi_python_client/parser/openapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 229617559..07e4f1be5 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -234,9 +234,9 @@ def parse_request_binary_body( """Return binary_body""" binary_body = None for content_type, schema in body.content.items(): - content_type = get_content_type(content_type) # noqa: PLW2901 + parsed_content_type = get_content_type(content_type) # noqa: PLW2901 - if content_type == "application/octet-stream": + if parsed_content_type == "application/octet-stream": binary_body = schema break From ecd8395dcde629a68fc5802b4e28cd80d13d2d65 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 6 Dec 2023 17:47:17 -0700 Subject: [PATCH 10/10] remove extra comment --- 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 07e4f1be5..0ab5cd26c 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -234,7 +234,7 @@ def parse_request_binary_body( """Return binary_body""" binary_body = None for content_type, schema in body.content.items(): - parsed_content_type = get_content_type(content_type) # noqa: PLW2901 + parsed_content_type = get_content_type(content_type) if parsed_content_type == "application/octet-stream": binary_body = schema