Skip to content

Commit 6c3535c

Browse files
committed
Falcon2 support drop
1 parent 82738c3 commit 6c3535c

File tree

17 files changed

+136
-80
lines changed

17 files changed

+136
-80
lines changed

docs/integrations.rst

Lines changed: 4 additions & 3 deletions
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

Lines changed: 0 additions & 24 deletions
This file was deleted.

openapi_core/contrib/falcon/handlers.py

Lines changed: 1 addition & 2 deletions
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

Lines changed: 38 additions & 33 deletions
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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
"""OpenAPI core contrib falcon responses module"""
22
from json import dumps
33

4+
from falcon.constants import _UNSET
45
from werkzeug.datastructures import ImmutableMultiDict, Headers
56

6-
from openapi_core.contrib.falcon.compat import get_request_media
77
from openapi_core.validation.request.datatypes import (
88
OpenAPIRequest, RequestParameters,
99
)
1010

1111

1212
class FalconOpenAPIRequestFactory:
1313

14-
@classmethod
15-
def create(cls, request, default_when_empty={}):
14+
def __init__(self, default_when_empty=_UNSET):
15+
self.default_when_empty = default_when_empty
16+
17+
def create(self, request):
1618
"""
1719
Create OpenAPIRequest from falcon Request and route params.
1820
"""
19-
default = default_when_empty
2021
method = request.method.lower()
2122

22-
media = get_request_media(request, default=default)
23+
media = request.get_media(default_when_empty=self.default_when_empty)
2324
# Support falcon-jsonify.
2425
body = (
2526
dumps(getattr(request, "json", media))

openapi_core/contrib/falcon/responses.py

Lines changed: 1 addition & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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.20
77
djangorestframework==3.11.2

setup.cfg

Lines changed: 2 additions & 1 deletion
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

Lines changed: 24 additions & 1 deletion
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.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from falcon import App
2+
3+
from falconproject.openapi import openapi_middleware
4+
from falconproject.resources import BrowseDetailResource
5+
6+
app = App(middleware=[openapi_middleware])
7+
8+
resource = BrowseDetailResource()
9+
10+
app.add_route("/browse/{id}", resource)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import os
2+
3+
from openapi_core import create_spec
4+
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
5+
import yaml
6+
7+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8+
OPENAPI_SPEC_PATH = os.path.join(BASE_DIR, 'openapi.yaml')
9+
10+
with open(OPENAPI_SPEC_PATH) as file:
11+
spec_yaml = file.read()
12+
spec_dict = yaml.load(spec_yaml)
13+
spec = create_spec(spec_dict)
14+
openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 BrowseDetailResource:
8+
def on_get(self, request, response, id=None):
9+
assert id == '12'
10+
assert request.openapi
11+
assert not request.openapi.errors
12+
assert request.openapi.parameters.path == {
13+
'id': 12,
14+
}
15+
response.status = HTTP_200
16+
response.content_type = MEDIA_JSON
17+
response.body = dumps({"data": "test_val"})
18+
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

Lines changed: 1 addition & 1 deletion
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:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class TestBrowseDetailResource:
2+
3+
def test_get(self, client):
4+
headers = {'Content-Type': 'application/json'}
5+
6+
response = client.simulate_get(
7+
'/browse/12', host='localhost', headers=headers)
8+
9+
assert response.content == b'{"data": "test_val"}'

tests/integration/contrib/falcon/test_falcon_middlewares.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from json import dumps
22

3-
from falcon import API as App
3+
from falcon import App
44
from falcon.testing import TestClient
55
import pytest
66

@@ -15,7 +15,7 @@ class TestFalconOpenAPIMiddleware:
1515

1616
@pytest.fixture
1717
def spec(self, factory):
18-
specfile = 'contrib/falcon/data/v3.0/falcon_factory.yaml'
18+
specfile = 'contrib/falcon/data/v3.0/openapi.yaml'
1919
return create_spec(factory.spec_from_file(specfile))
2020

2121
@pytest.fixture

0 commit comments

Comments
 (0)