Skip to content

Commit 246f52a

Browse files
authored
Merge pull request #353 from p1c2u/refactor/falcon2-support-drop
Falcon2 support drop
2 parents 9df0dd4 + 1c87469 commit 246f52a

17 files changed

+225
-80
lines changed

docs/integrations.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Falcon
3939
------
4040

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

4344
Middleware
4445
~~~~~~~~~~
@@ -50,7 +51,7 @@ Falcon API can be integrated by `FalconOpenAPIMiddleware` middleware.
5051
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
5152
5253
openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
53-
api = falcon.API(middleware=[openapi_middleware])
54+
app = falcon.App(middleware=[openapi_middleware])
5455
5556
Low level
5657
~~~~~~~~~
@@ -62,7 +63,7 @@ For Falcon you can use FalconOpenAPIRequest a Falcon request factory:
6263
from openapi_core.validation.request.validators import RequestValidator
6364
from openapi_core.contrib.falcon import FalconOpenAPIRequestFactory
6465
65-
openapi_request = FalconOpenAPIRequestFactory.create(falcon_request)
66+
openapi_request = FalconOpenAPIRequestFactory().create(falcon_request)
6667
validator = RequestValidator(spec)
6768
result = validator.validate(openapi_request)
6869
@@ -73,7 +74,7 @@ You can use FalconOpenAPIResponse as a Falcon response factory:
7374
from openapi_core.validation.response.validators import ResponseValidator
7475
from openapi_core.contrib.falcon import FalconOpenAPIResponseFactory
7576
76-
openapi_response = FalconOpenAPIResponseFactory.create(falcon_response)
77+
openapi_response = FalconOpenAPIResponseFactory().create(falcon_response)
7778
validator = ResponseValidator(spec)
7879
result = validator.validate(openapi_request, openapi_response)
7980

openapi_core/contrib/falcon/compat.py

-24
This file was deleted.

openapi_core/contrib/falcon/handlers.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
HTTP_400, HTTP_404, HTTP_405, HTTP_415,
77
)
88

