# AGENTS.md — fastapi-template 开发规范(mongodb 分支) > 本分支为 **MongoDB(Beanie)单后端**:无 alembic、无 SQLAlchemy。 > Document 模型写 `src/{domain}/mongo.py`,新增 Document 必须在 `src/mongo.py::_collect_documents()` 登记。 本文件是 AI Agent / 开发者在本仓库工作的**必读规范**。先读这个,再动手。 通用 FastAPI 最佳实践参考 [docs/AGENTS_GENERIC.md](./docs/AGENTS_GENERIC.md) 与 [docs/BEST_PRACTICES_ZH.md](./docs/BEST_PRACTICES_ZH.md)。 ## 0. 这个模板是什么 FastAPI 生产级项目骨架。**核心特性:`DB_BACKEND` 一个环境变量切换 MySQL / MongoDB,业务代码零改动**。 任何修改都不得破坏这个特性——router/schemas 永远不允许 import 具体 DB 实现。 ## 1. 技术栈基线 | 项 | 版本 | 说明 | |---|---|---| | Python | ≥3.11 | 用 `X \| Y`、`StrEnum` | | 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()` 登记 Document; ② `alembic/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. 双数据库后端规范(本模板灵魂) ```python # ✅ 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` 统一用 `str`(MySQL int 自增 / Mongo ObjectId 都转 str), 转换在各 repo 的 `to_out()` 里完成。 - MySQL 改表结构 → 必须 `alembic revision --autogenerate` 生成迁移,禁手改生产库。 - Mongo 新增 Document → 只登记 `_collect_documents()`,beanie 自动建索引。 - `src/database.py`、`src/mongo.py` 只在 lifespan / dependencies 里被引用; 非对应后端模式下它们可以 import 但**不得产生连接**(engine 惰性,别主动 connect)。 ## 4. 异步纪律 | 场景 | 写法 | |---|---| | 可 await 的 I/O | `async def` | | 只有同步 SDK | `def`(FastAPI 自动进线程池)或 `run_in_threadpool` | | CPU 密集 >50ms | 扔任务队列,别放请求路径 | ```python # ❌ async 路由里调用阻塞库 = 冻结整个事件循环 @router.get("/bad") async def bad(): time.sleep(5) # ✅ @router.get("/ok") def ok(): time.sleep(5) ``` ## 5. 配置与异常 - 配置只走 `src/config.py` 的 `settings`(pydantic-settings);域级配置放 `{domain}/config.py`,`env_prefix="XXX_"`。 - 禁在代码里硬编码 DSN / 密钥;`.env` 不入库,`.env.example` 必须同步更新。 - 业务异常继承 `AppError`,404 用 `NotFoundError`;统一由 `register_exception_handlers` 输出 `{"detail": ...}`。 - 禁裸 `except:`,禁 `except Exception: pass`。 ## 6. Pydantic 规范 ```python # ✅ 约束写清楚;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_test`,MySQL 用 `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. 常用命令速查 ```bash 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)