tezvyn:

Per-Field Validation with @field_validator

AI-drafted, machine-checkedintermediate

@field_validator scrubs a single Pydantic field before it enters the model. Use it for rules like 'password must contain a digit' or 'port must exceed 1024'. It only sees one field at a time, so cross-field checks belong in a model validator instead.

WHY IT EXISTS: Pydantic models automatically coerce and validate standard types, but real schemas often need domain-specific rules that raw types cannot express. Without a dedicated hook, engineers resort to manual cleanup in route handlers or custom init methods, scattering validation logic far from the schema definition. Field validators solve this by giving each attribute its own reusable gatekeeper so that invalid data never reaches application logic.

THE MENTAL MODEL: Imagine a quality-control sensor mounted directly on a single conveyor belt. Every item headed for that bin passes through the sensor first. If the item fails the check, the belt stops immediately and flashes an error for that exact station. Other belts keep running, and the factory floor knows precisely which gate rejected the package.

HOW IT WORKS: You define a method on the model class and decorate it with @field_validator followed by the field name. The method receives the incoming value and optionally validation info. It returns the cleaned value or raises ValueError to trigger a validation failure. In Pydantic v2 you can choose mode=before to intercept raw strings before type coercion, or mode=after to refine an already converted value. The decorator accepts multiple field names if the same logic applies to several attributes. These validators run during instantiation and, depending on model configuration, can re-run on field updates.

WHEN TO USE IT: Reach for a field validator when a single attribute carries an invariant independent of sibling fields. Good fits include normalizing an email to lowercase, trimming whitespace from a username, verifying that an integer port falls between 1024 and 65535, or checking that a file extension belongs to an allowed set. It is also the right place to convert human-friendly inputs like 1MB into raw bytes for a single field.

WHEN NOT TO USE IT: Do not use it when the rule involves comparing two or more fields, such as ensuring that end_date exceeds start_date; that belongs in a model validator. Avoid field validators for heavy side effects like database queries or external API calls during model construction because validators should remain fast and deterministic. If the constraint is already covered by a built-in constrained type such as PositiveInt or a string pattern in Annotated, prefer the standard tool instead of writing custom logic.

ONE CANONICAL EXAMPLE: Consider a user settings model with a display_name field. You want the name stripped of leading whitespace and limited to alphanumeric characters to prevent injection in downstream rendering. You write a static method decorated with @field_validator("display_name") that takes the raw string, strips whitespace with value.strip(), checks value.isalnum(), and returns the cleaned string. If a client sends a string like admin<script>, the validator raises ValueError and Pydantic returns a 422 response pinpointing display_name before the model ever instantiates.

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.