Skip to content

Tutorial

This is a guided first session with valgebra. You will start from a single scalar check and finish having validated a structured record — with a constraint, an optional field, a real failure you inspect, and a JSON document — building each step on the last. Every snippet runs as written; copy them in order.

It assumes valgebra is installed. For the meaning of each form you meet here, the schema language is the reference; this page is the path, not the catalogue.

1. Your first validator

A schema denotes a set of Python values, and validating asks whether a value is in that set. Compile one with Validator, then ask with is_valid:

from valgebra import Validator

is_int = Validator(int)
assert is_int.is_valid(42)
assert not is_int.is_valid("42")  # a str is not in the set of ints

Validator(int) compiles once; call is_valid as often as you like.

2. Failing loudly

is_valid returns a bool. When you want an exception instead, use validate — it raises ValidationError on a value outside the set:

from valgebra import ValidationError, Validator

is_int = Validator(int)
raised = False
try:
    is_int.validate("42")
except ValidationError:
    raised = True
assert raised

You will read what a ValidationError carries in step 6.

3. Validating a collection

Schemas nest. A list[int] is the set of lists whose every element is an int, and valgebra checks each element:

from valgebra import Validator

numbers = Validator(list[int])
assert numbers.is_valid([1, 2, 3])
assert not numbers.is_valid([1, "two", 3])  # one bad element fails the list

4. Describing a record

Real data is usually structured. Write a record as a TypedDict — the standard typing form — and valgebra validates every declared field:

from typing import TypedDict

from valgebra import Validator


class User(TypedDict):
    name: str
    age: int


users = Validator(User)
assert users.is_valid({"name": "Ada", "age": 36})
assert not users.is_valid({"name": "Ada", "age": "old"})  # age must be an int
assert users.is_valid({"name": "Ada", "age": 36, "note": "extra"})  # open

That last line is the one to notice: a TypedDict is open, so a key it does not declare is admitted. That is the typing spec's reading of the form rather than a valgebra choice, and step 6 shows the spelling that is closed. The schema language has both, and closed=True for a TypedDict that should refuse.

5. Adding a constraint

Plain types say what kind; a refinement says which values. Narrow a field with Annotated and an annotated-types marker — here, an age that cannot be negative:

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})  # the bound holds

6. Optional fields, and reading a failure

A dict literal is a compact record: a trailing ? on a key marks it optional, and the record is closed — an undeclared key is rejected.

from valgebra import Validator

profile = Validator({"name": str, "age?": int})
assert profile.is_valid({"name": "Ada"})  # age omitted: fine
assert profile.is_valid({"name": "Ada", "age": 36})
assert not profile.is_valid({"name": "Ada", "extra": 1})  # closed: no extra keys

When a check fails, validate raises a ValidationError that tells you exactly what failed and where — a machine-readable code and the path to the offending value, even deep in a nested structure:

from valgebra import ValidationError, Validator

schema = Validator({"user": {"name": str}})
try:
    schema.validate({"user": {"name": 5}})
except ValidationError as err:
    assert err.code == "string_type"
    assert err.path == ("user", "name")

The error model covers aggregation and union reporting.

7. Validating JSON

You do not have to parse first. validate_json and is_valid_json read JSON on the Rust path and run the very same checks, so a document is judged exactly as json.loads of it would be:

from valgebra import Validator

users = Validator({"name": str, "age?": int})
users.validate_json('{"name": "Ada", "age": 36}')  # passes, raises nothing
assert Validator(list[int]).is_valid_json("[1, 2, 3]")  # str or bytes

Those two answer a verdict and throw the document away. When you want the data as well, load validates and returns the parsed value, so the document is parsed once rather than once by you and once by the check:

from valgebra import Validator

users = Validator({"name": str, "age?": int})
record = users.load('{"name": "Ada", "age": 36}')
assert record == {"name": "Ada", "age": 36}

Where to go next

You can now check scalars, collections, records, constraints, failures, and JSON. From here: