Files
doluedu-fastapi-template/AGENTS.md

6.4 KiB
Raw Permalink Blame History

AGENTS.md — fastapi-template 开发规范mysql 分支)

本分支为 MySQL 单后端ORM 模型写 src/{domain}/mysql.py 改表结构必须 uv run alembic revision --autogenerate 生成迁移 + alembic upgrade head 执行。

本文件是 AI Agent / 开发者在本仓库工作的必读规范。先读这个,再动手。 通用 FastAPI 最佳实践参考 docs/AGENTS_GENERIC.mddocs/BEST_PRACTICES_ZH.md

0. 这个模板是什么

FastAPI 生产级项目骨架。核心特性:DB_BACKEND 一个环境变量切换 MySQL / MongoDB业务代码零改动。 任何修改都不得破坏这个特性——router/schemas 永远不允许 import 具体 DB 实现。

1. 技术栈基线

版本 说明
Python ≥3.11 X | YStrEnum
FastAPI ≥0.115 依赖注入一律 Annotated[T, Depends(...)]
Pydantic v2 禁用 v1 API.dict() / json_encoders
SQLAlchemy 2.0 async AsyncSession + async_sessionmaker,禁 sync session
Beanie ≥1.27 MongoDB ODM
包管理 uv 禁 pip/poetry/conda加依赖 uv add xxx
Lint ruff 提交前 uv run ruff check --fix . 必须零告警
测试 pytest + pytest-asyncio + httpx asyncio_mode=auto

2. 目录结构铁律

按**域domain**组织,不按文件类型。一个域一个包,照抄 src/items/

src/{domain}/
├── router.py        # 路由:只做参数校验和调用 repo禁写 SQL/查询
├── schemas.py       # Pydantic 契约:与 DB 无关,前后端共用语义
├── dependencies.py  # 定义 Repo Protocol + 按 DB_BACKEND 选实现
├── mysql.py         # MySQL ORM 模型 + MySQLXxxRepo
├── mongo.py         # Beanie Document + MongoXxxRepo
├── service.py       # (可选)跨 repo 的复杂业务逻辑
├── constants.py     # (可选)常量、错误码
└── exceptions.py    # (可选)域内异常,继承 src.exceptions.AppError
  • 新增域后必做三件事:① src/mongo.py::_collect_documents() 登记 Documentalembic/env.py import mysql 模型;③ src/main.py include_router。
  • 跨域 import 必须显式模块名:from src.items import constants as item_constants,禁 import *
  • 全局共享的东西才放 src/config / exceptions / database / mongo

3. 双数据库后端规范(本模板灵魂)

# ✅ router 只依赖 Protocol
async def get_item(item_id: str, repo: ItemRepoDep): ...

# ✅ dependencies.py 模块加载时选定实现
get_item_repo = _get_mysql_repo if settings.DB_BACKEND == "mysql" else _get_mongo_repo

# ❌ 禁止在 router/schemas/service 里出现
from src.items.mysql import Item      # 不许!
from src.items.mongo import ItemDoc   # 不许!
  • 两个 repo 实现同一 Protocol,方法签名逐字一致。
  • schemas 的 id 统一用 strMySQL int 自增 / Mongo ObjectId 都转 str 转换在各 repo 的 to_out() 里完成。
  • MySQL 改表结构 → 必须 alembic revision --autogenerate 生成迁移,禁手改生产库。
  • Mongo 新增 Document → 只登记 _collect_documents()beanie 自动建索引。
  • src/database.pysrc/mongo.py 只在 lifespan / dependencies 里被引用; 非对应后端模式下它们可以 import 但不得产生连接engine 惰性,别主动 connect

4. 异步纪律

场景 写法
可 await 的 I/O async def
只有同步 SDK defFastAPI 自动进线程池)或 run_in_threadpool
CPU 密集 >50ms 扔任务队列,别放请求路径
# ❌ async 路由里调用阻塞库 = 冻结整个事件循环
@router.get("/bad")
async def bad():
    time.sleep(5)

# ✅
@router.get("/ok")
def ok():
    time.sleep(5)

5. 配置与异常

  • 配置只走 src/config.pysettingspydantic-settings域级配置放 {domain}/config.pyenv_prefix="XXX_"
  • 禁在代码里硬编码 DSN / 密钥;.env 不入库,.env.example 必须同步更新。
  • 业务异常继承 AppError404 用 NotFoundError;统一由 register_exception_handlers 输出 {"detail": ...}
  • 禁裸 except:,禁 except Exception: pass

6. Pydantic 规范

# ✅ 约束写清楚required 和 optional 二选一,别自相矛盾
name: str = Field(min_length=1, max_length=128)
age: int | None = Field(default=None, ge=0)      # 可选
age: int = Field(ge=18)                           # 必填

# ❌ 矛盾写法
age: int = Field(ge=18, default=None)
  • 序列化定制用 @field_serializer,不用 json_encoders
  • 入参模型XxxCreate/XxxUpdate与出参模型XxxOut分开XxxUpdate 全字段可选 + model_dump(exclude_unset=True)

7. 测试规范

  • 测试从第一天就是异步:httpx.AsyncClient + ASGITransport,见 tests/conftest.py
  • 测试库与开发库隔离Mongo 用 app_testMySQL 用 app_test
  • 每个域至少覆盖create / get / list / update / delete + 404 路径。
  • 跑 MySQL 后端测试:DB_BACKEND=mysql uv run pytest

8. Git 规范

  • 提交信息Conventional Commits —— feat: xxx / fix: xxx / refactor: / docs: / test: / chore:
  • 主分支 main;功能开发开 feature/xxx 分支,提 PR 合并。
  • 提交前自检三连:uv run ruff check --fix . && uv run pytest,全绿才推。

9. 常用命令速查

uv sync                              # 装依赖
uv add fastapi                       # 加依赖dev 依赖uv add --dev xxx
uv run uvicorn src.main:app --reload # 开发起服务
uv run pytest                        # 测试
uv run ruff check --fix .            # lint
uv run alembic revision --autogenerate -m "msg"  # MySQL 迁移
docker compose up -d --build         # 容器化整套

10. 反模式清单Review 时逐条核对)

  • router 里出现 SQL / ItemDoc.find() 等具体 DB 调用
  • async 路由里调用 requests / time.sleep 等阻塞库
  • print 调试残留(用 logging
  • 硬编码连接串 / 密钥
  • from x import *、裸 except
  • 改了 MySQL 模型没生成 alembic 迁移
  • 新增 Mongo Document 没登记 _collect_documents()
  • 新域只在单一后端实现(两个 repo 必须成对)
  • 提交信息 freestyle非 Conventional Commits