valid8
+ other tools¶
Other validation styles¶
The checktypes package provides a way to easily embed your validation requirements as classes, for easy reuse.
from valid8 import validate
from checktypes import checktype
PositiveInt = checktype('PositiveInt', int, lambda x: x > 0)
x = 1
validate('x', x, custom=PositiveInt.validate)
x = -1
validate('x', x, custom=PositiveInt.validate) # ValidationError
For functions and classes¶
PEP484 type checkers¶
Although simple type validation may be performed using valid8
, for function inputs/outputs type validation as well as class field type validation you may wish to rely on a proper PEP484 type checker such as enforce or pytypes.
The following snippet shows a build_house
function with two inputs name
and surface
, where each input is:
- validated against the expected type thanks to the PEP484 type checking library (enforce in this example)
- further value-validated with valid8 (
len(name) > 0
andsurface >= 0
), with the help of themini_lambda
syntax.
# Imports - for type validation
from numbers import Integral
from typing import Tuple, Optional
from enforce import runtime_validation, config
config(dict(mode='covariant')) # type validation will accept subclasses too
# Imports - for value validation
from mini_lambda import s, x, Len
from valid8 import validate_arg, is_multiple_of, InputValidationError
# Define our 2 applicative error types
class InvalidNameError(InputValidationError):
help_msg = 'name should be a non-empty string'
class InvalidSurfaceError(InputValidationError):
help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'
# Apply type + value validation
@runtime_validation
@validate_arg('name', Len(s) > 0, error_type=InvalidNameError)
@validate_arg('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
error_type=InvalidSurfaceError)
def build_house(name: str, surface: Optional[Integral]=None) \
-> Tuple[str, Optional[Integral]]:
print('Building house... DONE !')
return name, surface
We can test that validation works:
> build_house('sweet home', 200) # valid
> build_house('sweet home') # valid (PEP484 Optional, default=None)
> build_house('', 100) # InvalidNameError (valid8)
> build_house('sweet home', 10000) # InvalidSurfaceError (valid8)
> build_house('test', 100.1) # RuntimeTypeError (enforce)
Note concerning PEP484 type validation: how can you make sure to accept both plain old float
, int
and bool
, as well as their numpy
equivalents ? Use the handy Real
(=float) and Integral
(=int) abstract numeric types provided in the numbers
built-in module ! They provide an easy way to support both python primitives AND others, e.g. numpy primitives.
Unfortunately no equivalent type is provided in the stdlib for booleans, so in valid8
we provide an additional Boolean
class supporting numpy, to complete the picture.
For classes¶
pyfields¶
TODO
autoclass¶
valid8
plays well with autoclass to quickly create small but validated classes:
If you decorate the whole class with @validate_field
the property setters generated by @autoclass
will include validation, as expected:
from autoclass import autoclass
from mini_lambda import s, x, Len
from valid8 import validate_arg, instance_of, is_multiple_of
class InvalidNameError(ClassFieldValidationError):
help_msg = 'name should be a non-empty string'
class InvalidSurfaceError(ClassFieldValidationError):
help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'
@validate_field('name', instance_of(str), Len(s) > 0, error_type=InvalidNameError)
@validate_field('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
error_type=InvalidSurfaceError)
@autoclass
class House:
def __init__(self, name, surface=None):
pass
which yields:
> h = House('sweet home', 200)
> h.surface = None # Valid (surface is nonable by signature)
> h.name = '' # InvalidNameError
> h.surface = 10000 # InvalidSurfaceError
Note: if you decorate the class constructor with @validate_arg
, the property setters generated by @autoclass
will include validation too.
from autoclass import autoclass
from mini_lambda import s, x, Len
from valid8 import validate_arg, instance_of, is_multiple_of, InputValidationError
class InvalidNameError(InputValidationError):
help_msg = 'name should be a non-empty string'
class InvalidSurfaceError(InputValidationError):
help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'
@autoclass
class House:
@validate_arg('name', instance_of(str), Len(s) > 0,
error_type=InvalidNameError)
@validate_arg('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
error_type=InvalidSurfaceError)
def __init__(self, name, surface=None):
pass
which yields:
> h = House('sweet home', 200)
> h.surface = None # Valid (surface is nonable by signature)
> h.name = '' # InvalidNameError
> h.surface = 10000 # InvalidSurfaceError
Of course you can also add PEP484 type checking on top of that, see autoclass documentation for details.
attrs¶
valid8
also integrates well with attrs to quickly create small but validated classes: if you decorate a class with @validate_field
it will work because attrs
generates a compliant class constructor behind the scenes. However WARNING validation will only be called at initial object creation, not at subsequent field modifications!
import attr
from mini_lambda import s, x, Len
from valid8 import validate_field, instance_of, is_multiple_of
class InvalidNameError(ClassFieldValidationError):
help_msg = 'name should be a non-empty string'
class InvalidSurfaceError(ClassFieldValidationError):
help_msg = 'Surface should be a multiple of 100 between 0 and 10000.'
@validate_field('name', instance_of(str), Len(s) > 0, error_type=InvalidNameError)
@validate_field('surface', (x >= 0) & (x < 10000), is_multiple_of(100),
error_type=InvalidSurfaceError)
@attr.s
class House:
name = attr.ib()
surface = attr.ib(default=None)
Which we can test:
> h = House('sweet home') # Valid (surface is nonable by generated signature)
> h.name = '' # DOES NOT RAISE InvalidNameError (no setter!)
> House('', 10000) # InvalidNameError
> House('sweet home', 10000) # InvalidSurfaceError
As of today attrs
does not transform fields into descriptors or properties so there is no way to add validation to field setters. Note that this is actually also the case if you rely on the validation mechanisms built in attrs, as explained here. This feature has been requested here.