| name | debug:fastapi |
| description | Debug FastAPI applications systematically with this comprehensive troubleshooting skill. Covers async/await issues, Pydantic validation errors (422 responses), dependency injection failures, CORS configuration problems, database session management, and circular import resolution. Provides structured four-phase debugging methodology with FastAPI-specific tools including uvicorn logging, OpenAPI docs, and middleware debugging patterns. |
FastAPI Debugging Guide
Overview
This skill provides a systematic approach to debugging FastAPI applications. FastAPI is built on Starlette and Pydantic, which means debugging often involves understanding async behavior, request validation, and dependency injection patterns.
When to use this skill:
- 422 Unprocessable Entity errors
- Pydantic ValidationError exceptions
- Async/await related issues
- Dependency injection failures
- CORS errors in browser
- 500 Internal Server Errors
- Database session/connection issues
- Circular import errors on startup
Common Error Patterns
1. Pydantic ValidationError (422 Unprocessable Entity)
Symptoms:
- API returns 422 status code
- Response contains
detail array with validation errors
- Client receives "field required" or "type error" messages
Root Causes:
- Missing required fields in request body
- Incorrect data types (string instead of int, etc.)
- Invalid enum values
- Nested model validation failures
Debugging Steps:
{
"detail": [
{
"loc": ["body", "field_name"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
from pydantic import BaseModel, ValidationError
class UserCreate(BaseModel):
name: str
email: str
age: int
try:
user = UserCreate(**your_data)
except ValidationError as e:
print(e.json())
from typing import Optional
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
2. 500 Internal Server Error
Symptoms:
- Generic "Internal Server Error" response
- No detailed error in API response
- Error details only in server logs
Root Causes:
- Unhandled exceptions in endpoint code
- Database connection failures
- Dependency injection failures
- Division by zero, null attribute access
- External service timeouts
Debugging Steps:
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def log_exceptions(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.exception(f"Unhandled exception: {e}")
raise
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.exception(f"Unhandled: {exc}")
return JSONResponse(
status_code=500,
content={"detail": str(exc)}
)
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db
except Exception as e:
logger.error(f"DB error: {e}")
raise
finally:
db.close()
3. Async/Await Issues
Symptoms:
RuntimeError: Event loop is already running
RuntimeWarning: coroutine was never awaited
- Blocking behavior in async endpoints
TypeError: object X can't be used in 'await' expression
Root Causes:
- Mixing sync and async code incorrectly
- Using blocking I/O in async functions
- Missing await keywords
- Sync database calls in async context
Debugging Steps:
@app.get("/users")
async def get_users():
users = db.get_users()
return users
@app.get("/users")
async def get_users():
users = await db.get_users()
return users
@app.get("/data")
async def get_data():
import time
time.sleep(5)
return {"data": "done"}
import asyncio
@app.get("/data")
async def get_data():
await asyncio.sleep(5)
return {"data": "done"}
@app.get("/users")
def get_users(db: Session = Depends(get_db)):
return db.query(User).all()
4. Dependency Injection Errors
Symptoms:
TypeError: X() takes Y positional arguments but Z were given
- Dependencies not being called
ValidationError from dependency parameters
Debugging Steps:
from fastapi import Depends
@app.get("/items")
def get_items(db = get_db()):
pass
@app.get("/items")
def get_items(db = Depends(get_db)):
pass
def get_settings():
print("Loading settings...")
return Settings()
def get_db(settings: Settings = Depends(get_settings)):
print(f"Connecting to {settings.db_url}")
return create_engine(settings.db_url)
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
user = await verify_token(token)
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return user
except Exception as e:
logger.error(f"Auth failed: {e}")
raise HTTPException(status_code=401, detail="Authentication failed")
5. CORS Problems
Symptoms:
- Browser console shows CORS errors
- API works in Postman but not browser
- Preflight OPTIONS requests failing
Debugging Steps:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def log_requests(request, call_next):
print(f"Origin: {request.headers.get('origin')}")
print(f"Method: {request.method}")
response = await call_next(request)
print(f"CORS headers: {dict(response.headers)}")
return response
6. Database Session Issues
Symptoms:
sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back
- Database connections exhausted
- Stale data being returned
Debugging Steps:
from sqlalchemy.orm import Session
from contextlib import contextmanager
def get_db():
db = SessionLocal()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
pool_timeout=30,
pool_pre_ping=True,
echo=True,
)
@app.get("/debug-db")
def debug_db(db: Session = Depends(get_db)):
print(f"Session active: {db.is_active}")
print(f"Session dirty: {db.dirty}")
print(f"Session new: {db.new}")
return {"status": "ok"}
7. Circular Import Errors
Symptoms:
ImportError: cannot import name 'X' from partially initialized module
- Application fails to start
AttributeError: module has no attribute
Debugging Steps:
from app.schemas import UserSchema
from app.models import User
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.models import User
class UserSchema(BaseModel):
user: "User"
Debugging Tools
1. Python Debugger (pdb/breakpoint)
@app.get("/debug")
def debug_endpoint():
data = fetch_data()
breakpoint()
return process(data)
2. Uvicorn with Reload
uvicorn main:app --reload --log-level debug
uvicorn main:app --reload --host 0.0.0.0 --port 8000
uvicorn main:app --reload --access-log
3. OpenAPI /docs Endpoint
app = FastAPI(
title="My API",
description="Debug info here",
docs_url="/docs",
redoc_url="/redoc",
)
4. Logging Module
import logging
from fastapi import FastAPI
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.on_event("startup")
async def startup():
logger.info("Application starting...")
@app.get("/")
def root():
logger.debug("Root endpoint called")
logger.info("Processing request")
return {"status": "ok"}
5. httpx for Testing
import pytest
from httpx import AsyncClient, ASGITransport
from main import app
@pytest.mark.asyncio
async def test_endpoint():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/users")
assert response.status_code == 200
print(response.json())
from fastapi.testclient import TestClient
def test_sync():
client = TestClient(app)
response = client.get("/users")
assert response.status_code == 200
6. VS Code Debugging
{
"version": "0.2.0",
"configurations": [
{
"name": "FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": ["main:app", "--reload"],
"jinja": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
}
]
}
7. Debug Middleware
from starlette.middleware.base import BaseHTTPMiddleware
import time
class DebugMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
print(f"Request: {request.method} {request.url}")
print(f"Headers: {dict(request.headers)}")
start = time.time()
response = await call_next(request)
duration = time.time() - start
print(f"Response: {response.status_code} in {duration:.3f}s")
return response
app.add_middleware(DebugMiddleware)
The Four Phases of FastAPI Debugging
Phase 1: Reproduce and Identify
-
Reproduce the error consistently
curl -X POST http://localhost:8000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "test"}'
-
Check the error response
-
Review server logs
uvicorn main:app --log-level debug
Phase 2: Isolate the Problem
-
Simplify the endpoint
@app.post("/api/users")
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
print(f"User data: {user.dict()}")
print(f"DB connected: {db.is_active}")
return create_user_in_db(db, user)
-
Test dependencies individually
from app.dependencies import get_db
db = next(get_db())
print(db.execute("SELECT 1").scalar())
-
Validate request data
@app.post("/api/users")
async def create_user(request: Request):
body = await request.json()
print(f"Raw body: {body}")
from app.schemas import UserCreate
user = UserCreate(**body)
return {"validated": user.dict()}
Phase 3: Fix and Verify
-
Apply the fix
class UserCreate(BaseModel):
name: str
email: str
from pydantic import BaseModel, EmailStr, validator
class UserCreate(BaseModel):
name: str
email: EmailStr
@validator('name')
def name_not_empty(cls, v):
if not v.strip():
raise ValueError('Name cannot be empty')
return v
-
Test the fix
curl -X POST http://localhost:8000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "john@example.com"}'
curl -X POST http://localhost:8000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "", "email": "invalid"}'
Phase 4: Prevent Regression
-
Add tests
def test_create_user_valid():
response = client.post("/api/users", json={
"name": "John",
"email": "john@example.com"
})
assert response.status_code == 200
def test_create_user_invalid_email():
response = client.post("/api/users", json={
"name": "John",
"email": "invalid"
})
assert response.status_code == 422
-
Add error handling
from fastapi import HTTPException
@app.post("/api/users")
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
try:
return create_user_in_db(db, user)
except IntegrityError:
raise HTTPException(status_code=409, detail="User already exists")
except Exception as e:
logger.exception("Failed to create user")
raise HTTPException(status_code=500, detail="Internal error")
Quick Reference Commands
Starting and Running
uvicorn main:app --reload --log-level debug
uvicorn main:app --reload --host 0.0.0.0 --port 8000
uvicorn main:app --workers 4
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
Testing Endpoints
curl http://localhost:8000/api/users
curl -X POST http://localhost:8000/api/users \
-H "Content-Type: application/json" \
-d '{"name": "test", "email": "test@example.com"}'
curl -H "Authorization: Bearer TOKEN" \
http://localhost:8000/api/protected
curl -v http://localhost:8000/api/users
Python Debugging
breakpoint()
import pdb; pdb.set_trace()
print(f"DEBUG: {variable=}")
import logging
logging.debug(f"Variable value: {variable}")
Database Debugging
engine = create_engine(DATABASE_URL, echo=True)
print(f"Session: dirty={db.dirty}, new={db.new}, deleted={db.deleted}")
result = db.execute("SELECT * FROM users WHERE id = :id", {"id": 1})
print(result.fetchall())
Async Debugging
import asyncio
try:
loop = asyncio.get_running_loop()
print(f"Running in event loop: {loop}")
except RuntimeError:
print("No event loop running")
import asyncio
asyncio.run(your_async_function())
Environment and Configuration
python -c "import fastapi; print(fastapi.__version__)"
python -c "import pydantic; print(pydantic.__version__)"
pip list | grep -E "(fastapi|pydantic|uvicorn|starlette)"
python -c "import os; print(os.environ.get('DATABASE_URL'))"
Additional Resources