Request Validation with Pydantic v2 in FastAPI
Pydantic models are the heart of FastAPI. Every field type and constraint becomes runtime validation and documentation.
Field constraints
from typing import Annotated
from pydantic import BaseModel, EmailStr, Field
class SignupRequest(BaseModel):
email: EmailStr
password: Annotated[str, Field(min_length=8, max_length=128)]
name: Annotated[str, Field(min_length=1, max_length=80)]
age: Annotated[int, Field(ge=13, le=120)] | None = None
tags: list[str] = Field(default_factory=list, max_length=10)
Custom validators
from datetime import date
from pydantic import field_validator, model_validator
class BookingRequest(BaseModel):
start: date
end: date
coupon: str | None = None
@field_validator("coupon")
@classmethod
def normalise_coupon(cls, v: str | None) -> str | None:
return v.strip().upper() if v else v
@model_validator(mode="after")
def check_dates(self):
if self.end <= self.start:
raise ValueError("end must be after start")
return self
Separate input and output models
class UserCreate(BaseModel):
email: EmailStr
password: str
class UserOut(BaseModel):
id: int
email: EmailStr
model_config = {"from_attributes": True} # read from ORM objects
@app.post("/users", response_model=UserOut)
async def create_user(data: UserCreate, db: DbSession):
user = await users.create(db, data)
return user # password hash is filtered out by UserOut
response_model (or the return type) guarantees you never leak fields like password_hash.
Validation errors
Invalid requests get an automatic 422 response listing each problem with its location (["body", "email"]), which front-ends can map to form fields.
Pydantic v2 changes to remember
.dict()became.model_dump(),.json()became.model_dump_json().@validatorbecame@field_validator;@root_validatorbecame@model_validator.class Config: orm_mode = Truebecamemodel_config = {"from_attributes": True}.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.