typing_validation.composition
Building a validation function specialised to one fixed type, by composing a closure per node of the interned graph.
Construction is cheap and structurally shared: nodes are interned on the type
itself, so list[int] is analysed once and its closure reused everywhere it
occurs — inside dict[str, list[int]], inside tuple[list[int], ...], and
inside anything else mentioning it. The result is a graph over distinct
sub-types, not a tree over syntactic occurrences.
The shape here is chosen by measurement, and it is not the obvious one. Composing
closures that simply call one another is the obvious one, and it is 3x faster
than the interpreter per node — but it recurses once per level of the value,
so it raises RecursionError on exactly the deeply nested values the
interpreter goes out of its way to handle. Two mechanisms disagreeing about one
value, one of them by crashing, is what the architecture exists to prevent.
Composing closures that all push onto a shared work stack fixes that, and is only
1.16x faster, which does not earn a second mechanism at all.
Depth grows only where a check can descend. A check that answers from the
value alone cannot grow the stack, so calling it directly costs one call and
risks nothing. So a container calls the children that cannot descend and
pushes the ones that can. On list[int] that is the fast shape, at 2.9x the
interpreter; on a recursive alias it is the safe one.
Check
- Check = Check
One node’s composed check: given a value, the work stack and the union bookkeeping, whether the value is valid here, having pushed whatever remains.
Every check takes all three whether it uses them or not. A check that cannot descend ignores both, which costs two unused arguments and saves the wrapper that dropping them would need.
Composed
- Composed = Composed
A check, and whether it can push.
The second is what decides call-versus-push at every parent, and it is a property of the check rather than of the node’s children: a union of plain classes has children and still collapses to a single
isinstance, so it can no more descend thanintcan.
Runner
- Runner = Runner
A composed check with its driver attached, as a plain predicate.
Named rather than written inline because the documentation pipeline cannot parse a
Callableparameter list — see CLAUDE.md — and because naming it says what it is.
Stack
- Stack = Stack
Type alias.
Type aliases are created through the type statement:
type Alias = int
In this example, Alias and int will be treated equivalently by static type checkers.
At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.
Type aliases can also be generic:
type ListOrSet[T] = list[T] | set[T]
In this case, the type parameters of the alias are stored in the __type_params__ attribute.
See PEP 695 for more information.
Unions
- Unions = Unions
Type alias.
Type aliases are created through the type statement:
type Alias = int
In this example, Alias and int will be treated equivalently by static type checkers.
At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.
Type aliases can also be generic:
type ListOrSet[T] = list[T] | set[T]
In this case, the type parameters of the alias are stored in the __type_params__ attribute.
See PEP 695 for more information.
Validator
runner_for
- runner_for(node, /)[source]
A node’s composed check as a plain predicate, driver and all.
What a caller outside this module needs, and the only safe way to ask: a check that can descend pushes onto a stack that something must then drain, and handing it a throwaway list silently loses the work — which reports valid for a value that is not.
validator
- validator(t, /)[source]
A validation function specialised to one type.
Same contract as
validate, and the same verdict on every value. It pays for itself when the same type is validated repeatedly, because it analyses the type once rather than once per value:check = validator(list[int]) for payload in payloads: check(payload)
Unlike
validate, this refuses an unsupported type immediately rather than when a value happens to reach the unsupported part. It has to: it analyses the whole type before it sees any value. Sovalidator(list[Callable[[int], int]])raises here, wherevalidate([], list[Callable[[int], int]])returnsTrue.- Raises:
UnsupportedTypeError – if the type, or any component of it, is not one this library can validate against.
- Parameters:
t (
Any)- Return type: