Extending Cerberus

Though you can use functions in conjunction with the coerce and the validator rules, you can easily extend the Validator class with custom rules, types, validators, coercers and default_setters. While the function-based style is more suitable for special and one-off uses, a custom class leverages these possibilities:

  • custom rules can be defined with constrains in a schema
  • extending the available type s
  • use additional contextual data
  • schemas are serializable

The references in schemas to these custom methods can use space characters instead of underscores, e.g. {'foo': {'validator': 'is odd'}} is an alias for {'foo': {'validator': 'is_odd'}}.

Custom Rules

Suppose that in our use case some values can only be expressed as odd integers, therefore we decide to add support for a new isodd rule to our validation schema:

schema = {'amount': {'isodd': True, 'type': 'integer'}}

This is how we would go to implement that:

from cerberus import Validator

class MyValidator(Validator):
    def _validate_isodd(self, isodd, field, value):
        """ Test the oddity of a value.

        The rule's arguments are validated against this schema:
        {'type': 'boolean'}
        """
        if isodd and not bool(value & 1):
            self._error(field, "Must be an odd number")

By subclassing Cerberus Validator class and adding the custom _validate_<rulename> method, we just enhanced Cerberus to suit our needs. The custom rule isodd is now available in our schema and, what really matters, we can use it to validate all odd values:

>>> v = MyValidator(schema)
>>> v.validate({'amount': 10})
False
>>> v.errors
{'amount': ['Must be an odd number']}
>>> v.validate({'amount': 9})
True

As schemas themselves are validated, you can provide constraints as literal Python expression in the docstring of the rule’s implementing method to validate the arguments given in a schema for that rule. Either the docstring contains solely the literal or the literal is placed at the bottom of the docstring preceded by The rule's arguments are validated against this schema: See the source of the contributed rules for more examples.

Custom Data Types

Cerberus supports and validates several standard data types (see type). When building a custom validator you can add and validate your own data types.

For example Eve (a tool for quickly building and deploying RESTful Web Services) supports a custom objectid type, which is used to validate that field values conform to the BSON/MongoDB ObjectId format.

You extend the supported set of data types by adding a _validate_type_<typename> method to your own Validator subclass. This snippet, directly from Eve source, shows how the objectid has been implemented:

def _validate_type_objectid(self, value):
    """ Enables validation for `objectid` schema attribute.
    :param value: field value.
    """
    if re.match('[a-f0-9]{24}', value):
        return True

New in version 0.0.2.

Changed in version 1.0: The type validation logic changed, see Upgrading to Cerberus 1.0.

Custom Validators

If a validation test doesn’t depend on a specified constraint, it’s possible to rather define these as validators than as a rule. They are called when the validator rule is given a string as constraint. A matching method with the prefix _validator_ will be called with the field and value as argument:

def _validator_oddity(self, field, value):
    if not value & 1:
        self._error(field, "Must be an odd number")

Custom Coercers

You can also define custom methods that return a coerce d value or point to a method as rename_handler. The method name must be prefixed with _normalize_coerce_.

class MyNormalizer(Validator):
    def __init__(self, multiplier, *args, **kwargs):
        super(MyNormalizer, self).__init__(*args, **kwargs)
        self.multiplier = multiplier

    def _normalize_coerce_multiply(self, value):
        return value * self.multiplier
>>> schema = {'foo': {'coerce': 'multiply'}}
>>> document = {'foo': 2}
>>> MyNormalizer(2).normalized(document, schema)
{'foo': 4}

Custom Default Setters

Similar to custom rename handlers, it is also possible to create custom default setters.

from datetime import datetime

class MyNormalizer(Validator):
    def _normalize_default_setter_utcnow(self, document):
        return datetime.utcnow()
>>> schema = {'creation_date': {'type': 'datetime', 'default_setter': 'utcnow'}}
>>> MyNormalizer().normalized({}, schema)
{'creation_date': datetime.datetime(...)}

Limitations

It may be a bad idea to overwrite particular contributed rules.

Instantiating Custom Validators

To make use of additional contextual information in a sub-class of Validator, use a pattern like this:

class MyValidator(Validator):
    def __init__(self, *args, **kwargs):
        if 'additional_context' in kwargs:
            self.additional_context = kwargs['additional_context']
        super(MyValidator, self).__init__(*args, **kwargs)

    # alternatively define a property
    @property
    def additional_context(self):
        return self._config.get('additional_context', 'bar')

    def _validate_type_foo(self, field, value):
        make_use_of(self.additional_context)

This ensures that the additional context will be available in Validator child instances that may be used during validation.

New in version 0.9.

There’s a function validator_factory() to get a Validator mutant with concatenated docstrings.

New in version 1.0.

Relevant Validator-attributes

There are some attributes of a Validator that you should be aware of when writing custom Validators.

Validator.document

A validator accesses the document property when fetching fields for validation. It also allows validation of a field to happen in context of the rest of the document.

New in version 0.7.1.

Validator.schema

Alike, the schema property holds the used schema.

Note

This attribute is not the same object that was passed as schema to the validator at some point. Also, its content may differ, though it still represents the initial constraints. It offers the same interface like a dict.

Validator._error

There are three signatures that are accepted to submit errors to the Validator‘s error stash. If necessary the given information will be parsed into a new instance of ValidationError.

Full disclosure

In order to be able to gain complete insight into the context of an error at a later point, you need to call _error() with two mandatory arguments:

  • the field where the error occurred
  • an instance of a ErrorDefinition

For custom rules you need to define an error as ErrorDefinition with a unique id and the causing rule that is violated. See errors for a list of the contributed error definitions. Keep in mind that bit 7 marks a group error, bit 5 marks an error raised by a validation against different sets of rules.

Optionally you can submit further arguments as information. Error handlers that are targeted for humans will use these as positional arguments when formatting a message with str.format(). Serializing handlers will keep these values in a list.

New in version 1.0.

Simple custom errors

A simpler form is to call _error() with the field and a string as message. However the resulting error will contain no information about the violated constraint. This is supposed to maintain backward compatibility, but can also be used when an in-depth error handling isn’t needed.

Multiple errors

When using child-validators, it is a convenience to submit all their errors ; which is a list of ValidationError instances.

New in version 1.0.

Validator._get_child_validator

If you need another instance of your Validator-subclass, the _get_child_validator()-method returns another instance that is initiated with the same arguments as self was. You can specify overriding keyword-arguments. As the properties document_path and schema_path (see below) are inherited by the child validator, you can extend these by passing a single value or values-tuple with the keywords document_crumb and schema_crumb. Study the source code for example usages.

New in version 0.9.

Changed in version 1.0: Added document_crumb and schema_crumb as optional keyword- arguments.

Validator.root_document, .root_schema & root_allow_unknown

A child-validator - as used when validating a schema - can access the first generation validator’s document and schema that are being processed as well as the constraints for unknown fields via its root_document and root_schema root_allow_unknown-properties.

New in version 1.0.

Validator.document_path & Validator.schema_path

These properties maintain the path of keys within the document respectively the schema that was traversed by possible parent-validators. Both will be used as base path when an error is submitted.

New in version 1.0.

Validator.recent_error

The last single error that was submitted is accessible through the recent_error-attribute.

New in version 1.0.

Validator.mandatory_validations & Validator.priority_validations

You can override these class properties if you want to adjust the validation logic for each field validation. mandatory_validations is a tuple that contains rules that will be validated for each field, regardless if the rule is defined for a field in a schema or not. priority_validations is a tuple of ordered rules that will be validated before any other. If the validation method or function returns True, no further rule will be considered for that field.

New in version 1.0.