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.
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/PKG-INFO +8 -6
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/README.md +7 -5
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/pyproject.toml +1 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/__init__.py +1 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/create.py +8 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/prompts.py +8 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.development.example.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.example.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.env.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/README.md.j2 +7 -5
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/celery_app.py.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/config.py.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/main.py.j2 +11 -7
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/sample_tasks.py.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/docs/agent_guide.md.j2 +16 -14
- fastapp_cli-0.3.0/src/fastapp_cli/templates/project/pm2.config.js.j2 +50 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/pyproject.toml.j2 +6 -6
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_flower.sh.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_worker.sh.j2 +2 -1
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_create.py +29 -6
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_render.py +47 -3
- fastapp_cli-0.2.0/src/fastapp_cli/templates/project/pm2.config.json.j2 +0 -47
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/.gitignore +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/PLAN.md +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/main.py +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/naming.py +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/render.py +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/__init__.py +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.gitignore +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.python-version.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Dockerfile.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/Makefile.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/env.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/script.py.mako +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic.ini.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/health.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/endpoints/items.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/database.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/base.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/item.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/base.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/item_service.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2 +0 -0
- {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
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_health.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/services/test_item_service.py.j2 +0 -0
- {fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/tests/test_naming.py +0 -0
- {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.
|
|
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
|
|
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.
|
|
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、
|
|
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
|
|
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.
|
|
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、
|
|
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
|
|
|
@@ -68,13 +68,18 @@ def create(
|
|
|
68
68
|
pm2: bool = typer.Option(
|
|
69
69
|
None,
|
|
70
70
|
"--pm2/--no-pm2",
|
|
71
|
-
help="是否生成 pm2.config.
|
|
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)
|
|
@@ -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 %}
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/config.py.j2
RENAMED
|
@@ -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 %}
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/docs/agent_guide.md.j2
RENAMED
|
@@ -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
|
-
|
|
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
|
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/celery_beat.sh.j2
RENAMED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
94
|
+
pm2 = out / "pm2.config.js"
|
|
94
95
|
dockerfile = out / "Dockerfile"
|
|
95
96
|
assert pm2.exists()
|
|
96
97
|
assert dockerfile.exists()
|
|
97
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/.pre-commit-config.yaml.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.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.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/script.py.mako
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/alembic/versions/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/deps.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.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.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/api/v1/router.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/context_var.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/database.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/exceptions.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/logging.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/core/middleware.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/base.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/crud/item.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/base.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/models/item.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/common.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/schemas/item.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/services/base.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/tasks/__init__.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/app/utils/__init__.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/scripts/start.sh.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/api/test_items.py.j2
RENAMED
|
File without changes
|
{fastapp_cli-0.2.0 → fastapp_cli-0.3.0}/src/fastapp_cli/templates/project/tests/conftest.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|