mcpField
F
fastapi-patterns

fastapi-patterns

github.com/hieutrtr开发874 次安装39 周前更新
代码仓库

关于 fastapi-patterns

fastapi-patterns 是提供 FastAPI 进阶应用模式指导的技能,面向处理中间件、认证、依赖注入、WebSocket 和应用生命周期等问题的开发者。

主要功能

  1. 01中间件选择与顺序
  2. 02JWT 与角色权限依赖
  3. 03依赖缓存与资源清理
  4. 04后台任务指导
  5. 05WebSocket 连接管理
  6. 06生命周期与 OpenAPI 配置

适用场景

  1. 01添加日志或 CORS 中间件
  2. 02构建 JWT 认证依赖链
  3. 03管理应用启动与关闭
  4. 04实现经过认证的 WebSocket 端点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hieutrtr/ai1-skills --skill fastapi-patterns

安装后在 Claude Code 中输入/fastapi-patterns确认已加载。

附带文件references/dependency-injection-patterns.mdreferences/middleware-examples.md

详细介绍

FastAPI 模式

使用时机

在以下情况下启用此技能:

  • 配置 FastAPI 中间件(CORS、日志记录、计时、错误处理)
  • 创建复杂的依赖注入链
  • 实现带连接管理的 WebSocket 端点
  • 自定义 OpenAPI 文档(标签、示例、弃用标记)
  • 设置 JWT 身份验证和基于角色的访问依赖项
  • 实现后台任务(轻量级或分布式)
  • 管理应用生命周期(通过 lifespan 处理启动和关闭)
  • 设置速率限制或请求节流

不要在以下情况下使用此技能:

  • 基本的端点 CRUD、仓储或服务模式(请使用 python-backend-expert)
  • 为 FastAPI 端点编写测试(请使用 pytest-patterns)
  • API 契约设计或架构规划(请使用 api-design-patterns)
  • 架构决策(请使用 system-architecture)

说明

中间件栈

中间件按 LIFO(后进先出)顺序执行。最后添加的中间件位于最外层。

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Order matters: added last = executed first (outermost)
app.add_middleware(TimingMiddleware)          # 3rd added = runs 1st
app.add_middleware(RequestLoggingMiddleware)  # 2nd added = runs 2nd
app.add_middleware(                           # 1st added = runs 3rd (innermost)
    CORSMiddleware,
    allow_origins=["https://app.example.com"],  # NEVER use "*" in production
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)
ASGI 中间件(首选)

对于性能关键路径,使用纯 ASGI 中间件:

from starlette.types import ASGIApp, Receive, Scope, Send
import time

class TimingMiddleware:
    def __init__(self, app: ASGIApp) -> None:
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        start = time.perf_counter()
        await self.app(scope, receive, send)
        duration = time.perf_counter() - start
        # Log or record the duration
BaseHTTPMiddleware(更简单,但速度较慢)

仅在中间件需要读取或修改请求体或响应时使用:

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

class RequestIdMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
        request.state.request_id = request_id
        response = await call_next(request)
        response.headers["X-Request-ID"] = request_id
        return response

如何选择:

  • ASGI 中间件: 性能关键,且无需读取请求体或响应体
  • BaseHTTPMiddleware: 需要访问 Request/Response 对象,API 更简单
身份验证依赖项
JWT 令牌验证
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    session: AsyncSession = Depends(get_async_session),
) -> User:
    try:
        payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
        user_id: int = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=401, detail="Invalid token")
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

    user = await session.get(User, user_id)
    if user is None or not user.is_active:
        raise HTTPException(status_code=401, detail="User not found or inactive")
    return user
基于角色的访问控制(工厂模式)
def require_role(*roles: str):
    """Factory that creates a dependency requiring specific roles."""
    async def check_role(user: User = Depends(get_current_user)) -> User:
        if user.role not in roles:
            raise HTTPException(
                status_code=403,
                detail=f"Requires one of: {', '.join(roles)}",
            )
        return user
    return check_role

# Usage in routes
@router.delete("/users/{user_id}", dependencies=[Depends(require_role("admin"))])
async def delete_user(user_id: int, ...) -> None:
    ...

@router.patch("/posts/{post_id}")
async def update_post(
    post_id: int,
    user: User = Depends(require_role("admin", "editor")),
) -> PostResponse:
    ...
依赖注入链
缓存行为

FastAPI 会在单个请求中缓存依赖项的结果。同一依赖项被多次调用时,会返回同一个实例:

# get_async_session is called once per request, even if used by multiple deps
async def get_user_service(session: AsyncSession = Depends(get_async_session)) -> UserService:
    return UserService(session)

async def get_post_service(session: AsyncSession = Depends(get_async_session)) -> PostService:
    return PostService(session)  # Same session instance as user_service

要禁用缓存(每次都获取新实例),请使用 use_cache=False:

session: AsyncSession = Depends(get_async_session, use_cache=False)
Yield 依赖项(资源清理)
async def get_http_client() -> AsyncGenerator[httpx.AsyncClient, None]:
    async with httpx.AsyncClient(timeout=30.0) as client:
        yield client
    # Client is automatically closed after the request
在测试中覆盖依赖项
# In tests
from app.main import app
from app.dependencies.auth import get_current_user

async def mock_current_user() -> User:
    return User(id=1, email="[email protected]", role="admin")

