Notario

notario is a validation engine for Python dictionaries, it offers very succinct and flexible schemas and a very powerful way to express invalid data in exceptions.

Install it with pip:

pip install notario

In its most simple example, this is how you would validate a single key, value pair with notario:

>>> from notario import validate
>>> data = {'key': 'value'}
>>> schema = ('key', 'value')
>>> validate(data, schema)

And this is how it would look when it fails:

>>> data = {'foo': 1}
>>> schema = ('foo', 'bar')
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> 1  did not match bar

Getting started

You should know that writing notario has a few expectations that you should meet in order to be able to allow the engine to work as expected:

  • Data to be validated is automatically sorted alphabetically by key
  • Schemas must be written matching the alphabetical order of the data to be validated.
  • Schemas must always be tuples representing key value pairs
  • Data can contain any kind of data structure except for tuples.

Writing schemas can get overly verbose. Consider other validators where you need to define if a value is required or optional or that it should be of a certain length, minimum item count, or maximum values. This is crazy.

notario allows you to have callables that should be written to accept a single required argument and assert whatever you need to make sure the value complies with the expectation. The following example is how one of the validators from notario itself is written to make sure a value is a string:

def string(value):
    """
    Validates a given input is of type string.
    """
    assert isinstance(value, basestring), "not of type string"

If the value passed in is not a string, it will raise an AsssertionError and notario will catch this and report back where and how it failed.

This is how you would use it:

>>> from notario.validators import types
>>> from notario.exceptions import Invalid
>>> data = {'foo': 1}
>>> schema = (types.string, types.integer)
>>> from notario import validate
>>> validate(data, schema)

And when it fails, it would actually tell you where it did and against what, in the below example we change the value of the schema to be of types.string and not integer, forcing an Invalid exception:

>>> schema = (types.string, types.string)
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo  did not pass validation against callable: string

Custom messaging

There is no need to use the reporting string from the exception as-is. In the example string() validator, we used a custom message: “not of type string” which can be used when the validation fails:

>>> try:
...     validate(data, schema)
... except Invalid as e:
...     print e.reason
...
not of type string

Validators

notario comes with a few validators, most of them provide a low level validation model so you can build your own custom validators on top prividing you with a lot of flexibility. These are all the current validators available:

Handling nested data

When data has deeply nested structures, it might be a good idea to use the recursive validators as they can provide a way for validation of all (or any) of the nested items in a given value.

For example, if you have some data that looks like:

{
     'a': {
       'boo' : {
         'bar': True,
         'baz': False
         },
       'foo': {
         'bar': True,
         'baz': False
         },
       'yoo' : {
         'bar': True,
         'baz': False
       }
    }
}

You wouldn’t want to write a schema to match every single item inside the value for the 'a' key. To take advantage of the recursive validators, you first identify that all the objects have a similar structure and write a schema that matches that.

This is how a schema with the recursive validator would look like:

>>> from notario.validators import recursive, types
>>> schema = (
...           'a', recursive.AllObjects(
...                         (types.string, (
...                             ('bar', types.boolean),
...                             ('baz', types.boolean)))
...                         )
...         )
>>> validate(data, schema)

What the above schema says, is that all of the objects of the 'a' value should have:

  1. a single key (of string type)
  2. With a single dict object containing 2 keys
  3. One of them being 'bar'
  4. The other one 'baz'
  5. Both these keys should have boolean values.

Just as important to pass validation, is equally important to see what happens when it fails. Lets change the type from boolean to string for one of the expected value in 'baz' and see what happens:

>>> schema = (
...           'a', recursive.AllObjects(
...                         (types.string, (
...                             ('bar', types.string),
...                             ('baz', types.boolean)))
...                         )
...         )
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> a -> boo -> bar  did not pass validation against callable: string

As you can see by the output, when whe changed 'bar' to enforce a string and received a boolean, the exception message told you exactly where the failure was.

API

Recursive Validators

class notario.validators.recursive.AllObjects(schema)

For all the objects contained in a dictionary apply the schema passed in to the validator. If a single item object fails, it raises Invalid.

Example usage for single values:

data = {'foo': {{'a':10}, {'a':20}, {'a':20}}}
schema = ('foo', AllObjects(('a', 20)))
validate(data, schema)

Example usage for other data structures:

data = {'foo': [{'a': 1}, {'a': 1}]}
schema = ('foo', AllObjects(('a', 1))
validate(data, schema)

When a single item in the array fails to pass against the validator’s schema it stops further iteration and it will raise an error like:

>>> data = {'foo': {'a':{'a':10}, 'b':{'a':20}, 'c':{'a':20}}}
>>> schema = ('foo', AllObjects(('a', ('a', 90))))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> a -> a -> 10  did not match 90

In this particular validator, it remembers on what key of the dict object the failure was created and it goes even further giving the key and value of the object it went against.

class notario.validators.recursive.AnyObject(schema)

Go over all the items in an dict object and make sure that at least one of the items validates correctly against the schema provided. If no items pass it raises Invalid.

Example usage for single values:

data = {'foo': {{'a':10}, {'b':20}, {'c':40}}}
schema = ('foo', AnyObject(('a', 10)))
validate(data, schema)

Example usage for other data structures:

data = {'foo': [{'b': 10}, {'a': 1}]}
schema = ('foo', AnyObject(('a', 1))
validate(data, schema)

When a single item in the array fails to pass against the validator’s schema it stops further iteration and it will raise an error like:

>>> data = {'foo': {'a':{'a':10}, 'b':{'a':20}, 'c':{'a':20}}}
>>> schema = ('foo', AnyObject(('a', ('a', 90))))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: foo -> a  did not contain any valid objects against callable: AnyObject

In this particular validator, it remembers on what key of the dict object the failure was created and it goes even further giving the key and value of the object it went against.

class notario.validators.recursive.BasicRecursiveValidator(schema)

Base class for recursive validators, can be sub-classed for other type of recursive validators but should not be used directly.

class notario.validators.recursive.MultiRecursive(*schemas)

This validator is useful when there is a need for validating any number of schemas for a given data set.

If the requirement is, for example, to validate either string: string or string: int this will not be possible with any of the other validators in Notario because they all assume a rule that must dominate everything. Otherwise, it is required to specify the expectation.

In the case that the object to validate complies with the above requirement, this validator can accept any number of schemas as arguments, so if one schema fails, the next one will be tried until all the schemas are applied for a given item. The MultiRecursive validator will look like this in order to pass the incoming data:

>>> data = {'main': {'foo': 'bar'}}
>>> schema = ('main', MultiRecursive(('foo', 1), ('foo', 'bar')))
>>> validate(data, schema)

Because we can’t be sure what the data may hold we are forced to define different rules and apply them so that they can pass. If we repeat the expectation but an invalid value comes along, the validator will naturally fail:

>>> data = {'main': {'foo': False}}
>>> schema = ('main', MultiRecursive(('foo', 1), ('foo', 'bar')))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> False did not match 'bar'

Iterable Validators

Iterable validators for array objects only. They provide a way of applying a schema to any given items in an array.

class notario.validators.iterables.AllItems(schema)

For all the items in an array apply the schema passed in to the validator. If a single item fails, it raises Invalid.

Note

It only works on arrays, otherwise it will raise a SchemaError

Example usage for single values:

data = {'foo' : [10, 10, 10]}
schema = ('foo', AllItems(10))
validate(data, schema)

Example usage for other data structures:

data = {'foo': [{'a': 1}, {'a': 1}]}
schema = ('foo', AllItems(('a', 1))
validate(data, schema)

When a single item in the array fails to pass against the validator’s schema it stops further iteration and it will raise an error like:

>>> data = {'foo': [{'a': 1}, {'a': 2}]}
>>> schema = ('foo', AllItems(('a', 1)))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> list[1] -> a -> 2  did not match 1

In this particular validator, it remembers on what index of the array the failure was created and it goes even further giving the key and value of the object it went against.

class notario.validators.iterables.AnyItem(schema)

Go over all the items in an array and make sure that at least one of the items validates correctly against the schema provided. If no items pass it raises Invalid.

Note

It only works on arrays, otherwise it will raise a SchemaError

Example usage for single values:

data = {'foo' : [10, 30, 50]}
schema = ('foo', AnyItem(50))
validate(data, schema)

Example usage for other data structures:

data = {'foo': [{'a': 1}, {'b': 2}]}
schema = ('foo', AnyItem(('b', 2))
validate(data, schema)

When a single item in the array matches correctly against the validator’s schema it stops further iteration and the validation passes. Otherwise it will raise an error like:

>>> data = {'foo': [{'a': 1}, {'b': 2}]}
>>> schema = ('foo', AnyItem(('c', 4)))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> list[]  did not contain any valid items matching ('c', 4)
class notario.validators.iterables.BasicIterableValidator(schema)

Base class for iterable validators, can be sub-classed for other type of iterable validators but should not be used directly.

safe_type(data, tree)

Make sure that the incoming data complies with the class type we are expecting it to be. In this case, classes that inherit from this base class expect data to be of type list.

class notario.validators.iterables.MultiIterable(*schemas)

This validator is useful when there is a need for validating any number of schemas for a given data set.

If the requirement is, for example, to validate either string: string or string: int this will not be possible with any of the other validators in Notario because they all assume a rule that must dominate everything. Otherwise, it is required to specify the expectation.

In the case that the object to validate complies with the above requirement, this validator can accept any number of schemas as arguments, so if one schema fails, the next one will be tried until all the schemas are applied for a given item. The MultiRecursive validator will look like this in order to pass the incoming data:

>>> data = {'main': {'foo': 'bar'}}
>>> schema = ('main', MultiIterable(('foo', 1), ('foo', 'bar')))
>>> validate(data, schema)

Because we can’t be sure what the data may hold we are forced to define different rules and apply them so that they can pass. If we repeat the expectation but an invalid value comes along, the validator will naturally fail:

>>> data = {'main': [{'foo': False}]}
>>> schema = ('main', MultiIterable(('foo', 1), ('foo', 'bar')))
>>> validate(data, schema)
Traceback (most recent call last):
...
Invalid: -> foo -> False did not match 'bar'

Type Validators

Basic type validators

notario.validators.types.array(_object)

Validates a given input is of type list.

Example usage:

data = {'a' : [1,2]}
schema = ('a', array)

You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing.

Note

If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function.

notario.validators.types.boolean(_object)

Validates a given input is of type boolean.

Example usage:

data = {'a' : True}
schema = ('a', boolean)

You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing.

Note

If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function.

notario.validators.types.dictionary(_object, *args)

Validates a given input is of type dictionary.

Example usage:

data = {'a' : {'b': 1}}
schema = ('a', dictionary)

You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing.

Note

If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function.

notario.validators.types.integer(_object)

Validates a given input is of type int..

Example usage:

data = {'a' : 21}
schema = ('a', integer)

You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing.

Note

If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function.

notario.validators.types.string(_object)

Validates a given input is of type string.

Example usage:

data = {'a' : 21}
schema = (string, 21)

You can also use this as a decorator, as a way to check for the input before it even hits a validator you may be writing.

Note

If the argument is a callable, the decorating behavior will be triggered, otherwise it will act as a normal function.

Chainable Validators

Chainable validators are encapsulating validators. They usually will not validate per se, but can contain other validators inside them and pass the value to them.

class notario.validators.chainable.AllIn(*args)

Validates against all the validators passed in. This chainable validator will pass in the actual to every single validator that is contained as an argument.

Example usage:

from notario.validators import types

data = {'foo' : "some string"}
schema = ('foo', AllIn(types.string))
validate(data, schema)

When more than one validator needs to be chained this validator can take it in as another argument. Lets say that you have a validator that specifies a minimum length a maxium length and that it starts with the letter ‘s’:::

data = {'foo' : "some string"}
schema = ('foo', AllIn(min_length, max_length, StartsWith('s'))
validate(data, schema)
Raises:TypeError if the validator is not a callable
class notario.validators.chainable.AnyIn(*args)

If any contained validator passes it skips any others, even if those others might fail at some point. If no validators pass at the end it fails pointing out that the AnyIn validator was not able to pass against any contained validator.

Raises:TypeError if the validator is not a callable
class notario.validators.chainable.BasicChainValidator(*args)

The base chainable validator, should not be used directly but can be sub-classed to extend into custom chainable validators.

Optional Validators

notario.utils.optional(validator)

Other Decorators

notario.decorators.delay(func)

When schemas are referencing to each other, this decorator will help by marking a schema as delayed to avoid the need for calling a schema to generate itself until it is actually needed.

For example, if a schema function references to itself in this manner:

def my_schema():
    return (
        ('a', 'foo'),
        ('b', my_schema()),
    )

Because my_schema is being called within itself, it will get into a recursion problem as soon as it is executed.

To avoid this, applying the decorator will make it so that the engine will acknowledge this is the case, and will expand the schema only when it is needed. No recursion problems will happen then since we are effectively delaying its execution.

class notario.decorators.instance_of(valid_types=None)

When trying to make sure the value is coming from any number of valid objects, you will want to use this decorator as it will make sure that before executing the validator it will comply being of any of the valid_types.

For example, if the input for a given validator can be either a dictionary or a list, this validator could be used like:

from notario import ensure
@instance_of((list, dict))
def my_validator(value):
    ensure(len(value) > 0)

This decorator needs to be called as it has a default for valid types, which is: (list, dict, str). A working implementation would look like this with the default types:

from notario import ensure
@instance_of()
def my_validator(value):
    ensure(len(value) > 0)

When it fails, as almost all of Notario’s exceptions, it will return a meaningful error, this is how passing a boolean to a validator that accepts the defaults would raise said error:

>>> from notario.decorators import instance_of
>>> from notario import ensure
>>> @instance_of()
... def my_validator(value):
...     ensure(len(value) == 2)
...
>>> my_validator(True)
Traceback (most recent call last):
...
AssertionError: not of any valid types: ['list', 'dict', 'str']
notario.decorators.not_empty(_object)

Validates the given input (has to be a valid data structure) is empty. Input has to be one of: list, dict, or string.

It is specially useful when most of the validators being created are dealing with data structures that should not be empty.

notario.decorators.optional(_object)

This decorator has a double functionality, it can wrap validators and make them optional or it can wrap keys and make that entry optional.

Optional Validator: Allows to have validators work only when there is a value that contains some data, otherwise it will just not pass the information to the actual validator and will not fail as a result.

As any normal decorator, it can be used corectly with the decorator syntax or in the actual schema.

This is how it would look in a schema:

('key', optional(my_validator))

Where my_validator can be any validator that accepts a single argument.

In case a class based validator is being used (like the recursive or iterables then it would look like:

('key', optional(class_validator(('key', 'value'))))

Of course, the schema should vary depending on your needs, it is just the way of constructing the validator call that should be important.

Optional Keys: Sometimes a given data structure may present optional entries. For example this data:

data = {'required': 1, 'optional': 2}

To represent this, you will need to declare the optional key in the schema but by wrapping the key with this decorator you will basically tell the validation engine that if that key is present it should be validated, otherwise, it should be skipped. This is how the schema would look:

schema = (('required', 1), (optional('optional'), 1))

The above schema would allow data that is missing the optional key. The data below would pass validation without any issues:

data = {'required': 1}
Fork me on GitHub