9-
from openapi_core.contrib.falcon.compat import set_response_text
109
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
1110
from openapi_core.templating.paths.exceptions import (
1211
ServerNotFound, OperationNotFound, PathNotFound,
@@ -43,7 +42,7 @@ def handle(cls, req, resp, errors):
4342
resp.content_type = MEDIA_JSON
4443
resp.status = cls.FALCON_STATUS_CODES.get(
4544
data_error_max['status'], HTTP_400)
46-
set_response_text(resp, data_str)
45+
resp.text = data_str
4746
resp.complete = True
4847

4948
@classmethod

openapi_core/contrib/falcon/middlewares.py

+38-33
Original file line numberDiff line numberDiff line change
@@ -8,63 +8,68 @@
88
from openapi_core.validation.response.validators import ResponseValidator
99

1010

11-
class FalconOpenAPIMiddleware(OpenAPIProcessor):
11+
class FalconOpenAPIMiddleware:
12+
13+
request_factory = FalconOpenAPIRequestFactory()
14+
response_factory = FalconOpenAPIResponseFactory()
15+
errors_handler = FalconOpenAPIErrorsHandler()
1216

1317
def __init__(
14-
self,
15-
request_validator,
16-
response_validator,
17-
request_factory,
18-
response_factory,
19-
openapi_errors_handler,
18+
self,
19+
validation_processor,
20+
request_factory=None,
21+
response_factory=None,
22+
errors_handler=None,
23+
):
24+
self.validation_processor = validation_processor
25+
self.request_factory = request_factory or self.request_factory
26+
self.response_factory = response_factory or self.response_factory
27+
self.errors_handler = errors_handler or self.errors_handler
28+
29+
@classmethod
30+
def from_spec(
31+
cls,
32+
spec,
33+
request_factory=None,
34+
response_factory=None,
35+
errors_handler=None,
2036
):
21-
super().__init__(request_validator, response_validator)
22-
self.request_factory = request_factory
23-
self.response_factory = response_factory
24-
self.openapi_errors_handler = openapi_errors_handler
37+
request_validator = RequestValidator(spec)
38+
response_validator = ResponseValidator(spec)
39+
validation_processor = OpenAPIProcessor(
40+
request_validator, response_validator)
41+
return cls(
42+
validation_processor,
43+
request_factory=request_factory,
44+
response_factory=response_factory,
45+
errors_handler=errors_handler,
46+
)
2547

2648
def process_request(self, req, resp):
2749
openapi_req = self._get_openapi_request(req)
28-
req_result = super().process_request(openapi_req)
50+
req_result = self.validation_processor.process_request(openapi_req)
2951
if req_result.errors:
3052
return self._handle_request_errors(req, resp, req_result)
3153
req.openapi = req_result
3254

3355
def process_response(self, req, resp, resource, req_succeeded):
3456
openapi_req = self._get_openapi_request(req)
3557
openapi_resp = self._get_openapi_response(resp)
36-
resp_result = super().process_response(openapi_req, openapi_resp)
58+
resp_result = self.validation_processor.process_response(
59+
openapi_req, openapi_resp)
3760
if resp_result.errors:
3861
return self._handle_response_errors(req, resp, resp_result)
3962

4063
def _handle_request_errors(self, req, resp, request_result):
41-
return self.openapi_errors_handler.handle(
64+
return self.errors_handler.handle(
4265
req, resp, request_result.errors)
4366

4467
def _handle_response_errors(self, req, resp, response_result):
45-
return self.openapi_errors_handler.handle(
68+
return self.errors_handler.handle(
4669
req, resp, response_result.errors)
4770

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

5174
def _get_openapi_response(self, response):
5275
return self.response_factory.create(response)
53-
54-
@classmethod
55-
def from_spec(
56-
cls,
57-
spec,
58-
request_factory=FalconOpenAPIRequestFactory,
59-
response_factory=FalconOpenAPIResponseFactory,
60-
openapi_errors_handler=FalconOpenAPIErrorsHandler,
61-
):
62-
request_validator = RequestValidator(spec)
63-
response_validator = ResponseValidator(spec)
64-
return cls(
65-
request_validator=request_validator,
66-
response_validator=response_validator,
67-
request_factory=request_factory,
68-
response_factory=response_factory,
69-
openapi_errors_handler=openapi_errors_handler,
70-
)

openapi_core/contrib/falcon/requests.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33

44
from werkzeug.datastructures import ImmutableMultiDict, Headers
55

6-
from openapi_core.contrib.falcon.compat import get_request_media
76
from openapi_core.validation.request.datatypes import (
87
OpenAPIRequest, RequestParameters,
98
)
109

1110

1211
class FalconOpenAPIRequestFactory:
1312

14-
@classmethod
15-
def create(cls, request, default_when_empty={}):
13+
def __init__(self, default_when_empty=None):
14+
if default_when_empty is None:
15+
default_when_empty = {}
16+
self.default_when_empty = default_when_empty
17+
18+
def create(self, request):
1619
"""
1720
Create OpenAPIRequest from falcon Request and route params.
1821
"""
19-
default = default_when_empty
2022
method = request.method.lower()
2123

22-
media = get_request_media(request, default=default)
24+
media = request.get_media(default_when_empty=self.default_when_empty)
2325
# Support falcon-jsonify.
2426
body = (
2527
dumps(getattr(request, "json", media))

openapi_core/contrib/falcon/responses.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""OpenAPI core contrib falcon responses module"""
22
from werkzeug.datastructures import Headers
33

4-
from openapi_core.contrib.falcon.compat import get_response_text
54
from openapi_core.validation.response.datatypes import OpenAPIResponse
65

76

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

19-
data = get_response_text(response)
18+
data = response.text
2019
headers = Headers(response.headers)
2120

2221
return OpenAPIResponse(

requirements_dev.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pytest==5.4.3
22
pytest-flake8
33
pytest-cov==2.5.1
4-
falcon==3.0.0
4+
falcon==3.0.1
55
flask
66
django==2.2.21
77
djangorestframework==3.11.2

setup.cfg

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ tests_require =
3737
pytest>=5.0.0
3838
pytest-flake8
3939
pytest-cov
40-
falcon
40+
falcon>=3.0
4141
flask
4242
responses
4343
webob
@@ -48,6 +48,7 @@ exclude =
4848

4949
[options.extras_require]
5050
django = django>=2.2
51+
falcon = falcon>=3.0
5152
flask = flask
5253
requests = requests
5354

tests/integration/contrib/falcon/conftest.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import os
2+
import sys
3+
14
from falcon import Request, Response, RequestOptions, ResponseOptions
25
from falcon.routing import DefaultRouter
36
from falcon.status_codes import HTTP_200
4-
from falcon.testing import create_environ
7+
from falcon.testing import create_environ, TestClient
58
import pytest
69

710

@@ -50,3 +53,23 @@ def create_response(
5053
resp.set_headers(headers or {})
5154
return resp
5255
return create_response
56+
57+
58+
@pytest.fixture(autouse=True, scope='module')
59+
def falcon_setup():
60+
directory = os.path.abspath(os.path.dirname(__file__))
61+
falcon_project_dir = os.path.join(directory, 'data/v3.0')
62+
sys.path.insert(0, falcon_project_dir)
63+
yield
64+
sys.path.remove(falcon_project_dir)
65+
66+
67+
@pytest.fixture
68+
def app():
69+
from falconproject.__main__ import app
70+
return app
71+
72+
73+
@pytest.fixture
74+
def client(app):
75+
return TestClient(app)

tests/integration/contrib/falcon/data/v3.0/falconproject/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from falcon import App
2+
3+
from falconproject.openapi import openapi_middleware
4+
from falconproject.resources import PetListResource, PetDetailResource
5+
6+
app = App(middleware=[openapi_middleware])
7+
8+
pet_list_resource = PetListResource()
9+
pet_detail_resource = PetDetailResource()
10+
11+
app.add_route("/v1/pets", pet_list_resource)
12+
app.add_route("/v1/pets/{petId}", pet_detail_resource)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pathlib import Path
2+
3+
from openapi_core import create_spec
4+
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
5+
import yaml
6+
7+
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
8+
spec_yaml = openapi_spec_path.read_text()
9+
spec_dict = yaml.load(spec_yaml)
10+
spec = create_spec(spec_dict)
11+
openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from json import dumps
2+
3+
from falcon.constants import MEDIA_JSON
4+
from falcon.status_codes import HTTP_200
5+
6+
7+
class PetListResource:
8+
def on_get(self, request, response):
9+
assert request.openapi
10+
assert not request.openapi.errors
11+
assert request.openapi.parameters.query == {
12+
'page': 1,
13+
'limit': 12,
14+
'search': '',
15+
}
16+
data = [
17+
{
18+
'id': 12,
19+
'name': 'Cat',
20+
'ears': {
21+
'healthy': True,
22+
},
23+
},
24+
]
25+
response.status = HTTP_200
26+
response.content_type = MEDIA_JSON
27+
response.text = dumps({"data": data})
28+
response.set_header('X-Rate-Limit', '12')
29+
30+
31+
class PetDetailResource:
32+
def on_get(self, request, response, petId=None):
33+
assert petId == '12'
34+
assert request.openapi
35+
assert not request.openapi.errors
36+
assert request.openapi.parameters.path == {
37+
'petId': 12,
38+
}
39+
data = {
40+
'id': 12,
41+
'name': 'Cat',
42+
'ears': {
43+
'healthy': True,
44+
},
45+
}
46+
response.status = HTTP_200
47+
response.content_type = MEDIA_JSON
48+
response.text = dumps({"data": data})
49+
response.set_header('X-Rate-Limit', '12')

tests/integration/contrib/falcon/data/v3.0/falcon_factory.yaml renamed to tests/integration/contrib/falcon/data/v3.0/openapi.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ paths:
2121
type: integer
2222
get:
2323
responses:
24-
200:
24+
'200':
2525
description: Return the resource.
2626
content:
2727
application/json:

0 commit comments

Comments
 (0)