Guide¶
The validation functions provided by this library are intended to be used at the head of public functions to check their arguments.
from validation import (
validate_int, validate_float,
validate_structure,
validate_text,
)
def function(int_arg, dict_arg, unicode_arg=None):
"""
A normal function that expects to be called in a particular way.
:param int int_arg:
A non-optional integer. Must be between one and ten.
:param dict dict_arg:
A dictionary containing an integer ID, and a floating point amount.
:param str unicode_arg:
An optional string.
"""
validate_int(int_arg, min_value=0, max_value=10)
validate_structure(dict_arg, schema={
'id': validate_int(min_value=0)
'amount': validate_float(),
})
validate_text(unicode_argument, required=False)
# Do something.
...
Exceptions raised by the validation functions are allowed to propagate through. Everything is inline, with no separate schema object or function.
Basics¶
All validators are functions which take a single value to check as their first argument, check its type and that it meets some preconditions, and raise an exception if they find something wrong.
>>> validation.validate_int(1)
>>> validation.validate_int(1.5)
Traceback (most recent call last):
...
TypeError: expected 'int', but value is of type 'float'
>>> validation.validate_int(-1, min_value=0)
Traceback (most recent call last):
...
ValueError: expected value greater than 0, but got -1
If the first argument is missing, validator functions will return a closure that can be called later to check a value.
>>> validator = validation.validate_int(min_value=0)
>>> validator(-1)
Traceback (most recent call last):
...
ValueError: expected value greater than 0, but got -1
This is important when using the datastructure validators.
Composing Validators¶
Datastructure validation functions usually accept as an argument a function to be applied to each of the values they contain.
>>> value = [1, -2]
>>> validate_list(value, validator=lambda v: validate_int(v, min_value=0))
Traceback (most recent call last):
...
ValueError: invalid item at position 1: expected value greater than 0 but got -1
Having validation functions return a validator closure means that this can be expressed more succinctly as:
>>> value = [1, -2]
>>> validate_list(value, validator=validate_int(min_value=0))
Traceback (most recent call last):
...
ValueError: invalid item at position 1: expected value greater than 0 but got -1
Mixing with python validation¶
This library provides a shorthand for performing simple checks on single variables. It does not prevent you from writing more checks in normal python! As an example, to validate two mutually exclusive arguments:
def do_something(arg_a=None, arg_b=None):
validation.validate_text(arg_a, required=False)
validation.validate_text(arg_b, required=False)
if arg_a is None == arg_b is None:
raise TypeError('arg_a and arg_b are mutually exclusive')
...
We recommend that the validation functions only be called with static constraints. Even where it is possible to use dynamic constraints to check a value
This example could be implemented by passing lower
as min_value
to
validate_int
when checking upper
, but there is a risk that the
def clamp(value, *, lower, upper):
validate_int(lower)
validate_int(upper)
if lower > upper:
raise ValueError((
'lower bound {lower!r} is greater than upper bound {upper!r}'
).format(lower=lower, upper=upper))
return min(max(lower, value), upper)
Validating iterators¶
The validation functions cannot handle iterators directly as attempting to do so would consume the iterator.
For the same reason we have not included a generic function for validating any iterable.
You have two options:
list
then validate_list
, or validate in loop.
def process_iterable_eager(iterable):
iterable = list(iterable)
validate_list(iterable, validator=validate_int())
for item in iterable:
# Do something.
...
def process_iterable_lazy(iterable):
for item in iterable:
validate_int(item)
# Do something
...
Tips¶
Avoid writing wrappers that hide details your code depends on.
Catch validation errors at the top level.
Alternate validation and assignment to make it clear when validation is missing.