From 40e1552c6de4a4ce19a114048e268d7aa14b915a Mon Sep 17 00:00:00 2001 From: Constantinos Symeonides Date: Fri, 19 Mar 2021 20:39:51 +0000 Subject: [PATCH 1/2] chore: Update Changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb6c8f5d..d07f4e4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.8.1 - Unreleased + +### Additions + +- Multipart requests now support non-file fields + ## 0.8.0 - 2021-02-19 ### Breaking Changes From 6ab135f63d0b4fbb7885ce0749d0645251c236e5 Mon Sep 17 00:00:00 2001 From: Constantinos Symeonides Date: Fri, 19 Mar 2021 20:41:25 +0000 Subject: [PATCH 2/2] feat: Support non-file fields in Multipart requests --- .../api/tests/defaults_tests_defaults_post.py | 12 ++++++- .../api/tests/get_basic_list_of_booleans.py | 12 ++++++- .../api/tests/get_basic_list_of_floats.py | 12 ++++++- .../api/tests/get_basic_list_of_integers.py | 12 ++++++- .../api/tests/get_basic_list_of_strings.py | 12 ++++++- .../api/tests/get_user_list.py | 12 ++++++- .../api/tests/int_enum_tests_int_enum_post.py | 12 ++++++- .../tests/json_body_tests_json_body_post.py | 12 ++++++- .../no_response_tests_no_response_get.py | 12 ++++++- .../octet_stream_tests_octet_stream_get.py | 12 ++++++- ...tional_value_tests_optional_query_param.py | 12 ++++++- .../api/tests/test_inline_objects.py | 12 ++++++- ..._with_cookie_auth_token_with_cookie_get.py | 12 ++++++- ...d_content_tests_unsupported_content_get.py | 12 ++++++- .../tests/upload_file_tests_upload_post.py | 21 ++++++++++-- .../body_upload_file_tests_upload_post.py | 12 +++++-- end_to_end_tests/openapi.json | 5 +++ .../templates/endpoint_module.py.jinja | 32 +++++++++++++++---- tests/test_templates/endpoint_module.py | 24 ++++++++++++-- 19 files changed, 234 insertions(+), 28 deletions(-) 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 b6aaaea6e..ec2a752bd 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 @@ -130,7 +130,10 @@ def _get_kwargs( params.update(json_required_model_prop) params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -138,6 +141,13 @@ def _get_kwargs( "params": params, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: 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 d025860ec..25f368cf6 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[List[bool]]: if response.status_code == 200: 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 e2199a3b9..6751bf143 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[List[float]]: if response.status_code == 200: 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 dc3c6af6a..9721bf282 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[List[int]]: if response.status_code == 200: 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 150ab9a22..548cf7057 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[List[str]]: if response.status_code == 200: 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 ec2216810..ad78d6c69 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 @@ -38,7 +38,10 @@ def _get_kwargs( } params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -46,6 +49,13 @@ def _get_kwargs( "params": params, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]: if response.status_code == 200: 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 7d14632c4..1ad0df805 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 @@ -25,7 +25,10 @@ def _get_kwargs( } params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -33,6 +36,13 @@ def _get_kwargs( "params": params, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: 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 074ab9d89..efb8d79b0 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 @@ -20,7 +20,10 @@ def _get_kwargs( json_json_body = json_body.to_dict() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -28,6 +31,13 @@ def _get_kwargs( "json": json_json_body, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: 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 f1e02671d..5f28c379a 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _build_response(*, response: httpx.Response) -> Response[None]: return 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 b1e3a0cf7..0711761ad 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 @@ -16,13 +16,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[File]: if response.status_code == 200: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py index 64431ba2f..d07ccae44 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/optional_value_tests_optional_query_param.py @@ -26,7 +26,10 @@ def _get_kwargs( } params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -34,6 +37,13 @@ def _get_kwargs( "params": params, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: 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 8680e8ef8..b232eb594 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 @@ -20,7 +20,10 @@ def _get_kwargs( json_json_body = json_body.to_dict() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, @@ -28,6 +31,13 @@ def _get_kwargs( "json": json_json_body, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[TestInlineObjectsResponse_200]: if response.status_code == 200: 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 c497a0b3a..a3c3f7e61 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 @@ -18,13 +18,23 @@ def _get_kwargs( cookies["MyToken"] = my_token - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, None]]: if response.status_code == 200: 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 a1d5d5a0d..9077ecdeb 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 @@ -15,13 +15,23 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _build_response(*, response: httpx.Response) -> Response[None]: return Response( 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 2ef1278bc..bfc0c1863 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 @@ -22,14 +22,31 @@ def _get_kwargs( if keep_alive is not UNSET: headers["keep-alive"] = keep_alive - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + non_files: Dict[str, Any] = {} + for key, value in multipart_data.to_dict().items(): + if type(value) is tuple: + files[key] = value + else: + non_files[key] = value + data.update(non_files) + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), - "files": multipart_data.to_dict(), } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]: if response.status_code == 200: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py index 97db03356..a250dbb37 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/body_upload_file_tests_upload_post.py @@ -1,9 +1,9 @@ from io import BytesIO -from typing import Any, Dict, Type, TypeVar +from typing import Any, Dict, Type, TypeVar, Union import attr -from ..types import File +from ..types import UNSET, File, Unset T = TypeVar("T", bound="BodyUploadFileTestsUploadPost") @@ -13,16 +13,21 @@ class BodyUploadFileTestsUploadPost: """ """ some_file: File + some_string: Union[Unset, str] = "some_default_string" def to_dict(self) -> Dict[str, Any]: some_file = self.some_file.to_tuple() + some_string = self.some_string + field_dict: Dict[str, Any] = {} field_dict.update( { "some_file": some_file, } ) + if some_string is not UNSET: + field_dict["some_string"] = some_string return field_dict @@ -31,8 +36,11 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() some_file = File(payload=BytesIO(d.pop("some_file"))) + some_string = d.pop("some_string", UNSET) + body_upload_file_tests_upload_post = cls( some_file=some_file, + some_string=some_string, ) return body_upload_file_tests_upload_post diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 3cc49b58d..5bd42221a 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -923,6 +923,11 @@ "title": "Some File", "type": "string", "format": "binary" + }, + "some_string": { + "title": "Some String", + "type": "string", + "default": "some_default_string" } }, "additionalProperties": false diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index bd738073e..258f38a03 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -36,17 +36,28 @@ def _get_kwargs( {{ json_body(endpoint) | indent(4) }} - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + {% if endpoint.form_body_reference %} + data.update(asdict(form_data)) + {% endif %} + + {% if endpoint.multipart_body_reference %} + non_files: Dict[str, Any] = {} + for key, value in multipart_data.to_dict().items(): + if type(value) is tuple: + files[key] = value + else: + non_files[key] = value + data.update(non_files) + {% endif %} + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), - {% if endpoint.form_body_reference %} - "data": asdict(form_data), - {% endif %} - {% if endpoint.multipart_body_reference %} - "files": multipart_data.to_dict(), - {% endif %} {% if endpoint.json_body %} "json": {{ "json_" + endpoint.json_body.python_name }}, {% endif %} @@ -55,6 +66,13 @@ def _get_kwargs( {% endif %} } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + {% if parsed_responses %} def _parse_response(*, response: httpx.Response) -> Optional[{{ return_string }}]: diff --git a/tests/test_templates/endpoint_module.py b/tests/test_templates/endpoint_module.py index d0b6a9b03..c0c736a68 100644 --- a/tests/test_templates/endpoint_module.py +++ b/tests/test_templates/endpoint_module.py @@ -22,16 +22,34 @@ def _get_kwargs( headers: Dict[str, Any] = client.get_headers() cookies: Dict[str, Any] = client.get_cookies() - return { + data: Dict[str, Any] = {} + files: Dict[str, Any] = {} + + data.update(asdict(form_data)) + + non_files: Dict[str, Any] = {} + for key, value in multipart_data.to_dict().items(): + if type(value) is tuple: + files[key] = value + else: + non_files[key] = value + data.update(non_files) + + kwargs = { "url": url, "headers": headers, "cookies": cookies, "timeout": client.get_timeout(), - "data": asdict(form_data), - "files": multipart_data.to_dict(), "json": json_json_body, } + if data: + kwargs["data"] = data + if files: + kwargs["files"] = files + + return kwargs + def _parse_response(*, response: httpx.Response) -> Optional[Union[str, int]]: if response.status_code == 200: