debug:fastapi

FastAPI Debugging Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "debug:fastapi" with this command: npx skills add snakeo/claude-debug-and-refactor-skills-plugin/snakeo-claude-debug-and-refactor-skills-plugin-debug-fastapi

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:

1. Check the exact error response

{ "detail": [ { "loc": ["body", "field_name"], "msg": "field required", "type": "value_error.missing" } ] }

2. Validate your Pydantic model directly

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()) # Detailed error info

3. Use Optional for non-required fields

from typing import Optional

class UserCreate(BaseModel): name: str email: str age: Optional[int] = None # Now optional with default

  1. 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:

1. Add exception logging middleware

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

2. Add global exception handler

@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)} # Only in dev! )

3. Check dependency injection

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()

  1. 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:

1. Check for missing await

Wrong

@app.get("/users") async def get_users(): users = db.get_users() # If async, needs await! return users

Correct

@app.get("/users") async def get_users(): users = await db.get_users() return users

2. Don't use blocking I/O in async functions

Wrong - blocks event loop

@app.get("/data") async def get_data(): import time time.sleep(5) # BLOCKS! return {"data": "done"}

Correct - use asyncio.sleep or run_in_executor

import asyncio @app.get("/data") async def get_data(): await asyncio.sleep(5) # Non-blocking return {"data": "done"}

3. For sync database operations, use def instead of async def

@app.get("/users") def get_users(db: Session = Depends(get_db)): # FastAPI runs sync functions in threadpool return db.query(User).all()

  1. Dependency Injection Errors

Symptoms:

  • TypeError: X() takes Y positional arguments but Z were given

  • Dependencies not being called

  • ValidationError from dependency parameters

Debugging Steps:

1. Ensure Depends() is used correctly

from fastapi import Depends

Wrong - function is called immediately

@app.get("/items") def get_items(db = get_db()): # WRONG! pass

Correct - FastAPI manages the dependency

@app.get("/items") def get_items(db = Depends(get_db)): pass

2. Debug dependency chain

def get_settings(): print("Loading settings...") # Debug print return Settings()

def get_db(settings: Settings = Depends(get_settings)): print(f"Connecting to {settings.db_url}") # Debug print return create_engine(settings.db_url)

3. Handle dependency failures gracefully

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")

  1. 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()

1. Add CORS middleware (MUST be before routes)

app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # Or [""] for dev allow_credentials=True, allow_methods=[""], allow_headers=["*"], )

2. Debug: Log all requests

@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

3. Common issues:

- allow_origins must match EXACTLY (including protocol and port)

- allow_credentials=True requires specific origins (not "*")

- Check if middleware order is correct

  1. 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

1. Proper session management

def get_db(): db = SessionLocal() try: yield db db.commit() # Commit on success except Exception: db.rollback() # Rollback on error raise finally: db.close() # Always close

2. Check connection pool settings

from sqlalchemy import create_engine

engine = create_engine( DATABASE_URL, pool_size=5, max_overflow=10, pool_timeout=30, pool_pre_ping=True, # Test connections before use echo=True, # Log all SQL (debug only!) )

3. Debug session state

@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"}

  1. Circular Import Errors

Symptoms:

  • ImportError: cannot import name 'X' from partially initialized module

  • Application fails to start

  • AttributeError: module has no attribute

Debugging Steps:

1. Identify the circular dependency

app/models.py

from app.schemas import UserSchema # Imports schemas

app/schemas.py

from app.models import User # Imports models - CIRCULAR!

2. Solution: Use TYPE_CHECKING

from typing import TYPE_CHECKING

if TYPE_CHECKING: from app.models import User

3. Or use string annotations

class UserSchema(BaseModel): user: "User" # Forward reference

4. Or restructure: Move shared code to separate module

app/base.py - Contains shared Base class

app/models.py - Imports from base

app/schemas.py - Imports from base

Debugging Tools

  1. Python Debugger (pdb/breakpoint)

Insert breakpoint in your code

@app.get("/debug") def debug_endpoint(): data = fetch_data() breakpoint() # Execution stops here return process(data)

Run with: uvicorn main:app --reload

When breakpoint hits, use pdb commands:

n - next line

s - step into

c - continue

p variable - print variable

l - list code around current line

q - quit debugger

  1. Uvicorn with Reload

Development server with auto-reload

uvicorn main:app --reload --log-level debug

With specific host/port

uvicorn main:app --reload --host 0.0.0.0 --port 8000

Show access logs

uvicorn main:app --reload --access-log

  1. OpenAPI /docs Endpoint

FastAPI auto-generates interactive docs

Access at: http://localhost:8000/docs (Swagger UI)

Or: http://localhost:8000/redoc (ReDoc)

Customize docs

app = FastAPI( title="My API", description="Debug info here", docs_url="/docs", # Or None to disable redoc_url="/redoc", )

Test endpoints directly in browser!

  1. Logging Module

import logging from fastapi import FastAPI

Configure logging

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"}

  1. httpx for Testing

tests/test_api.py

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()) # Debug output

Sync testing with TestClient

from fastapi.testclient import TestClient

def test_sync(): client = TestClient(app) response = client.get("/users") assert response.status_code == 200

  1. VS Code Debugging

// .vscode/launch.json { "version": "0.2.0", "configurations": [ { "name": "FastAPI", "type": "debugpy", "request": "launch", "module": "uvicorn", "args": ["main:app", "--reload"], "jinja": true, "env": { "PYTHONPATH": "${workspaceFolder}" } } ] }

  1. Debug Middleware

from starlette.middleware.base import BaseHTTPMiddleware import time

class DebugMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): # Log request details print(f"Request: {request.method} {request.url}") print(f"Headers: {dict(request.headers)}")

    # Time the request
    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