app.dependency_overrides[get_current_user] = mock_current_user
后台任务
FastAPI BackgroundTasks(轻量级)

对于无需在服务重启后继续执行的任务:

from fastapi import BackgroundTasks

@router.post("/users", status_code=201)
async def create_user(
    data: UserCreate,
    background_tasks: BackgroundTasks,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    user = await service.create_user(data)
    background_tasks.add_task(send_welcome_email, user.email)
    return UserResponse.model_validate(user)

async def send_welcome_email(email: str) -> None:
    """Runs after the response is sent. Creates its own session."""
    async with async_session_factory() as session:
        async with session.begin():
            # Send email, log activity, etc.
            ...

规则:

  • 切勿在后台任务中复用请求会话——请创建新会话
  • 后台任务在同一进程中运行——不重试、不持久化
  • 对于需要可靠性、重试或分布式执行的任务,请使用 Celery 或类似工具
WebSocket 模式
from fastapi import WebSocket, WebSocketDisconnect

class ConnectionManager:
    def __init__(self) -> None:
        self.active: dict[int, list[WebSocket]] = {}

    async def connect(self, user_id: int, ws: WebSocket) -> None:
        await ws.accept()
        self.active.setdefault(user_id, []).append(ws)

    def disconnect(self, user_id: int, ws: WebSocket) -> None:
        if user_id in self.active:
            self.active[user_id].remove(ws)
            if not self.active[user_id]:
                del self.active[user_id]

    async def send_to_user(self, user_id: int, message: dict) -> None:
        for ws in self.active.get(user_id, []):
            await ws.send_json(message)

manager = ConnectionManager()

@router.websocket("/ws")
async def websocket_endpoint(ws: WebSocket, token: str) -> None:
    # Auth via query parameter: /ws?token=xxx
    user = await verify_ws_token(token)
    if not user:
        await ws.close(code=4001)
        return

    await manager.connect(user.id, ws)
    try:
        while True:
            data = await ws.receive_json()
            # Process incoming messages
            await handle_message(user.id, data)
    except WebSocketDisconnect:
        manager.disconnect(user.id, ws)

WebSocket 身份验证方式:

  1. 查询参数:ws://host/ws?token=xxx(最简单,但令牌会显示在日志中)
  2. 首条消息:连接后将令牌作为首条消息发送(更安全)
  3. Cookie:使用现有的会话 Cookie(要求域名相同)
应用生命周期

使用 lifespan 上下文管理器(不要使用已弃用的 on_event):

from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    # Startup: initialize resources
    await init_database()
    redis = await aioredis.from_url(settings.redis_url)
    app.state.redis = redis

    yield  # Application runs here

    # Shutdown: cleanup resources
    await redis.close()
    await dispose_engine()

app = FastAPI(lifespan=lifespan)

生命周期职责:

  • 初始化和释放数据库连接池
  • 设置和拆除 Redis/缓存连接
  • 创建 HTTP 客户端连接池
  • 启动和关闭后台调度器
  • 启动时预热缓存
异常处理器

注册全局异常处理器,以统一错误响应:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
async def validation_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=422,
        content={
            "detail": "Validation error",
            "code": "VALIDATION_ERROR",
            "field_errors": [
                {"field": e["loc"][-1], "message": e["msg"], "code": e["type"]}
                for e in exc.errors()
            ],
        },
    )
OpenAPI 自定义
app = FastAPI(
    title="My API",
    version="1.0.0",
    description="API description with **markdown** support",
    openapi_tags=[
        {"name": "Users", "description": "User management operations"},
        {"name": "Auth", "description": "Authentication endpoints"},
    ],
    docs_url="/docs",        # Swagger UI
    redoc_url="/redoc",      # ReDoc
    openapi_url="/openapi.json",
)

示例

JWT 身份验证依赖链

从令牌到已授权用户的完整身份验证链:

Request with Authorization: Bearer <token>
    ↓
oauth2_scheme (extracts token from header)
    ↓
get_current_user (decodes JWT, loads user from DB)
    ↓
require_role("admin") (checks user.role)
    ↓
Route handler (receives verified admin user)

链中的每个依赖项都可以通过 dependency_overrides 独立测试。

边缘情况

  • 中间件与依赖项: 使用中间件处理横切关注点(日志记录、计时、CORS)。使用依赖项处理路由级逻辑(身份验证、分页参数、功能标志)。

  • ASGI 与 BaseHTTPMiddleware: 优先使用 ASGI 中间件以获得更好的性能。BaseHTTPMiddleware 会将整个响应正文读入内存,从而导致流式传输和大型响应出现问题。

  • Lifespan 与 on_event: 始终使用 lifespan 上下文管理器。FastAPI 0.109+ 中的 @app.on_event("startup") 和 @app.on_event("shutdown") 已弃用。

  • 跨子应用的 Depends 缓存: 依赖项缓存仅在单个应用实例内按请求生效。如果使用 app.mount() 挂载子应用,每个子应用都有自己的依赖项解析范围。

  • WebSocket 扩展: 单个服务实例会持有所有 WebSocket 连接。对于多实例部署,请使用 Redis pub/sub 在各实例之间广播消息。

参见 references/middleware-examples.md,了解完整的中间件实现。 参见 references/dependency-injection-patterns.md,了解高级 DI 模式。

评论

支持 Markdown