FastAPI 模式¶
面向生产环境的 FastAPI 服务模式。
何时使用¶
- 构建或审查 FastAPI 应用时。
- 拆分路由、模式、依赖项和数据库访问时。
- 编写调用数据库或外部服务的异步端点时。
- 添加认证、授权、OpenAPI 文档、测试或部署配置时。
- 需要确认 FastAPI PR 中可复制的示例和风险时。
工作原理¶
将 FastAPI 应用视为建立在显式依赖项和服务代码之上的薄 HTTP 层:
main.py负责应用构建、中间件、异常处理程序和路由注册。schemas/负责 Pydantic 请求和响应模型。dependencies.py负责数据库、认证、分页和请求作用域依赖项。services/或crud/负责业务和持久化操作。tests/通过覆盖依赖项来避免打开生产资源。
优先使用小型路由和显式的 response_model 声明。不要在响应模式中包含原始 ORM 对象、机密信息或框架全局变量。
项目布局¶
app/
|-- main.py
|-- config.py
|-- dependencies.py
|-- exceptions.py
|-- api/
| `-- routes/
| |-- users.py
| `-- health.py
|-- core/
| |-- security.py
| `-- middleware.py
|-- db/
| |-- session.py
| `-- crud.py
|-- models/
|-- schemas/
`-- tests/
应用工厂¶
使用工厂,以便测试和工作进程能够以受控配置构建应用。
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import health, users
from app.config import settings
from app.db.session import close_db, init_db
from app.exceptions import register_exception_handlers
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
yield
await close_db()
def create_app() -> FastAPI:
app = FastAPI(
title=settings.api_title,
version=settings.api_version,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=bool(settings.cors_origins),
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
register_exception_handlers(app)
app.include_router(health.router, prefix="/health", tags=["health"])
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
return app
app = create_app()
不要将 allow_credentials=True 与 allow_origins=["*"] 一起使用;浏览器会拒绝该组合,Starlette 也会禁止带凭据的请求使用它。
Pydantic 模式¶
分离请求、更新和响应模型。
from datetime import datetime
from typing import Annotated
from uuid import UUID
from pydantic import BaseModel, ConfigDict, EmailStr, Field
class UserBase(BaseModel):
email: EmailStr
full_name: Annotated[str, Field(min_length=1, max_length=100)]
class UserCreate(UserBase):
password: Annotated[str, Field(min_length=12, max_length=128)]
class UserUpdate(BaseModel):
email: EmailStr | None = None
full_name: Annotated[str | None, Field(min_length=1, max_length=100)] = None
class UserResponse(UserBase):
model_config = ConfigDict(from_attributes=True)
id: UUID
created_at: datetime
updated_at: datetime
响应模型中不得包含密码哈希、访问令牌、刷新令牌或内部授权状态。
依赖项¶
对请求作用域资源使用依赖注入。
from collections.abc import AsyncIterator
from uuid import UUID
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import decode_token
from app.db.session import session_factory
from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_db() -> AsyncIterator[AsyncSession]:
async with session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
payload = decode_token(token)
user_id = UUID(payload["sub"])
user = await db.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return user
不要在路由处理程序中内联创建会话、客户端或凭据。
异步端点¶
如果执行 I/O,请将路由处理程序设为异步,并在其中使用异步库。
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_user, get_db
from app.models.user import User
from app.schemas.user import UserResponse
router = APIRouter()
@router.get("/", response_model=list[UserResponse])
async def list_users(
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
result = await db.execute(
select(User).order_by(User.created_at.desc()).limit(limit).offset(offset)
)
return result.scalars().all()
从异步处理程序发起外部 HTTP 调用时,请使用 httpx.AsyncClient。不要在异步路由中调用 requests。
错误处理¶
集中处理领域异常,并保持响应形状稳定。
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class ApiError(Exception):
def __init__(self, status_code: int, code: str, message: str):
self.status_code = status_code
self.code = code
self.message = message
def register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(ApiError)
async def api_error_handler(request: Request, exc: ApiError):
return JSONResponse(
status_code=exc.status_code,
content={"error": {"code": exc.code, "message": exc.message}},
)
OpenAPI 自定义¶
将自定义 OpenAPI 可调用对象赋值给 app.openapi;不要只调用一次函数。
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def install_openapi(app: FastAPI) -> None:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
app.openapi_schema = get_openapi(
title="Service API",
version="1.0.0",
routes=app.routes,
)
return app.openapi_schema
app.openapi = custom_openapi
测试¶
覆盖在 Depends 中使用的依赖项,而不是路由处理器从不引用的内部辅助函数。
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db
from app.main import create_app
@pytest.fixture
async def client(test_session: AsyncSession):
app = create_app()
async def override_get_db():
yield test_session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
) as test_client:
yield test_client
app.dependency_overrides.clear()
安全检查清单¶
- 使用
argon2-cffi、bcrypt或当前 passlib 兼容的哈希器对密码进行哈希。 - 验证 JWT 的签发者、受众、有效期和签名算法。
- 保持 CORS 源与部署环境一致。
- 对认证端点和写入密集型端点设置速率限制。
- 对所有请求体使用 Pydantic 模型。
- 使用 ORM 参数绑定或 SQLAlchemy Core 表达式;不要使用 f 字符串构建 SQL。
- 从日志中移除令牌、授权头、Cookie 和密码。
- 在 CI 中运行依赖项审计工具。
性能检查清单¶
- 显式配置数据库连接池。
- 为列表端点添加分页。
- 注意 N+1 查询,并有意使用预加载。
- 在异步路径中使用异步 HTTP/数据库客户端。
- 在添加压缩之前,确认负载大小与 CPU 之间的权衡。
- 在显式失效控制下缓存稳定的高成本读取。
使用示例¶
这些示例应作为模式使用,而不是整个项目的模板:
- 应用工厂:使用
create_app一次性配置中间件和路由器。 - 模式拆分:
UserCreate、UserUpdate、UserResponse分别具有不同的职责。 - 依赖项覆盖:测试直接覆盖
get_db。 - 自定义 OpenAPI:设置
app.openapi = custom_openapi。
相关信息¶
- 代理:
fastapi-reviewer - 命令:
/fastapi-review - 技能:
python-patterns - 技能:
python-testing - 技能:
api-design
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC