typing_validation.inspection

Asking about a type, rather than about a value: what it is, whether it can be validated against, and — when it cannot — precisely what stopped it.

The structure is a real artifact, built from the node model, which exists anyway to serve the validators.

can_validate

can_validate(t, /)[source]

Whether this library can validate against a type at all.

Support is all-or-nothing: tuple[int, Callable[[int], int]] answers False even though the int component is perfectly checkable, because a validation that silently skipped part of its obligation would be worse than none. This function exists so a caller can ask up front.

Note that validate is lazier than this. It walks the value and the type together and raises only when it reaches an unsupported component, so validate([], list[Callable[[int], int]]) returns True while this returns False. That is deliberate: scanning the type on every call is exactly the overhead that mechanism exists to avoid. This is the total answer, and it is the one to branch on.

Parameters:

t (Any)

Return type:

bool

clear_cache

clear_cache()[source]

Drop every interned node.

Safe by construction: interning is never semantically observable, so this changes cost and never an answer. Without that guarantee, clearing a cache would be a semantic operation and no user could be expected to reason about it.

Return type:

None

forget_type

forget_type(t, /)[source]

Drop the interned node for one type, if it has one.

Returns whether anything was dropped. Note that nodes for its components are untouched and may still be shared by other types.

Parameters:

t (Any)

Return type:

bool

inspect_type

inspect_type(t, /)[source]

The structure of a type: its shape, its components, and whether each is supported.

When a type is unsupported this reports the whole structure and marks precisely which component poisoned it. Totality means the answer to “can this be validated” is then always “no”, but it should never be an opaque “no” — use unsupported_components to name the culprits.

Parameters:

t (Any)

Return type:

TypeNode

scoped_cache

scoped_cache()[source]

Intern nodes into a temporary tier, dropped whole on exit.

For callers who want the sharing without the retention: a strong reference to a type transitively pins the classes it mentions, and through them their modules and closures, so a process that builds types dynamically — synthesised TypedDicts, classes from a factory, types built per request — would otherwise accumulate them forever.

While the tier is active, lookups consult tiers innermost-first and every new node is created in the innermost tier. Exiting drops that tier in one operation, with no per-entry bookkeeping:

with scoped_cache():
    for spec in incoming_specs:
        t = build_a_type_from(spec)
        if can_validate(t):
            report(inspect_type(t))

Note that validate is not what this is for: it analyses nothing and interns nothing, so it neither fills this tier nor benefits from it. What fills the cache is asking about a type — can_validate, inspect_type, and the explanation built when a validation fails.

Nodes created inside may reference nodes in enclosing tiers, which outlive them; nothing enclosing can reference inward, because while this tier is active it is where all new nodes go. So dropping it can never leave a dangling reference — and can never change an answer, only a cost.

Return type:

Iterator[None]