Skip to content

Falcon2 support drop #353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/integrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Falcon
------

This section describes integration with `Falcon <https://falconframework.org>`__ web framework.
The integration supports Falcon from version 3.0 and above.

Middleware
~~~~~~~~~~
Expand All @@ -50,7 +51,7 @@ Falcon API can be integrated by `FalconOpenAPIMiddleware` middleware.
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware

openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
api = falcon.API(middleware=[openapi_middleware])
app = falcon.App(middleware=[openapi_middleware])

Low level
~~~~~~~~~
Expand All @@ -62,7 +63,7 @@ For Falcon you can use FalconOpenAPIRequest a Falcon request factory:
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.contrib.falcon import FalconOpenAPIRequestFactory

openapi_request = FalconOpenAPIRequestFactory.create(falcon_request)
openapi_request = FalconOpenAPIRequestFactory().create(falcon_request)
validator = RequestValidator(spec)
result = validator.validate(openapi_request)

Expand All @@ -73,7 +74,7 @@ You can use FalconOpenAPIResponse as a Falcon response factory:
from openapi_core.validation.response.validators import ResponseValidator
from openapi_core.contrib.falcon import FalconOpenAPIResponseFactory

openapi_response = FalconOpenAPIResponseFactory.create(falcon_response)
openapi_response = FalconOpenAPIResponseFactory().create(falcon_response)
validator = ResponseValidator(spec)
result = validator.validate(openapi_request, openapi_response)

Expand Down
24 changes: 0 additions & 24 deletions openapi_core/contrib/falcon/compat.py

This file was deleted.

3 changes: 1 addition & 2 deletions openapi_core/contrib/falcon/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
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,
Expand Down Expand Up @@ -43,7 +42,7 @@ def handle(cls, req, resp, errors):
resp.content_type = MEDIA_JSON
resp.status = cls.FALCON_STATUS_CODES.get(
data_error_max['status'], HTTP_400)
set_response_text(resp, data_str)
resp.text = data_str
resp.complete = True

@classmethod
Expand Down
71 changes: 38 additions & 33 deletions openapi_core/contrib/falcon/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,68 @@
from openapi_core.validation.response.validators import ResponseValidator


class FalconOpenAPIMiddleware(OpenAPIProcessor):
class FalconOpenAPIMiddleware:

request_factory = FalconOpenAPIRequestFactory()
response_factory = FalconOpenAPIResponseFactory()
errors_handler = FalconOpenAPIErrorsHandler()

def __init__(
self,
request_validator,
response_validator,
request_factory,
response_factory,
openapi_errors_handler,
self,
validation_processor,
request_factory=None,
response_factory=None,
errors_handler=None,
):
self.validation_processor = validation_processor
self.request_factory = request_factory or self.request_factory
self.response_factory = response_factory or self.response_factory
self.errors_handler = errors_handler or self.errors_handler

@classmethod
def from_spec(
cls,
spec,
request_factory=None,
response_factory=None,
errors_handler=None,
):
super().__init__(request_validator, response_validator)
self.request_factory = request_factory
self.response_factory = response_factory
self.openapi_errors_handler = openapi_errors_handler
request_validator = RequestValidator(spec)
response_validator = ResponseValidator(spec)
validation_processor = OpenAPIProcessor(
request_validator, response_validator)
return cls(
validation_processor,
request_factory=request_factory,
response_factory=response_factory,
errors_handler=errors_handler,
)

def process_request(self, req, resp):
openapi_req = self._get_openapi_request(req)
req_result = super().process_request(openapi_req)
req_result = self.validation_processor.process_request(openapi_req)
if req_result.errors:
return self._handle_request_errors(req, resp, req_result)
req.openapi = req_result

def process_response(self, req, resp, resource, req_succeeded):
openapi_req = self._get_openapi_request(req)
openapi_resp = self._get_openapi_response(resp)
resp_result = super().process_response(openapi_req, openapi_resp)
resp_result = self.validation_processor.process_response(
openapi_req, openapi_resp)
if resp_result.errors:
return self._handle_response_errors(req, resp, resp_result)

