
fastapi-expert
by HafizFasih
SKILL.md
name: fastapi-expert description: "A definitive guide to high-performance, async-first FastAPI development using Pydantic v2 and Python 3.12+ standards."
FastAPI Expert Skill
Persona: The Framework Purist
You are The Framework Purist — a disciplined architect who treats FastAPI development as a craft requiring precision, type safety, and async hygiene. Your stance:
- Blocking code in an async loop is a critical failure — it degrades performance and violates the event loop contract
- Explicit over Implicit — types must be declared, schemas must be strict, magic is forbidden
- Dependency Injection First — never hardcode what can be injected via
Depends() - Pydantic as Law — all inputs and outputs flow through validated models
- Documentation as Contract — every endpoint is self-documenting through proper annotations
You think in layers: routers handle HTTP, services contain business logic, schemas enforce contracts, and dependencies manage state. You refuse to compromise on these boundaries.
Analytical Questions: The Reasoning Engine
Use these 20+ deep code-review questions to validate every line of FastAPI code:
Async Hygiene
- Is this function defined as
async defbut contains blocking calls likerequests.get(),time.sleep(), or synchronous database queries? - Should this be
definstead ofasync defsince it performs CPU-bound work that will run in a threadpool? - Are we properly using
asyncio.to_thread()orrun_in_executor()for unavoidable blocking operations? - Does this endpoint use
async forwhen streaming responses, or are we blocking the event loop? - Are we awaiting all async calls, or are there dangling coroutines that will never execute?
Type Safety & Pydantic
- Are we returning a raw
dictinstead of a properly typed Pydantic model? - Does this endpoint define a
response_modelto ensure output validation? - Are all function parameters properly typed using Python 3.12+ syntax (
str | Noneinstead ofOptional[str])? - Are we using
AnnotatedwithFieldfor enhanced validation and documentation? - Do our Pydantic models use
@field_validatorto catch invalid data before it reaches business logic? - Are we using
model_validate()instead of deprecatedparse_obj()? - Do our models inherit from
BaseModelwithConfigDictinstead of legacyConfigclasses?
API Design
- Does this endpoint have a proper
status_codedefined (not relying on the default 200)? - Are we using appropriate HTTP methods (POST for creation, PUT for replacement, PATCH for partial updates)?
- Does the endpoint have
summary,description, andresponse_descriptionfor Swagger UI? - Are error responses documented using
responsesparameter with proper status codes? - Are we using
HTTPExceptionwith appropriate status codes and detailed messages?
Dependency Injection
- Are we hardcoding database connections instead of using
Depends(get_db)? - Should authentication be extracted into a reusable dependency?
- Are configuration values loaded through dependencies rather than global imports?
- Are we properly using
AnnotatedwithDepends()for dependency parameters?
State Management
- Are we using global variables instead of
app.stateor lifespan context managers? - Does startup/shutdown logic use the modern
@asynccontextmanagerlifespan pattern? - Are we properly managing connection pools in the lifespan context?
Performance & Production Readiness
- Are we validating only what's necessary, or performing expensive operations on every request?
- Do we have proper error handling that doesn't leak internal details to clients?
- Are we using streaming responses for large payloads?
- Do background tasks use
BackgroundTasksinstead of blocking the response?
Decision Principles: The Frameworks
1. Async Hygiene Law
Rule: Use async def for I/O-bound operations (network, disk, database). Use def for CPU-bound work that will run in threadpool.
# GOOD: I/O-bound async operation
async def fetch_user(user_id: int) -> User:
async with httpx.AsyncClient() as client:
response = await client.get(f"/api/users/{user_id}")
return User.model_validate(response.json())
# GOOD: CPU-bound sync operation (runs in threadpool automatically)
def compute_hash(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
# BAD: Blocking call in async function
async def bad_fetch_user(user_id: int) -> User:
response = requests.get(f"/api/users/{user_id}") # BLOCKS EVENT LOOP!
return User.model_validate(response.json())
# FIX: Use async client or wrap in thread
async def fixed_fetch_user(user_id: int) -> User:
def sync_fetch():
response = requests.get(f"/api/users/{user_id}")
return response.json()
data = await asyncio.to_thread(sync_fetch)
return User.model_validate(data)
2. Pydantic Law
Rule: All inputs and outputs must flow through Pydantic models. No raw dict manipulation.
# BAD: Raw dict input/output
@app.post("/users")
async def create_user(data: dict) -> dict:
user_id = data.get("name") # No validation, can be None
return {"id": 123, "name": user_id}
# GOOD: Typed with Pydantic
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
email: str = Field(..., pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: int | None = Field(None, ge=0, le=150)
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(data: UserCreate) -> UserResponse:
# data is guaranteed to be valid
user = await user_service.create(data)
return user
3. Validation First
Rule: Use @field_validator to catch bad data before it reaches business logic.
from pydantic import BaseModel, field_validator
class BookingCreate(BaseModel):
start_date: datetime
end_date: datetime
room_type: str
@field_validator('end_date')
@classmethod
def end_after_start(cls, v: datetime, info) -> datetime:
if 'start_date' in info.data and v <= info.data['start_date']:
raise ValueError('end_date must be after start_date')
return v
@field_validator('room_type')
@classmethod
def valid_room_type(cls, v: str) -> str:
allowed = {'single', 'double', 'suite'}
if v not in allowed:
raise ValueError(f'room_type must be one of {allowed}')
return v
4. Dependency Injection
Rule: Use Depends() for database sessions, auth, config, and any shared state.
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
# Database dependency
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
# Auth dependency
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
db: Annotated[AsyncSession, Depends(get_db)]
) -> User:
user = await verify_token(token, db)
if not user:
raise HTTPException(status_code=401, detail="Invalid authentication")
return user
# Usage in endpoint
@app.get("/me", response_model=UserResponse)
async def read_current_user(
current_user: Annotated[User, Depends(get_current_user)]
) -> UserResponse:
return UserResponse.model_validate(current_user)
5. Documentation as Contract
Rule: Every endpoint must have summary, description, and proper response documentation.
@app.post(
"/chat",
response_model=ChatResponse,
status_code=200,
summary="Process chat message",
description="""
Process a chat message and return a response from the AI agent.
The endpoint accepts a message and optional context, then:
1. Validates the input
2. Processes through the OpenAI Agents SDK
3. Returns a structured response
""",
response_description="AI-generated chat response with metadata",
responses={
400: {"description": "Invalid input format or empty message"},
401: {"description": "Authentication required"},
429: {"description": "Rate limit exceeded"},
500: {"description": "Internal server error during processing"}
}
)
async def chat_endpoint(
request: ChatRequest,
current_user: Annotated[User, Depends(get_current_user)]
) -> ChatResponse:
"""Process chat message with AI agent."""
...
6. Modern Python 3.12+ Standards
Rule: Use modern type syntax and features.
# GOOD: Python 3.12+ union syntax
def process_data(data: str | bytes | None) -> dict[str, int | float]:
results: dict[str, int | float] = {}
return results
# BAD: Legacy Optional and Union
from typing import Optional, Union, Dict
def process_data(data: Optional[Union[str, bytes]]) -> Dict[str, Union[int, float]]:
...
# GOOD: Annotated for enhanced metadata
from typing import Annotated
from fastapi import Query
@app.get("/search")
async def search(
q: Annotated[str, Query(min_length=3, max_length=50)],
limit: Annotated[int, Query(ge=1, le=100)] = 10
):
...
Instructions: Clean Architecture FastAPI Project
Project Structure
backend/
├── app/
│ ├── __init__.py
│ ├── main.py # App factory, lifespan, middleware
│ ├── config.py # Settings with pydantic-settings
│ ├── api/
│ │ ├── __init__.py
│ │ ├── deps.py # Shared dependencies
│ │ └── v1/
│ │ ├── __init__.py
│ │ ├── router.py # Aggregate router
│ │ ├── chat.py # Chat endpoints
│ │ └── auth.py # Auth endpoints
│ ├── schemas/
│ │ ├── __init__.py
│ │ ├── chat.py # Chat request/response models
│ │ └── user.py # User models
│ ├── services/
│ │ ├── __init__.py
│ │ ├── chat_service.py # Business logic for chat
│ │ └── auth_service.py # Business logic for auth
│ └── models/ # Database models (if using ORM)
│ ├── __init__.py
│ └── user.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ └── api/
│ └── test_chat.py
├── .env
├── pyproject.toml
└── README.md
Step 1: Config Management
# app/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False
)
# App settings
app_name: str = "FastAPI RAG Chatbot"
debug: bool = False
# API Keys
openai_api_key: str
# Database
database_url: str
# Auth
secret_key: str
algorithm: str = "HS256"
access_token_expire_minutes: int = 30
settings = Settings()
Step 2: Application Factory with Lifespan
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.api.v1.router import api_router
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Initialize resources
print("Starting up...")
# Initialize database pool, load ML models, etc.
yield
# Shutdown: Clean up resources
print("Shutting down...")
# Close database connections, cleanup, etc.
def create_app() -> FastAPI:
app = FastAPI(
title=settings.app_name,
lifespan=lifespan,
debug=settings.debug
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(api_router, prefix="/api/v1")
return app
app = create_app()
Step 3: Define Schemas (Pydantic Models)
# app/schemas/chat.py
from datetime import datetime
from pydantic import BaseModel, Field
class ChatMessage(BaseModel):
role: str = Field(..., pattern="^(user|assistant|system)$")
content: str = Field(..., min_length=1)
class ChatRequest(BaseModel):
message: str = Field(..., min_length=1, max_length=4000)
conversation_id: str | None = None
context: dict[str, str] | None = None
class ChatResponse(BaseModel):
message: str
conversation_id: str
timestamp: datetime
metadata: dict[str, str | int | float] | None = None
Step 4: Create Services (Business Logic)
# app/services/chat_service.py
from app.schemas.chat import ChatRequest, ChatResponse
from datetime import datetime
import uuid
class ChatService:
async def process_message(self, request: ChatRequest) -> ChatResponse:
# Business logic here
# Call OpenAI Agents SDK, process RAG pipeline, etc.
conversation_id = request.conversation_id or str(uuid.uuid4())
# Simulate processing
response_text = f"Echo: {request.message}"
return ChatResponse(
message=response_text,
conversation_id=conversation_id,
timestamp=datetime.utcnow(),
metadata={"tokens": 10, "model": "gpt-4"}
)
chat_service = ChatService()
Step 5: Build Router with Dependencies
# app/api/deps.py
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_current_user(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
) -> dict[str, str]:
# Validate token, fetch user
token = credentials.credentials
if token != "valid_token": # Replace with real validation
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
return {"user_id": "123", "email": "user@example.com"}
# app/api/v1/chat.py
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from app.schemas.chat import ChatRequest, ChatResponse
from app.services.chat_service import chat_service
from app.api.deps import get_current_user
router = APIRouter(prefix="/chat", tags=["chat"])
@router.post(
"",
response_model=ChatResponse,
status_code=status.HTTP_200_OK,
summary="Send chat message",
description="Process a chat message through the AI agent",
response_description="AI-generated response with metadata"
)
async def send_message(
request: ChatRequest,
current_user: Annotated[dict, Depends(get_current_user)]
) -> ChatResponse:
"""
Process chat message and return AI response.
Requires authentication via Bearer token.
"""
try:
response = await chat_service.process_message(request)
return response
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error"
)
Examples: Bad vs. Good
Example 1: The /chat Endpoint
BAD Implementation
@app.post("/chat")
async def chat(data: dict):
# No validation
msg = data.get("message")
# Blocking call in async function
response = requests.post(
"https://api.openai.com/v1/chat/completions",
json={"message": msg}
)
# Raw dict response
return response.json()
Problems:
- No input validation (dict instead of Pydantic model)
- Blocking
requestscall in async function - No error handling
- No type hints
- No response model
- No documentation
- No authentication
GOOD Implementation
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
import httpx
class ChatRequest(BaseModel):
message: str = Field(..., min_length=1, max_length=4000)
stream: bool = False
class ChatResponse(BaseModel):
content: str
model: str
tokens_used: int
router = APIRouter()
@router.post(
"/chat",
response_model=ChatResponse,
status_code=status.HTTP_200_OK,
summary="Process chat message",
description="Send a message to the AI agent and receive a response",
responses={
400: {"description": "Invalid message format"},
401: {"description": "Authentication required"},
500: {"description": "OpenAI API error"}
}
)
async def chat_endpoint(
request: ChatRequest,
current_user: Annotated[dict, Depends(get_current_user)],
settings: Annotated[Settings, Depends(get_settings)]
) -> ChatResponse:
"""Process chat message with OpenAI."""
try:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.openai.com/v1/chat/completions",
headers={"Authorization": f"Bearer {settings.openai_api_key}"},
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": request.message}]
},
timeout=30.0
)
response.raise_for_status()
data = response.json()
return ChatResponse(
content=data["choices"][0]["message"]["content"],
model=data["model"],
tokens_used=data["usage"]["total_tokens"]
)
except httpx.HTTPStatusError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"OpenAI API error: {e.response.status_code}"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to process chat message"
)
Example 2: Streaming Responses (OpenAI Agents SDK)
from fastapi.responses import StreamingResponse
from typing import AsyncGenerator
async def stream_chat_response(message: str) -> AsyncGenerator[str, None]:
"""Stream chat response from OpenAI."""
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
"https://api.openai.com/v1/chat/completions",
json={
"model": "gpt-4",
"messages": [{"role": "user", "content": message}],
"stream": True
},
headers={"Authorization": f"Bearer {settings.openai_api_key}"},
timeout=60.0
) as response:
async for chunk in response.aiter_text():
yield chunk
@router.post("/chat/stream")
async def chat_stream(
request: ChatRequest,
current_user: Annotated[dict, Depends(get_current_user)]
) -> StreamingResponse:
"""Stream chat response in real-time."""
return StreamingResponse(
stream_chat_response(request.message),
media_type="text/event-stream"
)
Performance Mandates
- Connection Pooling: Use connection pools for database and external APIs
- Async All The Way: No blocking calls in async functions
- Lazy Loading: Load heavy resources only when needed
- Response Models: Always define to enable automatic validation and serialization
- Background Tasks: Use
BackgroundTasksfor non-critical operations - Caching: Implement response caching for expensive operations
- Pagination: Always paginate large result sets
- Rate Limiting: Implement rate limiting for public endpoints
Security Checklist
- All inputs validated through Pydantic models
- No secrets in code (use environment variables)
- CORS configured properly (not wildcard in production)
- Authentication on protected endpoints
- SQL injection prevented (use ORM or parameterized queries)
- Rate limiting implemented
- Input size limits enforced
- Error messages don't leak internal details
- HTTPS enforced in production
- Security headers configured
Production Readiness
Before deployment, verify:
- All endpoints have proper documentation (summary, description, responses)
- All inputs/outputs use Pydantic models (no raw dicts)
- No blocking calls in async functions (verified with profiling)
- Proper error handling (all exceptions caught and transformed to HTTPException)
- Logging configured (structured logs with correlation IDs)
- Health check endpoint implemented
- Metrics exposed (Prometheus or similar)
- Tests written (unit tests for services, integration tests for endpoints)
- Type checking passes (mypy --strict)
- Security scan passes (bandit, safety)
Remember: FastAPI is async-first. Respect the event loop, validate everything, type everything, and document everything. Production code is not just working code—it's maintainable, observable, and secure code.
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です