Skip to content

Response headers support in contrib #333

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
May 20, 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
8 changes: 7 additions & 1 deletion openapi_core/contrib/django/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
)


def get_headers(req):
def get_request_headers(req):
# in Django 1 headers is not defined
return req.headers if hasattr(req, 'headers') else \
HttpHeaders(req.META)


def get_response_headers(resp):
# in Django 2 headers is not defined
return resp.headers if hasattr(resp, 'headers') else \
dict(resp._headers.values())


def get_current_scheme_host(req):
# in Django 1 _current_scheme_host is not defined
return req._current_scheme_host if hasattr(req, '_current_scheme_host') \
Expand Down
4 changes: 2 additions & 2 deletions openapi_core/contrib/django/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from six.moves.urllib.parse import urljoin

from openapi_core.contrib.django.compat import (
get_headers, get_current_scheme_host,
get_request_headers, get_current_scheme_host,
)
from openapi_core.validation.request.datatypes import (
RequestParameters, OpenAPIRequest,
Expand Down Expand Up @@ -39,7 +39,7 @@ def create(cls, request):
path_pattern = '/' + route

path = request.resolver_match and request.resolver_match.kwargs or {}
headers = get_headers(request)
headers = get_request_headers(request)
parameters = RequestParameters(
path=path,
query=request.GET,
Expand Down
3 changes: 3 additions & 0 deletions openapi_core/contrib/django/responses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""OpenAPI core contrib django responses module"""
from openapi_core.contrib.django.compat import get_response_headers
from openapi_core.validation.response.datatypes import OpenAPIResponse


Expand All @@ -7,8 +8,10 @@ class DjangoOpenAPIResponseFactory(object):
@classmethod
def create(cls, response):
mimetype = response["Content-Type"]
headers = get_response_headers(response)
return OpenAPIResponse(
data=response.content,
status_code=response.status_code,
headers=headers.items(),
mimetype=mimetype,
)
1 change: 1 addition & 0 deletions openapi_core/contrib/falcon/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ def create(cls, response):
return OpenAPIResponse(
data=data,
status_code=status_code,
headers=response.headers,
mimetype=mimetype,
)
1 change: 1 addition & 0 deletions openapi_core/contrib/flask/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ def create(cls, response):
return OpenAPIResponse(
data=response.data,
status_code=response._status_code,
headers=response.headers,
mimetype=response.mimetype,
)
2 changes: 2 additions & 0 deletions openapi_core/contrib/requests/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ class RequestsOpenAPIResponseFactory(object):
@classmethod
def create(cls, response):
mimetype = response.headers.get('Content-Type')
headers = dict(response.headers)
return OpenAPIResponse(
data=response.content,
status_code=response.status_code,
headers=headers,
mimetype=mimetype,
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def get(self, request, pk):
"test": "test_val",
}
django_response = JsonResponse(response_dict)
django_response['X-Rate-Limit'] = '12'

openapi_response = DjangoOpenAPIResponse(django_response)
validator = ResponseValidator(spec)
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/contrib/django/data/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ paths:
type: string
required:
- test
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
parameters:
- required: true
in: path
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/contrib/falcon/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ def create_request(
@pytest.fixture
def response_factory(environ_factory):
def create_response(
data, status_code=200, content_type='application/json'):
data, status_code=200, headers=None,
content_type='application/json'):
options = ResponseOptions()
resp = Response(options)
resp.body = data
resp.content_type = content_type
resp.status = HTTP_200
resp.set_headers(headers or {})
return resp
return create_response
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ paths:
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/contrib/falcon/test_falcon_middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def view_response_callable(request, response, id):
response.content_type = MEDIA_HTML
response.status = HTTP_200
response.body = 'success'
response.set_header('X-Rate-Limit', '12')
self.view_response_callable = view_response_callable
headers = {'Content-Type': 'application/json'}
result = client.simulate_get(
Expand Down Expand Up @@ -193,6 +194,7 @@ def view_response_callable(request, response, id):
response.body = dumps({
'data': 'data',
})
response.set_header('X-Rate-Limit', '12')
self.view_response_callable = view_response_callable

headers = {'Content-Type': 'application/json'}
Expand Down
5 changes: 4 additions & 1 deletion tests/integration/contrib/falcon/test_falcon_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ def test_response_validator_path_pattern(self,
validator = ResponseValidator(spec)
request = request_factory('GET', '/browse/12', subdomain='kb')
openapi_request = FalconOpenAPIRequestFactory.create(request)
response = response_factory('{"data": "data"}', status_code=200)
response = response_factory(
'{"data": "data"}',
status_code=200, headers={'X-Rate-Limit': '12'},
)
openapi_response = FalconOpenAPIResponseFactory.create(response)
result = validator.validate(openapi_request, openapi_response)
assert not result.errors
Expand Down
7 changes: 5 additions & 2 deletions tests/integration/contrib/flask/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def create_request(method, path, subdomain=None, query_string=None):
@pytest.fixture
def response_factory():
def create_response(
data, status_code=200, content_type='application/json'):
return Response(data, status=status_code, content_type=content_type)
data, status_code=200, headers=None,
content_type='application/json'):
return Response(
data, status=status_code, headers=headers,
content_type=content_type)
return create_response
6 changes: 6 additions & 0 deletions tests/integration/contrib/flask/data/v3.0/flask_factory.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ paths:
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
Expand Down
8 changes: 6 additions & 2 deletions tests/integration/contrib/flask/test_flask_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def view_response_callable(*args, **kwargs):
assert request.openapi.parameters == RequestParameters(path={
'id': 12,
})
return make_response('success', 200)
resp = make_response('success', 200)
resp.headers['X-Rate-Limit'] = '12'
return resp
self.view_response_callable = view_response_callable
result = client.get('/browse/12/')

Expand Down Expand Up @@ -172,7 +174,9 @@ def view_response_callable(*args, **kwargs):
assert request.openapi.parameters == RequestParameters(path={
'id': 12,
})
return jsonify(data='data')
resp = jsonify(data='data')
resp.headers['X-Rate-Limit'] = '12'
return resp
self.view_response_callable = view_response_callable

result = client.get('/browse/12/')
Expand Down
5 changes: 4 additions & 1 deletion tests/integration/contrib/flask/test_flask_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ def test_response_validator_path_pattern(self,
validator = ResponseValidator(flask_spec)
request = request_factory('GET', '/browse/12/', subdomain='kb')
openapi_request = FlaskOpenAPIRequest(request)
response = response_factory('{"data": "data"}', status_code=200)
response = response_factory(
'{"data": "data"}',
status_code=200, headers={'X-Rate-Limit': '12'},
)
openapi_response = FlaskOpenAPIResponse(response)
result = validator.validate(openapi_request, openapi_response)
assert not result.errors
Expand Down
24 changes: 24 additions & 0 deletions tests/integration/contrib/flask/test_flask_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def view(self, app, details_view_func, list_view_func):

def test_invalid_content_type(self, client):
self.view_response = make_response('success', 200)
self.view_response.headers['X-Rate-Limit'] = '12'

result = client.get('/browse/12/')

Expand Down Expand Up @@ -158,8 +159,31 @@ def test_endpoint_error(self, client):
assert result.status_code == 400
assert result.json == expected_data

def test_missing_required_header(self, client):
self.view_response = jsonify(data='data')

result = client.get('/browse/12/')

expected_data = {
'errors': [
{
'class': (
"<class 'openapi_core.exceptions."
"MissingRequiredHeader'>"
),
'status': 400,
'title': (
"Missing required header: X-Rate-Limit"
)
}
]
}
assert result.status_code == 400
assert result.json == expected_data

def test_valid(self, client):
self.view_response = jsonify(data='data')
self.view_response.headers['X-Rate-Limit'] = '12'

result = client.get('/browse/12/')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ paths:
properties:
data:
type: string
headers:
X-Rate-Limit:
description: Rate limit
schema:
type: integer
required: true
default:
description: Return errors.
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_response_validator_path_pattern(self, spec):
responses.add(
responses.POST, 'http://localhost/browse/12/?q=string',
json={"data": "data"}, status=200, match_querystring=True,
headers={'X-Rate-Limit': '12'},
)
validator = ResponseValidator(spec)
request = requests.Request(
Expand Down