How do you create Pydantic models for request validation?
Pydantic models are Python classes that inherit from BaseModel and use type annotations to define fields. FastAPI uses these models to automatically validate request bodies, serialize responses, and generate OpenAPI schemas.
How it works:
- Define a class inheriting from
BaseModel - Declare fields with type annotations
- Use the model as a parameter type in your path operation
- FastAPI validates incoming JSON against the model and returns 422 if invalid
Key features:
- Automatic type coercion: Pydantic will try to convert compatible types (e.g.,
"42"to42for anintfield) - Nested models: Models can reference other models
- Default values: Fields with defaults become optional in the request
- Validation errors: Automatically returns detailed 422 errors listing every invalid field
class ItemCreate(BaseModel):
name: str
price: float
quantity: int = 0 # optional with default
@app.post("/items")
def create_item(item: ItemCreate):
# item is already validated
return item
from pydantic import BaseModel
from typing import Optional
class Address(BaseModel):
street: str
city: str
zip_code: str
class UserCreate(BaseModel):
username: str
email: str
age: int
bio: Optional[str] = None # optional field
address: Address # nested model
# Successful validation
data = {
"username": "alice",
"email": "alice@example.com",
"age": 30,
"address": {"street": "123 Main St", "city": "Portland", "zip_code": "97201"}
}
user = UserCreate(**data)
print(user.username)
print(user.age)
print(user.address.city)
print(user.bio)
# Type coercion -- string "25" becomes int 25
user2 = UserCreate(
username="bob", email="bob@example.com", age="25",
address={"street": "1 Elm", "city": "Salem", "zip_code": "97301"}
)
print(user2.age, type(user2.age))
# Validation error
try:
bad = UserCreate(username="x", email="x@x.com", age="not_a_number",
address={"street": "1", "city": "X", "zip_code": "0"})
except Exception as e:
print(f"Validation error: {e.error_count()} error(s)")
- •Inherit from BaseModel and use type annotations for automatic validation
- •Fields without defaults are required; fields with defaults are optional
- •Pydantic v2 performs strict or lax type coercion depending on mode
- •FastAPI returns 422 Unprocessable Entity with detailed error info on validation failure
- •Pydantic v2 does type coercion by default in lax mode -- '42' becomes 42 for int fields which can hide bugs
- •Optional[str] = None is not the same as str | None without a default -- the latter is still required
- •Model fields are immutable by default in Pydantic v2 unless you set model_config = ConfigDict(frozen=False)