fastapp-cli 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/PKG-INFO +1 -1
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/pyproject.toml +1 -1
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/__init__.py +1 -1
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/README.md.j2 +2 -0
- fastapp_cli-0.2.0/src/fastapp_cli/templates/project/docs/agent_guide.md.j2 +305 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/.gitignore +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/PLAN.md +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/README.md +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/create.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/main.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/naming.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/prompts.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/render.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/__init__.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.env.development.example.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.env.example.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.env.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.gitignore +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.python-version.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/Dockerfile.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/Makefile.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic/env.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic/script.py.mako +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic.ini.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/health.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/items.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/celery_app.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/config.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/database.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/main.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/base.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/item.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/services/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/services/base.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/services/item_service.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/tasks/sample_tasks.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/docs/SQLModel/345/256/232/344/271/211/347/244/272/344/276/213.md" +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/pm2.config.json.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/pyproject.toml.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/celery_flower.sh.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/celery_worker.sh.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/api/test_health.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/services/test_item_service.py.j2 +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/tests/test_create.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/tests/test_naming.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/tests/test_render.py +0 -0
- {fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/uv.lock +0 -0
|
@@ -62,6 +62,8 @@ app/
|
|
|
62
62
|
|
|
63
63
|
## 扩展指南
|
|
64
64
|
|
|
65
|
+
> AI Agent / 新成员开发前请先阅读 [docs/agent_guide.md](docs/agent_guide.md)——包含架构说明、核心方法清单(优先复用)、标准开发流程与编码规范。
|
|
66
|
+
|
|
65
67
|
1. **新增业务域**:仿照 `item` 五件套(model / schema / crud / service / endpoint),在 `models/__init__.py` 注册新模型,在 `api/v1/router.py` 挂载新路由;
|
|
66
68
|
2. **生成迁移**:`make revision m="add xxx table"` 后检查 `alembic/versions/`,再 `make migrate`;
|
|
67
69
|
3. **接入真实认证**:替换 `app/api/deps.py::get_current_user` 的占位实现;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# {{ project_name }} — AI Agent 开发参考文档
|
|
2
|
+
|
|
3
|
+
> 本文档面向 AI Agent(或新加入的开发者),目标是帮助你在不了解项目历史的情况下,快速掌握项目设计并**正确地**进行开发。
|
|
4
|
+
> 核心原则:**优先复用已有方法与基础设施,不要重复造轮子。**
|
|
5
|
+
|
|
6
|
+
## 1. 项目概述与整体架构
|
|
7
|
+
|
|
8
|
+
本项目是由 fastapp-cli v{{ fastapp_cli_version }} 生成的 FastAPI 工程,技术栈:
|
|
9
|
+
|
|
10
|
+
| 层次 | 技术 | 说明 |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Web 框架 | FastAPI | 工厂函数 `create_app` 创建,lifespan 管理启动/关闭 |
|
|
13
|
+
| ORM | SQLModel (SQLAlchemy) | PEP 695 泛型 CRUD 基类 |
|
|
14
|
+
| 数据库 | MySQL(默认)/ SQLite(测试) | SQLAlchemy 引擎,连接池参数已调优 |
|
|
15
|
+
| 迁移 | Alembic | 增量迁移,`MANAGED_TABLES` 约束只管理业务表 |
|
|
16
|
+
| 异步任务 | Celery + Redis | redis 不可达时自动降级 memory broker |
|
|
17
|
+
| 分页 | fastapi-pagination | 统一分页响应结构 |
|
|
18
|
+
| 过滤 | fastapi-filter | 声明式查询过滤器 |
|
|
19
|
+
| 日志 | loguru | 拦截标准 logging,全链路 trace_id |
|
|
20
|
+
| 配置 | pydantic-settings | 多环境 `.env` 加载 |
|
|
21
|
+
| 包管理 | uv | 依赖分组(dev 组含 pytest/ruff/mypy) |
|
|
22
|
+
| 质量工具 | ruff + mypy + pytest | 必须全绿才能合入 |
|
|
23
|
+
|
|
24
|
+
**分层架构(自上而下的调用方向,禁止跨层 / 反向依赖):**
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Endpoint (app/api/v1/endpoints/) 路由层:参数解析、依赖注入、组装 Response
|
|
28
|
+
↓ 调用
|
|
29
|
+
Service (app/services/) 业务层:业务规则、事务编排,继承 ServiceBase
|
|
30
|
+
↓ 调用
|
|
31
|
+
CRUD (app/crud/) 数据访问层:只做数据读写,继承 CRUDBase
|
|
32
|
+
↓ 操作
|
|
33
|
+
Model (app/models/) SQLModel 表模型 + Pydantic 校验二合一
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**横切能力(在 core 中,所有层可直接使用):** 统一响应 / 统一异常 / TraceID / 日志 / 配置 / 数据库会话。
|
|
37
|
+
|
|
38
|
+
**请求处理链路:**
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
TraceIDMiddleware (最外层,生成/透传 X-Trace-Id)
|
|
42
|
+
→ CORS
|
|
43
|
+
→ register_exception_handlers 注册的异常处理器(兜底所有异常)
|
|
44
|
+
→ Endpoint(注入 DBSession / CurrentUser)
|
|
45
|
+
→ Response[T] 自动注入 trace_id
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 2. 目录结构及各模块职责
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
.
|
|
52
|
+
├── app/
|
|
53
|
+
│ ├── main.py # create_app 工厂 + lifespan(DB/redis 探测,缺席降级不阻塞启动)
|
|
54
|
+
│ ├── core/ # 横切基础设施(禁止在此写业务逻辑)
|
|
55
|
+
│ │ ├── config.py # settings 配置中心(多环境 .env 加载)
|
|
56
|
+
│ │ ├── database.py # engine / get_session / init_db
|
|
57
|
+
│ │ ├── logging.py # setup_logging / with_trace / InterceptHandler
|
|
58
|
+
│ │ ├── exceptions.py # AppExceptionError 体系 + 全局异常处理器
|
|
59
|
+
│ │ ├── middleware.py # TraceIDMiddleware(trace_id 生成与响应头注入)
|
|
60
|
+
│ │ ├── context_var.py # trace_id_var / get_trace_id
|
|
61
|
+
│ │ └── celery_app.py # build_celery_app / degrade_to_memory / celery_app 单例
|
|
62
|
+
│ ├── api/
|
|
63
|
+
│ │ ├── deps.py # DBSession / CurrentUser 依赖(认证占位)
|
|
64
|
+
│ │ └── v1/
|
|
65
|
+
│ │ ├── router.py # v1 总路由,新业务路由在此挂载
|
|
66
|
+
│ │ └── endpoints/ # health.py(健康检查)、items.py(分页 CRUD 示例域)
|
|
67
|
+
│ ├── models/ # SQLModel 表模型;新模型必须在 __init__.py 注册
|
|
68
|
+
│ │ ├── base.py # IDMixin / TimestampMixin / utcnow
|
|
69
|
+
│ │ └── item.py # Item 示例模型
|
|
70
|
+
│ ├── schemas/ # Pydantic Schema:入参 / 出参 / 过滤器
|
|
71
|
+
│ │ ├── common.py # Response / PageParams / PageResult / PageResponse
|
|
72
|
+
│ │ └── item.py # ItemCreate / ItemUpdate / ItemRead / ItemFilter
|
|
73
|
+
│ ├── crud/ # 数据访问层
|
|
74
|
+
│ │ ├── base.py # CRUDBase 泛型基类(核心复用点)
|
|
75
|
+
│ │ └── item.py # CRUDItem(CRUDBase[Item])
|
|
76
|
+
│ ├── services/ # 业务逻辑层
|
|
77
|
+
│ │ ├── base.py # ServiceBase 泛型基类(核心复用点)
|
|
78
|
+
│ │ └── item_service.py # ItemService(ServiceBase[CRUDItem])
|
|
79
|
+
│ ├── tasks/ # Celery 任务(sample_tasks.py 为示例)
|
|
80
|
+
│ └── utils/ # 通用工具扩展位(空)
|
|
81
|
+
├── alembic/ # 迁移环境(env.py 已配置 MANAGED_TABLES 白名单过滤)
|
|
82
|
+
├── docs/ # 本文档 + SQLModel定义示例.md(建模规范)
|
|
83
|
+
├── scripts/ # start.sh / celery_worker.sh / celery_beat.sh / celery_flower.sh
|
|
84
|
+
├── tests/ # pytest:conftest 用 sqlite 内存库,无需真实 MySQL
|
|
85
|
+
├── alembic.ini / Makefile / pyproject.toml
|
|
86
|
+
└── .env / .env.example # 配置文件(.env 入库,敏感信息按环境覆盖)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 3. 已有核心方法与工具函数清单(优先复用)
|
|
90
|
+
|
|
91
|
+
> **强制要求:开发新功能前先检索本清单。能用现有方法解决的,必须调用现有方法,禁止重写等价实现。**
|
|
92
|
+
|
|
93
|
+
### 3.1 数据访问层 — `CRUDBase`(`app/crud/base.py`)
|
|
94
|
+
|
|
95
|
+
泛型基类 `CRUDBase[ModelType: SQLModel]`,所有业务 CRUD 继承它即可获得以下能力,**任何手写 select/commit 循环前必须先确认此处没有现成方法**:
|
|
96
|
+
|
|
97
|
+
| 方法 | 签名 | 说明 |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `get` | `(db: Session, obj_id: Any) -> ModelType \| None` | 按主键查询 |
|
|
100
|
+
| `get_or_404` | `(db: Session, obj_id: Any) -> ModelType` | 按主键查询,不存在抛 `NotFoundError` |
|
|
101
|
+
| `get_by_field` | `(db: Session, field: str, value: Any) -> ModelType \| None` | 按任意字段查单个(字段不存在抛 `ValueError`) |
|
|
102
|
+
| `get_page_multi` | `(db: Session, *, filter_params: Filter \| None = None) -> PageResponse[Any]` | 分页 + 过滤 + 排序查询 |
|
|
103
|
+
| `get_multi` | `(db: Session, *, filter_params: Filter \| None = None) -> Sequence[ModelType]` | 非分页列表查询 |
|
|
104
|
+
| `count` | `(db: Session, query: Select \| SelectOfScalar \| None = None) -> int` | 记录总数(可传入自定义 query) |
|
|
105
|
+
| `create` | `(db: Session, *, obj_in: BaseModel \| dict[str, Any]) -> ModelType` | 创建单条(自动 model_dump、commit、refresh) |
|
|
106
|
+
| `create_multi` | `(db: Session, *, objs_in: list[...]) -> list[ModelType]` | 批量创建 |
|
|
107
|
+
| `update` | `(db: Session, *, db_obj: ModelType, obj_in: BaseModel \| dict[str, Any]) -> ModelType` | 更新(`exclude_unset` 语义,只更新显式传入字段) |
|
|
108
|
+
| `create_or_update` | `(db: Session, *, obj_in, unique_fields: str \| Sequence[str] \| None = None) -> ModelType` | 按唯一字段 upsert(未指定时用主键) |
|
|
109
|
+
| `create_or_update_multi` | `(db: Session, *, objs_in: list[...], unique_fields=None) -> list[ModelType]` | 批量 upsert(同一事务提交) |
|
|
110
|
+
| `remove` | `(db: Session, *, obj_id: int) -> ModelType` | 按主键删除(不存在抛 404) |
|
|
111
|
+
| `remove_multi` | `(db: Session, *, obj_ids: list[Any]) -> list[ModelType]` | 批量删除 |
|
|
112
|
+
| `exists` | `(db: Session, *, obj_id: Any) -> bool` | 判断是否存在 |
|
|
113
|
+
|
|
114
|
+
使用示例(新增业务域的 CRUD 通常只需一行继承):
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from app.crud.base import CRUDBase
|
|
118
|
+
from app.models.product import Product
|
|
119
|
+
|
|
120
|
+
class CRUDProduct(CRUDBase[Product]):
|
|
121
|
+
pass # 复杂查询逻辑才在此扩展
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3.2 业务层 — `ServiceBase`(`app/services/base.py`)
|
|
125
|
+
|
|
126
|
+
泛型基类 `ServiceBase[CRUDType: CRUDBase]`,内部持有 `self.db` 与 `self.crud`,方法返回值标注为 `Any`(由子类按业务模型收窄):
|
|
127
|
+
|
|
128
|
+
| 方法 | 签名 | 说明 |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `create` | `(obj_in: BaseModel \| dict) -> Any` | 创建 |
|
|
131
|
+
| `create_multi` | `(objs_in: list) -> list[Any]` | 批量创建 |
|
|
132
|
+
| `create_or_update` | `(obj_in, unique_fields=None) -> Any` | upsert |
|
|
133
|
+
| `create_or_update_multi` | `(objs_in, unique_fields=None) -> list[Any]` | 批量 upsert |
|
|
134
|
+
| `get` | `(obj_id: int) -> Any` | 获取单个,不存在抛 `NotFoundError` |
|
|
135
|
+
| `update` | `(obj_id: int, obj_in) -> Any` | 更新(不存在抛 `NotFoundError`) |
|
|
136
|
+
| `delete` | `(obj_id: int) -> Any` | 删除 |
|
|
137
|
+
| `list_objs` | `(filter_params: Filter \| None = None) -> Sequence[Any]` | 列表查询 |
|
|
138
|
+
| `list_page_objs` | `(filter_params: Filter \| None = None) -> PageResponse[Any]` | 分页查询 |
|
|
139
|
+
|
|
140
|
+
使用示例(纯 CRUD 型业务,Service 通常也只是继承):
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from app.crud.product import CRUDProduct
|
|
144
|
+
from app.services.base import ServiceBase
|
|
145
|
+
|
|
146
|
+
class ProductService(ServiceBase[CRUDProduct]):
|
|
147
|
+
def activate(self, obj_id: int) -> None: # 自定义业务方法
|
|
148
|
+
obj = self.get(obj_id)
|
|
149
|
+
self.crud.update(self.db, db_obj=obj, obj_in={"is_active": True})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 3.3 API 依赖(`app/api/deps.py`)
|
|
153
|
+
|
|
154
|
+
| 名称 | 类型 | 说明 |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `DBSession` | `Annotated[Session, Depends(get_session)]` | 端点中直接作参数注解,获取数据库会话 |
|
|
157
|
+
| `CurrentUser` | `Annotated[str, Depends(get_current_user)]` | 当前登录用户。非生产环境返回 `settings.MOCK_USER`;生产环境抛 `UnauthorizedError`(认证占位,接入真实认证时只改 `get_current_user`) |
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
@router.post("", response_model=Response[ProductRead])
|
|
161
|
+
def create_product(payload: ProductCreate, service: ProductServiceDep, current_user: CurrentUser) -> Response[ProductRead]:
|
|
162
|
+
data: ProductRead = service.create(payload)
|
|
163
|
+
return Response(data=data)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 3.4 统一响应与分页(`app/schemas/common.py`)
|
|
167
|
+
|
|
168
|
+
| 名称 | 说明 |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `Response[T]` | 统一响应体 `{code, message, data, trace_id}`;`code=0` 为成功;trace_id 自动从请求上下文注入 |
|
|
171
|
+
| `PageParams` | 分页入参(`page` / `page_size`,`size` 的 URL alias 为 `page_size`) |
|
|
172
|
+
| `PageResult[T]` | 分页数据结构 `{total, page, page_size, content}` |
|
|
173
|
+
| `PageResponse[T]` | 分页统一响应(继承 `Response[PageResult[T]]`),作为端点 `response_model` 使用 |
|
|
174
|
+
|
|
175
|
+
### 3.5 统一异常(`app/core/exceptions.py`)
|
|
176
|
+
|
|
177
|
+
**业务代码中禁止直接 `raise HTTPException` 或返回错误 dict**,统一抛以下异常,由全局异常处理器转换成统一返回体 `{code, message, data, trace_id}`:
|
|
178
|
+
|
|
179
|
+
| 异常 | HTTP 状态码 | 业务码 | 默认消息 |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| `AppExceptionError(message, *, code, status_code, data)` | 400 | 1 | 业务异常基类,自定义业务异常继承它 |
|
|
182
|
+
| `NotFoundError` | 404 | 40400 | 资源不存在 |
|
|
183
|
+
| `UnauthorizedError` | 401 | 40100 | 未认证或凭证已失效 |
|
|
184
|
+
| `ForbiddenError` | 403 | 40300 | 无访问权限 |
|
|
185
|
+
| `ConflictError` | 409 | 40900 | 资源冲突 |
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from app.core.exceptions import ConflictError, NotFoundError, AppExceptionError
|
|
189
|
+
|
|
190
|
+
raise NotFoundError(message="订单不存在")
|
|
191
|
+
raise AppExceptionError(message="余额不足", code=10001, status_code=400)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
未捕获异常统一返回 HTTP 500 + `{"code": 50000, "message": "服务器内部错误"}`,不会裸抛堆栈(应用实例固定 `debug=False`,勿改)。
|
|
195
|
+
|
|
196
|
+
### 3.6 模型公共字段(`app/models/base.py`)
|
|
197
|
+
|
|
198
|
+
| 名称 | 说明 |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `IDMixin` | 主键 `id: int`(primary_key + index) |
|
|
201
|
+
| `TimestampMixin` | `create_time` / `update_time`,数据库自动生成与刷新,JSON 序列化为 `%Y-%m-%d %H:%M:%S` |
|
|
202
|
+
| `utcnow()` | 当前时间(`default_factory` 用) |
|
|
203
|
+
| `FormattedDatetime` | 可复用的 datetime 序列化类型标注 |
|
|
204
|
+
|
|
205
|
+
新表模型标准写法:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from sqlmodel import Field
|
|
209
|
+
from app.models.base import IDMixin, TimestampMixin
|
|
210
|
+
|
|
211
|
+
class ProductBase(TimestampMixin):
|
|
212
|
+
name: str = Field(max_length=128, description="名称")
|
|
213
|
+
|
|
214
|
+
class Product(IDMixin, ProductBase, table=True):
|
|
215
|
+
__tablename__ = "t_product"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 3.7 配置(`app/core/config.py`)
|
|
219
|
+
|
|
220
|
+
- 单例 `settings`:所有环境变量集中于此,**禁止在业务代码中直接读 `os.environ`**。
|
|
221
|
+
- 加载顺序(后者覆盖前者):`.env` → `.env.{APP_ENV}` → 系统环境变量。
|
|
222
|
+
- 常用字段:`APP_NAME` / `APP_ENV`(development|production|test)/ `DEBUG` / `API_V1_PREFIX` / `ALLOWED_ORIGINS` / `DATABASE_URI`(未设置时自动按 `MYSQL_*` 拼装)/ `SQL_ECHO` / `CELERY_BROKER_URL` / `MOCK_USER`。
|
|
223
|
+
- 属性:`settings.is_production` / `settings.is_test`。
|
|
224
|
+
- 新增配置项:在 `Settings` 类中加字段 + 默认值,并在 `.env.example` / `.env` 中补充说明。
|
|
225
|
+
|
|
226
|
+
### 3.8 数据库(`app/core/database.py`)
|
|
227
|
+
|
|
228
|
+
| 名称 | 说明 |
|
|
229
|
+
|---|---|
|
|
230
|
+
| `engine` | 全局引擎。sqlite(测试)与 MySQL(连接池 + `pool_recycle=3600` + `REPEATABLE READ` + 3 秒连接超时)已分别调优,勿重复配置 |
|
|
231
|
+
| `get_session()` | FastAPI 依赖,yield 语法保证 Session 关闭;**通过 `DBSession` 注解使用**,不要自建 Session |
|
|
232
|
+
| `init_db()` | 本地/测试快速建表(生产用 alembic 迁移) |
|
|
233
|
+
|
|
234
|
+
### 3.9 Celery(`app/core/celery_app.py`)
|
|
235
|
+
|
|
236
|
+
| 名称 | 说明 |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `celery_app` | 全局单例(worker 与 web 共用),新任务直接 `@celery_app.task` |
|
|
239
|
+
| `build_celery_app(broker_url, backend_url, *, name)` | 构造入口(自定义 broker 时使用) |
|
|
240
|
+
| `degrade_to_memory(celery)` | 降级为 memory broker/backend(lifespan 探测失败时自动调用,勿手动调用) |
|
|
241
|
+
|
|
242
|
+
新任务示例:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from app.core.celery_app import celery_app
|
|
246
|
+
|
|
247
|
+
@celery_app.task
|
|
248
|
+
def send_notice(user_id: int) -> str:
|
|
249
|
+
...
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 3.10 日志与 TraceID(`app/core/logging.py`、`app/core/context_var.py`)
|
|
253
|
+
|
|
254
|
+
| 名称 | 说明 |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `setup_logging()` | lifespan 启动时自动调用,勿重复调用 |
|
|
257
|
+
| `logger`(loguru) | 业务代码统一 `from loguru import logger`;标准 logging 已被拦截转发到 loguru |
|
|
258
|
+
| `with_trace` | 装饰器:把请求 trace_id 透传到后台任务(`BackgroundTasks.add_task` 场景**必须**用它包裹) |
|
|
259
|
+
| `get_trace_id()` | 获取当前请求 trace_id(无上下文返回空串) |
|
|
260
|
+
|
|
261
|
+
## 4. 新增功能的标准开发流程
|
|
262
|
+
|
|
263
|
+
### 4.1 新增业务域(以 `product` 为例,五件套 + 两处注册 + 迁移 + 测试)
|
|
264
|
+
|
|
265
|
+
1. **Model**:`app/models/product.py`,继承 `IDMixin, TimestampMixin`(见 §3.6 示例);在 `app/models/__init__.py` 中 import 并加入 `__all__`(否则 alembic autogenerate 无法发现)。
|
|
266
|
+
2. **Schema**:`app/schemas/product.py`,定义 `ProductCreate` / `ProductUpdate` / `ProductRead`(继承 `ProductBase` + `id` 字段)与 `ProductFilter(Filter)`(`Constants.model` 指向模型,过滤字段用 `__ilike` / `__gte` 等后缀,参考 `app/schemas/item.py`)。
|
|
267
|
+
3. **CRUD**:`app/crud/product.py`,`class CRUDProduct(CRUDBase[Product]): pass`。
|
|
268
|
+
4. **Service**:`app/services/product_service.py`,`class ProductService(ServiceBase[CRUDProduct])`,业务规则写在这里。
|
|
269
|
+
5. **Endpoint**:`app/api/v1/endpoints/product.py`,仿照 `items.py`:`get_service` 依赖 + `ServiceDep` + 各路由方法返回 `Response[...]` / `PageResponse[...]`。
|
|
270
|
+
6. **注册路由**:`app/api/v1/router.py` 中 `include_router(product_router, prefix="/products", tags=["Products"])`。
|
|
271
|
+
7. **迁移**:`make revision m="add t_product"` → 检查 `alembic/versions/` 生成脚本 → `make migrate`。注意 `alembic/env.py` 用 `MANAGED_TABLES` 白名单过滤,新表模型注册到 `app/models/__init__.py` 后会被自动纳入。
|
|
272
|
+
8. **测试**:在 `tests/` 下仿照 `tests/api/test_items.py` 编写(conftest 已用 sqlite 内存库,无需真实 MySQL),运行 `make test && make lint`。
|
|
273
|
+
|
|
274
|
+
### 4.2 其他扩展场景
|
|
275
|
+
|
|
276
|
+
| 场景 | 做法 |
|
|
277
|
+
|---|---|
|
|
278
|
+
| 新增配置项 | `Settings` 类加字段 + `.env.example` 补说明 |
|
|
279
|
+
| 接入真实认证 | 只替换 `app/api/deps.py::get_current_user` 实现,端点代码不动 |
|
|
280
|
+
| 新增中间件 | `app/core/middleware.py` 实现,并在 `main.py::create_app` 注册;**注意注册顺序**:TraceIDMiddleware 必须最后添加(处于最外层) |
|
|
281
|
+
| 新增 Celery 定时任务 | `app/tasks/` 写任务,`celery_app.conf.beat_schedule` 中注册(该文件内有注释示例) |
|
|
282
|
+
| 自定义业务异常 | 继承 `AppExceptionError`,类属性覆盖 `status_code` / `code` / `message`,全局处理器自动生效 |
|
|
283
|
+
| 复杂查询 | 在业务 CRUD 子类中扩展方法,保持 Service 只编排不写 SQL 细节 |
|
|
284
|
+
|
|
285
|
+
## 5. 编码规范与注意事项
|
|
286
|
+
|
|
287
|
+
### 5.1 强制规范
|
|
288
|
+
|
|
289
|
+
1. **统一响应**:所有端点 `response_model` 必须是 `Response[T]` 或 `PageResponse[T]`;错误场景抛异常(§3.5),禁止返回裸 dict / 直接 `HTTPException`。
|
|
290
|
+
2. **禁止 `FastAPI(debug=True)`**:`main.py` 中已固定 `debug=False`。`DEBUG=true` 仅控制日志级别、SQL echo 与 uvicorn reload;传 True 会导致 Starlette 绕过统一异常处理器返回原始堆栈。
|
|
291
|
+
3. **分层依赖方向**:endpoint → service → crud → model,单向依赖;core 不依赖业务层。
|
|
292
|
+
4. **复用优先**:新写任何 select/commit/upsert/分页逻辑前,先查 §3 清单;CRUDBase / ServiceBase 未覆盖的场景才在子类扩展。
|
|
293
|
+
5. **模型注册**:新模型必须加入 `app/models/__init__.py`,否则 `init_db()` 与 alembic autogenerate 均无法感知。
|
|
294
|
+
6. **质量门禁**:提交前 `make format && make test && make lint`(ruff + mypy)必须全绿。类型注解用 PEP 695 语法(`class Foo[T]`),SQLModel `inspect()` 返回可空处沿用既有的 `cast(...)` + `# type: ignore[union-attr]` 惯例。
|
|
295
|
+
7. **导入风格**:`from __future__ import annotations` 置于模块首行(docstring 之后);第三方与本地导入分组,遵循 isort 规则(ruff 强制)。
|
|
296
|
+
|
|
297
|
+
### 5.2 注意事项
|
|
298
|
+
|
|
299
|
+
- **时间字段**:一律通过 `TimestampMixin`;JSON 输出格式 `%Y-%m-%d %H:%M:%S` 由 `FormattedDatetime` 统一处理,勿自行 strftime。
|
|
300
|
+
- **Session 生命周期**:请求内通过 `DBSession` 注入;后台任务/脚本中用 `with Session(engine) as session:`,不要复用请求 Session。
|
|
301
|
+
- **降级行为**:MySQL / Redis 缺席不阻塞启动(health 返回 down、Celery 降级 memory)。本地开发无需强制装依赖,但数据接口在无库时统一返回 500。
|
|
302
|
+
- **测试环境**:测试用 sqlite 内存库(`tests/conftest.py`),依赖 sqlite 不支持的特性时需显式跳过。
|
|
303
|
+
- **`.env` 提交策略**:`.env` 与 `.env.example` 入库作默认配置;敏感信息(生产密码)放 `.env.production`(不入库)或真实环境变量。
|
|
304
|
+
- **docs/**:`SQLModel定义示例.md` 是建模规范,新表定义先对照该文档。
|
|
305
|
+
- **业务域隔离**:`item` 域为示例,可整体替换为实际业务;删除时同步清理 `router.py` 挂载与对应测试。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/.python-version.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic/script.py.mako
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/__init__.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/celery_app.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/config.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/database.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/base.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/models/item.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/services/base.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/pm2.config.json.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.1.0 → fastapp_cli-0.2.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|