| name | python-adapters-failure |
| description | Fires when a boundary crossing is about to be hand-rolled instead of built as the matching construct in python-adapters-success — a before-validator that indexes/renames/routes/computes on raw foreign data, a try/except catching Exception or ValidationError or running multiple statements in its except body, or .model_dump/.model_dump_json called anywhere but a route reply or client binding. |
Adapters — failure patterns
Three ways a boundary crossing gets hand-rolled instead of built as a foreign model, contract model, or ordered union (python-adapters-success).
Pre-construction munging
A before-validator outside an ordered-union file, or one that indexes, renames, routes, or computes rather than the bare wrap, is data-fixing smuggled ahead of construction. The crossing the model performs in one call is being done by hand first, so the value that reaches construction is already unprovenly reshaped.
A before-validator that indexes, renames, routes, or computes is an alias, a path, or a nested model not yet written:
- a key rename is
Field(alias=...);
- data wrapped at one key is
validation_alias, nested wrappers an AliasPath, the wrapper modeled and never indexed past;
- nested structure is a nested foreign model.
The only legal before-validator is the ordered union's wrap: a single line on the failure variant that places the bare input under its field name so the variants can be attempted, with a recorded substrate run showing the declarative inventory refuses the shape. One return, constant keys, the input as every value.
@model_validator(mode="before")
def fix(cls, v):
v["price"] = v.pop("px")
return v
Swallowed failure
A try/except wider than the single capture form turns failure into procedure: catching Exception or ValidationError, or running a multi-statement except body, instead of letting the failure propagate or constructing it as a value.
One question routes every failure: did the domain say no, or did the proof fail? A construction refusal proves nothing, is never caught, and propagates; catching ValidationError manufactures the unproven value. A domain no is a value, modeled through the ordered union: the capture lives in a verb as one call assigned, each declared exception assigned to the same variable, then ordered-union construction from that variable.
try:
result = call()
except Exception:
result = None
Serialization in the domain
.model_dump or .model_dump_json turns a proven value into a dict or JSON: the crossing out of the application. That crossing belongs only at the edge, where the program meets the wire. Inside the domain a value stays whole, a constructed fact moving between constructions; the domain hands the edge that whole value, never a serialized one, and never reaches past the edge to the transport itself.
The two legal serialization sites are the route reply (api/) and the client binding (service/). Everywhere else, in a concept model, a derivation, a verb, the consistency model, a .model_dump/.model_dump_json flattens a value that is still moving inside the program. A verb emits the proven value itself through its client fields and lets the binding serialize at the wire; it never builds the JSON itself, and never hands JSON forward to the binding or the transport.
def book(self, fill: VenueFill) -> None:
self.latest = Position(prior=self.latest, fill=fill)
self.bus.publish(self.latest.model_dump())