Failures and diagnostics
Let lifecycle and Pydantic errors surface at their natural boundary:
Error |
Meaning |
|---|---|
|
A draft field was read before it had a usable value |
|
A draft was copied or sent through Pydantic serialization, or a final was finalized again |
|
An unbound template was used as though it had validated field values |
Pydantic |
Final input, interpolation output, or a structural Config position was invalid |
|
A Config class disabled a lifecycle invariant or declared an unsupported schema shape |
|
A draft write used an unknown field or interpolation read unavailable context |
Missing draft values
Required scalar reads fail immediately. Required direct Config fields create a child draft, but a recursive required spine without a base case fails rather than recursing forever. Pending interpolation can be assigned and finalized but cannot be read from a draft.
Interpolation failures
Interpolation errors report the target path and callable site. Common causes are:
reading a later field;
selecting an ancestor above the current root;
requesting the wrong typed context model;
returning an active incomplete branch;
mutating a read-only built-in container view;
raising inside the user callable.
Reorder source fields before dependent fields. For a default child that needs
parent context, use the direct child: Child = Child() spelling. The standalone
child constructor becomes an unbound template and binds in the parent’s field
pipeline. Do not use a draft-returning default factory.
An unbound template is created only by normal Config(...) construction when at
least one interpolation lacks an ancestor/root/nearest context or raises
NameError, with no other errors except omitted required fields. A repeated
NameError during binding is reported as a normal interpolation failure. Other
Pydantic validation entry points never return templates.
Structural graph failures
Draft collection and template binding follow concrete annotations through Config
fields and supported built-in containers. A Config under Any, object, a wrong
union branch, or an incompatible tuple or mapping position is rejected. Markers
inside built-in containers are also rejected.
Arbitrary user objects are opaque and are not crawled for drafts or markers. If such an object needs lifecycle-aware children, move those children into directly annotated Config fields.
Native Pydantic footguns
model_copy(update=...) does not validate updates. Model-after validators and
model_post_init may mutate values after interpolation. Shallowly frozen finals
may still have mutable container contents. Revalidate current concrete values with
type(final).model_validate(final) when needed.