diff --git a/openapi_core/contrib/falcon/compat.py b/openapi_core/contrib/falcon/compat.py new file mode 100644 index 00000000..4e60e86c --- /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 85d96a6b..924e600c 100644 --- a/openapi_core/contrib/falcon/handlers.py +++ b/openapi_core/contrib/falcon/handlers.py @@ -5,6 +5,8 @@ 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, @@ -36,11 +38,12 @@ def handle(cls, req, resp, errors): 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) + 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 e48b4fa7..83dc5020 100644 --- a/openapi_core/contrib/falcon/requests.py +++ b/openapi_core/contrib/falcon/requests.py @@ -3,6 +3,7 @@ from werkzeug.datastructures import ImmutableMultiDict +from openapi_core.contrib.falcon.compat import get_request_media from openapi_core.validation.request.datatypes import ( OpenAPIRequest, RequestParameters, ) @@ -11,19 +12,20 @@ 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 = {} + media = get_request_media(request, default=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/openapi_core/contrib/falcon/responses.py b/openapi_core/contrib/falcon/responses.py index 9cca6597..cc996920 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,8 +14,10 @@ def create(cls, response): else: mimetype = response.options.default_media_type + data = get_response_text(response) + return OpenAPIResponse( - data=response.body, + data=data, status_code=status_code, mimetype=mimetype, ) diff --git a/requirements_dev.txt b/requirements_dev.txt index 2254af2b..c55a9398 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==2.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 60ac8d65..5ad05030 100644 --- a/tests/integration/contrib/falcon/conftest.py +++ b/tests/integration/contrib/falcon/conftest.py @@ -33,7 +33,6 @@ def create_request( 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 diff --git a/tests/integration/contrib/falcon/test_falcon_middlewares.py b/tests/integration/contrib/falcon/test_falcon_middlewares.py index d41a7385..a2348357 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 API as App from falcon.testing import TestClient import pytest @@ -24,7 +24,7 @@ def middleware(self, spec): @pytest.fixture def app(self, middleware): - return API(middleware=[middleware]) + return App(middleware=[middleware]) @pytest.yield_fixture def client(self, app):