Skip to content

Commit 9376b2e

Browse files
authored
Merge pull request #141 from bjmc/flask_params
Modify FlaskOpenAPIRequest to accomodate path variables
2 parents 0df1d05 + 3093364 commit 9376b2e

File tree

3 files changed

+101
-47
lines changed

3 files changed

+101
-47
lines changed

openapi_core/wrappers/flask.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
"""OpenAPI core wrappers module"""
2+
import re
3+
24
from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse
35

6+
# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
7+
PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>'
8+
49

510
class FlaskOpenAPIRequest(BaseOpenAPIRequest):
611

12+
path_regex = re.compile(PATH_PARAMETER_PATTERN)
13+
714
def __init__(self, request):
815
self.request = request
916

@@ -24,7 +31,7 @@ def path_pattern(self):
2431
if self.request.url_rule is None:
2532
return self.path
2633

27-
return self.request.url_rule.rule
34+
return self.path_regex.sub(r'{\1}', self.request.url_rule.rule)
2835

2936
@property
3037
def parameters(self):
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
openapi: "3.0.0"
2+
info:
3+
title: Basic OpenAPI specification used with test_wrappers.TestFlaskOpenAPIIValidation
4+
version: "0.1"
5+
servers:
6+
- url: 'http://localhost'
7+
paths:
8+
'/browse/{id}/':
9+
parameters:
10+
- name: id
11+
in: path
12+
required: true
13+
description: the ID of the resource to retrieve
14+
schema:
15+
type: integer
16+
get:
17+
responses:
18+
default:
19+
description: Return the resource.

tests/integration/test_wrappers.py

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,61 @@
1-
import pytest
2-
31
from flask.wrappers import Request, Response
42
from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict
53
from werkzeug.routing import Map, Rule, Subdomain
64
from werkzeug.test import create_environ
75

8-
from openapi_core.wrappers.flask import (
9-
FlaskOpenAPIRequest, FlaskOpenAPIResponse,
10-
)
11-
6+
import pytest
7+
from openapi_core.shortcuts import create_spec
8+
from openapi_core.validation.response.validators import ResponseValidator
9+
from openapi_core.validation.request.validators import RequestValidator
10+
from openapi_core.wrappers.flask import (FlaskOpenAPIRequest,
11+
FlaskOpenAPIResponse)
12+
13+
14+
@pytest.fixture
15+
def environ_factory():
16+
return create_environ
17+
18+
19+
@pytest.fixture
20+
def map():
21+
return Map([
22+
# Static URLs
23+
Rule('/', endpoint='static/index'),
24+
Rule('/about', endpoint='static/about'),
25+
Rule('/help', endpoint='static/help'),
26+
# Knowledge Base
27+
Subdomain('kb', [
28+
Rule('/', endpoint='kb/index'),
29+
Rule('/browse/', endpoint='kb/browse'),
30+
Rule('/browse/<int:id>/', endpoint='kb/browse'),
31+
Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
32+
])
33+
], default_subdomain='www')
1234

13-
class TestFlaskOpenAPIRequest(object):
1435

36+
@pytest.fixture
37+
def request_factory(map, environ_factory):
1538
server_name = 'localhost'
1639

17-
@pytest.fixture
18-
def environ_factory(self):
19-
return create_environ
40+
def create_request(method, path, subdomain=None, query_string=None):
41+
environ = environ_factory(query_string=query_string)
42+
req = Request(environ)
43+
urls = map.bind_to_environ(
44+
environ, server_name=server_name, subdomain=subdomain)
45+
req.url_rule, req.view_args = urls.match(
46+
path, method, return_rule=True)
47+
return req
48+
return create_request
2049

21-
@pytest.fixture
22-
def map(self):
23-
return Map([
24-
# Static URLs
25-
Rule('/', endpoint='static/index'),
26-
Rule('/about', endpoint='static/about'),
27-
Rule('/help', endpoint='static/help'),
28-
# Knowledge Base
29-
Subdomain('kb', [
30-
Rule('/', endpoint='kb/index'),
31-
Rule('/browse/', endpoint='kb/browse'),
32-
Rule('/browse/<int:id>/', endpoint='kb/browse'),
33-
Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
34-
])
35-
], default_subdomain='www')
3650

37-
@pytest.fixture
38-
def request_factory(self, map, environ_factory):
39-
def create_request(method, path, subdomain=None, query_string=None):
40-
environ = environ_factory(query_string=query_string)
41-
req = Request(environ)
42-
urls = map.bind_to_environ(
43-
environ, server_name=self.server_name, subdomain=subdomain)
44-
req.url_rule, req.view_args = urls.match(
45-
path, method, return_rule=True)
46-
return req
47-
return create_request
51+
@pytest.fixture
52+
def response_factory():
53+
def create_response(data, status_code=200):
54+
return Response(data, status=status_code)
55+
return create_response
4856

49-
@pytest.fixture
50-
def openapi_request(self, request):
51-
return FlaskOpenAPIRequest(request)
57+
58+
class TestFlaskOpenAPIRequest(object):
5259

5360
def test_simple(self, request_factory, request):
5461
request = request_factory('GET', '/', subdomain='www')
@@ -115,19 +122,13 @@ def test_url_rule(self, request_factory, request):
115122
assert openapi_request.host_url == request.host_url
116123
assert openapi_request.path == request.path
117124
assert openapi_request.method == request.method.lower()
118-
assert openapi_request.path_pattern == request.url_rule.rule
125+
assert openapi_request.path_pattern == '/browse/{id}/'
119126
assert openapi_request.body == request.data
120127
assert openapi_request.mimetype == request.mimetype
121128

122129

123130
class TestFlaskOpenAPIResponse(object):
124131

125-
@pytest.fixture
126-
def response_factory(self):
127-
def create_response(data, status_code=200):
128-
return Response(data, status=status_code)
129-
return create_response
130-
131132
def test_invalid_server(self, response_factory):
132133
response = response_factory('Not Found', status_code=404)
133134

@@ -137,3 +138,30 @@ def test_invalid_server(self, response_factory):
137138
assert openapi_response.data == response.data
138139
assert openapi_response.status_code == response._status_code
139140
assert openapi_response.mimetype == response.mimetype
141+
142+
143+
class TestFlaskOpenAPIValidation(object):
144+
145+
@pytest.fixture
146+
def flask_spec(self, factory):
147+
specfile = 'data/v3.0/flask_wrapper.yaml'
148+
return create_spec(factory.spec_from_file(specfile))
149+
150+
def test_response_validator_path_pattern(self,
151+
flask_spec,
152+
request_factory,
153+
response_factory):
154+
validator = ResponseValidator(flask_spec)
155+
request = request_factory('GET', '/browse/12/', subdomain='kb')
156+
openapi_request = FlaskOpenAPIRequest(request)
157+
response = response_factory('Some item', status_code=200)
158+
openapi_response = FlaskOpenAPIResponse(response)
159+
result = validator.validate(openapi_request, openapi_response)
160+
assert not result.errors
161+
162+
def test_request_validator_path_pattern(self, flask_spec, request_factory):
163+
validator = RequestValidator(flask_spec)
164+
request = request_factory('GET', '/browse/12/', subdomain='kb')
165+
openapi_request = FlaskOpenAPIRequest(request)
166+
result = validator.validate(openapi_request)
167+
assert not result.errors

0 commit comments

Comments
 (0)