Validators¶
didactic.api.validates decorates a method as a per-field check that runs at construction time:
import didactic.api as dx
class User(dx.Model):
id: str
email: str
@dx.validates("email")
def _email_must_have_at(self, value: str) -> bool:
return "@" in value
The validator runs after the field's type translation. Its argument
is the decoded value; its return value must be bool. False
raises a ValidationError with a validates_failed entry.
Multiple validators per field¶
Multiple @validates methods on the same field run in declaration
order. The first failure short-circuits the rest for that field.
Cross-field invariants¶
@validates runs against one field at a time. For a check that
spans multiple fields, declare an axiom instead:
Validation error shape¶
try:
User(id="u1", email="not-an-email")
except dx.ValidationError as exc:
exc.entries # tuple[ValidationErrorEntry, ...]
exc.model # type[Model]
str(exc) # rendered message
Each entry has:
loc: a tuple of strings naming the location. For per-field validators this is(field_name,); for cross-field axioms it is().type: a discriminator string (missing_required,extra_field,type_error,validates_failed,axiom_failed).msg: a human-readable description.
The set of type values is open; new versions may add more.