FastAPI Pydantic Models
Pydantic v2 model patterns for FastAPI: field constraints, validators, nested models, response schemas, and serialization configuration.
1. BaseModel Fundamentals
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
from uuid import UUID
class UserBase(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=100, examples=["Alice"])
age: Optional[int] = Field(None, ge=0, le=150)
role: str = Field(default="user", pattern="^(user|admin|moderator)$")
class UserCreate(UserBase):
password: str = Field(..., min_length=8, max_length=128)
class UserResponse(UserBase):
id: UUID
created_at: datetime
is_active: bool = True
model_config = {"from_attributes": True} # allows ORM model โ Pydantic
# Usage in route
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user_in: UserCreate, db: DB):
user = User(**user_in.model_dump(exclude={"password"}), hashed_password=hash(user_in.password))
db.add(user)
db.commit()
db.refresh(user)
return user
2. Field Validators (Pydantic v2)
from pydantic import BaseModel, field_validator, model_validator
import re
class RegisterRequest(BaseModel):
username: str
password: str
confirm_password: str
phone: Optional[str] = None
@field_validator("username")
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not re.match(r"^[a-zA-Z0-9_]{3,30}$", v):
raise ValueError("Username must be 3-30 alphanumeric chars or underscores")
return v.lower()
@field_validator("phone")
@classmethod
def phone_format(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return v
cleaned = re.sub(r"[\s\-\(\)]", "", v)
if not re.match(r"^\+?\d{7,15}$", cleaned):
raise ValueError("Invalid phone number")
return cleaned
@model_validator(mode="after")
def passwords_match(self) -> "RegisterRequest":
if self.password != self.confirm_password:
raise ValueError("Passwords do not match")
return self
3. Nested Models
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
country: str = "CN"
postal_code: Optional[str] = None
class Tag(BaseModel):
id: int
name: str
color: str = "#000000"
class Article(BaseModel):
title: str
content: str
author_id: int
tags: List[Tag] = []
address: Optional[Address] = None
# Nested validation happens automatically
article = Article(
title="Hello",
content="World",
author_id=1,
tags=[{"id": 1, "name": "python"}],
address={"street": "123 Main St", "city": "Beijing"},
)
4. Response Models & Filtering
from pydantic import BaseModel, computed_field
class UserInDB(BaseModel):
id: int
email: str
name: str
hashed_password: str
is_active: bool
created_at: datetime
model_config = {"from_attributes": True}
# Response model โ excludes sensitive fields
class UserPublic(BaseModel):
id: int
name: str
created_at: datetime
model_config = {"from_attributes": True}
@computed_field
@property
def member_since(self) -> str:
return self.created_at.strftime("%B %Y")
# Paginated response
class Page(BaseModel):
items: List[UserPublic]
total: int
page: int
size: int
pages: int
@app.get("/users", response_model=Page)
async def list_users(page: int = 1, size: int = 20, db: DB = Depends(get_db)):
total = db.query(User).count()
users = db.query(User).offset((page-1)*size).limit(size).all()
return Page(items=users, total=total, page=page, size=size, pages=-(-total // size))
5. model_config Options
from pydantic import BaseModel, ConfigDict
class StrictModel(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True, # strip whitespace from strings
str_min_length=1, # global min length for strings
validate_assignment=True, # validate on attribute assignment
use_enum_values=True, # store enum value not enum instance
from_attributes=True, # allow ORM object instantiation
populate_by_name=True, # allow both alias and field name
frozen=False, # True = immutable model
json_schema_extra={
"example": {"name": "Alice", "age": 30}
},
)
6. Common Field Types
| Type | Import | Notes |
|---|---|---|
| EmailStr | pydantic | Validates email format |
| HttpUrl | pydantic | Validates URL format |
| UUID | uuid | Accepts str or UUID |
| datetime | datetime | ISO 8601 parsing |
| Decimal | decimal | Precise numeric |
| Literal["a","b"] | typing | Enum-like string |
| constr() | pydantic | Constrained string |
| conint() | pydantic | Constrained integer |