Use curl to reproduce

curl -X POST http://localhost:8000/api/users
-H "Content-Type: application/json"
-d '{"name": "test"}'

Check the error response

422 = Validation error (check Pydantic model)

401/403 = Auth issue (check dependencies)

500 = Server error (check logs)

404 = Route not found (check URL and method)

Review server logs

Check uvicorn output

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)): # Comment out sections to isolate # return {"debug": "step 1"}

# Check user data
print(f"User data: {user.dict()}")

# Check db connection
print(f"DB connected: {db.is_active}")

return create_user_in_db(db, user)

- Test dependencies individually

Test in Python shell

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}")

# Try manual validation
from app.schemas import UserCreate
user = UserCreate(**body)  # Will raise if invalid
return {"validated": user.dict()}

Phase 3: Fix and Verify

Apply the fix

Before

class UserCreate(BaseModel): name: str email: str

After (with proper validation)

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

Test valid request

curl -X POST http://localhost:8000/api/users
-H "Content-Type: application/json"
-d '{"name": "John", "email": "john@example.com"}'

Test edge cases

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

Development server

uvicorn main:app --reload --log-level debug

With custom host/port

uvicorn main:app --reload --host 0.0.0.0 --port 8000

Production (multiple workers)

uvicorn main:app --workers 4

With gunicorn

gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker

Testing Endpoints

GET request

curl http://localhost:8000/api/users

POST with JSON

curl -X POST http://localhost:8000/api/users
-H "Content-Type: application/json"
-d '{"name": "test", "email": "test@example.com"}'

With authentication

curl -H "Authorization: Bearer TOKEN"
http://localhost:8000/api/protected

Verbose output for debugging

curl -v http://localhost:8000/api/users

Python Debugging

Insert breakpoint

breakpoint()

Or use pdb directly

import pdb; pdb.set_trace()

Quick debug print

print(f"DEBUG: {variable=}") # Python 3.8+ f-string debugging

Log at debug level

import logging logging.debug(f"Variable value: {variable}")

Database Debugging

Enable SQLAlchemy echo

engine = create_engine(DATABASE_URL, echo=True)

Check session state

print(f"Session: dirty={db.dirty}, new={db.new}, deleted={db.deleted}")

Raw SQL for debugging

result = db.execute("SELECT * FROM users WHERE id = :id", {"id": 1}) print(result.fetchall())

Async Debugging

Check if running in async context

import asyncio try: loop = asyncio.get_running_loop() print(f"Running in event loop: {loop}") except RuntimeError: print("No event loop running")

Debug coroutines

import asyncio asyncio.run(your_async_function())

Environment and Configuration

Check Python environment

python -c "import fastapi; print(fastapi.version)" python -c "import pydantic; print(pydantic.version)"

List installed packages

pip list | grep -E "(fastapi|pydantic|uvicorn|starlette)"

Check environment variables

python -c "import os; print(os.environ.get('DATABASE_URL'))"

Additional Resources

  • FastAPI Official Debugging Guide

  • FastAPI Error Handling Tutorial

  • Better Stack: FastAPI Error Handling Patterns

  • VS Code FastAPI Tutorial

  • Orchestra: FastAPI Debugging Guide

  • Better Stack: Logging with FastAPI

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

refactor:flutter

No summary provided by upstream source.

Repository SourceNeeds Review
General

refactor:nestjs

No summary provided by upstream source.

Repository SourceNeeds Review
General

debug:flutter

No summary provided by upstream source.

Repository SourceNeeds Review
General

refactor:spring-boot

No summary provided by upstream source.

Repository SourceNeeds Review