Skip to content

valgebra

Fast runtime validation through a closed Boolean algebra of schemas.

A schema denotes a set of Python values. Validation is membership: you ask whether the object you already hold belongs to the set — no copy, no coercion. Schemas compile once into a Rust validator tree, and the hot path crosses into Rust exactly once per call.

from typing import Annotated, TypedDict

import annotated_types as at

from valgebra import Validator


class User(TypedDict):
    name: str
    age: Annotated[int, at.Ge(0)]


users = Validator(User)
assert users.is_valid({"name": "Ada", "age": 36})
assert not users.is_valid({"name": "Ada", "age": -1})

Pre-alpha

valgebra is under active development and published to PyPI. The APIs described here work today but may change before a stable 0.1.0 release. See the release notes for what is built.

What valgebra is for

valgebra is a contracts-and-checking tool, not a parsing framework. Reach for it when you want to check an object you already hold against a composable, inspectable contract — cheaply enough to run on every request or every agent turn.

For ingesting untrusted input into typed models with coercion and defaults, use pydantic; for the fastest deserialization into structs, use msgspec. Neither answers the membership question: both check while building a value, and hand back one they are given without re-examining it. valgebra occupies the niche neither covers: a closed, lawful algebra of schemas (union, intersection, complement, refinement, fixpoints) with check-only semantics, on a Rust core.

That is a difference in job as well as in speed, and the difference in job has consequences a benchmark does not measure. Both parsers check while building a value from untyped input: handed a value that is already an instance of the target class, each returns it without checking its fields, and neither re-examines a value it built when something mutates it later. A membership question can be asked again, of the same object, as many times as the contract needs. And because a schema here is a value in an algebra rather than a class declaration, is_subtype_of, is_equivalent and is_empty ask about the schemas themselves, with no value involved. tests/test_pydantic_boundary.py pins each of these against pydantic, including the case that runs the other way: a pydantic BaseModel reaches valgebra as a bare class and is not deep-checked.

What makes it different

  • Schemas denote sets; validation is membership. Subtyping is set inclusion and equivalence is mutual inclusion — decided soundly over a wide fragment and deliberately conservative beyond it. The whole model is one idea.
  • A real Boolean algebra. union, intersection, and complement compose any schema into a lattice whose laws are property-tested, and a schema is built in the lattice normal form: repr shows it and == compares it, so no later pass is needed to reach it.
  • Typing-first. Standard annotations are the primary notation, read through the typing spec's own introspection.
  • Check, don't parse. validate and is_valid never copy or coerce; ensure is the explicit, separate conversion mode.
  • Few boundary crossings. Tree walks, key lookups, and bound checks run in Rust; a comparison against a Python object — a literal, a refinement predicate, or an instance or attribute check — is the documented step into Python, never a silent fallback.
  • JSON on the Rust path. validate_json parses and validates JSON in Rust, consistent with the object path.
  • Immutable and thread-safe by design. Free-threaded CPython 3.14 is supported with a dedicated cp314t wheel where the release image exposes that interpreter.

The set

Read in order, or jump: each page owns one subject and says what it does not cover.

Page Covers
00-installation.md installing the package and its build toolchain
01-tutorial.md a guided path from a scalar schema to an inspected failure
02-quickstart.md the condensed tour, for a reader who knows the domain
03-schema-language.md every schema form, with its denotation as a set of values
04-algebra.md the Boolean lattice, the normal form construction builds, and the composition recipes
05-refinements.md constraints and predicate refinements over a base schema
06-recursion.md the fixpoint for self-referential schemas
07-json.md parsing and validating JSON on the Rust path
08-error-model.md the exception type, the codes, and the violation path
09-inspection.md interrogating a codebase that has no schemas of its own
10-limits.md the resource bounds the validator enforces
11-performance.md the measured benchmarks, and what they do not claim
12-changelog.md every released version, and what to do on upgrade
13-foundations.md the denotational frame and the theory it is sourced from
14-soundness.md why an accept is never wrong, node by node
15-decidability.md what is decided exactly, and what stays conservative
17-boundaries.md what a validator cannot do, and why each boundary is where it is
16-api.md everything callable

Where to start