Skip to content

morepath/more.cerberus

Repository files navigation

more.cerberus: validation and normalization support for Morepath

This package provides Morepath integration for the Cerberus data validation library:

Cerberus can automate user input validation and normalization in a HTTP API.

Schema

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']

Validate

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

Update models

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):

Customize the Validator

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.

Use the request or app instance in your custom validator

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)

Error handling

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

About

validation and normalization support for Morepath

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages