import logging
logger = logging.getLogger(__name__)
from functools import wraps
import json
import collections
# Flask is a test only dependency. We let people use this module without
# triggering a flask install.
try:
import flask
except ImportError:
logger.error(
'You must have flask installed to use this optional module'
)
raise
from ..operation import Operation
from ..errors import ValidationErrors
from ..request import BasicRequest
[docs]def convert_request(flask_request):
"""
Converts a flask :class:`flask.Request` to
a :class:`~spectastic.request.BasicRequest`
:return: :class:`~spectastic.request.BasicRequest`
"""
return BasicRequest(
flask.request.data,
flask.request.headers,
flask.request.args,
flask.request.path,
)
[docs]def default_responder(validation_errors):
"""
Generates a flask response from provided ``validation_errors``.
:param ~spectastic.errors.ValidationErrors validation_errors:
A instance containing an aggregate of all errors discovered during
request parsing.
"""
response_body = {"errors": []}
for error in validation_errors.errors:
response_body['errors'].append({
'msg': error.msg,
'location': error.location,
'field': error.field,
})
return flask.make_response(
(json.dumps(response_body), 400, {'Content-Type': 'application/json'})
)
[docs]def validate_route(schema, operation_id, responder=None):
"""
:param ~spectastic.schema.Schema schema:
The schema to use for validation. May also be a callable that receives
a flask request and returns a :class:`~spectastic.schema.Schema`.
:param string operation_id:
The operation id to validate. May also be a callable that receives
a flask request and returns a :class:`~spectastic.schema.Schema`.
"""
def decorator(wrapped_func):
@wraps(wrapped_func)
def _decorated_function(*args, **kwargs):
if responder is None:
_responder = default_responder
else:
_responder = responder
if _is_callable(schema):
_schema = schema(flask.request)
else:
_schema = schema
if _is_callable(operation_id):
_operation_id = operation_id(flask.request)
else:
_operation_id = operation_id
validation_response = _validate_route(
_schema, _operation_id, _responder
)
if validation_response:
return validation_response
else:
return wrapped_func(*args, **kwargs)
return _decorated_function
return decorator
def _validate_route(schema, operation_id, responder):
"""
Broken out into a function for decorator testability.
"""
operation = Operation.from_schema(schema, operation_id)
try:
operation.validate_request(convert_request(flask.request))
except ValidationErrors as e:
return responder(e)
return
def _is_callable(value):
"""
The method used by Python's 2-3 tool for detecting whether a value
is callable.
:return: bool
"""
return isinstance(value, collections.Callable)