LLMOP FastAPI 项目编程规范
本文档定义了基于 LLMOP (Large-language-model-Oriented Programming) 原则的 FastAPI 项目的编程规范。 LLMOP 的核心思想是接口极小、命名唯一易理解、契约与实现硬绑定, 旨在让代码对大型语言模型 (LLM) 更易于理解、维护和扩展。
1. LLMOP 核心原则
接口极小 (Minimal Interface)
- R1.1.1采用五层架构,每层职责明确且接口最小化。
- R1.1.2每个模块只暴露必要的接口,隐藏内部实现细节。
- R1.1.3模块间交互只能通过
*_api.py层进行。
命名唯一 (Unique Naming)
- R1.2.1所有命名不言自明,清晰反映业务含义和技术角色。
- R1.2.2避免使用模糊或过于简短的名称。
- R1.2.3禁止无业务逻辑的命名,如
get_info。
契约与实现硬绑定
- R1.3.1在
*_api.py中使用typing.Protocol定义服务契约。 - R1.3.2API 层是模块唯一对外接口。
- R1.3.3修改 service 公有方法,需同步修改 api 接口。
2. 项目结构规范
2.1 根目录结构
R2.1.1
核心应用代码位于 app/ 目录下。
R2.1.2
根目录必须包含:main.py, requirements.txt, env.example, .gitignore。
2.2 应用目录结构 (app/)
应用目录按照功能分离,职责清晰:
module/: 业务模块目录,包含所有业务领域模块。util/: 全局工具函数目录,存放完全不依赖特定业务模块、具有普适性的工具函数。core/: 存放应用核心组件,如配置、数据库、认证、异常、中间件等。
2.3 业务模块结构 (app/module/*/)
R2.3.1
每个业务模块必须包含以下文件,构成五层架构:
*_model.py*_util.py*_service.py*_api.py*_router.pytest/
3. 文件和命名规范
3.1 文件命名
R3.1.1
使用小写蛇形命名法 (snake_case) 并用全称,例如 user_model.py。
R3.1.2
统一使用单数形式:所有文件名、目录名、类名、变量名等均使用单数形式。
R3.1.3
每个文件开始的第一行注释必须是文件名的相对路径,如:# app/module/dummy/dummy_router.py。
3.2 类命名
R3.2.2
模型类命名必须包含 Model 后缀,如 UserModel。
R3.2.3
Pydantic schema 命名规范:以 <资源><动作?>In/Out 命名,如 UserCreateIn, UserOut。
3.3 函数和变量命名
R3.3.3
函数/方法命名要含义清楚,哪怕很长。不要用 get_user,应该用 get_user_by_id。
4. 五层架构规范
4.1 架构依赖关系
依赖关系流向图
R4.1.2
跨模块交互:任何层只能依赖对方的 API 层 (*_api.py),不得直接依赖对方的 Service 或 Model。
R4.1.3
严禁反向依赖和循环依赖。
5. 代码风格规范
5.2 类型提示
R5.2.1
所有 Python 文件都必须在文件开头添加 from __future__ import annotations。
R5.2.2
所有函数、方法参数和返回值都必须有明确的类型提示 (Type Hints)。
R5.2.3
函数局部变量不需要添加类型提示,保持代码简洁性。
# 正确:函数参数和返回值有类型提示,局部变量无类型提示
async def create_user(self, *, user_data: UserCreateIn) -> UserOut:
# 局部变量不需要类型提示
user_dict = user_data.model_dump()
new_user = UserModel(**user_dict)
self.db.add(new_user)
await self.db.flush()
return UserOut.model_validate(new_user)
# 错误:局部变量添加了类型提示,导致代码冗余
async def create_user_wrong(self, *, user_data: UserCreateIn) -> UserOut:
user_dict: dict = user_data.model_dump() # 错误:不需要
new_user: UserModel = UserModel(**user_dict) # 错误:不需要
# ...
6. 异常处理规范
R6.1.1
在 app/core/exceptions.py 中定义基础异常类,数量控制在 6 个以内,全部继承自 fastapi.HTTPException。
R6.2.3
Router 层不处理异常。异常统一在 main.py 中通过全局异常处理器捕获并返回标准化的 JSON 响应。
# app/core/exceptions.py
from fastapi import HTTPException
class AuthenticationRequiredException(HTTPException):
def __init__(self, detail: str = "Authentication required"):
super().__init__(status_code=401, detail=detail)
class PermissionDeniedException(HTTPException):
def __init__(self, detail: str = "Permission denied"):
super().__init__(status_code=403, detail=detail)
class ResourceNotFoundException(HTTPException):
def __init__(self, detail: str = "Resource not found"):
super().__init__(status_code=404, detail=detail)
# ... etc.
7. FastAPI 特定规范
7.1 依赖注入
R7.1.1
广泛使用 FastAPI 的依赖注入系统 (Depends) 来获取数据库会话、认证用户和服务实例。
R7.1.3
依赖项使用 Protocol 接口类型提示,而非具体实现类,以实现解耦。
7.3 认证与授权
R7.3.2
Actor 定义:在所有上下文中,actor 都是指当前操作的用户数据,类型为 UserOut | None。
R7.3.5
授权主要在 Service 层进行。服务通过构造函数接收 actor,并在内部方法中进行细粒度的权限控制。
8. 数据库规范
8.2 Session 管理
R8.2.1
统一的 Session 生命周期通过 Depends(get_db) 管理,实现每个请求一个事务。
# app/core/db.py
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
9. 配置管理规范
R9.2
使用 Pydantic Settings 类集中管理所有应用配置。
R9.6
敏感信息(如数据库密码)必须通过环境变量或 env 文件管理,绝不能硬编码。
10. 日志规范
R10.2
推荐使用结构化日志格式(JSON),便于日志分析和监控。
R10.4
日志中应包含 request_id 以串联调用链,通过 ContextVar 和中间件实现。
11. 测试规范
11.6 测试环境隔离 (核心原则)
重要
测试代码中严禁直接从 main.py 导入全局的 app 对象。必须在测试 fixture 中动态创建一个全新的、隔离的 FastAPI 实例,并使用 dependency_overrides 覆盖数据库依赖。
11.7 全局 Fixture 配置 (conftest.py)
项目根目录的 conftest.py 必须提供核心 fixtures,以确保测试环境的隔离性和一致性。
# conftest.py (位于项目根目录)
import pytest
from httpx import AsyncClient
from fastapi import FastAPI
from app.core.db import get_db, Base
@pytest.fixture(scope="function")
async def test_db():
# ... setup in-memory db ...
yield session
# ... teardown db ...
@pytest.fixture(scope="function")
async def client(test_db: AsyncSession):
test_app = FastAPI()
# [核心] 覆盖数据库依赖
test_app.dependency_overrides[get_db] = lambda: test_db
async with AsyncClient(app=test_app, base_url="http://test") as ac:
yield ac, test_app
test_app.dependency_overrides.clear()
11.8 认证客户端 Fixture (最佳实践)
R11.8.3
Fixture 内部应执行完整的业务流程来获取认证状态(如调用注册和登录服务),而不是模拟(Mock)认证依赖,这样更能有效地测试完整的集成链路。
12. 静态类型检查规范
R12.1
在 CI 流水线中使用 mypy --strict app/ 对整个应用进行静态类型检查。
13. 模块扩展规范
13.2 模块间交互
R13.2.2
Service 层可以调用其他模块的 API 层服务。通过服务工厂函数 create_*_service(db, actor) 直接创建所需的服务实例。
14. 服务工厂模式规范
R14.1
为了实现类型安全并明确职责,服务创建分为两个独立的函数:
get_*_service_with_depends(...): 专用于 FastAPI 依赖注入。create_*_service(db, actor): 专用于直接的、程序化的服务创建。
15. 数据契约规范
R15.2
API 模型字段类型必须与数据库模型保持一致,避免类型不匹配导致的验证错误。
R15.3
时间字段统一使用 datetime 类型,由 Pydantic 自动处理序列化,不要使用 str 类型。
16. 循环依赖处理规范
R16.2.1
延迟导入法(推荐):在需要调用的函数内部进行导入,而不是在文件顶部,这是解决 Python 循环导入最直接有效的方法。
# app/module/user/user_service.py
class UserService(UserServiceProtocol):
async def get_user_with_orders(self, *, user_id: int) -> UserWithOrdersOut:
# 延迟导入,避免在文件顶层产生循环依赖
from app.module.order import order_api
user = await self._get_user_by_id(user_id=user_id)
order_service = order_api.create_order_service(db=self.db, actor=self.actor)
orders = await order_service.get_orders_by_user_id(user_id=user_id)
return UserWithOrdersOut(user=user, orders=orders)
17. 完整项目目录结构
project_root/
├── app/ # 核心应用代码
│ ├── __init__.py
│ ├── core/ # 核心组件 (配置, DB, 认证, 异常)
│ ├── module/ # 业务模块
│ │ └── user/ # 用户模块示例
│ │ ├── __init__.py
│ │ ├── user_api.py
│ │ ├── user_model.py
│ │ ├── user_router.py
│ │ ├── user_service.py
│ │ ├── user_util.py
│ │ └── test/ # 模块测试
│ └── util/ # 全局工具函数
├── conftest.py # 全局测试 Fixture 配置
├── data/ # 数据目录 (e.g., SQLite DB)
├── env # 环境变量文件 (git ignored)
├── env.example # 环境变量模板
├── log/ # 日志文件目录
├── main.py # FastAPI 应用入口点
├── migration/ # 数据库迁移文件目录
├── pytest.ini # Pytest 配置文件
├── requirements.txt # 项目依赖
├── script/ # 辅助脚本
└── .gitignore # Git 忽略规则