Cornice provides a way to control the request before it’s passed to the code. A validator is a simple callable that gets the request object and some keywords arguments, and fills request.errors in case the request isn’t valid.
Validators can also convert values and saves them so they can be reused by the code. This is done by filling the request.validated dictionary.
Once the request had been sent to the view, you can filter the results using so called filters. This document describe both concepts, and how to deal with them.
Some validators and filters are activated by default, for all the services. In case you want to disable them, or if you
You can register a filter for all the services by tweaking the DEFAULT_FILTER parameter:
from cornice.validators import DEFAULT_FILTERS
def includeme(config):
DEFAULT_FILTERS.append(your_callable)
(this also works for validators)
You also can add or remove filters and validators for a particular service. To do that, you need to define its default_validators and default_filters class parameters.
When validating inputs using the different validation mechanisms (described in this document), Cornice can return errors. In case it returns errors, it will do so in JSON by default.
The default returned JSON object is a dictionary of the following form:
{
'status': 'error',
'errors': errors
}
With errors
being a JSON dictionary with the keys “location”, “name” and
“description”.
You can override the default JSON error handler for a view with your own callable. The following function, for instance, returns the error response with an XML document as its payload:
def xml_error(request):
errors = request.errors
lines = ['<errors>']
for error in errors:
lines.append('<error>'
'<location>%(location)s</location>'
'<type>%(name)s</type>'
'<message>%(description)s</message>'
'</error>' % error)
lines.append('</errors>')
return HTTPBadRequest(body=''.join(lines),
content_type='application/xml')
Configure your views by passing your handler as error_handler
:
@service.post(validators=my_validator, error_handler=xml_error)
def post(request):
return {'OK': 1}
Cornice provide a simple mechanism to let you validate incoming requests before they are processed by your views.
Let’s take an example: we want to make sure the incoming request has an X-Verified header. If not, we want the server to return a 400:
from cornice import Service
foo = Service(name='foo', path='/foo')
def has_paid(request, **kwargs):
if not 'X-Verified' in request.headers:
request.errors.add('header', 'X-Verified', 'You need to provide a token')
@foo.get(validators=has_paid)
def get_value(request):
"""Returns the value.
"""
return 'Hello'
Notice that you can chain the validators by passing a sequence to the validators option.
You also can change the status code returned from your validators. Here is an example of this:
def user_exists(request):
if not request.POST['userid'] in userids:
request.errors.add('body', 'userid', 'The user id does not exist')
request.errors.status = 404
If you want to use class methods to do validation, you can do so by passing the klass parameter to the hook_view or @method decorators, plus a string representing the name of the method you want to invoke on validation.
Take care, though, because this only works if the class you are using has an __init__ method which takes a request as the first argument.
This means something like this:
class MyClass(object):
def __init__(self, request):
self.request = request
def validate_it(self, request, **kw):
# pseudo-code validation logic
if whatever is wrong:
request.errors.add('body', description="Something is wrong")
@service.get(klass=MyClass, validators=('validate_it',))
def view(request):
return "ok"
There are two flavors of media/content type validations Cornice can apply to services:
- Content negotiation checks if Cornice is able to respond with an appropriate response body content type requested by the client sending an
Accept
header. Otherwise it will croak with a406 Not Acceptable
.- Request media type validation will match the
Content-Type
request header designating the request body content type against a list of allowed content types. When failing on that, it will croak with a415 Unsupported Media Type
.
Validate the Accept
header in http requests
against a defined or computed list of internet media types.
Otherwise, signal 406 Not Acceptable
to the client.
By passing the accept argument to the service definition decorator, we define the media types we can generate http response bodies for:
@service.get(accept="text/html")
def foo(request):
return 'Foo'
When doing this, Cornice automatically deals with egress content negotiation for you.
If services don’t render one of the appropriate response body formats asked
for by the requests HTTP Accept header, Cornice will respond with a http
status of 406 Not Acceptable
.
The accept argument can either be a string or a list of accepted values made of internet media type(s) or a callable returning the same.
When a callable is specified, it is called before the request is passed to the destination function, with the request object as an argument.
The callable obtains the request object and returns a list or a single scalar value of accepted media types:
def _accept(request):
# interact with request if needed
return ("text/xml", "text/json")
@service.get(accept=_accept)
def foo(request):
return 'Foo'
When requests are rejected, an appropriate error response
is sent to the client using the configured error_handler.
To give the service consumer a hint about the valid internet
media types to use for the Accept
header,
the error response contains a list of allowed types.
When using the default json error_handler, the response might look like this:
{
'status': 'error',
'errors': [
{
'location': 'header',
'name': 'Accept',
'description': 'Accept header should be one of ["text/xml", "text/json"]'
}
]
}
Validate the Content-Type
header in http requests
against a defined or computed list of internet media types.
Otherwise, signal 415 Unsupported Media Type
to the client.
By passing the content_type argument to the service definition decorator, we define the media types we accept as http request bodies:
@service.post(content_type="application/json")
def foo(request):
return 'Foo'
All requests sending a different internet media type
using the HTTP Content-Type header will be rejected
with a http status of 415 Unsupported Media Type
.
The content_type argument can either be a string or a list of accepted values made of internet media type(s) or a callable returning the same.
When a callable is specified, it is called before the request is passed to the destination function, with the request object as an argument.
The callable obtains the request object and returns a list or a single scalar value of accepted media types:
def _content_type(request):
# interact with request if needed
return ("text/xml", "application/json")
@service.post(content_type=_content_type)
def foo(request):
return 'Foo'
The match is done against the plain internet media type string without
additional parameters like charset=utf-8
or the like.
When requests are rejected, an appropriate error response
is sent to the client using the configured error_handler.
To give the service consumer a hint about the valid internet
media types to use for the Content-Type
header,
the error response contains a list of allowed types.
When using the default json error_handler, the response might look like this:
{
'status': 'error',
'errors': [
{
'location': 'header',
'name': 'Content-Type',
'description': 'Content-Type header should be one of ["text/xml", "application/json"]'
}
]
}
You can also specify a way to deal with ACLs: pass in a function that takes a request and returns an ACL, and that ACL will be applied to all views in the service:
foo = Service(name='foo', path='/foo', acl=_check_acls)
Cornice can also filter the response returned by your views. This can be useful if you want to add some behaviour once a response has been issued.
Here is how to define a validator for a service:
foo = Service(name='foo', path='/foo', filters=your_callable)
You can just add the filter for a specific method:
@foo.get(filters=your_callable)
def foo_get(request):
"""some description of the validator for documentation reasons"""
pass
In case you would like to register a filter for all the services but one, you can use the exclude parameter. It works either on services or on methods:
@foo.get(exclude=your_callable)