Skip to content

Instantly share code, notes, and snippets.

@danielholmstrom
Created May 8, 2014 11:28
Show Gist options
  • Save danielholmstrom/218ba0ad89d24034cb2e to your computer and use it in GitHub Desktop.
Save danielholmstrom/218ba0ad89d24034cb2e to your computer and use it in GitHub Desktop.
import copy
import functools
import json
from pyramid.httpexceptions import (
HTTPUnsupportedMediaType,
)
from colander import (
Invalid,
)
class ContentTypePredicate(object):
"""Predicate for content types
Sets request.content_type to the request content_type.
"""
def __init__(self, val, config):
self.val = val
def text(self):
return 'content type = {0}'.format(self.val)
phash = text
def __call__(self, context, request):
"""Check if the requests content type matches"""
return request.content_type == self.val
class RequestDataValidationError(Exception):
"""Error for when the data from the client was invalid"""
def __init__(self, errors=None, message=None, status=400):
"""Create a new Error
:param errors: Dict with validation errors \
(Same format as :class:`colander.Invalid.asdict`)
:param message: An error message
:param status: HTTP Status code
"""
self._errors = errors or {}
self.message = message
self.status = int(status)
@property
def asdict(self):
"""Get as dict"""
return {
'message': self.message and str(self.message),
'errors': copy.copy(self._errors),
}
class InvalidDataError(RequestDataValidationError):
"""Error for when the validation was a success but the data was invalid
Error for when validation of client data was a success but other
requirements for the data like a foreign key id, failed.
"""
pass
def request_data_invalid_response_adapter(request):
"""Adapter that converts a `RequestDataValidationError` to a json response
:returns: A response with content-type 'application/json'
"""
response = request.response
response.content_type = 'application/json'
response.status = request.exception.status
response.text = json.dumps(request.exception.asdict)
return response
def get_request_json_data(request):
"""Get request json data
:raises: HTTPUnsupportedMediaType If the request.content_type isn't json
:returns: The request json body or None
"""
if request.content_type != 'application/json':
raise HTTPUnsupportedMediaType(
"Content-Type '{0}' not supported".format(request.content_type))
else:
# Get json body or None
return getattr(request, 'json_body', None)
def validate_request_data(request, schema, error_message=None):
"""Validate data with a schema
:param request: The request
:param schema: Colander shema for validating the data
:param error_message: Additional error message if the validation failed
:raises: :class:`RequestDataValidationError` If data is None
:raises: :class:`RequestDataValidationError` If validation failed
:returns: Sanitized and validated data
"""
data = get_request_json_data(request)
if data is None:
raise RequestDataValidationError({}, message='Request data missing')
try:
return schema.deserialize(data)
except Invalid as e:
raise RequestDataValidationError(e.asdict(), message=error_message)
def client_data_schema(schema, message=None, status=400):
"""Adds client data validation to a view
The schema validates the client data, not route parameters.
The validated data will be added to the request under 'validated_data'
:returns: View wrapper
"""
def decorator(view):
@functools.wraps(view)
def wrapper(request):
try:
data = schema().deserialize(request.client_data())
except Invalid as e:
raise InvalidDataError(message=message,
errors=e.asdict(),
status=status)
setattr(request, 'validated_data', data)
return view(request)
return wrapper
return decorator
def validate(validator, message=None, status=400):
"""Run a validator before the view
The validator method should raise an error if validation failed.
The validator will be called like this: validator(request).
"""
def decorator(view):
@functools.wraps(view)
def wrapper(request):
validator(request)
return view(request)
return wrapper
return decorator
def includeme(config):
config.add_view_predicate('content_type', ContentTypePredicate)
config.add_view(request_data_invalid_response_adapter,
context=RequestDataValidationError)
config.add_request_method(get_request_json_data,
'client_data',
reify=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment