Skip to content

Othernet-Project/chainable-validators

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chainable Validators

This package contains a set of simple functions to facilitate creation of chainable validator methods for developing data validation schemata.

Although it provides a few validation function, it's main purpose is extensibility.

Installing

The PyPI package name is chainable-validators. You can install the package using pip or easy_install:

pip install chainable-validators

easy_install chainable-validators

Basic concepts

The basis of all validation are chainable validators (validation functions) and the validation chains which are created using make_chain() calls.

The chainable validators are found in the validators.validators module. For convenience, they can be imported directly from the validators package:

>>> from validators import required, istype, gte

The chainable validators can be used stand-alone or as part of a chain. For standalone usage, pass the value to the validator.

>>> required(None)
Traceback (most recent call last):
...
ValueError: required value missing

Some validators are parametric. They are first invoked with a parameter, and the return value is then used as a chainable validator.

>>> isint = istype(int)
>>> isint('foo')
Traceback (most recent call last):
...
ValueError: not int

To build a chain of desired validators, we first compile a list of chainable validators:

>>> from validators import make_chain
>>> fns = [required, istype(int), gte(2)]
>>> chain = make_chain(fns)
>>> chain(None)
Traceback (most recent call last):
...
ValueError: required value missing
>>> chain('1')
Traceback (most recent call last):
...
ValueError: not int
>>> chain(1)
Traceback (most recent call last):
...
ValueError: value too small
>>> chain(3)
3

The validators in the chain are invoked in order until the last one is called, or ValueError or validators.ReturnEarly is raised. When no exceptions are raised, the return value of the last chainable validator is returned. In case ReturnEarly is raised, it is not propagated to the chain's caller, but instead the original value is returned.

List of built-in validators

The following is a list of built-in vaidators.

Validators can be parametric or simple. Simple validators can be used directly. Parametric validators take an argument and return a chainable validator functions.

In the list below, if you encounter a validator that looks like foo(bar), it's a parametric validator.

  • optional(default=None) - interrupts validation chain if value is None or a user-supplied default value
  • required - rejects None
  • nonemtpy - rejects empty sequences (string, list, dict)
  • boolean - reject non-boolean values (other than True, False, 1, and 0)
  • istype(t) - rejects values that are not of type t
  • isin(collection) - rejects values that are not in collection (collection is a sequence such as string, list, or dict)
  • gte(num) - rejects values that are not greater than or equal to num
  • lte(num) - rejects values that are not less than or equal to num
  • match(regex) - rejects values that do not match the regex object (regex object is a valid re.RegExp instance or object with a match() method)
  • url - rejects values that are not URLs
  • timestamp(fmt) - rejects values that cannot be converted to datetime using datetime.strptime() and given format string

Helper functions

There are a few helper functions that help you modify the behavior or one or more functions in the chain.

  • OR(*fns) - rejects value if and only if all of the functions passed to it fail to validate, otherwise passes
  • NOT(fn) - reverses the behavior of fn

For example:

>>> my_chain = [foo, OR(bar, baz), NOT(fam)]

This chain will validate using foo first, then either bar or baz (whichever passes), then fam, reversing the results of fam (if it raises, then the validation will succeed and vice versa).

Spec validator

Another helper function, that is not mentioned in the previous section, is spec_validator() factory function. It takes a spec, which is a dict mapping key/attribute names to chains and returns a validator function that validates objects.

Let's take a look at an example, and then explain things as we go:

>>> import re
>>> from validator import *
>>> spec = {
...     'foo': [required, istype(int)],
...     'bar': [optional, match(re.compile(r'te.*')],
...     'baz': [optional, boolean]
... }

Each key in spec represents the key we expect to find in the object. The key could be a dictionary key, list/tuple index, or an object attribute. It could also be an arbitrary value based on which the value will be extracted.

The way keys map to values is defined by a key function which can be passed using the key argument. This function must accept a spec key name and return a function that returns the value given an object. The default key function is operator.itemgetter. For example, if we have an object that as attributes we want to validate, we could create the validator like so:

>>> import operator
>>> attr_validator = spec_validator(spec, key=operator.attrgetter)

Each key maps to an iterable which represents the validator chain. Chains are applied to values matching the key.

The spec_validator() function returns a validator function.

>>> validator = spec_validator(spec)

When passed the object to be validated, the validator function returns a dict which maps keys to any ValueError exceptions raised by the individual chains. If data is valid, the dict is empty.

>>> data = {'foo': 1, 'bar': 'test', 'baz': None}
>>> validator(data)
{}
>>> data['foo'] = None
>>> validator(data)
{'foo': ValueError('required value is missing')}

Thanks to this behavior, you can test whether object is valid, by testing if the returned dict is empty.

Writing your own validators

It is possible to write your own validators. To write a simple chainable validator, use the validators.chain.chainable decorator.

>>> from validators import chainable
>>> @chainable
... def my_validator(s):
...     if not s.startswith('foo'):
...         raise ValueError('does not start with foo')
...     return s
...
>>> my_validator('foobar')
'foobar'
>>> my_validator('barfoo')
Traceback (most recent call last):
...
ValueError: does not start with foo

To write a parametric validator, define the chainable validator in a closure:

>>> def my_parametric(start):
...     @chainable
...     def validator(s):
...         if not s.startswith(start):
...             raise ValueError('does not sart with {}'.format(start))
...         return s
...     return validator
...
>>> validator = my_parametric('baz')
>>> validator('bazfoo')
'bazfoo'
>>> validator('foo')
Traceback (most recent call last):
...
ValueError: does not sart with baz

Now you can use these validators in chains like other validators.

Reporting bugs

Please report any bugs or feature requests to the issue tracker.

About

Python data validation framework that uses chainable validator functions

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages