Validators#
Apart from providing standard validation features, Oblate also provides the ability to write custom validators.
Defining validators#
The most common and concise way of adding a validator is using the validate.field()
decorator.
Example:
class User(oblate.Schema):
id = fields.Integer()
username = fields.String()
@validate.field(id)
def validate_id(self, value: int, ctx: oblate.LoadContext):
assert value >= 1000 and value <= 9999, 'Value must be within the 1000-9999 range'
The parameter to validate.field()
can also be a string representing the name of field.
The validator function takes two parameters apart from self, the value being validated and the
LoadContext
instance. If the validation fails, the function should not raise one of
the following errors:
ValueError
AssertionError
The first two errors are automatically wrapped into a FieldError
. It is important to note
that raising any other exception would not be accounted as a validation error.
Warning
It is recommended to raise ValueError
if you intend to run your script using
the Python’s -O
or -OO
optimization flags. These flags remove the assertion
statements from the code causing the validators to stop working properly.
The other, less concise way of adding a validator is by using the fields.Field.add_validator()
function. This function takes a callback having three parameters: the schema instance, the value
being valdiated and the LoadContext
instance.
Example:
def validate_id(schema: oblate.Schema, value: int, ctx: oblate.LoadContext):
assert value >= 1000 and value <= 9999, 'Value must be within the 1000-9999 range'
class User(oblate.Schema):
id = fields.Integer()
username = fields.String()
id.add_validator(validate_id)
The third way of registering a validator is by passing it to field using the validators
parameter:
class User(oblate.Schema):
id = fields.Integer(validators=[validate_id])
Raw Validators#
By default, validators are ran after the deserialization of raw value i.e after the fields.Field.value_load()
method is called. If the deserialization fails, validators will not be called. This essentially means that
the value
parameter will always be of correct (deserialized) type.
There are also “raw validators”. These validators are called with the raw value (the one provided in the raw data).
In order to define a raw validator, simply pass raw=True
in the validate.field()
decorator
or fields.Field.add_validator()
function.
Example:
class User(oblate.Schema):
id = fields.Integer(strict=False)
@validate.field(id, raw=True)
def validate_id_raw(self, value, ctx: oblate.LoadContext):
print('Raw validator got value of type', type(value))
@validate.field(id)
def validate_id(self, value: int, ctx: oblate.LoadContext):
print('Default validator got value of type', type(value))
User({'id': '32'})
The output will be:
Raw validator got value of type <class 'str'>
Default validator got value of type <class 'int'>
Warning
Be careful when dealing with values in raw validators, values to these validators are passed without prior validation. This means these validators can even have values of invalid type.
Class-based validators#
Sometimes it is desired for validator to take into account some data for validating a value. In this
case, class based validators are a better option. These validators are created by inheriting from the
validate.Validator
class.
Example:
class MinLengthValidator(validate.Validator[str]):
def __init__(self, length: int) -> None:
self.length = length
def validate(self, value: str, ctx: LoadContext):
assert len(value) >= self.length
The subclasses are required to implement the validate()
method which will
perform the validation.
Adding it to the field:
class User(oblate.Schema):
username = fields.String(validators=[MinLengthValidator(8)])
In order to turn a class-based validator into a raw validator, the raw
parameter is set to
True
while subclassing:
class MinLengthValidator(validate.Validator[str], raw=True):
...
Tip
validate.Validator
is a typing generic and takes a single type argument which is
the expected type of value that is being validated.
Prebuilt Validators#
Some validators are provided by Oblate to suit common use cases. These validators are provided
by the oblate.validate
module. All validators provided by Oblate are class based and
are registered the same way as described above.
For example, the validate.Range
validator for validating integer ranges:
class User(oblate.Schema):
id = fields.Integer(validators=[validate.Range(100, 999)])
User({'id': 120}) # valid
User({'id': 1023}) # invalid
User({'id': 23}) # invalid
All prebuilt validators are documented in Validators section of API reference.