This schema very succinctly and roughly describes the data required by
the API, and will work fine. But it has a few problems. Firstly, it
doesn't fully express the constraints of the API. According to the API,
per_page should be restricted to at most 20, defaulting to 5, for
example. To describe the semantics of the API more accurately, our
schema will need to be more thoroughly defined:

Validation functions

Validators are simple callables that raise an Invalid exception when
they encounter invalid data. The criteria for determining validity is
entirely up to the implementation; it may check that a value is a valid
username with pwd.getpwnam(), it may check that a value is of a
specific type, and so on.

The simplest kind of validator is a Python function that raises
ValueError when its argument is invalid. Conveniently, many builtin
Python functions have this property. Here's an example of a date
validator:

In addition to simply determining if a value is valid, validators may
mutate the value into a valid form. An example of this is the
Coerce(type) function, which returns a function that coerces its
argument to the given type:

defCoerce(type, msg=None):
"""Coerce a value to a type. If the type constructor throws a ValueError, the value will be marked as Invalid."""deff(v):
try:
returntype(v)
exceptValueError:
raise Invalid(msg or ('expected %s'%type.__name__))
return f

This example also shows a common idiom where an optional human-readable
message can be provided. This can vastly improve the usefulness of the
resulting error messages.

Dictionaries

Each key-value pair in a schema dictionary is validated against each
key-value pair in the corresponding data dictionary:

Error reporting

Validators must throw an Invalid exception if invalid data is passed
to them. All other exceptions are treated as errors in the validator and
will not be caught.

Each Invalid exception has an associated path attribute representing
the path in the data structure to our currently validating value, as well
as an error_message attribute that contains the message of the original
exception. This is especially useful when you want to catch Invalid
exceptions and give some feedback to the user, for instance in the context of
an HTTP API.

The path attribute is used during error reporting, but also during matching
to determine whether an error should be reported to the user or if the next
match should be attempted. This is determined by comparing the depth of the
path where the check is, to the depth of the path where the error occurred. If
the error is more than one level deeper, it is reported.

The upshot of this is that matching is depth-first and fail-fast.

To illustrate this, here is an example schema:

>>> schema = Schema([[2, 3], 6])

Each value in the top-level list is matched depth-first in-order. Given
input data of [[6]], the inner list will match the first element of
the schema, but the literal 6 will not match any of the elements of
that list. This error will be reported back to the user immediately. No
backtracking is attempted: