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.
| 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
|