From bcdbf6eadda7b3b2f13a0f0f3f7e9bb234dd8587 Mon Sep 17 00:00:00 2001 From: Sergiu-Vlad Bonta Date: Wed, 21 Apr 2021 19:04:05 +0300 Subject: [PATCH 1/5] Bugfix #311, openapicore works with falcon 3.0.0 --- openapi_core/contrib/falcon/requests.py | 13 +++++++++---- tests/integration/contrib/falcon/conftest.py | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/openapi_core/contrib/falcon/requests.py b/openapi_core/contrib/falcon/requests.py index e48b4fa..2e88ff1 100644 --- a/openapi_core/contrib/falcon/requests.py +++ b/openapi_core/contrib/falcon/requests.py @@ -11,19 +11,24 @@ from openapi_core.validation.request.datatypes import ( class FalconOpenAPIRequestFactory: @classmethod - def create(cls, request): + def create(cls, request, default_when_empty={}): """ Create OpenAPIRequest from falcon Request and route params. """ + default = default_when_empty method = request.method.lower() # gets deduced by path finder against spec path = {} - # Support falcon-jsonify. + # in falcon 3 we must hadle empty media or an exception will be raised + if hasattr(request, "get_media"): + media = request.get_media(default_when_empty=default) + else: + media = request.media if request.media else default + # # Support falcon-jsonify. body = ( - dumps(request.json) if getattr(request, "json", None) - else dumps(request.media) + dumps(getattr(request, "json", media)) ) mimetype = request.options.default_media_type if request.content_type: diff --git a/tests/integration/contrib/falcon/conftest.py b/tests/integration/contrib/falcon/conftest.py index 60ac8d6..5ad0503 100644 --- a/tests/integration/contrib/falcon/conftest.py +++ b/tests/integration/contrib/falcon/conftest.py @@ -33,7 +33,6 @@ def request_factory(environ_factory, router): options = RequestOptions() # return create_req(options=options, **environ) req = Request(environ, options) - resource, method_map, params, req.uri_template = router.find(path, req) return req return create_request From 8eff3dd736b1ac5a0d22c819c486b8a5046bebb5 Mon Sep 17 00:00:00 2001 From: Sergiu-Vlad Bonta Date: Wed, 21 Apr 2021 19:05:53 +0300 Subject: [PATCH 2/5] Uncommented a comment --- openapi_core/contrib/falcon/requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_core/contrib/falcon/requests.py b/openapi_core/contrib/falcon/requests.py index 2e88ff1..171c3ea 100644 --- a/openapi_core/contrib/falcon/requests.py +++ b/openapi_core/contrib/falcon/requests.py @@ -26,7 +26,7 @@ class FalconOpenAPIRequestFactory: media = request.get_media(default_when_empty=default) else: media = request.media if request.media else default - # # Support falcon-jsonify. + # Support falcon-jsonify. body = ( dumps(getattr(request, "json", media)) ) From ffa54aae886863cd43a1e243e9962a40f2c86e87 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 1 May 2021 00:01:22 +0100 Subject: [PATCH 3/5] Falcon3 tests --- openapi_core/contrib/falcon/handlers.py | 7 ++++++- openapi_core/contrib/falcon/responses.py | 8 +++++++- requirements_dev.txt | 2 +- tests/integration/contrib/falcon/conftest.py | 2 +- .../integration/contrib/falcon/test_falcon_middlewares.py | 8 ++++---- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/openapi_core/contrib/falcon/handlers.py b/openapi_core/contrib/falcon/handlers.py index 85d96a6..a4f6053 100644 --- a/openapi_core/contrib/falcon/handlers.py +++ b/openapi_core/contrib/falcon/handlers.py @@ -36,11 +36,16 @@ class FalconOpenAPIErrorsHandler(object): data = { 'errors': data_errors, } + data_str = dumps(data) data_error_max = max(data_errors, key=lambda x: x['status']) resp.content_type = MEDIA_JSON resp.status = cls.FALCON_STATUS_CODES.get( data_error_max['status'], HTTP_400) - resp.body = dumps(data) + # in falcon 3 body is deprecated + if hasattr(resp, 'text'): + resp.text = data_str + else: + resp.body = data_str resp.complete = True @classmethod diff --git a/openapi_core/contrib/falcon/responses.py b/openapi_core/contrib/falcon/responses.py index 9cca659..22ee93a 100644 --- a/openapi_core/contrib/falcon/responses.py +++ b/openapi_core/contrib/falcon/responses.py @@ -13,8 +13,14 @@ class FalconOpenAPIResponseFactory(object): else: mimetype = response.options.default_media_type + # in falcon 3 body is deprecated + if hasattr(response, "text"): + data = response.text + else: + data = response.body + return OpenAPIResponse( - data=response.body, + data=data, status_code=status_code, mimetype=mimetype, ) diff --git a/requirements_dev.txt b/requirements_dev.txt index 2254af2..5ee74a8 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,7 +2,7 @@ mock==2.0.0 pytest==3.5.0 pytest-flake8 pytest-cov==2.5.1 -falcon==2.0.0 +falcon==3.0.0 flask django==2.2.18; python_version>="3.0" requests==2.22.0 diff --git a/tests/integration/contrib/falcon/conftest.py b/tests/integration/contrib/falcon/conftest.py index 5ad0503..6513356 100644 --- a/tests/integration/contrib/falcon/conftest.py +++ b/tests/integration/contrib/falcon/conftest.py @@ -43,7 +43,7 @@ def response_factory(environ_factory): data, status_code=200, content_type='application/json'): options = ResponseOptions() resp = Response(options) - resp.body = data + resp.text = data resp.content_type = content_type resp.status = HTTP_200 return resp diff --git a/tests/integration/contrib/falcon/test_falcon_middlewares.py b/tests/integration/contrib/falcon/test_falcon_middlewares.py index d41a738..b55a78b 100644 --- a/tests/integration/contrib/falcon/test_falcon_middlewares.py +++ b/tests/integration/contrib/falcon/test_falcon_middlewares.py @@ -1,6 +1,6 @@ from json import dumps -from falcon import API +from falcon import App from falcon.testing import TestClient import pytest @@ -24,7 +24,7 @@ class TestFalconOpenAPIMiddleware(object): @pytest.fixture def app(self, middleware): - return API(middleware=[middleware]) + return App(middleware=[middleware]) @pytest.yield_fixture def client(self, app): @@ -67,7 +67,7 @@ class TestFalconOpenAPIMiddleware(object): }) response.content_type = MEDIA_HTML response.status = HTTP_200 - response.body = 'success' + response.text = 'success' self.view_response_callable = view_response_callable headers = {'Content-Type': 'application/json'} result = client.simulate_get( @@ -190,7 +190,7 @@ class TestFalconOpenAPIMiddleware(object): }) response.status = HTTP_200 response.content_type = MEDIA_JSON - response.body = dumps({ + response.text = dumps({ 'data': 'data', }) self.view_response_callable = view_response_callable From 2c864595e5d9b769776f886c60badc21d7cac7f6 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 1 May 2021 00:30:55 +0100 Subject: [PATCH 4/5] Falcon compat module --- openapi_core/contrib/falcon/compat.py | 24 ++++++++++++++++++++++++ openapi_core/contrib/falcon/handlers.py | 8 +++----- openapi_core/contrib/falcon/requests.py | 7 ++----- openapi_core/contrib/falcon/responses.py | 7 ++----- 4 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 openapi_core/contrib/falcon/compat.py diff --git a/openapi_core/contrib/falcon/compat.py b/openapi_core/contrib/falcon/compat.py new file mode 100644 index 0000000..4e60e86 --- /dev/null +++ b/openapi_core/contrib/falcon/compat.py @@ -0,0 +1,24 @@ +"""OpenAPI core contrib falcon compat module""" +try: + from falcon import App # noqa: F401 + HAS_FALCON3 = True +except ImportError: + HAS_FALCON3 = False + + +def get_request_media(req, default=None): + # in falcon 3 media is deprecated + return req.get_media(default_when_empty=default) if HAS_FALCON3 else \ + (req.media if req.media else default) + + +def get_response_text(resp): + # in falcon 3 body is deprecated + return getattr(resp, 'text') if HAS_FALCON3 else \ + getattr(resp, 'body') + + +def set_response_text(resp, text): + # in falcon 3 body is deprecated + setattr(resp, 'text', text) if HAS_FALCON3 else \ + setattr(resp, 'body', text) diff --git a/openapi_core/contrib/falcon/handlers.py b/openapi_core/contrib/falcon/handlers.py index a4f6053..924e600 100644 --- a/openapi_core/contrib/falcon/handlers.py +++ b/openapi_core/contrib/falcon/handlers.py @@ -5,6 +5,8 @@ from falcon.constants import MEDIA_JSON from falcon.status_codes import ( HTTP_400, HTTP_404, HTTP_405, HTTP_415, ) + +from openapi_core.contrib.falcon.compat import set_response_text from openapi_core.templating.media_types.exceptions import MediaTypeNotFound from openapi_core.templating.paths.exceptions import ( ServerNotFound, OperationNotFound, PathNotFound, @@ -41,11 +43,7 @@ class FalconOpenAPIErrorsHandler(object): resp.content_type = MEDIA_JSON resp.status = cls.FALCON_STATUS_CODES.get( data_error_max['status'], HTTP_400) - # in falcon 3 body is deprecated - if hasattr(resp, 'text'): - resp.text = data_str - else: - resp.body = data_str + set_response_text(resp, data_str) resp.complete = True @classmethod diff --git a/openapi_core/contrib/falcon/requests.py b/openapi_core/contrib/falcon/requests.py index 171c3ea..83dc502 100644 --- a/openapi_core/contrib/falcon/requests.py +++ b/openapi_core/contrib/falcon/requests.py @@ -3,6 +3,7 @@ from json import dumps from werkzeug.datastructures import ImmutableMultiDict +from openapi_core.contrib.falcon.compat import get_request_media from openapi_core.validation.request.datatypes import ( OpenAPIRequest, RequestParameters, ) @@ -21,11 +22,7 @@ class FalconOpenAPIRequestFactory: # gets deduced by path finder against spec path = {} - # in falcon 3 we must hadle empty media or an exception will be raised - if hasattr(request, "get_media"): - media = request.get_media(default_when_empty=default) - else: - media = request.media if request.media else default + media = get_request_media(request, default=default) # Support falcon-jsonify. body = ( dumps(getattr(request, "json", media)) diff --git a/openapi_core/contrib/falcon/responses.py b/openapi_core/contrib/falcon/responses.py index 22ee93a..cc99692 100644 --- a/openapi_core/contrib/falcon/responses.py +++ b/openapi_core/contrib/falcon/responses.py @@ -1,4 +1,5 @@ """OpenAPI core contrib falcon responses module""" +from openapi_core.contrib.falcon.compat import get_response_text from openapi_core.validation.response.datatypes import OpenAPIResponse @@ -13,11 +14,7 @@ class FalconOpenAPIResponseFactory(object): else: mimetype = response.options.default_media_type - # in falcon 3 body is deprecated - if hasattr(response, "text"): - data = response.text - else: - data = response.body + data = get_response_text(response) return OpenAPIResponse( data=data, From 74d49d19a3f19c993bf5557dc937e4eb7f0ac9a8 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 1 May 2021 00:37:39 +0100 Subject: [PATCH 5/5] Falcon2 for python2 tests --- requirements_dev.txt | 3 ++- tests/integration/contrib/falcon/conftest.py | 2 +- tests/integration/contrib/falcon/test_falcon_middlewares.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 5ee74a8..c55a939 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,7 +2,8 @@ mock==2.0.0 pytest==3.5.0 pytest-flake8 pytest-cov==2.5.1 -falcon==3.0.0 +falcon==2.0.0; python_version<"3.0" +falcon==3.0.0; python_version>="3.0" flask django==2.2.18; python_version>="3.0" requests==2.22.0 diff --git a/tests/integration/contrib/falcon/conftest.py b/tests/integration/contrib/falcon/conftest.py index 6513356..5ad0503 100644 --- a/tests/integration/contrib/falcon/conftest.py +++ b/tests/integration/contrib/falcon/conftest.py @@ -43,7 +43,7 @@ def response_factory(environ_factory): data, status_code=200, content_type='application/json'): options = ResponseOptions() resp = Response(options) - resp.text = data + resp.body = data resp.content_type = content_type resp.status = HTTP_200 return resp diff --git a/tests/integration/contrib/falcon/test_falcon_middlewares.py b/tests/integration/contrib/falcon/test_falcon_middlewares.py index b55a78b..a234835 100644 --- a/tests/integration/contrib/falcon/test_falcon_middlewares.py +++ b/tests/integration/contrib/falcon/test_falcon_middlewares.py @@ -1,6 +1,6 @@ from json import dumps -from falcon import App +from falcon import API as App from falcon.testing import TestClient import pytest @@ -67,7 +67,7 @@ class TestFalconOpenAPIMiddleware(object): }) response.content_type = MEDIA_HTML response.status = HTTP_200 - response.text = 'success' + response.body = 'success' self.view_response_callable = view_response_callable headers = {'Content-Type': 'application/json'} result = client.simulate_get( @@ -190,7 +190,7 @@ class TestFalconOpenAPIMiddleware(object): }) response.status = HTTP_200 response.content_type = MEDIA_JSON - response.text = dumps({ + response.body = dumps({ 'data': 'data', }) self.view_response_callable = view_response_callable