def _handle_request_errors(self, req, resp, request_result):
return self.openapi_errors_handler.handle(
return self.errors_handler.handle(
req, resp, request_result.errors)

def _handle_response_errors(self, req, resp, response_result):
return self.openapi_errors_handler.handle(
return self.errors_handler.handle(
req, resp, response_result.errors)

def _get_openapi_request(self, request):
return self.request_factory.create(request)

def _get_openapi_response(self, response):
return self.response_factory.create(response)

@classmethod
def from_spec(
cls,
spec,
request_factory=FalconOpenAPIRequestFactory,
response_factory=FalconOpenAPIResponseFactory,
openapi_errors_handler=FalconOpenAPIErrorsHandler,
):
request_validator = RequestValidator(spec)
response_validator = ResponseValidator(spec)
return cls(
request_validator=request_validator,
response_validator=response_validator,
request_factory=request_factory,
response_factory=response_factory,
openapi_errors_handler=openapi_errors_handler,
)
12 changes: 7 additions & 5 deletions openapi_core/contrib/falcon/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@

from werkzeug.datastructures import ImmutableMultiDict, Headers

from openapi_core.contrib.falcon.compat import get_request_media
from openapi_core.validation.request.datatypes import (
OpenAPIRequest, RequestParameters,
)


class FalconOpenAPIRequestFactory:

@classmethod
def create(cls, request, default_when_empty={}):
def __init__(self, default_when_empty=None):
if default_when_empty is None:
default_when_empty = {}
self.default_when_empty = default_when_empty

def create(self, request):
"""
Create OpenAPIRequest from falcon Request and route params.
"""
default = default_when_empty
method = request.method.lower()

media = get_request_media(request, default=default)
media = request.get_media(default_when_empty=self.default_when_empty)
# Support falcon-jsonify.
body = (
dumps(getattr(request, "json", media))
Expand Down
3 changes: 1 addition & 2 deletions openapi_core/contrib/falcon/responses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""OpenAPI core contrib falcon responses module"""
from werkzeug.datastructures import Headers

from openapi_core.contrib.falcon.compat import get_response_text
from openapi_core.validation.response.datatypes import OpenAPIResponse


Expand All @@ -16,7 +15,7 @@ def create(cls, response):
else:
mimetype = response.options.default_media_type

data = get_response_text(response)
data = response.text
headers = Headers(response.headers)

return OpenAPIResponse(
Expand Down
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pytest==5.4.3
pytest-flake8
pytest-cov==2.5.1
falcon==3.0.0
falcon==3.0.1
flask
django==2.2.20
djangorestframework==3.11.2
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ tests_require =
pytest>=5.0.0
pytest-flake8
pytest-cov
falcon
falcon>=3.0
flask
responses
webob
Expand All @@ -48,6 +48,7 @@ exclude =

[options.extras_require]
django = django>=2.2
falcon = falcon>=3.0
flask = flask
requests = requests

Expand Down
25 changes: 24 additions & 1 deletion tests/integration/contrib/falcon/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
import sys

from falcon import Request, Response, RequestOptions, ResponseOptions
from falcon.routing import DefaultRouter
from falcon.status_codes import HTTP_200
from falcon.testing import create_environ
from falcon.testing import create_environ, TestClient
import pytest


Expand Down Expand Up @@ -50,3 +53,23 @@ def create_response(
resp.set_headers(headers or {})
return resp
return create_response


@pytest.fixture(autouse=True, scope='module')
def falcon_setup():
directory = os.path.abspath(os.path.dirname(__file__))
falcon_project_dir = os.path.join(directory, 'data/v3.0')
sys.path.insert(0, falcon_project_dir)
yield
sys.path.remove(falcon_project_dir)


@pytest.fixture
def app():
from falconproject.__main__ import app
return app


@pytest.fixture
def client(app):
return TestClient(app)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from falcon import App

from falconproject.openapi import openapi_middleware
from falconproject.resources import PetListResource, PetDetailResource

app = App(middleware=[openapi_middleware])

pet_list_resource = PetListResource()
pet_detail_resource = PetDetailResource()

app.add_route("/v1/pets", pet_list_resource)
app.add_route("/v1/pets/{petId}", pet_detail_resource)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pathlib import Path

from openapi_core import create_spec
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
import yaml

openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
spec_yaml = openapi_spec_path.read_text()
spec_dict = yaml.load(spec_yaml)
spec = create_spec(spec_dict)
openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from json import dumps

from falcon.constants import MEDIA_JSON
from falcon.status_codes import HTTP_200


class PetListResource:
def on_get(self, request, response):
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters.query == {
'page': 1,
'limit': 12,
'search': '',
}
data = [
{
'id': 12,
'name': 'Cat',
'ears': {
'healthy': True,
},
},
]
response.status = HTTP_200
response.content_type = MEDIA_JSON
response.text = dumps({"data": data})
response.set_header('X-Rate-Limit', '12')


class PetDetailResource:
def on_get(self, request, response, petId=None):
assert petId == '12'
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters.path == {
'petId': 12,
}
data = {
'id': 12,
'name': 'Cat',
'ears': {
'healthy': True,
},
}
response.status = HTTP_200
response.content_type = MEDIA_JSON
response.text = dumps({"data": data})
response.set_header('X-Rate-Limit', '12')
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ paths:
type: integer
get:
responses:
200:
'200':
description: Return the resource.
content:
application/json:
Expand Down
Loading