| name | fastapi-validation |
| user-invocable | false |
| description | Use when FastAPI validation with Pydantic models. Use when building type-safe APIs with robust request/response validation. |
| allowed-tools | ["Bash","Read"] |
FastAPI Validation
Master FastAPI validation with Pydantic for building type-safe APIs
with comprehensive request and response validation.
Pydantic BaseModel Fundamentals
Core Pydantic patterns with Pydantic v2.
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
class User(BaseModel):
id: int
name: str
email: str
created_at: datetime
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
is_active: bool = True
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, le=1000000)
quantity: int = Field(default=0, ge=0)
description: Optional[str] = Field(None, max_length=500)
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
json_schema_extra={
'example': {
'name': 'Widget',
'price': 29.99,
'quantity': 100,
'description': 'A useful widget'
}
}
)
Request Body Validation
Validating complex request bodies.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field
from typing import List
app = FastAPI()
class CreateUserRequest(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr
password: str = Field(..., min_length=8)
age: int = Field(..., ge=13, le=120)
@app.post('/users')
async def create_user(user: CreateUserRequest):
return {'username': user.username, 'email': user.email}
class Address(BaseModel):
street: str
city: str
state: str = Field(..., min_length=2, max_length=2)
zip_code: str = Field(..., pattern=r'^\d{5}(-\d{4})?$')
class UserProfile(BaseModel):
name: str
email: EmailStr
address: Address
phone: Optional[str] = Field(, pattern=)
():
profile
():
users: [CreateUserRequest] = Field(..., min_length=, max_length=)
():
{: (request.users)}
():
name:
color: = Field(..., pattern=)
():
title: = Field(..., min_length=, max_length=)
content:
tags: [Tag] = []
author: UserProfile
published: =
():
post
Query Parameter Validation
Validating query parameters with Field constraints.
from fastapi import FastAPI, Query
from typing import Optional, List
from enum import Enum
app = FastAPI()
@app.get('/users')
async def get_users(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
search: Optional[str] = Query(None, min_length=3, max_length=50)
):
return {'skip': skip, 'limit': limit, 'search': search}
class SortOrder(str, Enum):
asc = 'asc'
desc = 'desc'
class SortField(str, Enum):
name = 'name'
created_at = 'created_at'
updated_at = 'updated_at'
@app.get('/items')
async def get_items(
sort_by: SortField = Query(),
order: SortOrder = Query()
):
{: sort_by, : order}
():
{: tags, : categories}
():
{: q}
Path Parameter Validation
Validating URL path parameters.
from fastapi import FastAPI, Path
from typing import Annotated
app = FastAPI()
@app.get('/users/{user_id}')
async def get_user(
user_id: int = Path(..., gt=0, description='The user ID')
):
return {'user_id': user_id}
@app.get('/items/{item_id}/reviews/{review_id}')
async def get_review(
item_id: Annotated[int, Path(gt=0)],
review_id: Annotated[int, Path(gt=0)]
):
return {'item_id': item_id, 'review_id': review_id}
@app.get('/categories/{category_name}')
async def get_category(
category_name: str = Path(..., min_length=1, max_length=50, pattern=r'^[a-z-]+$')
):
return {'category': category_name}
Custom Validators
Field validators and model validators with Pydantic v2.
from pydantic import BaseModel, field_validator, model_validator
from typing import Any
import re
class UserRegistration(BaseModel):
username: str
email: str
password: str
password_confirm: str
@field_validator('username')
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not re.match(r'^[a-zA-Z0-9_]+$', v):
raise ValueError('Username must be alphanumeric')
if len(v) < 3:
raise ValueError('Username must be at least 3 characters')
return v.lower()
@field_validator('email')
@classmethod
def validate_email_domain(cls, v: str) -> str:
if not v.endswith(('@example.com', '@example.org')):
raise ValueError('Email must be from example.com or example.org')
return v.lower()
() -> :
(v) < :
ValueError()
re.search(, v):
ValueError()
re.search(, v):
ValueError()
re.search(, v):
ValueError()
v
() -> :
.password != .password_confirm:
ValueError()
():
start_date: datetime
end_date: datetime
() -> :
.start_date >= .end_date:
ValueError()
pydantic computed_field
():
name:
price:
tax_rate: =
() -> :
(.price * ( + .tax_rate), )
():
name:
email:
() -> :
(v, ):
v.strip()
v
Field Types
Specialized field types for validation.
from pydantic import (
BaseModel,
EmailStr,
HttpUrl,
SecretStr,
conint,
constr,
confloat,
conlist,
UUID4,
IPvAnyAddress,
FilePath,
DirectoryPath,
Json
)
from typing import List
from datetime import date, time
class AdvancedUser(BaseModel):
username: constr(min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')
bio: constr(max_length=500) | None = None
email: EmailStr
website: HttpUrl | None = None
age: conint(ge=13, le=120)
rating: confloat(ge=0.0, le=5.0)
password: SecretStr
api_key: SecretStr
user_id: UUID4
ip_address: IPvAnyAddress | None = None
birth_date: date
preferred_time: time | None = None
tags: conlist(str, min_length=1, max_length=10)
metadata: Json | None = None
class FileUploadConfig(BaseModel):
upload_dir: DirectoryPath
allowed_file: FilePath | =
Nested Models and Composition
Building complex models from simpler ones.
from pydantic import BaseModel
from typing import List, Optional
class Coordinates(BaseModel):
latitude: float = Field(..., ge=-90, le=90)
longitude: float = Field(..., ge=-180, le=180)
class Location(BaseModel):
name: str
coordinates: Coordinates
address: Optional[str] = None
class Event(BaseModel):
title: str
description: str
location: Location
attendees: List[str] = []
class BaseUser(BaseModel):
username: str
email: EmailStr
class AdminUser(BaseUser):
permissions: List[str]
is_superuser: bool = False
class RegularUser(BaseUser):
subscription_tier: str = 'free'
class ():
created_at: datetime
updated_at: datetime
():
title:
content:
author_id:
():
content:
post_id:
author_id:
Model Configuration
ConfigDict options for model behavior.
from pydantic import BaseModel, ConfigDict, Field
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True)
id: int
name: str
class UserORM(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class UserModel(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
@app.get('/users/{user_id}', response_model=UserORM)
async def get_user(user_id: int, db = Depends(get_db)):
user = db.query(UserModel).filter(UserModel.id == user_id).first()
return user
class ():
model_config = ConfigDict(populate_by_name=)
user_id: = Field(alias=)
user_name: = Field(alias=)
():
model_config = ConfigDict(extra=)
name:
():
model_config = ConfigDict(extra=)
name:
Response Models
Validating and shaping API responses.
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserResponse(BaseModel):
id: int
username: str
email: str
model_config = ConfigDict(from_attributes=True)
@app.post('/users', response_model=UserResponse)
async def create_user(user: UserCreate):
db_user = create_user_in_db(user)
return db_user
class UserDetail(BaseModel):
id: int
username: str
email: str
password_hash: str
secret_key: str
@app.get('/users/{user_id}', response_model=UserDetail, response_model_exclude={'password_hash', 'secret_key'})
async def ():
get_user_from_db(user_id)
():
get_user_from_db(user_id)
():
get_all_users()
typing
():
user = get_user_from_db(user_id)
user
typing
():
status: =
data:
():
status: =
message:
():
:
data = fetch_data()
SuccessResponse(data=data)
Exception e:
ErrorResponse(message=(e))
Error Handling
Custom error messages and validation error handling.
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ValidationError
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = []
for error in exc.errors():
errors.append({
'field': '.'.join(str(loc) for loc in error['loc'][1:]),
'message': error['msg'],
'type': error['type']
})
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={'errors': errors}
)
class User(BaseModel):
username: str = Field(..., min_length=3, description='Username must be at least 3 characters')
age: int = Field(..., ge=18, description='Must be 18 or older')
async def validate_user_data():
:
user = User(**data)
user
ValidationError e:
HTTPException(
status_code=,
detail=e.errors()
)
File Upload Validation
Validating file uploads.
from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List
app = FastAPI()
@app.post('/upload')
async def upload_file(file: UploadFile = File(...)):
allowed_types = ['image/jpeg', 'image/png', 'image/gif']
if file.content_type not in allowed_types:
raise HTTPException(
status_code=400,
detail=f'File type {file.content_type} not allowed'
)
contents = await file.read()
max_size = 5 * 1024 * 1024
if len(contents) > max_size:
raise HTTPException(
status_code=400,
detail='File too large (max 5MB)'
)
if not file.filename.endswith(('.jpg', '.jpeg', '.png', '.gif')):
raise HTTPException(
status_code=400,
detail='Invalid file extension'
)
return {'filename': file.filename, : (contents)}
():
(files) > :
HTTPException(
status_code=,
detail=
)
results = []
file files:
contents = file.read()
results.append({
: file.filename,
: (contents)
})
results
Form Data Validation
Validating form data submissions.
from fastapi import FastAPI, Form
from pydantic import BaseModel, ValidationError
app = FastAPI()
@app.post('/login')
async def login(
username: str = Form(..., min_length=3),
password: str = Form(..., min_length=8)
):
return {'username': username}
class LoginForm(BaseModel):
username: str = Field(..., min_length=3)
password: str = Field(..., min_length=8)
@app.post('/login-validated')
async def login_validated(
username: str = Form(...),
password: str = Form(...)
):
try:
form = LoginForm(username=username, password=password)
return {'username': form.username}
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
@app.post('/profile')
():
result = {: name, : bio}
avatar:
result[] = avatar.filename
result
Advanced Patterns
Discriminated unions and recursive models.
from pydantic import BaseModel, Field, Discriminator
from typing import Literal, Union, List
class Cat(BaseModel):
pet_type: Literal['cat']
meows: int
class Dog(BaseModel):
pet_type: Literal['dog']
barks: float
Pet = Union[Cat, Dog]
class PetOwner(BaseModel):
name: str
pet: Pet
@app.post('/pets')
async def create_pet(owner: PetOwner):
return owner
class TreeNode(BaseModel):
value: int
children: List['TreeNode'] = []
TreeNode.model_rebuild()
@app.post('/tree')
async def create_tree(tree: TreeNode):
return tree
typing TypeVar,
T = TypeVar()
(BaseModel, [T]):
data: T
message:
success: =
():
:
name:
():
user = UserData(=user_id, name=)
Response(data=user, message=)
When to Use This Skill
Use fastapi-validation when:
- Building APIs that require strict input validation
- Ensuring type safety across request and response models
- Implementing complex validation rules and business logic
- Converting between database models and API schemas
- Documenting API schemas with OpenAPI
- Preventing invalid data from entering your system
- Building forms with server-side validation
- Handling file uploads with validation
- Creating reusable validation patterns
FastAPI Validation Best Practices
- Use specific types - Use EmailStr, HttpUrl, UUID instead of plain str
for better validation
- Separate request and response - Create different models for input and
output
- Leverage computed fields - Use computed fields for derived values
instead of manual calculation
- Validate early - Validate at API boundary before business logic
- Custom validators - Create reusable validators for common patterns
- Meaningful error messages - Provide clear, actionable error messages
- Use aliases - Handle different naming conventions (camelCase,
snake_case) with aliases
- Exclude sensitive data - Always exclude passwords and secrets from responses
- ORM mode - Enable from_attributes for database model conversion
- Document examples - Use json_schema_extra to provide example data
FastAPI Validation Common Pitfalls
- Missing response_model - Not using response_model exposes all fields
including secrets
- Incorrect Field usage - Using Field without ... for required fields
makes them optional
- Validator order - Validators run in definition order, dependencies
matter
- Coercion confusion - Pydantic coerces types by default, use strict
mode when needed
- Recursive model rebuild - Forgetting model_rebuild() on recursive
models causes errors
- Form data limitations - Form data doesn't support nested models
directly
- List validation - Not setting max_length on lists can allow resource
exhaustion
- Regex complexity - Complex regex patterns can cause performance issues
- Timezone handling - datetime fields need explicit timezone handling
- Union validation - Union types validate in order, put more specific
types first
Resources