This package provides Morepath integration for the Cerberus data validation library:
Cerberus can automate user input validation and normalization in a HTTP API.
You can define a schema simply as a Python dict:
user_schema = {
'name': {'type': 'string', 'minlength' : 3, 'required': True},
'age': {'type': 'integer', 'min': 0, 'required': True}
}
Altenatively you can define the schema in yaml and load it with pyyaml:
user:
name:
type: string
minlength: 3
required: true
age:
type: integer
min: 0
required: true
import yaml
with open('schema.yml') as schema:
schema = yaml.load(schema)
user_schema = schema['user']
The more.cerberus
integration helps
with validation of the request body as it is POSTed or PUT to a view.
First we must create a loader for our schema:
from more.cerberus import loader
user_schema_load = loader(user_schema)
We can use this loader to handle a PUT or POST request for instance:
@App.json(model=User, request_method='POST', load=user_schema_load)
def user_post(self, request, json):
# json is now a validated and normalized dict of whatever got
# POST onto this view that you can use to update
# self
By default in PUT or PATCH requests the load
function
sets the update
flag of the validate()
method to True
,
so required fields won’t be checked. For other requests like
POST update
is False
.
You can set this manually by passing the update
argument
to the load
function:
user_schema_load = loader(user_schema, update=False)
@App.json(model=User, request_method='PUT', load=user_schema_load)
def user_put(self, request, json):
With Cerberus you can customize the rules, data types, validators, coercers (for normalization) and default setters by subclassing CerberusValidator:
import re
from more.cerberus import CerberusValidator
class CustomValidator(CerberusValidator):
def _check_with_validate_email(self, field, value):
match = re.match(
'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$',value
)
if match == None:
self._error(field, 'Not valid email')
def _normalize_coerce_normalize_email(self, value):
parts = value.split('@')
if len(parts) != 2:
return value
else:
domain = parts[1].lower
if domain == 'googlemail.com':
domain = 'gmail.com'
return parts[0] + '@' + domain
You have to pass the custom Validator class to the load
function:
user_schema_load = loader(user_schema, validator=CustomValidator)
Now you can use the new email validator and normalizer in your schema:
user_schema = {
'name': {
'type': 'string',
'minlength' : 3,
'required': True,
},
'email': {
'type': 'string',
'check_with': 'validate_email',
'coerce': 'normalize_email',
'required': True,
}
}
or with YAML:
user:
name:
type: string
minlength: 3
required: true
email:
type: string
check_with: validate_email
coerce: normalize_email
required: true
For more information how to customize the Validator take a look at the Cerberus documentation.
In CerberusValidator
you can access the request
through
self.request
and the app
through self.request.app
.
Like this you can use e.g. Morepath settings and services when
extending rules.
Here an example from auth-boilerplate for custom email validation and normalization using a service based on email_validator:
from more.cerberus import CerberusValidator
from email_validator import EmailSyntaxError, EmailUndeliverableError
class Validator(CerberusValidator):
def _check_with_verify_email(self, field, value):
email_validation_service = self.request.app.service(
name='email_validation'
)
try:
email_validation_service.verify(value)
except EmailSyntaxError:
self._error(field, 'Not valid email')
except EmailUndeliverableError:
self._error(field, 'Email could not be delivered')
def _normalize_coerce_normalize_email(self, value):
email_validation_service = self.request.app.service(
name='email_validation'
)
return email_validation_service.normalize(value)
If validation fails due to a validation error (a required field is
missing, or a field is of the wrong datatype, for instance), you want
to show some kind of error message. The load
function created by
more.cerberus
raises the more.cerberus.ValidationError
exception
in case of errors.
This exception object has an errors
attribute with the validation errors.
You must define an exception view for it, otherwise validation errors are
returned as "500 internal server error" to API users.
This package provides a default exception view implementation. If you subclass
your application from more.cerberus.CerberusApp
then you get a default
error view for ValidationError
that has a 422 status code with a JSON
response with the Cerberus errors structure:
from more.cerberus import CerberusApp
class App(CerberusApp):
pass
Now your app has reasonable error handling built-in.
If you want a different error view you can instead create it by yourself, e.g.:
from more.cerberus.error import ValidationError
from .app import App
@App.json(model=ValidationError)
def validation_error(self, request):
@request.after
def set_status(response):
response.status = 422
errors = list(self.errors.values())[0][0]
return {
'errors': errors
}
This could be used to extract the errors from a schema wrapped into a dictionary like:
article-schema:
article:
type: dict
schema:
title:
type: string
required: true
body:
type: string
required: true