The validation model
A schema is data that describes data. You write it with plain Python, Probatio compiles it once into a fast validator, and then you call that validator with a value. This page is the mental model the rest of the guides build on.
A schema is a value
Section titled “A schema is a value”You do not call a builder API to construct a schema. You hand Schema an
ordinary Python object, and its shape is the rule:
from probatio import Schema
Schema(int)(42) # 42Schema("on")("on") # 'on'Schema([int])([1, 2, 3]) # [1, 2, 3]Schema({"a": int})({"a": 1}) # {'a': 1}Each kind of object means something:
| You write | It means |
|---|---|
a type (int) | the value must be an instance of that type |
a literal ("on") | the value must equal that literal |
a dict | a mapping, each key and value validated by its own schema |
a list/tuple/set | a sequence, each item matching one of the element schemas |
| a callable | called with the value; it returns the result or raises |
a nested Schema | validated as its own schema |
Because a schema is just data, schemas compose: a dict can hold lists of nested
dicts, and any value position can be a validator like All or Coerce.
Compile once, validate many
Section titled “Compile once, validate many”Schema(...) does its analysis up front. Building the schema is the expensive
step; calling it is cheap, so build a schema once and reuse it:
from probatio import Schema, Required
schema = Schema({Required("name"): str}) # compiled here
for record in [{"name": "a"}, {"name": "b"}]: schema(record) # cheap, repeated callsKeep schemas at module scope, or build them once in a constructor, rather than rebuilding inside a hot loop.
Validation returns a new value
Section titled “Validation returns a new value”A validator returns the validated result; it does not mutate the input. The
result can differ from the input, because validators may normalize: Coerce
converts types, defaults fill in absent keys, and string transforms rewrite the
value.
from probatio import Schema, Coerce
schema = Schema({"port": Coerce(int)})data = {"port": "443"}
schema(data) # {'port': 443}data # {'port': '443'} (unchanged)Failure is an exception, not a return value
Section titled “Failure is an exception, not a return value”A valid value comes back from the call. An invalid one raises, so there is no
“is it valid” flag to check; you validate inside a try, or let the error
propagate.
from probatio import Schema, Invalid
schema = Schema(int)
try: schema("not a number")except Invalid as err: print(err) # expected intEverything Probatio raises for bad data is an Invalid (or a
MultipleInvalid collecting several). A broken schema definition, by contrast,
raises SchemaError, because that is a programming mistake, not bad input. See
error handling for the full picture.
Order matters
Section titled “Order matters”Within All, validators run left to right and each receives the previous
result, so All(Coerce(int), Range(min=0)) coerces before it range-checks. The
reverse order would range-check a string. Marker defaults and group rules also
have defined timing. Where order is significant, the guides call it out, and it
matches voluptuous.
Where to next
Section titled “Where to next”- Dict schemas and markers: the mapping rules in depth.
- Combinators:
All,Any,Union, and friends. - Built-in validators: the toolbox.