fastapp-cli 0.1.0__tar.gz → 0.3.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.
Files changed (74) hide show
  1. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/PKG-INFO +8 -6
  2. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/README.md +7 -5
  3. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/pyproject.toml +1 -1
  4. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/__init__.py +1 -1
  5. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/create.py +8 -1
  6. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/prompts.py +8 -1
  7. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.development.example.j2 +2 -1
  8. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.example.j2 +2 -1
  9. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.j2 +2 -1
  10. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/README.md.j2 +9 -5
  11. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/celery_app.py.j2 +2 -1
  12. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/config.py.j2 +2 -1
  13. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/main.py.j2 +11 -7
  14. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/sample_tasks.py.j2 +2 -1
  15. fastapp_cli-0.3.0/src/fastapp_cli/templates/project/docs/agent_guide.md.j2 +307 -0
  16. fastapp_cli-0.3.0/src/fastapp_cli/templates/project/pm2.config.js.j2 +50 -0
  17. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/pyproject.toml.j2 +6 -6
  18. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2 +2 -1
  19. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_flower.sh.j2 +2 -1
  20. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_worker.sh.j2 +2 -1
  21. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/tests/test_create.py +29 -6
  22. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/tests/test_render.py +47 -3
  23. fastapp_cli-0.1.0/src/fastapp_cli/templates/project/pm2.config.json.j2 +0 -47
  24. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/.gitignore +0 -0
  25. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/PLAN.md +0 -0
  26. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/main.py +0 -0
  27. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/naming.py +0 -0
  28. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/render.py +0 -0
  29. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/__init__.py +0 -0
  30. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.gitignore +0 -0
  31. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2 +0 -0
  32. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.python-version.j2 +0 -0
  33. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Dockerfile.j2 +0 -0
  34. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Makefile.j2 +0 -0
  35. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/env.py.j2 +0 -0
  36. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/script.py.mako +0 -0
  37. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep +0 -0
  38. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic.ini.j2 +0 -0
  39. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/__init__.py.j2 +0 -0
  40. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2 +0 -0
  41. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2 +0 -0
  42. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/__init__.py.j2 +0 -0
  43. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/__init__.py.j2 +0 -0
  44. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/health.py.j2 +0 -0
  45. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/items.py.j2 +0 -0
  46. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2 +0 -0
  47. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2 +0 -0
  48. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2 +0 -0
  49. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/database.py.j2 +0 -0
  50. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2 +0 -0
  51. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2 +0 -0
  52. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2 +0 -0
  53. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2 +0 -0
  54. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2 +0 -0
  55. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2 +0 -0
  56. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2 +0 -0
  57. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/base.py.j2 +0 -0
  58. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/item.py.j2 +0 -0
  59. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2 +0 -0
  60. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2 +0 -0
  61. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2 +0 -0
  62. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/__init__.py.j2 +0 -0
  63. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/base.py.j2 +0 -0
  64. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/item_service.py.j2 +0 -0
  65. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2 +0 -0
  66. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2 +0 -0
  67. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/docs/SQLModel/345/256/232/344/271/211/347/244/272/344/276/213.md" +0 -0
  68. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2 +0 -0
  69. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_health.py.j2 +0 -0
  70. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2 +0 -0
  71. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2 +0 -0
  72. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/services/test_item_service.py.j2 +0 -0
  73. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/tests/test_naming.py +0 -0
  74. {fastapp_cli-0.1.0 → fastapp_cli-0.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapp-cli
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: FastAPI 项目脚手架:一条命令生成完整可运行的 FastAPI 工程
5
5
  Author: aidenmo
6
6
  License: MIT
@@ -31,11 +31,12 @@ fastapp create my-server
31
31
  - **分层架构**:`api / schemas / crud / services / models` 分层,内置泛型 CRUD / Service 基类
32
32
  - **开箱即用的工程能力**:统一响应 / 异常体系、TraceID 中间件、loguru 日志、pydantic-settings 多环境配置
33
33
  - **分页 CRUD + 过滤**:基于 fastapi-pagination 与 fastapi-filter 的完整示例(`/api/v1/items`)
34
- - **Celery 骨架**:默认 redis broker,本地无 redis 时启动自动降级 `memory://` 并告警
34
+ - **Celery 可选**:`--celery` 启用时生成完整任务骨架(默认 redis broker,本地无 redis 时启动自动降级 `memory://` 并告警);默认不启用
35
35
  - **Alembic 迁移**:接入 SQLModel.metadata,含 autogenerate 过滤钩子
36
36
  - **优雅降级**:MySQL 缺席不阻塞启动,`/api/v1/health` 返回 `database: up/down`
37
37
  - **工程化配套**:Makefile、scripts/、pre-commit、ruff + mypy + pytest 配置全套
38
- - **可选部署文件**:`--pm2` / `--docker` 按需生成
38
+ - **可选部署文件**:`--pm2` / `--docker` 按需生成(pm2.config.js,celery 未启用时对应启动命令自动注释)
39
+ - **AI Agent 友好**:生成项目自带 `docs/agent_guide.md` 开发参考文档(架构、核心方法清单、扩展流程)
39
40
 
40
41
  ## 安装
41
42
 
@@ -60,8 +61,9 @@ fastapp create <project_name> [options]
60
61
  |---|---|---|
61
62
  | `project_name` | 必填 | 项目名;小写字母/数字/连字符,如 `my-server` |
62
63
  | `--description` | 交互提问 | 项目描述 |
63
- | `--pm2 / --no-pm2` | 交互提问 | 是否生成 `pm2.config.json` |
64
+ | `--pm2 / --no-pm2` | 交互提问 | 是否生成 `pm2.config.js` |
64
65
  | `--docker / --no-docker` | 交互提问 | 是否生成 `Dockerfile` |
66
+ | `--celery / --no-celery` | 交互提问(默认不启用) | 是否启用 Celery 异步任务 |
65
67
  | `--python` | `3.12` | 目标 Python 版本(写入 `.python-version`) |
66
68
  | `--author` | `git config user.name` | 作者署名 |
67
69
  | `--force` | 关 | 目标目录已存在时覆盖重建 |
@@ -70,7 +72,7 @@ fastapp create <project_name> [options]
70
72
  全部关键项通过 flag 提供时零交互:
71
73
 
72
74
  ```bash
73
- fastapp create my-server --description "demo" --no-pm2 --no-docker
75
+ fastapp create my-server --description "demo" --no-pm2 --no-docker --no-celery
74
76
  ```
75
77
 
76
78
  生成完成后:
@@ -82,7 +84,7 @@ make dev # 访问 /api/v1/docs
82
84
 
83
85
  ## 生成项目依赖
84
86
 
85
- fastapi、sqlmodel、alembic、celery[redis]、fastapi-pagination、fastapi-filter、loguru、pydantic-settings 等,dev 组含 pytest / ruff / mypy / pre-commit。
87
+ fastapi、sqlmodel、alembic、fastapi-pagination、fastapi-filter、loguru、pydantic-settings 等,dev 组含 pytest / ruff / mypy / pre-commit;`--celery` 启用时额外引入 celery[redis] 与 flower。
86
88
 
87
89
  ## 开发 fastapp-cli 自身
88
90
 
@@ -11,11 +11,12 @@ fastapp create my-server
11
11
  - **分层架构**:`api / schemas / crud / services / models` 分层,内置泛型 CRUD / Service 基类
12
12
  - **开箱即用的工程能力**:统一响应 / 异常体系、TraceID 中间件、loguru 日志、pydantic-settings 多环境配置
13
13
  - **分页 CRUD + 过滤**:基于 fastapi-pagination 与 fastapi-filter 的完整示例(`/api/v1/items`)
14
- - **Celery 骨架**:默认 redis broker,本地无 redis 时启动自动降级 `memory://` 并告警
14
+ - **Celery 可选**:`--celery` 启用时生成完整任务骨架(默认 redis broker,本地无 redis 时启动自动降级 `memory://` 并告警);默认不启用
15
15
  - **Alembic 迁移**:接入 SQLModel.metadata,含 autogenerate 过滤钩子
16
16
  - **优雅降级**:MySQL 缺席不阻塞启动,`/api/v1/health` 返回 `database: up/down`
17
17
  - **工程化配套**:Makefile、scripts/、pre-commit、ruff + mypy + pytest 配置全套
18
- - **可选部署文件**:`--pm2` / `--docker` 按需生成
18
+ - **可选部署文件**:`--pm2` / `--docker` 按需生成(pm2.config.js,celery 未启用时对应启动命令自动注释)
19
+ - **AI Agent 友好**:生成项目自带 `docs/agent_guide.md` 开发参考文档(架构、核心方法清单、扩展流程)
19
20
 
20
21
  ## 安装
21
22
 
@@ -40,8 +41,9 @@ fastapp create <project_name> [options]
40
41
  |---|---|---|
41
42
  | `project_name` | 必填 | 项目名;小写字母/数字/连字符,如 `my-server` |
42
43
  | `--description` | 交互提问 | 项目描述 |
43
- | `--pm2 / --no-pm2` | 交互提问 | 是否生成 `pm2.config.json` |
44
+ | `--pm2 / --no-pm2` | 交互提问 | 是否生成 `pm2.config.js` |
44
45
  | `--docker / --no-docker` | 交互提问 | 是否生成 `Dockerfile` |
46
+ | `--celery / --no-celery` | 交互提问(默认不启用) | 是否启用 Celery 异步任务 |
45
47
  | `--python` | `3.12` | 目标 Python 版本(写入 `.python-version`) |
46
48
  | `--author` | `git config user.name` | 作者署名 |
47
49
  | `--force` | 关 | 目标目录已存在时覆盖重建 |
@@ -50,7 +52,7 @@ fastapp create <project_name> [options]
50
52
  全部关键项通过 flag 提供时零交互:
51
53
 
52
54
  ```bash
53
- fastapp create my-server --description "demo" --no-pm2 --no-docker
55
+ fastapp create my-server --description "demo" --no-pm2 --no-docker --no-celery
54
56
  ```
55
57
 
56
58
  生成完成后:
@@ -62,7 +64,7 @@ make dev # 访问 /api/v1/docs
62
64
 
63
65
  ## 生成项目依赖
64
66
 
65
- fastapi、sqlmodel、alembic、celery[redis]、fastapi-pagination、fastapi-filter、loguru、pydantic-settings 等,dev 组含 pytest / ruff / mypy / pre-commit。
67
+ fastapi、sqlmodel、alembic、fastapi-pagination、fastapi-filter、loguru、pydantic-settings 等,dev 组含 pytest / ruff / mypy / pre-commit;`--celery` 启用时额外引入 celery[redis] 与 flower。
66
68
 
67
69
  ## 开发 fastapp-cli 自身
68
70
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapp-cli"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "FastAPI 项目脚手架:一条命令生成完整可运行的 FastAPI 工程"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """fastapp-cli: FastAPI 项目脚手架."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.3.0"
@@ -68,13 +68,18 @@ def create(
68
68
  pm2: bool = typer.Option(
69
69
  None,
70
70
  "--pm2/--no-pm2",
71
- help="是否生成 pm2.config.json(未指定时交互提问)",
71
+ help="是否生成 pm2.config.js(未指定时交互提问)",
72
72
  ),
73
73
  docker: bool = typer.Option(
74
74
  None,
75
75
  "--docker/--no-docker",
76
76
  help="是否生成 Dockerfile(未指定时交互提问)",
77
77
  ),
78
+ celery: bool = typer.Option(
79
+ None,
80
+ "--celery/--no-celery",
81
+ help="是否启用 Celery 异步任务(未指定时交互提问,默认不启用)",
82
+ ),
78
83
  python_version: str = typer.Option(
79
84
  "3.12",
80
85
  "--python",
@@ -122,6 +127,7 @@ def create(
122
127
  desc = prompts.ask_description(description)
123
128
  use_pm2 = prompts.ask_use_pm2(pm2)
124
129
  use_docker = prompts.ask_use_docker(docker)
130
+ use_celery = prompts.ask_use_celery(celery)
125
131
  author_name = author or _git_user_name() or "fastapp-cli"
126
132
 
127
133
  # 3. 渲染
@@ -132,6 +138,7 @@ def create(
132
138
  "author_name": author_name,
133
139
  "use_pm2": use_pm2,
134
140
  "use_docker": use_docker,
141
+ "use_celery": use_celery,
135
142
  "year": datetime.now(timezone.utc).year,
136
143
  "fastapp_cli_version": __version__,
137
144
  }
@@ -1,6 +1,6 @@
1
1
  """混合模式交互提问.
2
2
 
3
- 关键项(描述 / pm2 / Dockerfile)在未通过 flag 提供时交互提问;
3
+ 关键项(描述 / pm2 / Dockerfile / celery)在未通过 flag 提供时交互提问;
4
4
  flag 已提供则跳过提问,实现零交互。
5
5
  """
6
6
 
@@ -33,3 +33,10 @@ def ask_use_docker(provided: bool | None) -> bool:
33
33
  if provided is not None:
34
34
  return provided
35
35
  return typer.confirm("是否包含 Dockerfile?", default=False)
36
+
37
+
38
+ def ask_use_celery(provided: bool | None) -> bool:
39
+ """是否启用 Celery 异步任务:未指定时交互提问,默认不启用."""
40
+ if provided is not None:
41
+ return provided
42
+ return typer.confirm("是否启用 Celery 异步任务?", default=False)
@@ -16,6 +16,7 @@ MYSQL_DB={{ project_name | replace('-', '_') }}_dev
16
16
  # 打印 SQL 方便排查
17
17
  SQL_ECHO=true
18
18
 
19
- # 本地 Redis
19
+ {% if use_celery %}# 本地 Redis
20
20
  CELERY_BROKER_URL=redis://localhost:6379/0
21
21
  CELERY_RESULT_BACKEND=redis://localhost:6379/1
22
+ {% endif %}
@@ -17,7 +17,7 @@ MYSQL_DB={{ project_name | replace('-', '_') }}
17
17
  # 是否打印 SQL(开发环境使用)
18
18
  SQL_ECHO=false
19
19
 
20
- # ========== Celery(redis,缺席时应用启动自动降级 memory://) ==========
20
+ {% if use_celery %}# ========== Celery(redis,缺席时应用启动自动降级 memory://) ==========
21
21
  CELERY_BROKER_URL=redis://localhost:6379/0
22
22
  CELERY_RESULT_BACKEND=redis://localhost:6379/1
23
23
  CELERY_TASK_DEFAULT_QUEUE=default
@@ -27,3 +27,4 @@ FLOWER_HOST=0.0.0.0
27
27
  FLOWER_PORT=5555
28
28
  # 基本认证(可选),格式:user:password,多个用逗号分隔
29
29
  # FLOWER_BASIC_AUTH=admin:admin
30
+ {% endif %}
@@ -17,7 +17,7 @@ MYSQL_PASSWORD=
17
17
  MYSQL_DB={{ project_name | replace('-', '_') }}_dev
18
18
  SQL_ECHO=true
19
19
 
20
- # ========== Celery(本地无 redis 时启动自动降级 memory://) ==========
20
+ {% if use_celery %}# ========== Celery(本地无 redis 时启动自动降级 memory://) ==========
21
21
  CELERY_BROKER_URL=redis://localhost:6379/0
22
22
  CELERY_RESULT_BACKEND=redis://localhost:6379/1
23
23
  CELERY_TASK_DEFAULT_QUEUE=default
@@ -25,3 +25,4 @@ CELERY_TASK_DEFAULT_QUEUE=default
25
25
  # ========== Flower ==========
26
26
  FLOWER_HOST=0.0.0.0
27
27
  FLOWER_PORT=5555
28
+ {% endif %}
@@ -3,7 +3,7 @@
3
3
  > {{ project_description }}
4
4
  > 由 [fastapp-cli](https://pypi.org/project/fastapp-cli/) v{{ fastapp_cli_version }} 生成于 {{ year }},作者:{{ author_name }}
5
5
 
6
- FastAPI + SQLModel + Celery 工程模板,含分层架构、统一响应 / 异常 / TraceID / 日志、分页 CRUD、Alembic 迁移与完整测试。
6
+ FastAPI + SQLModel{% if use_celery %} + Celery{% endif %} 工程模板,含分层架构、统一响应 / 异常 / TraceID / 日志、分页 CRUD、Alembic 迁移与完整测试。
7
7
 
8
8
  ## 快速开始
9
9
 
@@ -20,7 +20,8 @@ make dev # 或 uv run uvicorn app.main:app --reload
20
20
  | 组件 | 默认 | 缺席时行为 |
21
21
  |---|---|---|
22
22
  | MySQL | `.env` 中配置 | 启动不阻塞,`/api/v1/health` 返回 `database: "down"`,数据接口统一返回 500 |
23
- | Redis (Celery) | `redis://localhost:6379` | 启动不阻塞,自动降级为 `memory://` broker 并输出告警日志 |
23
+ {% if use_celery %}| Redis (Celery) | `redis://localhost:6379` | 启动不阻塞,自动降级为 `memory://` broker 并输出告警日志 |
24
+ {% endif %}
24
25
 
25
26
  生产环境只需在 `.env` 配置真实 MySQL / Redis 地址,无需改代码。
26
27
 
@@ -35,14 +36,14 @@ make migrate # alembic upgrade head
35
36
  make revision m="add xxx" # 生成迁移脚本
36
37
  ```
37
38
 
38
- Celery:
39
+ {% if use_celery %}Celery:
39
40
 
40
41
  ```bash
41
42
  bash scripts/celery_worker.sh # worker
42
43
  bash scripts/celery_beat.sh # 定时任务
43
44
  bash scripts/celery_flower.sh # 监控面板(:5555)
44
45
  ```
45
-
46
+ {% endif %}
46
47
  ## 目录结构
47
48
 
48
49
  ```text
@@ -62,7 +63,10 @@ app/
62
63
 
63
64
  ## 扩展指南
64
65
 
66
+ > AI Agent / 新成员开发前请先阅读 [docs/agent_guide.md](docs/agent_guide.md)——包含架构说明、核心方法清单(优先复用)、标准开发流程与编码规范。
67
+
65
68
  1. **新增业务域**:仿照 `item` 五件套(model / schema / crud / service / endpoint),在 `models/__init__.py` 注册新模型,在 `api/v1/router.py` 挂载新路由;
66
69
  2. **生成迁移**:`make revision m="add xxx table"` 后检查 `alembic/versions/`,再 `make migrate`;
67
70
  3. **接入真实认证**:替换 `app/api/deps.py::get_current_user` 的占位实现;
68
- 4. **新增 Celery 任务**:在 `app/tasks/` 中使用 `@celery_app.task` 装饰器编写。
71
+ {% if use_celery %}4. **新增 Celery 任务**:在 `app/tasks/` 中使用 `@celery_app.task` 装饰器编写。
72
+ {% endif %}
@@ -1,4 +1,4 @@
1
- """Celery 应用实例.
1
+ {% if use_celery %}"""Celery 应用实例.
2
2
 
3
3
  主应用与 worker 共用 ``build_celery_app`` 构造入口:
4
4
 
@@ -60,3 +60,4 @@ celery_app.conf.beat_schedule = {
60
60
  # "schedule": crontab(hour=2, minute=0),
61
61
  # },
62
62
  }
63
+ {% endif %}
@@ -80,7 +80,7 @@ class Settings(BaseSettings):
80
80
  DB_POOL_SIZE: int = 10
81
81
  DB_MAX_OVERFLOW: int = 20
82
82
 
83
- # ========== Celery 配置 ==========
83
+ {% if use_celery %} # ========== Celery 配置 ==========
84
84
  CELERY_BROKER_URL: str = "redis://localhost:6379/0"
85
85
  CELERY_RESULT_BACKEND: str = "redis://localhost:6379/1"
86
86
  CELERY_TASK_DEFAULT_QUEUE: str = "default"
@@ -90,6 +90,7 @@ class Settings(BaseSettings):
90
90
  FLOWER_PORT: int = 5555
91
91
  # 基本认证,格式:user:password,多个用逗号分隔
92
92
  FLOWER_BASIC_AUTH: str | None = None
93
+ {% endif %}
93
94
 
94
95
  @property
95
96
  def is_production(self) -> bool:
@@ -12,8 +12,8 @@ from loguru import logger
12
12
  from sqlalchemy import text
13
13
 
14
14
  from app.api.v1.router import api_router
15
- from app.core.celery_app import celery_app, degrade_to_memory
16
- from app.core.config import settings
15
+ {% if use_celery %}from app.core.celery_app import celery_app, degrade_to_memory
16
+ {% endif %}from app.core.config import settings
17
17
  from app.core.database import engine
18
18
  from app.core.exceptions import register_exception_handlers
19
19
  from app.core.logging import setup_logging
@@ -30,7 +30,7 @@ def _probe_database() -> None:
30
30
  logger.warning("数据库未就绪,数据接口将返回 500,请配置 .env: {}", exc)
31
31
 
32
32
 
33
- def _probe_redis() -> None:
33
+ {% if use_celery %}def _probe_redis() -> None:
34
34
  """启动时探测 redis(带超时),不可达时 Celery 自动降级为 memory broker."""
35
35
  broker_url = settings.CELERY_BROKER_URL
36
36
  if broker_url.startswith("memory"):
@@ -52,18 +52,18 @@ def _probe_redis() -> None:
52
52
  logger.warning("redis 未就绪,Celery 已降级为 memory broker: {}", exc)
53
53
 
54
54
 
55
- @asynccontextmanager
55
+ {% endif %}@asynccontextmanager
56
56
  async def lifespan(_: FastAPI) -> AsyncIterator[None]:
57
57
  """应用生命周期.
58
58
 
59
- - 启动:初始化日志、探测数据库 / redis(缺席降级,不阻塞启动)
59
+ - 启动:初始化日志、探测数据库{% if use_celery %} / redis(缺席降级,不阻塞启动){% endif %}
60
60
  - 关闭:优雅释放资源
61
61
  """
62
62
  setup_logging()
63
63
  logger.info("{} starting | env={} | debug={}", settings.APP_NAME, settings.APP_ENV, settings.DEBUG)
64
64
  _probe_database()
65
- _probe_redis()
66
-
65
+ {% if use_celery %} _probe_redis()
66
+ {% endif %}
67
67
  yield
68
68
 
69
69
  logger.info("{} stopped", settings.APP_NAME)
@@ -71,6 +71,10 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
71
71
 
72
72
  def create_app() -> FastAPI:
73
73
  """工厂函数:创建 FastAPI 应用实例."""
74
+ if settings.DEBUG:
75
+ # DEBUG 模式下打印全部配置,便于本地排查;生产环境(DEBUG=false)不输出,避免泄露敏感配置
76
+ logger.debug("settings: {}", settings.model_dump())
77
+
74
78
  app_ = FastAPI(
75
79
  title=settings.APP_NAME,
76
80
  # 注意:固定 False。DEBUG 环境变量仅控制日志级别与 SQL echo;
@@ -1,4 +1,4 @@
1
- """Celery 示例任务."""
1
+ {% if use_celery %}"""Celery 示例任务."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -26,3 +26,4 @@ def sample_task(self, name: str = "world") -> dict:
26
26
  logger.info("[task={}] sample_task running, name={}", self.request.id, name)
27
27
  time.sleep(1)
28
28
  return {"message": f"hello, {name}"}
29
+ {% endif %}
@@ -0,0 +1,307 @@
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
+ {% if use_celery %}| 异步任务 | Celery + Redis | redis 不可达时自动降级 memory broker |
17
+ {% endif %}| 分页 | 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{% if use_celery %}/redis{% endif %} 探测,缺席降级不阻塞启动)
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
+ {% if use_celery %}│ │ └── celery_app.py # build_celery_app / degrade_to_memory / celery_app 单例
62
+ {% endif %}│ ├── 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/{% if use_celery %} # Celery 任务(sample_tasks.py 为示例)
80
+ │ └── utils/ # 通用工具扩展位(空)
81
+ {% else %} # 预留任务目录
82
+ │ └── utils/ # 通用工具扩展位(空)
83
+ {% endif %}├── alembic/ # 迁移环境(env.py 已配置 MANAGED_TABLES 白名单过滤)
84
+ ├── docs/ # 本文档 + SQLModel定义示例.md(建模规范)
85
+ ├── scripts/ # start.sh{% if use_celery %} / celery_worker.sh / celery_beat.sh / celery_flower.sh{% endif %}
86
+ ├── tests/ # pytest:conftest 用 sqlite 内存库,无需真实 MySQL
87
+ ├── alembic.ini / Makefile / pyproject.toml
88
+ └── .env / .env.example # 配置文件(.env 入库,敏感信息按环境覆盖)
89
+ ```
90
+
91
+ ## 3. 已有核心方法与工具函数清单(优先复用)
92
+
93
+ > **强制要求:开发新功能前先检索本清单。能用现有方法解决的,必须调用现有方法,禁止重写等价实现。**
94
+
95
+ ### 3.1 数据访问层 — `CRUDBase`(`app/crud/base.py`)
96
+
97
+ 泛型基类 `CRUDBase[ModelType: SQLModel]`,所有业务 CRUD 继承它即可获得以下能力,**任何手写 select/commit 循环前必须先确认此处没有现成方法**:
98
+
99
+ | 方法 | 签名 | 说明 |
100
+ |---|---|---|
101
+ | `get` | `(db: Session, obj_id: Any) -> ModelType \| None` | 按主键查询 |
102
+ | `get_or_404` | `(db: Session, obj_id: Any) -> ModelType` | 按主键查询,不存在抛 `NotFoundError` |
103
+ | `get_by_field` | `(db: Session, field: str, value: Any) -> ModelType \| None` | 按任意字段查单个(字段不存在抛 `ValueError`) |
104
+ | `get_page_multi` | `(db: Session, *, filter_params: Filter \| None = None) -> PageResponse[Any]` | 分页 + 过滤 + 排序查询 |
105
+ | `get_multi` | `(db: Session, *, filter_params: Filter \| None = None) -> Sequence[ModelType]` | 非分页列表查询 |
106
+ | `count` | `(db: Session, query: Select \| SelectOfScalar \| None = None) -> int` | 记录总数(可传入自定义 query) |
107
+ | `create` | `(db: Session, *, obj_in: BaseModel \| dict[str, Any]) -> ModelType` | 创建单条(自动 model_dump、commit、refresh) |
108
+ | `create_multi` | `(db: Session, *, objs_in: list[...]) -> list[ModelType]` | 批量创建 |
109
+ | `update` | `(db: Session, *, db_obj: ModelType, obj_in: BaseModel \| dict[str, Any]) -> ModelType` | 更新(`exclude_unset` 语义,只更新显式传入字段) |
110
+ | `create_or_update` | `(db: Session, *, obj_in, unique_fields: str \| Sequence[str] \| None = None) -> ModelType` | 按唯一字段 upsert(未指定时用主键) |
111
+ | `create_or_update_multi` | `(db: Session, *, objs_in: list[...], unique_fields=None) -> list[ModelType]` | 批量 upsert(同一事务提交) |
112
+ | `remove` | `(db: Session, *, obj_id: int) -> ModelType` | 按主键删除(不存在抛 404) |
113
+ | `remove_multi` | `(db: Session, *, obj_ids: list[Any]) -> list[ModelType]` | 批量删除 |
114
+ | `exists` | `(db: Session, *, obj_id: Any) -> bool` | 判断是否存在 |
115
+
116
+ 使用示例(新增业务域的 CRUD 通常只需一行继承):
117
+
118
+ ```python
119
+ from app.crud.base import CRUDBase
120
+ from app.models.product import Product
121
+
122
+ class CRUDProduct(CRUDBase[Product]):
123
+ pass # 复杂查询逻辑才在此扩展
124
+ ```
125
+
126
+ ### 3.2 业务层 — `ServiceBase`(`app/services/base.py`)
127
+
128
+ 泛型基类 `ServiceBase[CRUDType: CRUDBase]`,内部持有 `self.db` 与 `self.crud`,方法返回值标注为 `Any`(由子类按业务模型收窄):
129
+
130
+ | 方法 | 签名 | 说明 |
131
+ |---|---|---|
132
+ | `create` | `(obj_in: BaseModel \| dict) -> Any` | 创建 |
133
+ | `create_multi` | `(objs_in: list) -> list[Any]` | 批量创建 |
134
+ | `create_or_update` | `(obj_in, unique_fields=None) -> Any` | upsert |
135
+ | `create_or_update_multi` | `(objs_in, unique_fields=None) -> list[Any]` | 批量 upsert |
136
+ | `get` | `(obj_id: int) -> Any` | 获取单个,不存在抛 `NotFoundError` |
137
+ | `update` | `(obj_id: int, obj_in) -> Any` | 更新(不存在抛 `NotFoundError`) |
138
+ | `delete` | `(obj_id: int) -> Any` | 删除 |
139
+ | `list_objs` | `(filter_params: Filter \| None = None) -> Sequence[Any]` | 列表查询 |
140
+ | `list_page_objs` | `(filter_params: Filter \| None = None) -> PageResponse[Any]` | 分页查询 |
141
+
142
+ 使用示例(纯 CRUD 型业务,Service 通常也只是继承):
143
+
144
+ ```python
145
+ from app.crud.product import CRUDProduct
146
+ from app.services.base import ServiceBase
147
+
148
+ class ProductService(ServiceBase[CRUDProduct]):
149
+ def activate(self, obj_id: int) -> None: # 自定义业务方法
150
+ obj = self.get(obj_id)
151
+ self.crud.update(self.db, db_obj=obj, obj_in={"is_active": True})
152
+ ```
153
+
154
+ ### 3.3 API 依赖(`app/api/deps.py`)
155
+
156
+ | 名称 | 类型 | 说明 |
157
+ |---|---|---|
158
+ | `DBSession` | `Annotated[Session, Depends(get_session)]` | 端点中直接作参数注解,获取数据库会话 |
159
+ | `CurrentUser` | `Annotated[str, Depends(get_current_user)]` | 当前登录用户。非生产环境返回 `settings.MOCK_USER`;生产环境抛 `UnauthorizedError`(认证占位,接入真实认证时只改 `get_current_user`) |
160
+
161
+ ```python
162
+ @router.post("", response_model=Response[ProductRead])
163
+ def create_product(payload: ProductCreate, service: ProductServiceDep, current_user: CurrentUser) -> Response[ProductRead]:
164
+ data: ProductRead = service.create(payload)
165
+ return Response(data=data)
166
+ ```
167
+
168
+ ### 3.4 统一响应与分页(`app/schemas/common.py`)
169
+
170
+ | 名称 | 说明 |
171
+ |---|---|
172
+ | `Response[T]` | 统一响应体 `{code, message, data, trace_id}`;`code=0` 为成功;trace_id 自动从请求上下文注入 |
173
+ | `PageParams` | 分页入参(`page` / `page_size`,`size` 的 URL alias 为 `page_size`) |
174
+ | `PageResult[T]` | 分页数据结构 `{total, page, page_size, content}` |
175
+ | `PageResponse[T]` | 分页统一响应(继承 `Response[PageResult[T]]`),作为端点 `response_model` 使用 |
176
+
177
+ ### 3.5 统一异常(`app/core/exceptions.py`)
178
+
179
+ **业务代码中禁止直接 `raise HTTPException` 或返回错误 dict**,统一抛以下异常,由全局异常处理器转换成统一返回体 `{code, message, data, trace_id}`:
180
+
181
+ | 异常 | HTTP 状态码 | 业务码 | 默认消息 |
182
+ |---|---|---|---|
183
+ | `AppExceptionError(message, *, code, status_code, data)` | 400 | 1 | 业务异常基类,自定义业务异常继承它 |
184
+ | `NotFoundError` | 404 | 40400 | 资源不存在 |
185
+ | `UnauthorizedError` | 401 | 40100 | 未认证或凭证已失效 |
186
+ | `ForbiddenError` | 403 | 40300 | 无访问权限 |
187
+ | `ConflictError` | 409 | 40900 | 资源冲突 |
188
+
189
+ ```python
190
+ from app.core.exceptions import ConflictError, NotFoundError, AppExceptionError
191
+
192
+ raise NotFoundError(message="订单不存在")
193
+ raise AppExceptionError(message="余额不足", code=10001, status_code=400)
194
+ ```
195
+
196
+ 未捕获异常统一返回 HTTP 500 + `{"code": 50000, "message": "服务器内部错误"}`,不会裸抛堆栈(应用实例固定 `debug=False`,勿改)。
197
+
198
+ ### 3.6 模型公共字段(`app/models/base.py`)
199
+
200
+ | 名称 | 说明 |
201
+ |---|---|
202
+ | `IDMixin` | 主键 `id: int`(primary_key + index) |
203
+ | `TimestampMixin` | `create_time` / `update_time`,数据库自动生成与刷新,JSON 序列化为 `%Y-%m-%d %H:%M:%S` |
204
+ | `utcnow()` | 当前时间(`default_factory` 用) |
205
+ | `FormattedDatetime` | 可复用的 datetime 序列化类型标注 |
206
+
207
+ 新表模型标准写法:
208
+
209
+ ```python
210
+ from sqlmodel import Field
211
+ from app.models.base import IDMixin, TimestampMixin
212
+
213
+ class ProductBase(TimestampMixin):
214
+ name: str = Field(max_length=128, description="名称")
215
+
216
+ class Product(IDMixin, ProductBase, table=True):
217
+ __tablename__ = "t_product"
218
+ ```
219
+
220
+ ### 3.7 配置(`app/core/config.py`)
221
+
222
+ - 单例 `settings`:所有环境变量集中于此,**禁止在业务代码中直接读 `os.environ`**。
223
+ - 加载顺序(后者覆盖前者):`.env` → `.env.{APP_ENV}` → 系统环境变量。
224
+ - 常用字段:`APP_NAME` / `APP_ENV`(development|production|test)/ `DEBUG` / `API_V1_PREFIX` / `ALLOWED_ORIGINS` / `DATABASE_URI`(未设置时自动按 `MYSQL_*` 拼装)/ `SQL_ECHO`{% if use_celery %} / `CELERY_BROKER_URL`{% endif %} / `MOCK_USER`。
225
+ - 属性:`settings.is_production` / `settings.is_test`。
226
+ - 新增配置项:在 `Settings` 类中加字段 + 默认值,并在 `.env.example` / `.env` 中补充说明。
227
+
228
+ ### 3.8 数据库(`app/core/database.py`)
229
+
230
+ | 名称 | 说明 |
231
+ |---|---|
232
+ | `engine` | 全局引擎。sqlite(测试)与 MySQL(连接池 + `pool_recycle=3600` + `REPEATABLE READ` + 3 秒连接超时)已分别调优,勿重复配置 |
233
+ | `get_session()` | FastAPI 依赖,yield 语法保证 Session 关闭;**通过 `DBSession` 注解使用**,不要自建 Session |
234
+ | `init_db()` | 本地/测试快速建表(生产用 alembic 迁移) |
235
+
236
+ {% if use_celery %}### 3.9 Celery(`app/core/celery_app.py`)
237
+
238
+ | 名称 | 说明 |
239
+ |---|---|
240
+ | `celery_app` | 全局单例(worker 与 web 共用),新任务直接 `@celery_app.task` |
241
+ | `build_celery_app(broker_url, backend_url, *, name)` | 构造入口(自定义 broker 时使用) |
242
+ | `degrade_to_memory(celery)` | 降级为 memory broker/backend(lifespan 探测失败时自动调用,勿手动调用) |
243
+
244
+ 新任务示例:
245
+
246
+ ```python
247
+ from app.core.celery_app import celery_app
248
+
249
+ @celery_app.task
250
+ def send_notice(user_id: int) -> str:
251
+ ...
252
+ ```
253
+
254
+ {% endif %}{% if use_celery %}### 3.10{% else %}### 3.9{% endif %} 日志与 TraceID(`app/core/logging.py`、`app/core/context_var.py`)
255
+
256
+ | 名称 | 说明 |
257
+ |---|---|
258
+ | `setup_logging()` | lifespan 启动时自动调用,勿重复调用 |
259
+ | `logger`(loguru) | 业务代码统一 `from loguru import logger`;标准 logging 已被拦截转发到 loguru |
260
+ | `with_trace` | 装饰器:把请求 trace_id 透传到后台任务(`BackgroundTasks.add_task` 场景**必须**用它包裹) |
261
+ | `get_trace_id()` | 获取当前请求 trace_id(无上下文返回空串) |
262
+
263
+ ## 4. 新增功能的标准开发流程
264
+
265
+ ### 4.1 新增业务域(以 `product` 为例,五件套 + 两处注册 + 迁移 + 测试)
266
+
267
+ 1. **Model**:`app/models/product.py`,继承 `IDMixin, TimestampMixin`(见 §3.6 示例);在 `app/models/__init__.py` 中 import 并加入 `__all__`(否则 alembic autogenerate 无法发现)。
268
+ 2. **Schema**:`app/schemas/product.py`,定义 `ProductCreate` / `ProductUpdate` / `ProductRead`(继承 `ProductBase` + `id` 字段)与 `ProductFilter(Filter)`(`Constants.model` 指向模型,过滤字段用 `__ilike` / `__gte` 等后缀,参考 `app/schemas/item.py`)。
269
+ 3. **CRUD**:`app/crud/product.py`,`class CRUDProduct(CRUDBase[Product]): pass`。
270
+ 4. **Service**:`app/services/product_service.py`,`class ProductService(ServiceBase[CRUDProduct])`,业务规则写在这里。
271
+ 5. **Endpoint**:`app/api/v1/endpoints/product.py`,仿照 `items.py`:`get_service` 依赖 + `ServiceDep` + 各路由方法返回 `Response[...]` / `PageResponse[...]`。
272
+ 6. **注册路由**:`app/api/v1/router.py` 中 `include_router(product_router, prefix="/products", tags=["Products"])`。
273
+ 7. **迁移**:`make revision m="add t_product"` → 检查 `alembic/versions/` 生成脚本 → `make migrate`。注意 `alembic/env.py` 用 `MANAGED_TABLES` 白名单过滤,新表模型注册到 `app/models/__init__.py` 后会被自动纳入。
274
+ 8. **测试**:在 `tests/` 下仿照 `tests/api/test_items.py` 编写(conftest 已用 sqlite 内存库,无需真实 MySQL),运行 `make test && make lint`。
275
+
276
+ ### 4.2 其他扩展场景
277
+
278
+ | 场景 | 做法 |
279
+ |---|---|
280
+ | 新增配置项 | `Settings` 类加字段 + `.env.example` 补说明 |
281
+ | 接入真实认证 | 只替换 `app/api/deps.py::get_current_user` 实现,端点代码不动 |
282
+ | 新增中间件 | `app/core/middleware.py` 实现,并在 `main.py::create_app` 注册;**注意注册顺序**:TraceIDMiddleware 必须最后添加(处于最外层) |
283
+ {% if use_celery %}| 新增 Celery 定时任务 | `app/tasks/` 写任务,`celery_app.conf.beat_schedule` 中注册(该文件内有注释示例) |
284
+ {% endif %}| 自定义业务异常 | 继承 `AppExceptionError`,类属性覆盖 `status_code` / `code` / `message`,全局处理器自动生效 |
285
+ | 复杂查询 | 在业务 CRUD 子类中扩展方法,保持 Service 只编排不写 SQL 细节 |
286
+
287
+ ## 5. 编码规范与注意事项
288
+
289
+ ### 5.1 强制规范
290
+
291
+ 1. **统一响应**:所有端点 `response_model` 必须是 `Response[T]` 或 `PageResponse[T]`;错误场景抛异常(§3.5),禁止返回裸 dict / 直接 `HTTPException`。
292
+ 2. **禁止 `FastAPI(debug=True)`**:`main.py` 中已固定 `debug=False`。`DEBUG=true` 仅控制日志级别、SQL echo 与 uvicorn reload;传 True 会导致 Starlette 绕过统一异常处理器返回原始堆栈。
293
+ 3. **分层依赖方向**:endpoint → service → crud → model,单向依赖;core 不依赖业务层。
294
+ 4. **复用优先**:新写任何 select/commit/upsert/分页逻辑前,先查 §3 清单;CRUDBase / ServiceBase 未覆盖的场景才在子类扩展。
295
+ 5. **模型注册**:新模型必须加入 `app/models/__init__.py`,否则 `init_db()` 与 alembic autogenerate 均无法感知。
296
+ 6. **质量门禁**:提交前 `make format && make test && make lint`(ruff + mypy)必须全绿。类型注解用 PEP 695 语法(`class Foo[T]`),SQLModel `inspect()` 返回可空处沿用既有的 `cast(...)` + `# type: ignore[union-attr]` 惯例。
297
+ 7. **导入风格**:`from __future__ import annotations` 置于模块首行(docstring 之后);第三方与本地导入分组,遵循 isort 规则(ruff 强制)。
298
+
299
+ ### 5.2 注意事项
300
+
301
+ - **时间字段**:一律通过 `TimestampMixin`;JSON 输出格式 `%Y-%m-%d %H:%M:%S` 由 `FormattedDatetime` 统一处理,勿自行 strftime。
302
+ - **Session 生命周期**:请求内通过 `DBSession` 注入;后台任务/脚本中用 `with Session(engine) as session:`,不要复用请求 Session。
303
+ - **降级行为**:MySQL{% if use_celery %} / Redis 缺席不阻塞启动(health 返回 down、Celery 降级 memory)。本地开发无需强制装依赖,但数据接口在无库时统一返回 500{% else %} 缺席不阻塞启动(health 返回 down)。本地开发无需强制装数据库,数据接口在无库时统一返回 500{% endif %}。
304
+ - **测试环境**:测试用 sqlite 内存库(`tests/conftest.py`),依赖 sqlite 不支持的特性时需显式跳过。
305
+ - **`.env` 提交策略**:`.env` 与 `.env.example` 入库作默认配置;敏感信息(生产密码)放 `.env.production`(不入库)或真实环境变量。
306
+ - **docs/**:`SQLModel定义示例.md` 是建模规范,新表定义先对照该文档。
307
+ - **业务域隔离**:`item` 域为示例,可整体替换为实际业务;删除时同步清理 `router.py` 挂载与对应测试。
@@ -0,0 +1,50 @@
1
+ {% if use_pm2 %}module.exports = {
2
+ apps: [
3
+ {
4
+ name: "{{ project_name }}-server",
5
+ script: "bash scripts/start.sh",
6
+ out_file: "./logs/server.log",
7
+ err_file: "./logs/server.log",
8
+ },
9
+ {% if use_celery %}
10
+ {
11
+ name: "{{ project_name }}-worker",
12
+ script: "bash scripts/celery_worker.sh",
13
+ out_file: "./logs/celery.log",
14
+ err_file: "./logs/celery.log",
15
+ },
16
+ {
17
+ name: "{{ project_name }}-beat",
18
+ script: "bash scripts/celery_beat.sh",
19
+ out_file: "./logs/celery_beat.log",
20
+ err_file: "./logs/celery_beat.log",
21
+ },
22
+ {
23
+ name: "{{ project_name }}-flower",
24
+ script: "bash scripts/celery_flower.sh",
25
+ out_file: "./logs/flower.log",
26
+ err_file: "./logs/flower.log",
27
+ },
28
+ {% else %}
29
+ // {
30
+ // name: "{{ project_name }}-worker",
31
+ // script: "bash scripts/celery_worker.sh",
32
+ // out_file: "./logs/celery.log",
33
+ // err_file: "./logs/celery.log",
34
+ // },
35
+ // {
36
+ // name: "{{ project_name }}-beat",
37
+ // script: "bash scripts/celery_beat.sh",
38
+ // out_file: "./logs/celery_beat.log",
39
+ // err_file: "./logs/celery_beat.log",
40
+ // },
41
+ // {
42
+ // name: "{{ project_name }}-flower",
43
+ // script: "bash scripts/celery_flower.sh",
44
+ // out_file: "./logs/flower.log",
45
+ // err_file: "./logs/flower.log",
46
+ // }
47
+ {% endif %}
48
+ ],
49
+ };
50
+ {% endif %}
@@ -7,12 +7,12 @@ requires-python = ">=3.12"
7
7
 
8
8
  dependencies = [
9
9
  "alembic>=1.18.4",
10
- "celery[redis]>=5.6.3",
11
- "fastapi>=0.136.1",
10
+ {% if use_celery %} "celery[redis]>=5.6.3",
11
+ {% endif %} "fastapi>=0.136.1",
12
12
  "fastapi-filter[sqlalchemy]>=2.0.1",
13
13
  "fastapi-pagination>=0.15.13",
14
- "flower>=2.0.1",
15
- "httpx>=0.28.1",
14
+ {% if use_celery %} "flower>=2.0.1",
15
+ {% endif %} "httpx>=0.28.1",
16
16
  "loguru>=0.7.3",
17
17
  "pydantic>=2.13.3",
18
18
  "pydantic-settings>=2.14.0",
@@ -180,9 +180,9 @@ exclude = [
180
180
  # 说明:这些库通常没有官方 type stubs,忽略可以避免 "Cannot find implementation" 报错
181
181
  [[tool.mypy.overrides]]
182
182
  module = [
183
- "celery.*",
183
+ {% if use_celery %} "celery.*",
184
184
  "flower.*",
185
- "sqlmodel.*", # SQLModel 的类型推断对 mypy 不友好,建议忽略
185
+ {% endif %} "sqlmodel.*", # SQLModel 的类型推断对 mypy 不友好,建议忽略
186
186
  ]
187
187
  ignore_missing_imports = true
188
188
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ {% if use_celery %}#!/usr/bin/env bash
2
2
  # 启动 Celery Beat(定时任务调度)
3
3
  set -euo pipefail
4
4
 
@@ -7,3 +7,4 @@ cd "$(dirname "$0")/.."
7
7
  LOG_LEVEL="${LOG_LEVEL:-INFO}"
8
8
 
9
9
  exec uv run celery -A app.core.celery_app.celery_app beat -l "${LOG_LEVEL}"
10
+ {% endif %}
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ {% if use_celery %}#!/usr/bin/env bash
2
2
  # 启动 Flower(Celery 任务监控面板)
3
3
  set -euo pipefail
4
4
 
@@ -20,3 +20,4 @@ exec uv run celery -A app.core.celery_app.celery_app flower \
20
20
  --port="${PORT}" \
21
21
  --loglevel="${LOG_LEVEL}" \
22
22
  ${BASIC_AUTH_ARG}
23
+ {% endif %}
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bash
1
+ {% if use_celery %}#!/usr/bin/env bash
2
2
  # 启动 Celery Worker
3
3
  set -euo pipefail
4
4
 
@@ -13,3 +13,4 @@ exec uv run celery -A app.core.celery_app.celery_app worker \
13
13
  -c "${CONCURRENCY}" \
14
14
  -l "${LOG_LEVEL}" \
15
15
  -n "worker@%h"
16
+ {% endif %}
@@ -33,6 +33,7 @@ def test_create_smoke_full_flags(tmp_path: Path, monkeypatch) -> None:
33
33
  "--description", "smoke test",
34
34
  "--pm2",
35
35
  "--docker",
36
+ "--celery",
36
37
  "--no-git",
37
38
  ],
38
39
  )
@@ -46,7 +47,8 @@ def test_create_smoke_full_flags(tmp_path: Path, monkeypatch) -> None:
46
47
  "app/main.py",
47
48
  "app/api/v1/endpoints/items.py",
48
49
  "tests/conftest.py",
49
- "pm2.config.json",
50
+ "pm2.config.js",
51
+ "app/core/celery_app.py",
50
52
  "Dockerfile",
51
53
  ):
52
54
  assert (root / rel).exists(), f"缺少 {rel}"
@@ -59,6 +61,26 @@ def test_create_smoke_full_flags(tmp_path: Path, monkeypatch) -> None:
59
61
  assert "make dev" in result.output
60
62
 
61
63
 
64
+ def test_create_default_no_celery(tmp_path: Path, monkeypatch) -> None:
65
+ """--no-celery(默认):不生成 celery 文件,pm2 命令被注释."""
66
+ monkeypatch.chdir(tmp_path)
67
+ result = runner.invoke(
68
+ app,
69
+ [
70
+ "create", "demo-server",
71
+ "--description", "x",
72
+ "--pm2", "--no-docker", "--no-celery", "--no-git",
73
+ ],
74
+ )
75
+ assert result.exit_code == 0, result.output
76
+
77
+ root = tmp_path / "demo-server"
78
+ assert not (root / "app/core/celery_app.py").exists()
79
+ assert not (root / "app/tasks/sample_tasks.py").exists()
80
+ pm2_text = (root / "pm2.config.js").read_text(encoding="utf-8")
81
+ assert "// script: \"bash scripts/celery_worker.sh\"" in pm2_text
82
+
83
+
62
84
  def test_create_existing_dir_rejected(tmp_path: Path, monkeypatch) -> None:
63
85
  """目标目录已存在且未指定 --force 时拒绝."""
64
86
  monkeypatch.chdir(tmp_path)
@@ -83,18 +105,19 @@ def test_create_invalid_name_rejected(tmp_path: Path, monkeypatch) -> None:
83
105
 
84
106
 
85
107
  def test_create_interactive_flow(tmp_path: Path, monkeypatch) -> None:
86
- """零 flag 交互路径:通过输入补全描述 / pm2 / Dockerfile."""
108
+ """零 flag 交互路径:通过输入补全描述 / pm2 / Dockerfile / celery."""
87
109
  monkeypatch.chdir(tmp_path)
88
- # 描述输入(回车取默认)、pm2 回答 y、Dockerfile 回答 n
110
+ # 描述输入(回车取默认)、pm2 回答 y、Dockerfile 回答 n、celery 回答 n(默认)
89
111
  result = runner.invoke(
90
112
  app,
91
113
  ["create", "demo-server", "--no-git"],
92
- input="\ny\nn\n",
114
+ input="\ny\nn\nn\n",
93
115
  )
94
116
  assert result.exit_code == 0, result.output
95
117
  root = tmp_path / "demo-server"
96
- assert (root / "pm2.config.json").exists()
118
+ assert (root / "pm2.config.js").exists()
97
119
  assert not (root / "Dockerfile").exists()
120
+ assert not (root / "app/core/celery_app.py").exists()
98
121
 
99
122
 
100
123
  def test_create_git_init(tmp_path: Path, monkeypatch) -> None:
@@ -104,7 +127,7 @@ def test_create_git_init(tmp_path: Path, monkeypatch) -> None:
104
127
  monkeypatch.chdir(tmp_path)
105
128
  result = runner.invoke(
106
129
  app,
107
- ["create", "demo-server", "--description", "x", "--no-pm2", "--no-docker"],
130
+ ["create", "demo-server", "--description", "x", "--no-pm2", "--no-docker", "--no-celery"],
108
131
  )
109
132
  assert result.exit_code == 0, result.output
110
133
  assert (tmp_path / "demo-server" / ".git").is_dir()
@@ -15,6 +15,7 @@ BASE_CTX: dict[str, object] = {
15
15
  "author_name": "tester",
16
16
  "use_pm2": False,
17
17
  "use_docker": False,
18
+ "use_celery": True,
18
19
  "year": 2026,
19
20
  "fastapp_cli_version": "0.1.0",
20
21
  }
@@ -70,7 +71,7 @@ def test_render_project_full(tmp_path: Path) -> None:
70
71
  assert (out / rel).exists(), f"缺少 {rel},实际输出 {len(written)} 个文件"
71
72
 
72
73
  # 条件文件默认不生成
73
- assert not (out / "pm2.config.json").exists()
74
+ assert not (out / "pm2.config.js").exists()
74
75
  assert not (out / "Dockerfile").exists()
75
76
 
76
77
  # 变量注入
@@ -90,9 +91,52 @@ def test_render_project_conditional_files(tmp_path: Path) -> None:
90
91
  out = tmp_path / "proj"
91
92
  render_project(out, ctx)
92
93
 
93
- pm2 = out / "pm2.config.json"
94
+ pm2 = out / "pm2.config.js"
94
95
  dockerfile = out / "Dockerfile"
95
96
  assert pm2.exists()
96
97
  assert dockerfile.exists()
97
- assert "demo-server" in pm2.read_text(encoding="utf-8")
98
+ pm2_text = pm2.read_text(encoding="utf-8")
99
+ assert "demo-server" in pm2_text
100
+ # celery 启用:worker/beat/flower 为生效条目
101
+ assert "celery_worker.sh" in pm2_text
102
+ assert "// name:" not in pm2_text
98
103
  assert "python3.12" in dockerfile.read_text(encoding="utf-8")
104
+
105
+
106
+ def test_render_project_no_celery(tmp_path: Path) -> None:
107
+ """use_celery=False:celery 文件不生成,pm2 中对应命令为注释,依赖剔除."""
108
+ from fastapp_cli.render import render_project
109
+
110
+ ctx = dict(BASE_CTX, use_celery=False, use_pm2=True)
111
+ out = tmp_path / "proj"
112
+ render_project(out, ctx)
113
+
114
+ # celery 相关文件不生成
115
+ for rel in (
116
+ "app/core/celery_app.py",
117
+ "app/tasks/sample_tasks.py",
118
+ "scripts/celery_worker.sh",
119
+ "scripts/celery_beat.sh",
120
+ "scripts/celery_flower.sh",
121
+ ):
122
+ assert not (out / rel).exists(), f"{rel} 不应生成"
123
+
124
+ # pm2.config.js 中 celery 启动命令被注释
125
+ pm2_text = (out / "pm2.config.js").read_text(encoding="utf-8")
126
+ assert "// name:" in pm2_text
127
+ assert "// script: \"bash scripts/celery_worker.sh\"" in pm2_text
128
+ # server 条目仍生效
129
+ assert 'name: "demo-server-server"' in pm2_text
130
+
131
+ # pyproject 无 celery/flower 依赖
132
+ pyproject = (out / "pyproject.toml").read_text(encoding="utf-8")
133
+ assert "celery" not in pyproject
134
+ assert "flower" not in pyproject
135
+
136
+ # main.py 无 celery 探测
137
+ main_text = (out / "app" / "main.py").read_text(encoding="utf-8")
138
+ assert "celery" not in main_text
139
+ assert "_probe_redis" not in main_text
140
+
141
+ # DEBUG 打印已注入
142
+ assert "settings.DEBUG" in main_text
@@ -1,47 +0,0 @@
1
- {% if use_pm2 %}
2
- {
3
- "apps": [
4
- {
5
- "name": "{{ project_name }}-server",
6
- "script": "bash scripts/start.sh",
7
- "output": "./logs/server.log",
8
- "error": "./logs/server-error.log",
9
- "env": {
10
- "HOST": "0.0.0.0",
11
- "PORT": "8000",
12
- "APP_ENV": "production",
13
- "WORKERS": "1"
14
- }
15
- },
16
- {
17
- "name": "{{ project_name }}-worker",
18
- "script": "bash scripts/celery_worker.sh",
19
- "output": "./logs/celery.log",
20
- "error": "./logs/celery-error.log",
21
- "env": {
22
- "CONCURRENCY": "4",
23
- "QUEUE": "default",
24
- "LOG_LEVEL": "INFO"
25
- }
26
- },
27
- {
28
- "name": "{{ project_name }}-beat",
29
- "script": "bash scripts/celery_beat.sh",
30
- "log_file": "./logs/celery_beat.log",
31
- "env": {
32
- "LOG_LEVEL": "INFO"
33
- }
34
- },
35
- {
36
- "name": "{{ project_name }}-flower",
37
- "script": "bash scripts/celery_flower.sh",
38
- "log_file": "./logs/flower.log",
39
- "env": {
40
- "FLOWER_HOST": "0.0.0.0",
41
- "FLOWER_PORT": "5555",
42
- "LOG_LEVEL": "INFO"
43
- }
44
- }
45
- ]
46
- }
47
- {% endif %}
File without changes
File without changes
File without changes