# 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.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）
