From b3290dc7a94e720df44fbe18a39fc75271179c3e Mon Sep 17 00:00:00 2001 From: Ruwan Date: Sun, 11 Jun 2023 08:19:14 +0200 Subject: [PATCH 01/11] Allow request body for GET and DELETE --- connexion/decorators/parameter.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 8aab04f5c..e4fdd3e50 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -211,18 +211,17 @@ def get_arguments( ) ) - if operation.method.upper() in ["PATCH", "POST", "PUT"]: - ret.update( - _get_body_argument( - body, - operation=operation, - arguments=arguments, - has_kwargs=has_kwargs, - sanitize=sanitize, - content_type=content_type, - ) + ret.update( + _get_body_argument( + body, + operation=operation, + arguments=arguments, + has_kwargs=has_kwargs, + sanitize=sanitize, + content_type=content_type, ) - ret.update(_get_file_arguments(files, arguments, has_kwargs)) + ) + ret.update(_get_file_arguments(files, arguments, has_kwargs)) return ret From 90a2fba51e73c1e779eb59cc6387c81eb82f9e9b Mon Sep 17 00:00:00 2001 From: Ruwan Date: Sun, 11 Jun 2023 08:30:30 +0200 Subject: [PATCH 02/11] Update tests for body in GET and DELETE --- tests/api/test_parameters.py | 4 ++-- tests/test_resolver_methodview.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index ab58fd193..991cb5ef7 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -456,11 +456,11 @@ def test_args_kwargs(simple_app): app_client = simple_app.test_client() resp = app_client.get("/v1.0/query-params-as-kwargs") assert resp.status_code == 200 - assert resp.json() == {} + assert resp.json() == {"body": {}} resp = app_client.get("/v1.0/query-params-as-kwargs?foo=a&bar=b") assert resp.status_code == 200 - assert resp.json() == {"foo": "a"} + assert resp.json() == {"foo": "a", "body": {}} if simple_app._spec_file == "openapi.yaml": body = {"foo": "a", "bar": "b"} diff --git a/tests/test_resolver_methodview.py b/tests/test_resolver_methodview.py index 3295305e3..2e935d0ea 100644 --- a/tests/test_resolver_methodview.py +++ b/tests/test_resolver_methodview.py @@ -201,10 +201,10 @@ def test_method_view_resolver_integration(spec): client = method_view_app.test_client() r = client.get("/v1.0/pets") - assert r.json() == [{"name": "get"}] + assert r.json() == {"name": "get", "body": {}} r = client.get("/v1.0/pets/1") - assert r.json() == {"name": "get", "petId": 1} + assert r.json() == {"name": "get", "petId": 1, "body": {}} r = client.post("/v1.0/pets", json={"name": "Musti"}) assert r.json() == {"name": "post", "body": {"name": "Musti"}} @@ -227,7 +227,7 @@ def test_method_resolver_integration(spec, app_class): assert r.json() == [{"name": "search"}] r = client.get("/v1.0/pets/1") - assert r.json() == {"name": "get", "petId": 1} + assert r.json() == {"name": "get", "petId": 1, "body": {}} r = client.post("/v1.0/pets", json={"name": "Musti"}) assert r.json() == {"name": "post", "body": {"name": "Musti"}} From 9dc8d83c2781d1cdbad3f4d936c8c2d0b0e777fa Mon Sep 17 00:00:00 2001 From: Ruwan Date: Tue, 13 Jun 2023 19:21:19 +0200 Subject: [PATCH 03/11] Update injector tests --- tests/decorators/test_parameter.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/decorators/test_parameter.py b/tests/decorators/test_parameter.py index 8356339f3..81f63efe4 100644 --- a/tests/decorators/test_parameter.py +++ b/tests/decorators/test_parameter.py @@ -21,6 +21,7 @@ def test_sync_injection(): request = MagicMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} func = MagicMock() @@ -34,7 +35,7 @@ def handler(**kwargs): parameter_decorator = SyncParameterDecorator(framework=FlaskFramework) decorated_handler = parameter_decorator(handler) decorated_handler(request) - func.assert_called_with(p1="123") + func.assert_called_with(p1="123", body={}) @pytest.mark.skipif( @@ -43,6 +44,8 @@ def handler(**kwargs): async def test_async_injection(): request = AsyncMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} + request.files.return_value = {} func = MagicMock() @@ -56,12 +59,13 @@ async def handler(**kwargs): parameter_decorator = AsyncParameterDecorator(framework=StarletteFramework) decorated_handler = parameter_decorator(handler) await decorated_handler(request) - func.assert_called_with(p1="123") + func.assert_called_with(p1="123", body={}) def test_sync_injection_with_context(): request = MagicMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} func = MagicMock() @@ -77,7 +81,7 @@ def handler(context_, **kwargs): parameter_decorator = SyncParameterDecorator(framework=FlaskFramework) decorated_handler = parameter_decorator(handler) decorated_handler(request) - func.assert_called_with(context, p1="123", test="success") + func.assert_called_with(context, p1="123", test="success", body={}) @pytest.mark.skipif( @@ -86,6 +90,8 @@ def handler(context_, **kwargs): async def test_async_injection_with_context(): request = AsyncMock(name="request") request.path_params = {"p1": "123"} + request.get_body.return_value = {} + request.files.return_value = {} func = MagicMock() @@ -101,7 +107,7 @@ async def handler(context_, **kwargs): parameter_decorator = AsyncParameterDecorator(framework=StarletteFramework) decorated_handler = parameter_decorator(handler) await decorated_handler(request) - func.assert_called_with(context, p1="123", test="success") + func.assert_called_with(context, p1="123", test="success", body={}) def test_pythonic_params(): From 5f1f5c3a4374c6e2774d21c3c9d33c210a870ab4 Mon Sep 17 00:00:00 2001 From: Ruwan Date: Tue, 20 Jun 2023 05:56:00 +0200 Subject: [PATCH 04/11] Don't return body if no body is defined on operation --- connexion/decorators/parameter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index e4fdd3e50..62585f2f0 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -376,6 +376,9 @@ def _get_body_argument( if len(arguments) <= 0 and not has_kwargs: return {} + if operation.request_body == {}: + return {} + body_name = sanitize(operation.body_name(content_type)) if content_type in FORM_CONTENT_TYPES: From a1e0f543f390f7afc76cc125c3c577c401a0cf1d Mon Sep 17 00:00:00 2001 From: Ruwan Date: Tue, 20 Jun 2023 05:57:22 +0200 Subject: [PATCH 05/11] Revert "Update tests for body in GET and DELETE" This reverts commit 074f3b0a8f754bcab502d27dfeee3662635d9ba8. --- tests/api/test_parameters.py | 4 ++-- tests/test_resolver_methodview.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index 991cb5ef7..ab58fd193 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -456,11 +456,11 @@ def test_args_kwargs(simple_app): app_client = simple_app.test_client() resp = app_client.get("/v1.0/query-params-as-kwargs") assert resp.status_code == 200 - assert resp.json() == {"body": {}} + assert resp.json() == {} resp = app_client.get("/v1.0/query-params-as-kwargs?foo=a&bar=b") assert resp.status_code == 200 - assert resp.json() == {"foo": "a", "body": {}} + assert resp.json() == {"foo": "a"} if simple_app._spec_file == "openapi.yaml": body = {"foo": "a", "bar": "b"} diff --git a/tests/test_resolver_methodview.py b/tests/test_resolver_methodview.py index 2e935d0ea..3295305e3 100644 --- a/tests/test_resolver_methodview.py +++ b/tests/test_resolver_methodview.py @@ -201,10 +201,10 @@ def test_method_view_resolver_integration(spec): client = method_view_app.test_client() r = client.get("/v1.0/pets") - assert r.json() == {"name": "get", "body": {}} + assert r.json() == [{"name": "get"}] r = client.get("/v1.0/pets/1") - assert r.json() == {"name": "get", "petId": 1, "body": {}} + assert r.json() == {"name": "get", "petId": 1} r = client.post("/v1.0/pets", json={"name": "Musti"}) assert r.json() == {"name": "post", "body": {"name": "Musti"}} @@ -227,7 +227,7 @@ def test_method_resolver_integration(spec, app_class): assert r.json() == [{"name": "search"}] r = client.get("/v1.0/pets/1") - assert r.json() == {"name": "get", "petId": 1, "body": {}} + assert r.json() == {"name": "get", "petId": 1} r = client.post("/v1.0/pets", json={"name": "Musti"}) assert r.json() == {"name": "post", "body": {"name": "Musti"}} From 5aa789877dc0cd4b7e37fa837f075b60a5616506 Mon Sep 17 00:00:00 2001 From: Ruwan Date: Tue, 20 Jun 2023 06:02:47 +0200 Subject: [PATCH 06/11] Update tests for when no request body is defined --- tests/decorators/test_parameter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/decorators/test_parameter.py b/tests/decorators/test_parameter.py index 81f63efe4..e727cce97 100644 --- a/tests/decorators/test_parameter.py +++ b/tests/decorators/test_parameter.py @@ -29,13 +29,14 @@ def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") + operation.request_body = {} operation.body_name = lambda _: "body" with TestContext(operation=operation): parameter_decorator = SyncParameterDecorator(framework=FlaskFramework) decorated_handler = parameter_decorator(handler) decorated_handler(request) - func.assert_called_with(p1="123", body={}) + func.assert_called_with(p1="123") @pytest.mark.skipif( @@ -53,13 +54,14 @@ async def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") + operation.request_body = {} operation.body_name = lambda _: "body" with TestContext(operation=operation): parameter_decorator = AsyncParameterDecorator(framework=StarletteFramework) decorated_handler = parameter_decorator(handler) await decorated_handler(request) - func.assert_called_with(p1="123", body={}) + func.assert_called_with(p1="123") def test_sync_injection_with_context(): @@ -75,13 +77,14 @@ def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") + operation.request_body = {} operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): parameter_decorator = SyncParameterDecorator(framework=FlaskFramework) decorated_handler = parameter_decorator(handler) decorated_handler(request) - func.assert_called_with(context, p1="123", test="success", body={}) + func.assert_called_with(context, p1="123", test="success") @pytest.mark.skipif( @@ -101,13 +104,14 @@ async def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") + operation.request_body = {} operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): parameter_decorator = AsyncParameterDecorator(framework=StarletteFramework) decorated_handler = parameter_decorator(handler) await decorated_handler(request) - func.assert_called_with(context, p1="123", test="success", body={}) + func.assert_called_with(context, p1="123", test="success") def test_pythonic_params(): From 11e365d80373b663ad96dbe00166f7978312fc1e Mon Sep 17 00:00:00 2001 From: Ruwan Date: Fri, 20 Oct 2023 19:25:26 +0200 Subject: [PATCH 07/11] Add property to indicate whether request body is defined --- connexion/decorators/parameter.py | 2 +- connexion/operations/abstract.py | 5 +++++ tests/decorators/test_parameter.py | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 62585f2f0..8b713cad9 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -376,7 +376,7 @@ def _get_body_argument( if len(arguments) <= 0 and not has_kwargs: return {} - if operation.request_body == {}: + if not operation.is_request_body_defined: return {} body_name = sanitize(operation.body_name(content_type)) diff --git a/connexion/operations/abstract.py b/connexion/operations/abstract.py index 9087db069..9a79c293e 100644 --- a/connexion/operations/abstract.py +++ b/connexion/operations/abstract.py @@ -87,6 +87,11 @@ def method(self): def request_body(self): """The request body for this operation""" + @property + def is_request_body_defined(self) -> bool: + """Whether the request body is defined for this operation""" + return self.request_body != {} + @property def path(self): """ diff --git a/tests/decorators/test_parameter.py b/tests/decorators/test_parameter.py index e727cce97..dd404d0df 100644 --- a/tests/decorators/test_parameter.py +++ b/tests/decorators/test_parameter.py @@ -29,7 +29,7 @@ def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") - operation.request_body = {} + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(operation=operation): @@ -54,7 +54,7 @@ async def handler(**kwargs): func(**kwargs) operation = MagicMock(name="operation") - operation.request_body = {} + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(operation=operation): @@ -77,7 +77,7 @@ def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") - operation.request_body = {} + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): @@ -104,7 +104,7 @@ async def handler(context_, **kwargs): context = {"test": "success"} operation = MagicMock(name="operation") - operation.request_body = {} + operation.is_request_body_defined = False operation.body_name = lambda _: "body" with TestContext(context=context, operation=operation): From 13022ea3fa0f970a4fbc86be2522a2dab31dc804 Mon Sep 17 00:00:00 2001 From: Ruwan Date: Sat, 21 Oct 2023 11:32:47 +0200 Subject: [PATCH 08/11] Update documentation for request body in v3 --- docs/v3.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/v3.rst b/docs/v3.rst index 80ebd9c31..74727507b 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -126,6 +126,7 @@ Smaller breaking changes * The ``MethodViewResolver`` has been renamed to ``MethodResolver``, and a new ``MethodViewResolver`` has been added to work with Flask's ``MethodView`` specifically. * Built-in support for uWSGI has been removed. You can re-add this functionality using a custom middleware. +* The request body is now passed through for ``PATCH``, ``POST``, and ``PUT`` methods as well. Non-breaking changes From aea2759d379cb3100abc413eb1eb996f846ad051 Mon Sep 17 00:00:00 2001 From: Ruwan Date: Sat, 21 Oct 2023 12:28:37 +0200 Subject: [PATCH 09/11] Add test for request body in GET --- tests/api/test_parameters.py | 12 ++++++++++++ tests/fakeapi/hello/__init__.py | 4 ++++ tests/fixtures/simple/openapi.yaml | 14 ++++++++++++++ tests/fixtures/simple/swagger.yaml | 21 +++++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index ab58fd193..f7b84b45a 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -347,6 +347,18 @@ def test_body_not_allowed_additional_properties(simple_app): assert "Additional properties are not allowed" in response["detail"] +def test_body_in_get_request(simple_app): + app_client = simple_app.test_client() + body = {"body1": "bodyString"} + resp = app_client.request( + "GET", + "/v1.0/body-in-get-request", + json=body, + ) + assert resp.status_code == 200 + assert resp.json() == body + + def test_bool_as_default_param(simple_app): app_client = simple_app.test_client() resp = app_client.get("/v1.0/test-bool-param") diff --git a/tests/fakeapi/hello/__init__.py b/tests/fakeapi/hello/__init__.py index b92347112..1921ac930 100644 --- a/tests/fakeapi/hello/__init__.py +++ b/tests/fakeapi/hello/__init__.py @@ -580,6 +580,10 @@ def test_body_not_allowed_additional_properties(body): return body +def test_body_in_get_request(body): + return body + + def post_wrong_content_type(): return "NOT OK" diff --git a/tests/fixtures/simple/openapi.yaml b/tests/fixtures/simple/openapi.yaml index 9f9e0da05..8d66a29a4 100644 --- a/tests/fixtures/simple/openapi.yaml +++ b/tests/fixtures/simple/openapi.yaml @@ -1079,6 +1079,20 @@ paths: body1: type: string additionalProperties: false + /body-in-get-request: + get: + operationId: fakeapi.hello.test_body_in_get_request + responses: + '200': + description: OK + requestBody: + content: + application/json: + schema: + type: object + properties: + body1: + type: string /get_non_conforming_response: get: operationId: fakeapi.hello.get_empty_dict diff --git a/tests/fixtures/simple/swagger.yaml b/tests/fixtures/simple/swagger.yaml index bb50c787f..e0b5f36ec 100644 --- a/tests/fixtures/simple/swagger.yaml +++ b/tests/fixtures/simple/swagger.yaml @@ -951,6 +951,27 @@ paths: 200: description: OK + /body-in-get-request: + get: + operationId: fakeapi.hello.test_body_in_get_request + consumes: + - application/json + produces: + - application/json + parameters: + - name: $body + description: A request body in a GET method. + in: body + required: true + schema: + type: object + properties: + body1: + type: string + responses: + 200: + description: OK + /get_non_conforming_response: get: operationId: fakeapi.hello.get_empty_dict From cd5e07c8fff6eac7188338fa3a85a032cc7548fd Mon Sep 17 00:00:00 2001 From: Ruwann Date: Mon, 23 Oct 2023 17:52:03 +0200 Subject: [PATCH 10/11] Specify correct HTTP methods in documentation Co-authored-by: Robbe Sneyders --- docs/v3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v3.rst b/docs/v3.rst index 74727507b..8c8b4859b 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -126,7 +126,7 @@ Smaller breaking changes * The ``MethodViewResolver`` has been renamed to ``MethodResolver``, and a new ``MethodViewResolver`` has been added to work with Flask's ``MethodView`` specifically. * Built-in support for uWSGI has been removed. You can re-add this functionality using a custom middleware. -* The request body is now passed through for ``PATCH``, ``POST``, and ``PUT`` methods as well. +* The request body is now passed through for ``GET``, ``HEAD``, ``DELETE``, ``CONNECT`` and ``OPTIONS`` methods as well. Non-breaking changes From f6349ab40eea76c7bda4b289fe43dd732ca63242 Mon Sep 17 00:00:00 2001 From: Ruwan Date: Mon, 23 Oct 2023 17:58:53 +0200 Subject: [PATCH 11/11] Don't include request body for TRACE --- connexion/decorators/parameter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 8b713cad9..3ae1e0fb0 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -211,6 +211,10 @@ def get_arguments( ) ) + if operation.method.upper() == "TRACE": + # TRACE requests MUST NOT include a body (RFC7231 section 4.3.8) + return ret + ret.update( _get_body_argument( body,