FastAPI Reviewer Skill
Purpose
Reviews FastAPI projects for API design, Pydantic usage, async patterns, and security.
When to Use
- FastAPI project code review
- Pydantic model review
- API endpoint design review
- Async/await pattern check
- Dependency injection review
Project Detection
fastapi in requirements.txt/pyproject.toml
from fastapi import imports
main.py with FastAPI() app
routers/ directory structure
Workflow
Step 1: Analyze Project
**FastAPI**: 0.100+
**Pydantic**: v2
**Database**: SQLAlchemy/Tortoise/Prisma
**Auth**: OAuth2/JWT
**Docs**: OpenAPI auto-generated
Step 2: Select Review Areas
AskUserQuestion:
"Which areas to review?"
Options:
- Full FastAPI review (recommended)
- Pydantic models and validation
- Dependency injection patterns
- Async/await usage
- Security and authentication
multiSelect: true
Detection Rules
Pydantic Models
| Check |
Recommendation |
Severity |
| dict instead of model |
Use Pydantic BaseModel |
MEDIUM |
| Missing Field validation |
Add Field constraints |
MEDIUM |
| No Config class |
Add model_config |
LOW |
| Mutable default in Field |
Use default_factory |
HIGH |
# BAD: Plain dict response
@app.get("/user")
async def get_user() -> dict:
return {"name": "John", "age": 30}
# GOOD: Pydantic model
class UserResponse(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
age: int = Field(..., ge=0, le=150)
model_config = ConfigDict(from_attributes=True)
@app.get("/user")
async def get_user() -> UserResponse:
return UserResponse(name="John", age=30)
# BAD: Mutable default
class Config(BaseModel):
items: list[str] = [] # Shared across instances!
# GOOD: default_factory
class Config(BaseModel):
items: list[str] = Field(default_factory=list)
Dependency Injection
| Check |
Recommendation |
Severity |
| Repeated code in endpoints |
Extract to Depends() |
MEDIUM |
| Global state access |
Use dependency injection |
HIGH |
| No cleanup in deps |
Use yield for cleanup |
MEDIUM |
| Hardcoded dependencies |
Use Depends for testability |
MEDIUM |
# BAD: Repeated DB session code
@app.get("/users")
async def get_users():
db = SessionLocal()
try:
return db.query(User).all()
finally:
db.close()
# GOOD: Dependency injection
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
@app.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User))
return result.scalars().all()
Async Patterns
| Check |
Recommendation |
Severity |
| sync def for I/O |
Use async def |
HIGH |
| Blocking call in async |
Use run_in_executor |
CRITICAL |
| No async DB driver |
Use asyncpg/aiosqlite |
HIGH |
| sync file I/O |
Use aiofiles |
MEDIUM |
# BAD: Blocking call in async
@app.get("/data")
async def get_data():
response = requests.get(url) # Blocks event loop!
return response.json()
# GOOD: Async HTTP client
@app.get("/data")
async def get_data():
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
# BAD: Sync file read
@app.get("/file")
async def read_file():
with open("data.txt") as f:
return f.read()
# GOOD: Async file read
@app.get("/file")
async def read_file():
async with aiofiles.open("data.txt") as f:
return await f.read()
API Design
| Check |
Recommendation |
Severity |
| No response_model |
Add response_model param |
MEDIUM |
| Missing status codes |
Add responses param |
LOW |
| No tags |
Add tags for grouping |
LOW |
| Inconsistent naming |
Use RESTful conventions |
MEDIUM |
# BAD: Minimal endpoint
@app.post("/user")
async def create(data: dict):
return {"id": 1}
# GOOD: Full specification
@app.post(
"/users",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
responses={
409: {"model": ErrorResponse, "description": "User exists"},
},
tags=["users"],
summary="Create a new user",
)
async def create_user(
user: UserCreate,
db: AsyncSession = Depends(get_db),
) -> UserResponse:
"""Create a new user with the provided details."""
return await user_service.create(db, user)
Security
| Check |
Recommendation |
Severity |
| No auth on endpoints |
Add Depends(get_current_user) |
CRITICAL |
| Secrets in code |
Use environment variables |
CRITICAL |
| No rate limiting |
Add slowapi/fastapi-limiter |
HIGH |
| Missing CORS config |
Configure CORSMiddleware |
HIGH |
# Security setup
from fastapi.security import OAuth2PasswordBearer
from fastapi.middleware.cors import CORSMiddleware
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
user = await verify_token(token, db)
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return user
# Protected endpoint
@app.get("/me")
async def get_me(user: User = Depends(get_current_user)):
return user
Response Template
## FastAPI Code Review Results
**Project**: [name]
**FastAPI**: 0.109 | **Pydantic**: v2 | **DB**: SQLAlchemy async
### Pydantic Models
| Status | File | Issue |
|--------|------|-------|
| HIGH | schemas.py:15 | Mutable default in Field |
### Dependency Injection
| Status | File | Issue |
|--------|------|-------|
| MEDIUM | routers/users.py | Repeated DB session code |
### Async Patterns
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | services/external.py:34 | Blocking requests.get() call |
### Security
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | main.py | No CORS configuration |
### Recommended Actions
1. [ ] Replace blocking HTTP calls with httpx async
2. [ ] Add CORS middleware configuration
3. [ ] Extract repeated code to dependencies
4. [ ] Add response_model to all endpoints
Best Practices
- Pydantic v2: Use model_config, Field validators
- Async Everything: DB, HTTP, file I/O
- Dependencies: Extract common logic
- Security: OAuth2, CORS, rate limiting
- Documentation: OpenAPI auto-docs with examples
Integration
python-reviewer: General Python patterns
security-scanner: API security audit
api-documenter: OpenAPI enhancement