API reference¶
The full public surface of the valgebra package. Every name is re-exported
from the top-level valgebra namespace.
Compiling and checking¶
Validator
¶
Combinators¶
union
builtin
¶
union(*schemas: object) -> Validator
The union of the given schemas: a value in at least one of their sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*schemas
|
object
|
The schema specs or validators to join. No argument at all is
the empty union, which is |
required |
Returns:
| Type | Description |
|---|---|
Validator
|
A |
Raises:
| Type | Description |
|---|---|
`NotImplementedError`
|
If an argument uses a form with no set (a set or tuple literal, or a typing construct that is not a type). |
`ValueError`
|
If the joined schema crosses a size bound: depth, definitions, or nodes. |
intersection
builtin
¶
intersection(*schemas: object) -> Validator
The intersection of the given schemas: a value in every one of their sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*schemas
|
object
|
The schema specs or validators to meet. No argument at all is
the empty intersection, which is |
required |
Returns:
| Type | Description |
|---|---|
Validator
|
A |
Raises:
| Type | Description |
|---|---|
`NotImplementedError`
|
If an argument uses a form with no set (a set or tuple literal, or a typing construct that is not a type). |
`ValueError`
|
If the met schema crosses a size bound: depth, definitions, or nodes. |
complement
builtin
¶
complement(schema: object) -> Validator
The complement of a schema: every value not in its set.
Membership is decided by the inner schema: a value belongs to the complement
exactly when it is not a member of the inner. When deciding the inner raises
an ordinary Python exception — a value whose comparison or __eq__ throws —
that value folds to a non-member of the inner, and therefore a member of
the complement. A filter of the form complement(P) over values whose own
methods can raise should not rely on the complement alone to exclude them;
intersect with a positive type that pins the shape instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
object
|
The schema spec or validator to complement. |
required |
Returns:
| Type | Description |
|---|---|
Validator
|
A |
Raises:
| Type | Description |
|---|---|
`NotImplementedError`
|
If |
`ValueError`
|
If the complemented schema crosses a size bound: depth, definitions, or nodes. |
The whole-schema transforms open and close are methods on the compiled
validator (Validator.open/close), documented above. What they move is the
key-type region no clause claims: opening frees it and closing refuses it,
and neither touches a region a clause already claims. A record claims none,
which is why opening one admits every key; a dict[str, int] claims the str
region, so opening it keeps str keys mapping to integers and frees the rest.
So is
simplify, which is deprecated: a schema is built in the lattice normal
form, so the reduction it promised is the schema a caller already holds
(the algebra guide). A fixed-length list is
the native [A, B] literal (see the schema language).
Refinement markers¶
Regex
¶
Annotated metadata: a string fully matches this regular expression.
Use as Annotated[str, Regex(r"[0-9a-f]{24}")]. The match is anchored — the
whole string must match, as re.fullmatch does — and runs natively on the
Rust path (a linear-time engine), so a pattern check stays on the validation
fast path rather than crossing into Python like a predicate. A bare
re.Pattern (from re.compile) is accepted as metadata too.
Immutable, because it is hashable: a marker whose pattern can be rebound
after it is written into an Annotated is one whose hash changes while a
schema holds it. Written out rather than taken from dataclasses, and
annotated without typing, because this module is on the import path of
every program that imports the package and both cost it modules --
tests/test_version.py holds the count.
Recursion¶
recursive
builtin
¶
Build a recursive schema as a checked fixpoint.
builder receives a placeholder validator standing for the schema being
defined and returns its body. The placeholder's self-reference is resolved
to a back edge, and a non-contractive body — one whose recursive reference
is not under a structural constructor — is rejected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
builder
|
Callable[[Validator], object]
|
Called with a placeholder validator, and returns the body of the fixpoint. |
required |
Returns:
| Type | Description |
|---|---|
Validator
|
A |
Raises:
| Type | Description |
|---|---|
`TypeError`
|
If |
`NotImplementedError`
|
If the body uses a form with no set. |
`ValueError`
|
If the body is not contractive -- the recursive reference sits outside every structural constructor -- or the fixpoint crosses a size bound: depth, definitions, or nodes. |
Operators on a validator¶
A compiled validator carries an operator surface as well as its methods. These are written here rather than generated, because CPython supplies its own text for the dunder behind a type slot and that is what introspection would show.
| Operator | Method | Meaning |
|---|---|---|
obj in validator |
__contains__ |
Membership: the operator form of is_valid, so a check reads as the set test it is. |
a \| b |
__or__, __ror__ |
The union of the two schemas. \| is the operator typing already uses for a union; intersection and complement have no typing operator and stay named calls. The reflected form is what makes None \| validator work. |
a == b |
__eq__ |
Equality of the normal form: the schema trees, recursive definitions, and pooled constants all match, after the lattice laws construction settles — so a difference of member order, of a repeat, or of an identity is not one. Ask is_equivalent for the semantic question — whether two schemas denote the same set, which needs a containment rather than a law. |
hash(validator) |
__hash__ |
Consistent with ==, so a validator is a dict key or a set member. It digests the schema shape and definitions only, never the pooled constants, so an unhashable constant cannot break it. |
repr(validator) |
__repr__ |
A rendering of the schema as an expression that builds it. |
copy.copy and copy.deepcopy both return an equivalent validator; a validator
is immutable, so the copy shares the pool rather than duplicating it.
repr is a rendering, not a serialization. What it gives back is an
expression that builds the same schema — a recursive schema as the recursive
call it is, an open record as the catch-all entry it carries, the nullary product
as tuple[()] — so it can be pasted into a session and read back. Five things
it cannot render as an expression, and none of them reads back quietly: a
class, which is an object rather than syntax and appears as its name; a
predicate, which is a function and appears as Predicate(...), and which
the frontend refuses where it is built; a constant too long to print, which
is cut mid-string and is a syntax error where it is parsed; a meet of two
classes that each declare attributes, which flattens to the two classes and
their two attribute records, and a record standing apart from its class prints
as object(x=int), a form no constructor spells -- the schema does not record
which class each record came from, so the render cannot fold them back; and a
schema past the renderer's own depth bound, which gives up and prints
<...>. The bound
is within reach: no single annotation
can be written deep enough, since the frontend refuses past MAX_SCHEMA_DEPTH
and the renderer's bound sits above it, but a chain of recursive definitions
composes — the render descends into each in turn, so a hundred shallow links
reach a depth one annotation cannot. The mark is deliberately not an ellipsis:
... reads as a schema inside a subscript, so a truncated render would parse
and hand back a different validator with nothing to say it had been cut. Do
not parse a repr: it is for a person to read, and
inspection says how to ask a schema questions instead.
Lattice bounds¶
anything and nothing are the two bounds of the lattice, and both are
Validator instances rather than schema forms you construct.
anything— the top: every Python value is a member, soanything.is_valid(x)isTruefor everyx. It is the identity ofintersectionand the absorbing element ofunion.nothing— the bottom: no value is a member, andnothing.is_empty()isTrue. It is the identity ofunionand the absorbing element ofintersection.
nothing is also where the third answer about emptiness is asked for:
is_empty is a predicate and reports the proof, while
relation_to(nothing) reports which of proof, refutation and decline the
procedure reached (the boundary).
Both are ordinary validators: they compose with the combinators, compare with
is_subtype_of, and are what the constructors fold to — intersection(int,
complement(int)) is nothing (the algebra). Any is the
same set and the same schema as anything, differing
only in what repr gives back; see
Any versus anything.
from valgebra import Validator, anything, complement, intersection, nothing, union
assert anything.is_valid(object())
assert nothing.is_empty()
assert Validator(int).is_subtype_of(anything)
assert nothing.is_subtype_of(int)
assert intersection(int, anything).is_equivalent(int)
assert union(int, nothing).is_equivalent(int)
assert complement(anything).is_equivalent(nothing)
assert Validator(int).relation_to(nothing) == "not_subset" # a value says so
What raises, and when¶
A failure at build time — when the schema is compiled — is a different kind
of event from a failure at validation time, and it raises a different
exception. ValidationError is only ever the second. Building raises one of
three, and which one says what went wrong:
| Raised | When | Example |
|---|---|---|
NotImplementedError |
The spec names a form with no decidable runtime membership. | Sequence[int], Mapping[str, int], a TypeVar, Final, ClassVar |
NotImplementedError |
Compiling descends one level past the construction depth bound without reaching a leaf. | a self-referential class, whose field type names the class |
ValueError |
A constructed schema crosses a size bound: depth, definitions, or nodes. | growing a schema in a loop with \|, union, intersection, open |
ValueError |
A marker's value cannot denote a set. | MultipleOf(0) |
ValueError |
A recursive body is not contractive — its back edge is not under a structural constructor. |
recursive(lambda s: s) |
TypeError |
An argument is the wrong Python type for the call. | validate_json(123), load(123) |
The two NotImplementedError rows are different bounds that happen to share
a class. The first is about the form and no depth would help it; the second is
about depth while compiling, and is what a self-referential class reaches
because its field type names the class again — the frontend descends one level
past what a constructed schema may carry, so the message names the bound and the
schema past it is refused rather than half-built. The ValueError depth row is
that construction bound itself, on the schema a sequence of calls has built, and
MAX_SCHEMA_DEPTH is importable so code that sizes a schema reads it rather
than repeating it. The resource limits guide covers all three
sizes.
from collections.abc import Sequence
from dataclasses import dataclass
from valgebra import Validator, recursive
@dataclass
class Node:
next: "Node"
for spec in (Sequence[int], Node):
try:
Validator(spec)
raise AssertionError("expected a rejection")
except NotImplementedError:
pass
try:
Validator(recursive(lambda schema: schema))
raise AssertionError("expected a rejection")
except ValueError:
pass
try:
Validator(int).validate_json(123)
raise AssertionError("expected a rejection")
except TypeError:
pass
Every one of these is raised before any value is checked, so a schema that compiles is a schema whose every membership question is answerable.
Errors¶
ValidationError¶
Raised by validate, validate_json, load and ensure when a value is not a
member of the schema's set. It subclasses Exception, and carries a structured,
machine-readable model as well as a message. The attributes are set on the
instance, so they are written here rather than generated.
| Attribute | Type | Meaning |
|---|---|---|
errors |
tuple[dict[str, object], ...] |
One item per independent failure, each a JSON-serializable dict with the keys code, path, message, expected and value. json.dumps(err.errors) is the JSON form of the whole report. One call reports every failure unless fail_fast=True stops the walk at the first. |
code |
str |
The first item's stable failure code, such as int_type, missing_key or literal_error. |
path |
tuple[str \| int, ...] |
The first item's location from the root, string keys and integer indices, empty at the root. |
message |
str |
The first item's rendered one-line message. str(err) summarizes every failure rather than only this one. |
expected |
str |
A short label of the set the first item expected, such as int. |
value |
str |
A repr-style summary of the first item's offending value. |
The error model guide gives the path format and the
properties a code carries; the codes themselves are enumerated by
tests/test_error_codes.py, which asserts each against the node kind that emits
it, because a list written out beside the walk drifts from it.
import json
from valgebra import ValidationError, Validator
try:
Validator({"a": int, "b": str}).validate({"a": "x", "b": 1})
except ValidationError as err:
assert err.code == "int_type"
assert err.path == ("a",)
assert [item["path"] for item in err.errors] == [("a",), ("b",)]
assert json.dumps(err.errors) # the whole report is JSON-serializable
Construction limits¶
Three module constants give the bounds every schema-growing call is checked against, so a caller sizing a schema reads the number rather than repeating it. Resource limits says what each one bounds and why.
| name | value | bounds |
|---|---|---|
MAX_SCHEMA_DEPTH |
128 | levels of structural nesting |
MAX_DEFINITIONS |
128 | recursive definitions in one schema |
MAX_SCHEMA_NODES |
100,000 | total schema nodes |
from valgebra import MAX_SCHEMA_DEPTH, Validator, complement
schema = Validator(int)
for _ in range(MAX_SCHEMA_DEPTH - 1):
schema = complement(schema)
# An odd number of complements is the complement of `int`, so `1` is outside.
assert schema.is_valid(1) is ((MAX_SCHEMA_DEPTH - 1) % 2 == 0)
Package version¶
valgebra.__version__ is the distribution version as a string. It comes from
the Cargo workspace manifest, which is what maturin derives the wheel's metadata
from — so it matches the built wheel and never drifts from a hand-maintained
literal. Taken from the compiled extension rather than read back out of the
installed metadata, because the metadata reader costs twenty milliseconds of
import time for a string the manifest already carries.
What is public¶
Every name above is public and is reached from the top-level valgebra
namespace. valgebra.__all__ lists the schema surface — Validator, the
combinators, Regex, recursive, the two lattice bounds, the three
construction limits, and ValidationError; __version__ is public too and is
not in it, being metadata rather than part of the algebra.
The compiled extension underneath, valgebra._valgebra, is private: its layout,
its module name, and which names it carries are free to change in any release.
Import from valgebra.