Skip to content

Pydantic Validators

🧮 Computed Fields

Computed fields derive values dynamically at runtime using @computed_field. These fields aren’t provided as input but are calculated from existing fields.

Usecase 1: Extracting a Username from an Email

from pydantic import BaseModel, Field, computed_field

def extract_username(email: str) -> str:
    return email.split("@")[0]

class User(BaseModel):
    email: str = Field(..., description="User's email address.")

    @computed_field
    @property
    def username(self) -> str:
        return extract_username(self.email)

# Example usage
user = User(email="foobar@example.com")
print(user.username)  # foobar
print(user.model_dump())  
"""
{
  'email': 'foobar@example.com',
  'username': 'foobar'
}
"""
Note

Computed fields are evaluated every time they are accessed. This means that calling user.username, printing it, or dumping the model (model_dump()) will recompute the value dynamically.

✅ Field Validators

Field validators modify, validate, or transform individual fields before assignment. They can run before or after field processing (mode="before" or "after").

Usecase 1: Replace None with Defaults

Pydantic keeps None if explicitly provided, even if a default exists. A field validator ensures None is replaced with the field’s default.

Tip

Useful for handling AI or API data where None should be replaced with defaults for consistency.

from typing import Optional
from pydantic import BaseModel, field_validator

class User(BaseModel):
    username: Optional[str] = "foo_user"
    age: Optional[int] = 25
    email: Optional[str] = "foo@example.com"

    @field_validator("username", "age", mode="before")
    @classmethod
    def replace_null_with_default(cls, value, info):
        if value is None:
            return cls.model_fields[info.field_name].default
        return value

# Example usage
llm_response = {"username": "bar_user",  "age": None,  "email": None}
user = User.model_validate(llm_response)
print(user.model_dump_json(indent=2))
"""
{
  "username": "bar_user",
  "age": 25,
  "email": null
}
"""

Usecase 2: Replace Empty Strings with "N/A"

By default, Pydantic does not modify empty strings. If you want to replace all empty string values with "N/A", you can use a field validator to process them automatically.

Tip

This is useful for ensuring that empty input fields from forms, APIs, or databases do not remain blank but are replaced with a meaningful default value.

from pydantic import BaseModel, field_validator

class User(BaseModel):
    name: str
    email: str

    @field_validator("*", mode="before")
    @classmethod
    def replace_empty(cls, value):
        return "N/A" if value == "" else value

# Example usage
user = User(name="", email="foo@bar.com")
print(user.model_dump())
"""
{
  'name': 'N/A',
  'email': 'foo@bar.com'
}
"""

🧩 Model Validators

Model validators enforce rules that involve multiple fields. They run after all fields are validated, ensuring cross-field constraints and dependencies are met.

Usecase 1: Enforce Exactly One Optional Field

Sometimes, a model has two optional fields, but at least one must be provided while preventing both from being set simultaneously.

Tip

This is useful for ensuring users provide either one type of input or another but not both.

from typing import Optional
from pydantic import BaseModel, model_validator

class FooBarModel(BaseModel):
    foo: Optional[str] = None
    bar: Optional[str] = None

    @model_validator(mode="after")
    def validate_exclusive_fields(self):
        if (self.foo is None) == (self.bar is None):
            raise ValueError("Either `foo` or `bar` must be provided, but not both.")
        return self

# Example usage
FooBarModel(foo="abc")  # ✅ Valid
FooBarModel(bar="xyz")  # ✅ Valid
FooBarModel()  # ❌ Raises error
FooBarModel(foo="abc", bar="xyz")  # ❌ Raises error