Back to skills
extension
Category: Development & EngineeringNo API key required

fastapi-reviewer

WHEN: FastAPI project review, Pydantic models, async endpoints, dependency injection WHAT: Pydantic validation + Dependency injection + Async patterns + OpenAPI docs + Security WHEN NOT: Django → django-reviewer, Flask → flask-reviewer, General Python → python-reviewer

personAuthor: jakexiaohubgithub

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

  1. Pydantic v2: Use model_config, Field validators
  2. Async Everything: DB, HTTP, file I/O
  3. Dependencies: Extract common logic
  4. Security: OAuth2, CORS, rate limiting
  5. Documentation: OpenAPI auto-docs with examples

Integration

  • python-reviewer: General Python patterns
  • security-scanner: API security audit
  • api-documenter: OpenAPI enhancement