Error handling
When data does not match, Probatio raises. This page covers what it raises, how to read the path to the offending value, how to collect every error at once, and how to turn an error into something a person or a machine can use.
One base: Invalid
Section titled “One base: Invalid”Everything Probatio raises for bad data is an Invalid or a subclass. Catching
Invalid catches them all, so you rarely need to name a specific subclass unless
you want to branch on the kind of failure.
from probatio import Schema, Invalid
schema = Schema(int)
try: schema("nope")except Invalid as err: print(err) # expected intA broken schema definition raises SchemaError instead: you wrote the schema
wrong, and no input would make it right. The two never overlap: SchemaError
means fix the code, Invalid means reject the data.
The path to the value
Section titled “The path to the value”An error knows where in the data it happened. str(error) appends that path as
a dotted trail (mapping keys joined with dots, sequence indices as [n]), and
error.path is the list of keys and indices to walk:
from probatio import Schema, Invalid
schema = Schema({"server": {"ports": [int]}})
try: schema({"server": {"ports": [80, "nope"]}})except Invalid as err: print(err) # expected int at 'server.ports[1]' print(err.path) # ['server', 'ports', 1]error.path is the machine-readable form; follow it into the original data to
find the exact value that failed.
Many errors at once
Section titled “Many errors at once”A dict schema does not stop at the first failing key (and a list schema not at
the first failing element). It collects every failure into a MultipleInvalid,
which is itself an Invalid. Its errors list holds the individual failures:
from probatio import Schema, MultipleInvalid
schema = Schema({"a": int, "b": int})
try: schema({"a": "x", "b": "y"})except MultipleInvalid as err: print(len(err.errors)) # 2 for sub in err.errors: print(sub.path) # ['a'] then ['b']For convenience, a MultipleInvalid proxies its first error, so error.msg and
error.path read the first failure without reaching into errors.
Readable messages
Section titled “Readable messages”humanize_error renders an error against the data, naming the value that failed.
It lives in probatio.humanize:
from probatio import Schema, Invalidfrom probatio.humanize import humanize_error
data = {"port": "nope"}schema = Schema({"port": int})
try: schema(data)except Invalid as err: print(humanize_error(data, err)) # expected int at 'port'. Got 'nope'validate_with_humanized_errors(data, schema) (also in probatio.humanize) does
both steps: it validates and, on failure, raises Error (importable from
probatio or probatio.error) carrying the humanized message. The
errors reference has the class.
Catching a specific kind
Section titled “Catching a specific kind”The subclasses let you branch on what went wrong. They mirror voluptuous, so
TypeInvalid, RangeInvalid, LengthInvalid, CoerceInvalid, and the rest are
all there. A schema always raises MultipleInvalid at the top, so you branch on
the individual errors inside it:
from probatio import Schema, Range, MultipleInvalidfrom probatio.error import RangeInvalid
schema = Schema(Range(min=0, max=10))
try: schema(99)except MultipleInvalid as err: first = err.errors[0] print(isinstance(first, RangeInvalid)) # True print(first.error_message) # value must be at most 10error_message is the bare message without the path; msg is the same text.
The errors reference lists the whole hierarchy.
The structured layer
Section titled “The structured layer”On top of the voluptuous-compatible fields, every error carries a structured,
machine-readable layer: a stable code, a context dict, and the pair that
identifies the message itself: translation_key (a stable key naming the
sentence, like length_min) and placeholders (the raw values the message
interpolates, like {"min": 10}). The built-in validators fill all of them;
the translation keys reference lists every key.
as_dict() serializes the whole layer, which is handy for an API that returns
validation errors as JSON.
from probatio import Schema, Invalid
schema = Schema({"port": int})
try: schema({"port": "nope"})except Invalid as err: first = err.errors[0] print(first.code) # type print(first.as_dict()["path"]) # ['port']The legacy fields (msg, path, error_message) are untouched by this layer,
so nothing about the voluptuous-compatible behavior changes; the structured data
is purely additive.
To override a validator’s message, or to build your own output (or translations) from this layer, see Custom error messages.