Using validators

Pankoff defines a small preset of validator, and it allow you to define your own.

Default validators

By default, Pankoff defines a few validators, String, Number, Type, Sized, Predicate, LazyLoad. We’ll go over each one below.

class pankoff.validators.String

Validate whether field is instance of type str.

>>> @autoinit
>>> class Person:
...     name  = String()
>>> person = Person(name="Guido")
class pankoff.validators.List

Validate whether field is instance of type list.

>>> @autoinit
>>> class Person:
...     items  = List()
>>> person = Person(items=[1, 2, 3])
class pankoff.validators.Dict(required_keys=None)

Validate whether field is instance of type dict.

>>> @autoinit
>>> class Person:
...     mapping  = Dict(required_keys=["name"])
>>> person = Person(mapping={"name": "Guido"})
class pankoff.validators.Tuple

Validate whether field is instance of type tuple.

>>> @autoinit
>>> class Person:
...     items  = Tuple()
>>> person = Person(items=(1, 2, 3))
class pankoff.validators.Iterable

Check whether field supports collections.abc.Iterable interface.

class pankoff.validators.Container

Check whether field supports collections.abc.Container interface.

class pankoff.validators.Hashable

Check whether field supports collections.abc.Hashable interface.

class pankoff.validators.Iterator

Check whether field supports collections.abc.Iterator interface.

class pankoff.validators.Reversible

Check whether field supports collections.abc.Reversible interface.

class pankoff.validators.Generator

Check whether field supports collections.abc.Generator interface.

class pankoff.validators.Callable

Check whether field supports collections.abc.Callable interface.

class pankoff.validators.Collection

Check whether field supports collections.abc.Collection interface.

class pankoff.validators.Sequence

Check whether field supports collections.abc.Sequence interface.

class pankoff.validators.MutableSequence

Check whether field supports collections.abc.MutableSequence interface.

class pankoff.validators.ByteString

Check whether field supports collections.abc.ByteString interface.

class pankoff.validators.Set

Check whether field supports collections.abc.Set interface.

class pankoff.validators.MutableSet

Check whether field supports collections.abc.MutableSet interface.

class pankoff.validators.Mapping

Check whether field supports collections.abc.Mapping interface.

class pankoff.validators.MutableMapping

Check whether field supports collections.abc.MutableMapping interface.

class pankoff.validators.Awaitable

Check whether field supports collections.abc.Awaitable interface.

class pankoff.validators.Coroutine

Check whether field supports collections.abc.Coroutine interface.

class pankoff.validators.AsyncIterable

Check whether field supports collections.abc.AsyncIterable interface.

class pankoff.validators.AsyncIterator

Check whether field supports collections.abc.AsyncIterator interface.

class pankoff.validators.AsyncGenerator

Check whether field supports collections.abc.AsyncGenerator interface.

class pankoff.validators.Number(mix_value=None, max_value=None)

Validate whether field is an instance of type int and within specified range.

Parameters
  • min_value (int, optional) – minimum value for a field

  • max_value (int, optional) – maximum value for a field

>>> @autoinit
>>> class Person:
...     age = Number(min_value=18, max_value=100)
>>> person = Person(age=25)
class pankoff.validators.Type(types=(int, str, ...))

Validate whether field is instance of at least one type in types.

>>> @autoinit
>>> class Car:
...     speed = Type(types=(int, float))
>>> car = Car(speed=500.4)
class pankoff.validators.Sized(min_size=None, max_size=None)

Validate whether field length is within specified range.

Parameters
  • min_size (int, optional) – minimum length for a field

  • max_size (int, optional) – maximum length for a field

>>> @autoinit
>>> class Box:
...     size = Sized(min_size=20, max_size=50)
>>> box = Box(size=40)
class pankoff.validators.LazyLoad(factory)

Calculate an attribute based on other fields.

Parameters

factory – callable to calculate value for current field, accepts current instance

>>> @autoinit
>>> class Person(Container):
...     name = String()
...     surname = String()
...     full_name = LazyLoad(factory=lambda instance: f"{instance.name} {instance.surname}")
>>> person = Person(name="Yaroslav", surname="Pankovych")
>>> print(person)
Person(name=Yaroslav, surname=Pankovych, full_name=Yaroslav Pankovych)
class pankoff.validators.Predicate(predicate, default=None, error_message=None)

Predicate is a bit more complex validator. Basically, it checks your field against specified condition in predicate.

predicate is a simple callable which should return either True or False. It’ll be called with current instance and value to validate: predicate(instance, value).

If predicate returned False, and default is set, instance and value will be propagated to default if default is callable, if it’s not, default will be returned straight away.

THe key feature of default is that it can “normalize” your value if it’s invalid. See example below.

Parameters
  • predicate (callable) – function to call in order to validate value

  • default (callable, any, optional) – default value to use if predicate returned False

>>> @autoinit
>>> class Person:
...     salary = Predicate(
...         predicate=lambda instance, value: value == "100 USD",
...         default=lambda instance, value: str(value) + " USD"
...     )
>>> person = Person(salary=100)
>>> person.salary
"100 USD"

As you can see, we just turned 100 into "100 USD". You can also chain (see Chaining validators) Predicate with other validators, and normalized value will be propagated to further validators.

Predicate supports rich error messages:

>>> @autoinit
>>> class Car:
...     speed = Predicate(
...         predicate=lambda instance, value: value == 100,
...         error_message="Invalid value in field: {field_name}, got {value} for {predicate}"
...     )
>>> car = Car(speed=50)
Traceback (most recent call last):
...
pankoff.exceptions.ValidationError: ['Invalid value in field: speed, got 50 for <lambda>']

Custom validators

You can define you ows validator by subclassing BaseValidator.

>>> from pankoff.base import BaseValidator
>>> from pankoff.exceptions import ValidationError
>>> class EnumValidator(BaseValidator):
...     def __setup__(self, allowed_values):
...         self.allowed_values = allowed_values
...     def mutate(self, instance, value):
...         return f"Mutated value: {value}"
...     def validate(self, instance, value):
...         if value not in self.allowed_values:
...             raise ValidationError(
...                 f"Invalid value in field {self.field_name}, value should be in {self.allowed_values} "
...                 f"got {value}"
...             )

It is required for validators to define validate, but __setup__ and mutate is optional.

You can use mutate to modify returned value when its being accessed. It won’t be cached, mutate is re-calculated on every attribute access.