typing_validation.diagnosis

Explaining a failure, after the fact.

The validators fail hard and say only that they failed. Everything a user reads about why is produced here, by a slower second traversal of the same (val, t). This is the single most valuable consequence of the architecture: because diagnostics are produced in exactly one place, there is exactly one implementation of them — so the conformance obligation between mechanisms reduces to “do they agree on the boolean”, which is a far smaller thing to police than “do they agree on the message”.

And because this runs only on a failure, which is by definition exceptional, it may be as slow, allocating and thorough as it likes — where its costs stop mattering and its quality is the entire point.

The second traversal is sound only because validation is pure: the value handed here is the value the validator saw, undisturbed.

Detail

final class Detail(*values)[source]

Bases: Enum

Why one node of a failure tree failed.

IN_COMPONENT = 'a component failed'

This node is fine in itself; something inside it is not. The causes say what, and their locations say where.

MISSING_KEY = 'missing required key'

A TypedDict required key that is absent.

NON_STRING_KEY = 'key is not a string'

A TypedDict key that is not a string.

NOT_AN_INSTANCE = 'not an instance'

The value is not an instance of the type, or of the type’s origin.

NOT_A_CLASS = 'not a class'

Type[T] was given something that is not a class.

NOT_A_NAMED_TUPLE = 'not a named tuple'

Bare typing.NamedTuple was given something that is not one.

NOT_A_SUBCLASS = 'not a subclass'

Type[T] was given a class that does not derive from T.

NOT_NONE = 'not None'

The type is None and the value is not.

NO_LITERAL = 'no matching literal'

The value equals none of the literals, at its own type.

NO_UNION_MEMBER = 'no member matched'

Every member of a union failed. The causes say how each one did.

PLUGIN_REJECTED = 'rejected by plugin'

A plugin’s own check said no, and offered no further detail.

WRONG_LENGTH = 'wrong length'

A fixed-length tuple of the wrong size.

DiagnosisFailure

final class DiagnosisFailure[source]

Bases: RuntimeError

Raised when a validator reports a failure that diagnosis cannot reproduce.

This is a library bug — a mechanism has drifted from the catalogue — and it is reported as one. Diagnosis must never answer a reported failure with an implicit “actually, it’s fine”: the failure is not swallowed, and no validation error is quietly downgraded to success.

Location

final class Location(place, at=None)[source]

Bases: object

Where a failure sits inside the value that contains it.

at

The index, key or field name, where the place has one.

Return type:

typing.Any

place

What kind of position this is.

Return type:

typing_validation.diagnosis.Place

Place

final class Place(*values)[source]

Bases: Enum

What kind of position a failure occupies within its parent value.

FIELD = 'field'

A named field of a TypedDict or named tuple.

INDEX = 'index'

A position in an ordered collection, which is addressable.

KEY = 'key'

A mapping key that is itself invalid.

MEMBER = 'union member'

One alternative of a union, all of which failed.

POSITION = 'position'

A position in an unordered collection.

Iteration order is not stable across runs, so this is a witness that something failed, not an address the user can go to. It must be reported as such rather than implying a location.

VALUE_AT = 'value at key'

The value stored under a mapping key.

WRAPPED = 'wrapped type'

What an alias, Annotated, NewType or type variable resolves to.

The wrapper is not transparent: it reports as itself and the cause reports what it stands for.

ValidationFailure

final class ValidationFailure(val, t, detail, location=None, causes=())[source]

Bases: object

One node of a failure tree: a value, the type it failed against, why, and what failed inside it.

Nothing here recurses over causes, and that is not fastidiousness. A failure tree is as deep as the value, so the generated __repr__ and __eq__ would raise RecursionError on exactly the deeply nested values the rest of this library goes out of its way to handle — turning an ordinary failure into a stack overflow at the moment someone tries to look at it. So they are turned off and replaced with iterative equivalents.

causes

The failures inside this one.

Return type:

tuple[typing_validation.diagnosis.ValidationFailure, …]

depth()[source]

How deep the tree goes.

Return type:

int

detail

Why it failed.

Return type:

typing_validation.diagnosis.Detail

location

Where this sits within the value that contains it. None at the root.

Return type:

typing_validation.diagnosis.Location | None

t

The type it failed against.

Return type:

typing.Any

val

The value that failed.

Return type:

typing.Any

walk()[source]

Every failure in the tree, this one first, depth-first.

Return type:

Iterator[ValidationFailure]

diagnose

diagnose(val, t, /)[source]

Explain why a value is not valid for a type.

Raises:

DiagnosisFailure – if the value turns out to be valid after all, which means a mechanism has drifted from the specification.

Parameters:
Return type:

ValidationFailure