fastapp-cli 0.2.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.2.0 → fastapp_cli-0.3.0}/PKG-INFO +8 -6
  2. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/README.md +7 -5
  3. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/pyproject.toml +1 -1
  4. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/__init__.py +1 -1
  5. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/create.py +8 -1
  6. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/prompts.py +8 -1
  7. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.development.example.j2 +2 -1
  8. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.example.j2 +2 -1
  9. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.j2 +2 -1
  10. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/README.md.j2 +7 -5
  11. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/celery_app.py.j2 +2 -1
  12. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/config.py.j2 +2 -1
  13. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/main.py.j2 +11 -7
  14. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/sample_tasks.py.j2 +2 -1
  15. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/docs/agent_guide.md.j2 +16 -14
  16. fastapp_cli-0.3.0/src/fastapp_cli/templates/project/pm2.config.js.j2 +50 -0
  17. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/pyproject.toml.j2 +6 -6
  18. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2 +2 -1
  19. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_flower.sh.j2 +2 -1
  20. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_worker.sh.j2 +2 -1
  21. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_create.py +29 -6
  22. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_render.py +47 -3
  23. fastapp_cli-0.2.0/src/fastapp_cli/templates/project/pm2.config.json.j2 +0 -47
  24. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/.gitignore +0 -0
  25. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/PLAN.md +0 -0
  26. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/main.py +0 -0
  27. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/naming.py +0 -0
  28. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/render.py +0 -0
  29. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/__init__.py +0 -0
  30. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.gitignore +0 -0
  31. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2 +0 -0
  32. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.python-version.j2 +0 -0
  33. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Dockerfile.j2 +0 -0
  34. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Makefile.j2 +0 -0
  35. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/env.py.j2 +0 -0
  36. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/script.py.mako +0 -0
  37. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep +0 -0
  38. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic.ini.j2 +0 -0
  39. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/__init__.py.j2 +0 -0
  40. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2 +0 -0
  41. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2 +0 -0
  42. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/__init__.py.j2 +0 -0
  43. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/__init__.py.j2 +0 -0
  44. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/health.py.j2 +0 -0
  45. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/items.py.j2 +0 -0
  46. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2 +0 -0
  47. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2 +0 -0
  48. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2 +0 -0
  49. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/database.py.j2 +0 -0
  50. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2 +0 -0
  51. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2 +0 -0
  52. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2 +0 -0
  53. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2 +0 -0
  54. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2 +0 -0
  55. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2 +0 -0
  56. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2 +0 -0
  57. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/base.py.j2 +0 -0
  58. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/item.py.j2 +0 -0
  59. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2 +0 -0
  60. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2 +0 -0
  61. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2 +0 -0
  62. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/__init__.py.j2 +0 -0
  63. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/base.py.j2 +0 -0
  64. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/item_service.py.j2 +0 -0
  65. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2 +0 -0
  66. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2 +0 -0
  67. {fastapp_cli-0.2.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.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2 +0 -0
  69. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_health.py.j2 +0 -0
  70. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2 +0 -0
  71. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2 +0 -0
  72. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/services/test_item_service.py.j2 +0 -0
  73. {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_naming.py +0 -0
  74. {fastapp_cli-0.2.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.2.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.2.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.2.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
@@ -67,4 +68,5 @@ app/
67
68
  1. **新增业务域**:仿照 `item` 五件套(model / schema / crud / service / endpoint),在 `models/__init__.py` 注册新模型,在 `api/v1/router.py` 挂载新路由;
68
69
  2. **生成迁移**:`make revision m="add xxx table"` 后检查 `alembic/versions/`,再 `make migrate`;
69
70
  3. **接入真实认证**:替换 `app/api/deps.py::get_current_user` 的占位实现;
70
- 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 %}
@@ -13,8 +13,8 @@
13
13
  | ORM | SQLModel (SQLAlchemy) | PEP 695 泛型 CRUD 基类 |
14
14
  | 数据库 | MySQL(默认)/ SQLite(测试) | SQLAlchemy 引擎,连接池参数已调优 |
15
15
  | 迁移 | Alembic | 增量迁移,`MANAGED_TABLES` 约束只管理业务表 |
16
- | 异步任务 | Celery + Redis | redis 不可达时自动降级 memory broker |
17
- | 分页 | fastapi-pagination | 统一分页响应结构 |
16
+ {% if use_celery %}| 异步任务 | Celery + Redis | redis 不可达时自动降级 memory broker |
17
+ {% endif %}| 分页 | fastapi-pagination | 统一分页响应结构 |
18
18
  | 过滤 | fastapi-filter | 声明式查询过滤器 |
19
19
  | 日志 | loguru | 拦截标准 logging,全链路 trace_id |
20
20
  | 配置 | pydantic-settings | 多环境 `.env` 加载 |
@@ -50,7 +50,7 @@ TraceIDMiddleware (最外层,生成/透传 X-Trace-Id)
50
50
  ```text
51
51
  .
52
52
  ├── app/
53
- │ ├── main.py # create_app 工厂 + lifespan(DB/redis 探测,缺席降级不阻塞启动)
53
+ │ ├── main.py # create_app 工厂 + lifespan(DB{% if use_celery %}/redis{% endif %} 探测,缺席降级不阻塞启动)
54
54
  │ ├── core/ # 横切基础设施(禁止在此写业务逻辑)
55
55
  │ │ ├── config.py # settings 配置中心(多环境 .env 加载)
56
56
  │ │ ├── database.py # engine / get_session / init_db
@@ -58,8 +58,8 @@ TraceIDMiddleware (最外层,生成/透传 X-Trace-Id)
58
58
  │ │ ├── exceptions.py # AppExceptionError 体系 + 全局异常处理器
59
59
  │ │ ├── middleware.py # TraceIDMiddleware(trace_id 生成与响应头注入)
60
60
  │ │ ├── context_var.py # trace_id_var / get_trace_id
61
- │ │ └── celery_app.py # build_celery_app / degrade_to_memory / celery_app 单例
62
- │ ├── api/
61
+ {% if use_celery %}│ │ └── celery_app.py # build_celery_app / degrade_to_memory / celery_app 单例
62
+ {% endif %}│ ├── api/
63
63
  │ │ ├── deps.py # DBSession / CurrentUser 依赖(认证占位)
64
64
  │ │ └── v1/
65
65
  │ │ ├── router.py # v1 总路由,新业务路由在此挂载
@@ -76,11 +76,13 @@ TraceIDMiddleware (最外层,生成/透传 X-Trace-Id)
76
76
  │ ├── services/ # 业务逻辑层
77
77
  │ │ ├── base.py # ServiceBase 泛型基类(核心复用点)
78
78
  │ │ └── item_service.py # ItemService(ServiceBase[CRUDItem])
79
- │ ├── tasks/ # Celery 任务(sample_tasks.py 为示例)
79
+ │ ├── tasks/{% if use_celery %} # Celery 任务(sample_tasks.py 为示例)
80
80
  │ └── utils/ # 通用工具扩展位(空)
81
- ├── alembic/ # 迁移环境(env.py 已配置 MANAGED_TABLES 白名单过滤)
81
+ {% else %} # 预留任务目录
82
+ │ └── utils/ # 通用工具扩展位(空)
83
+ {% endif %}├── alembic/ # 迁移环境(env.py 已配置 MANAGED_TABLES 白名单过滤)
82
84
  ├── docs/ # 本文档 + SQLModel定义示例.md(建模规范)
83
- ├── scripts/ # start.sh / celery_worker.sh / celery_beat.sh / celery_flower.sh
85
+ ├── scripts/ # start.sh{% if use_celery %} / celery_worker.sh / celery_beat.sh / celery_flower.sh{% endif %}
84
86
  ├── tests/ # pytest:conftest 用 sqlite 内存库,无需真实 MySQL
85
87
  ├── alembic.ini / Makefile / pyproject.toml
86
88
  └── .env / .env.example # 配置文件(.env 入库,敏感信息按环境覆盖)
@@ -219,7 +221,7 @@ class Product(IDMixin, ProductBase, table=True):
219
221
 
220
222
  - 单例 `settings`:所有环境变量集中于此,**禁止在业务代码中直接读 `os.environ`**。
221
223
  - 加载顺序(后者覆盖前者):`.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`。
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`。
223
225
  - 属性:`settings.is_production` / `settings.is_test`。
224
226
  - 新增配置项:在 `Settings` 类中加字段 + 默认值,并在 `.env.example` / `.env` 中补充说明。
225
227
 
@@ -231,7 +233,7 @@ class Product(IDMixin, ProductBase, table=True):
231
233
  | `get_session()` | FastAPI 依赖,yield 语法保证 Session 关闭;**通过 `DBSession` 注解使用**,不要自建 Session |
232
234
  | `init_db()` | 本地/测试快速建表(生产用 alembic 迁移) |
233
235
 
234
- ### 3.9 Celery(`app/core/celery_app.py`)
236
+ {% if use_celery %}### 3.9 Celery(`app/core/celery_app.py`)
235
237
 
236
238
  | 名称 | 说明 |
237
239
  |---|---|
@@ -249,7 +251,7 @@ def send_notice(user_id: int) -> str:
249
251
  ...
250
252
  ```
251
253
 
252
- ### 3.10 日志与 TraceID(`app/core/logging.py`、`app/core/context_var.py`)
254
+ {% endif %}{% if use_celery %}### 3.10{% else %}### 3.9{% endif %} 日志与 TraceID(`app/core/logging.py`、`app/core/context_var.py`)
253
255
 
254
256
  | 名称 | 说明 |
255
257
  |---|---|
@@ -278,8 +280,8 @@ def send_notice(user_id: int) -> str:
278
280
  | 新增配置项 | `Settings` 类加字段 + `.env.example` 补说明 |
279
281
  | 接入真实认证 | 只替换 `app/api/deps.py::get_current_user` 实现,端点代码不动 |
280
282
  | 新增中间件 | `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
+ {% if use_celery %}| 新增 Celery 定时任务 | `app/tasks/` 写任务,`celery_app.conf.beat_schedule` 中注册(该文件内有注释示例) |
284
+ {% endif %}| 自定义业务异常 | 继承 `AppExceptionError`,类属性覆盖 `status_code` / `code` / `message`,全局处理器自动生效 |
283
285
  | 复杂查询 | 在业务 CRUD 子类中扩展方法,保持 Service 只编排不写 SQL 细节 |
284
286
 
285
287
  ## 5. 编码规范与注意事项
@@ -298,7 +300,7 @@ def send_notice(user_id: int) -> str:
298
300
 
299
301
  - **时间字段**:一律通过 `TimestampMixin`;JSON 输出格式 `%Y-%m-%d %H:%M:%S` 由 `FormattedDatetime` 统一处理,勿自行 strftime。
300
302
  - **Session 生命周期**:请求内通过 `DBSession` 注入;后台任务/脚本中用 `with Session(engine) as session:`,不要复用请求 Session。
301
- - **降级行为**:MySQL / Redis 缺席不阻塞启动(health 返回 down、Celery 降级 memory)。本地开发无需强制装依赖,但数据接口在无库时统一返回 500。
303
+ - **降级行为**:MySQL{% if use_celery %} / Redis 缺席不阻塞启动(health 返回 down、Celery 降级 memory)。本地开发无需强制装依赖,但数据接口在无库时统一返回 500{% else %} 缺席不阻塞启动(health 返回 down)。本地开发无需强制装数据库,数据接口在无库时统一返回 500{% endif %}
302
304
  - **测试环境**:测试用 sqlite 内存库(`tests/conftest.py`),依赖 sqlite 不支持的特性时需显式跳过。
303
305
  - **`.env` 提交策略**:`.env` 与 `.env.example` 入库作默认配置;敏感信息(生产密码)放 `.env.production`(不入库)或真实环境变量。
304
306
  - **docs/**:`SQLModel定义示例.md` 是建模规范,新表定义先对照该文档。
@@ -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