aury-boot 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl
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.
- aury/boot/__init__.py +66 -0
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +120 -0
- aury/boot/application/app/__init__.py +39 -0
- aury/boot/application/app/base.py +511 -0
- aury/boot/application/app/components.py +434 -0
- aury/boot/application/app/middlewares.py +101 -0
- aury/boot/application/config/__init__.py +44 -0
- aury/boot/application/config/settings.py +663 -0
- aury/boot/application/constants/__init__.py +19 -0
- aury/boot/application/constants/components.py +50 -0
- aury/boot/application/constants/scheduler.py +28 -0
- aury/boot/application/constants/service.py +29 -0
- aury/boot/application/errors/__init__.py +55 -0
- aury/boot/application/errors/chain.py +80 -0
- aury/boot/application/errors/codes.py +67 -0
- aury/boot/application/errors/exceptions.py +238 -0
- aury/boot/application/errors/handlers.py +320 -0
- aury/boot/application/errors/response.py +120 -0
- aury/boot/application/interfaces/__init__.py +76 -0
- aury/boot/application/interfaces/egress.py +224 -0
- aury/boot/application/interfaces/ingress.py +98 -0
- aury/boot/application/middleware/__init__.py +22 -0
- aury/boot/application/middleware/logging.py +451 -0
- aury/boot/application/migrations/__init__.py +13 -0
- aury/boot/application/migrations/manager.py +685 -0
- aury/boot/application/migrations/setup.py +237 -0
- aury/boot/application/rpc/__init__.py +63 -0
- aury/boot/application/rpc/base.py +108 -0
- aury/boot/application/rpc/client.py +294 -0
- aury/boot/application/rpc/discovery.py +218 -0
- aury/boot/application/scheduler/__init__.py +13 -0
- aury/boot/application/scheduler/runner.py +123 -0
- aury/boot/application/server/__init__.py +296 -0
- aury/boot/commands/__init__.py +30 -0
- aury/boot/commands/add.py +76 -0
- aury/boot/commands/app.py +105 -0
- aury/boot/commands/config.py +177 -0
- aury/boot/commands/docker.py +367 -0
- aury/boot/commands/docs.py +284 -0
- aury/boot/commands/generate.py +1277 -0
- aury/boot/commands/init.py +890 -0
- aury/boot/commands/migrate/__init__.py +37 -0
- aury/boot/commands/migrate/app.py +54 -0
- aury/boot/commands/migrate/commands.py +303 -0
- aury/boot/commands/scheduler.py +124 -0
- aury/boot/commands/server/__init__.py +21 -0
- aury/boot/commands/server/app.py +541 -0
- aury/boot/commands/templates/generate/api.py.tpl +105 -0
- aury/boot/commands/templates/generate/model.py.tpl +17 -0
- aury/boot/commands/templates/generate/repository.py.tpl +19 -0
- aury/boot/commands/templates/generate/schema.py.tpl +29 -0
- aury/boot/commands/templates/generate/service.py.tpl +48 -0
- aury/boot/commands/templates/project/CLI.md.tpl +92 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +1397 -0
- aury/boot/commands/templates/project/README.md.tpl +111 -0
- aury/boot/commands/templates/project/admin_console_init.py.tpl +50 -0
- aury/boot/commands/templates/project/config.py.tpl +30 -0
- aury/boot/commands/templates/project/conftest.py.tpl +26 -0
- aury/boot/commands/templates/project/env.example.tpl +213 -0
- aury/boot/commands/templates/project/gitignore.tpl +128 -0
- aury/boot/commands/templates/project/main.py.tpl +41 -0
- aury/boot/commands/templates/project/modules/api.py.tpl +19 -0
- aury/boot/commands/templates/project/modules/exceptions.py.tpl +84 -0
- aury/boot/commands/templates/project/modules/schedules.py.tpl +18 -0
- aury/boot/commands/templates/project/modules/tasks.py.tpl +20 -0
- aury/boot/commands/worker.py +143 -0
- aury/boot/common/__init__.py +35 -0
- aury/boot/common/exceptions/__init__.py +114 -0
- aury/boot/common/i18n/__init__.py +16 -0
- aury/boot/common/i18n/translator.py +272 -0
- aury/boot/common/logging/__init__.py +716 -0
- aury/boot/contrib/__init__.py +10 -0
- aury/boot/contrib/admin_console/__init__.py +18 -0
- aury/boot/contrib/admin_console/auth.py +137 -0
- aury/boot/contrib/admin_console/discovery.py +69 -0
- aury/boot/contrib/admin_console/install.py +172 -0
- aury/boot/contrib/admin_console/utils.py +44 -0
- aury/boot/domain/__init__.py +79 -0
- aury/boot/domain/exceptions/__init__.py +132 -0
- aury/boot/domain/models/__init__.py +51 -0
- aury/boot/domain/models/base.py +69 -0
- aury/boot/domain/models/mixins.py +135 -0
- aury/boot/domain/models/models.py +96 -0
- aury/boot/domain/pagination/__init__.py +279 -0
- aury/boot/domain/repository/__init__.py +23 -0
- aury/boot/domain/repository/impl.py +423 -0
- aury/boot/domain/repository/interceptors.py +47 -0
- aury/boot/domain/repository/interface.py +106 -0
- aury/boot/domain/repository/query_builder.py +348 -0
- aury/boot/domain/service/__init__.py +11 -0
- aury/boot/domain/service/base.py +73 -0
- aury/boot/domain/transaction/__init__.py +404 -0
- aury/boot/infrastructure/__init__.py +104 -0
- aury/boot/infrastructure/cache/__init__.py +31 -0
- aury/boot/infrastructure/cache/backends.py +348 -0
- aury/boot/infrastructure/cache/base.py +68 -0
- aury/boot/infrastructure/cache/exceptions.py +37 -0
- aury/boot/infrastructure/cache/factory.py +94 -0
- aury/boot/infrastructure/cache/manager.py +274 -0
- aury/boot/infrastructure/database/__init__.py +39 -0
- aury/boot/infrastructure/database/config.py +71 -0
- aury/boot/infrastructure/database/exceptions.py +44 -0
- aury/boot/infrastructure/database/manager.py +317 -0
- aury/boot/infrastructure/database/query_tools/__init__.py +164 -0
- aury/boot/infrastructure/database/strategies/__init__.py +198 -0
- aury/boot/infrastructure/di/__init__.py +15 -0
- aury/boot/infrastructure/di/container.py +393 -0
- aury/boot/infrastructure/events/__init__.py +33 -0
- aury/boot/infrastructure/events/bus.py +362 -0
- aury/boot/infrastructure/events/config.py +52 -0
- aury/boot/infrastructure/events/consumer.py +134 -0
- aury/boot/infrastructure/events/middleware.py +51 -0
- aury/boot/infrastructure/events/models.py +63 -0
- aury/boot/infrastructure/monitoring/__init__.py +529 -0
- aury/boot/infrastructure/scheduler/__init__.py +19 -0
- aury/boot/infrastructure/scheduler/exceptions.py +37 -0
- aury/boot/infrastructure/scheduler/manager.py +478 -0
- aury/boot/infrastructure/storage/__init__.py +38 -0
- aury/boot/infrastructure/storage/base.py +164 -0
- aury/boot/infrastructure/storage/exceptions.py +37 -0
- aury/boot/infrastructure/storage/factory.py +88 -0
- aury/boot/infrastructure/tasks/__init__.py +24 -0
- aury/boot/infrastructure/tasks/config.py +45 -0
- aury/boot/infrastructure/tasks/constants.py +37 -0
- aury/boot/infrastructure/tasks/exceptions.py +37 -0
- aury/boot/infrastructure/tasks/manager.py +490 -0
- aury/boot/testing/__init__.py +24 -0
- aury/boot/testing/base.py +122 -0
- aury/boot/testing/client.py +163 -0
- aury/boot/testing/factory.py +154 -0
- aury/boot/toolkit/__init__.py +21 -0
- aury/boot/toolkit/http/__init__.py +367 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/METADATA +3 -2
- aury_boot-0.0.3.dist-info/RECORD +137 -0
- aury_boot-0.0.2.dist-info/RECORD +0 -5
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
"""项目脚手架初始化命令。
|
|
2
|
+
|
|
3
|
+
类似 Vue CLI 的交互式项目初始化。
|
|
4
|
+
|
|
5
|
+
前置条件:
|
|
6
|
+
1. mkdir my-service && cd my-service
|
|
7
|
+
2. uv init . --bare --name my_service
|
|
8
|
+
3. uv venv --python 3.13
|
|
9
|
+
4. uv add "aury-boot[recommended]"
|
|
10
|
+
|
|
11
|
+
初始化脚手架:
|
|
12
|
+
aury init # 交互式初始化
|
|
13
|
+
aury init -r # 推荐配置快速初始化
|
|
14
|
+
aury init my_package # 指定顶层包名
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
from rich.panel import Panel
|
|
25
|
+
from rich.prompt import Confirm, IntPrompt, Prompt
|
|
26
|
+
from rich.tree import Tree
|
|
27
|
+
import typer
|
|
28
|
+
|
|
29
|
+
from .config import ProjectConfig, save_project_config
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
# 最低 Python 版本要求
|
|
34
|
+
MIN_PYTHON_VERSION = (3, 13)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ============================================================
|
|
38
|
+
# 枚举定义
|
|
39
|
+
# ============================================================
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ServiceMode(str, Enum):
|
|
43
|
+
"""服务运行模式。"""
|
|
44
|
+
|
|
45
|
+
API = "api"
|
|
46
|
+
API_SCHEDULER = "api+scheduler"
|
|
47
|
+
FULL = "full"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CacheType(str, Enum):
|
|
51
|
+
"""缓存类型。"""
|
|
52
|
+
|
|
53
|
+
MEMORY = "memory"
|
|
54
|
+
REDIS = "redis"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DatabaseType(str, Enum):
|
|
58
|
+
"""数据库类型。"""
|
|
59
|
+
|
|
60
|
+
POSTGRESQL = "postgresql"
|
|
61
|
+
MYSQL = "mysql"
|
|
62
|
+
SQLITE = "sqlite"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ============================================================
|
|
66
|
+
# 依赖配置
|
|
67
|
+
# ============================================================
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# 模板目录
|
|
71
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates" / "project"
|
|
72
|
+
MODULE_TEMPLATES_DIR = TEMPLATES_DIR / "modules"
|
|
73
|
+
|
|
74
|
+
# 需要创建的目录结构(包内)
|
|
75
|
+
DIRECTORIES = [
|
|
76
|
+
"api",
|
|
77
|
+
"services",
|
|
78
|
+
"models",
|
|
79
|
+
"repositories",
|
|
80
|
+
"schemas",
|
|
81
|
+
"exceptions", # 自定义异常
|
|
82
|
+
"tasks", # 异步任务(Dramatiq)
|
|
83
|
+
"schedules", # 定时任务(Scheduler)
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Ruff 配置
|
|
87
|
+
RUFF_CONFIG = '''
|
|
88
|
+
[tool.ruff]
|
|
89
|
+
target-version = "py313"
|
|
90
|
+
line-length = 120
|
|
91
|
+
indent-width = 4
|
|
92
|
+
exclude = [
|
|
93
|
+
".git",
|
|
94
|
+
".venv",
|
|
95
|
+
"__pycache__",
|
|
96
|
+
"*.pyc",
|
|
97
|
+
".mypy_cache",
|
|
98
|
+
".pytest_cache",
|
|
99
|
+
".ruff_cache",
|
|
100
|
+
"build",
|
|
101
|
+
"dist",
|
|
102
|
+
"*.egg-info",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[tool.ruff.lint]
|
|
106
|
+
select = [
|
|
107
|
+
"E", # pycodestyle errors
|
|
108
|
+
"W", # pycodestyle warnings
|
|
109
|
+
"F", # Pyflakes
|
|
110
|
+
"I", # isort
|
|
111
|
+
"N", # pep8-naming
|
|
112
|
+
"UP", # pyupgrade
|
|
113
|
+
"B", # flake8-bugbear
|
|
114
|
+
"C4", # flake8-comprehensions
|
|
115
|
+
"SIM", # flake8-simplify
|
|
116
|
+
"T20", # flake8-print
|
|
117
|
+
"RUF", # Ruff-specific rules
|
|
118
|
+
]
|
|
119
|
+
ignore = [
|
|
120
|
+
"E501", # 行长度
|
|
121
|
+
"B008", # 函数调用中的默认参数
|
|
122
|
+
"B006", # 可变默认参数
|
|
123
|
+
"T201", # print 语句
|
|
124
|
+
"RUF001", # 中文标点
|
|
125
|
+
"RUF002", # 中文标点
|
|
126
|
+
"RUF003", # 中文标点
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[tool.ruff.lint.isort]
|
|
130
|
+
known-first-party = ["app", "api", "models", "services", "repositories", "schemas"]
|
|
131
|
+
force-sort-within-sections = true
|
|
132
|
+
|
|
133
|
+
[tool.pytest.ini_options]
|
|
134
|
+
asyncio_mode = "auto"
|
|
135
|
+
testpaths = ["tests"]
|
|
136
|
+
'''
|
|
137
|
+
|
|
138
|
+
# 开发依赖配置(单独处理,确保总是添加)
|
|
139
|
+
DEV_DEPS_CONFIG = '''
|
|
140
|
+
[dependency-groups]
|
|
141
|
+
dev = [
|
|
142
|
+
"pytest>=9.0.1",
|
|
143
|
+
"pytest-asyncio>=1.3.0",
|
|
144
|
+
"pytest-cov>=7.0.0",
|
|
145
|
+
"ruff>=0.14.0",
|
|
146
|
+
"mypy>=1.19.0",
|
|
147
|
+
"watchfiles>=0.24.0",
|
|
148
|
+
]
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# 模板文件映射(模板名 -> .tpl 文件名)
|
|
153
|
+
TEMPLATE_FILE_MAP = {
|
|
154
|
+
"main.py": "main.py.tpl",
|
|
155
|
+
"config.py": "config.py.tpl",
|
|
156
|
+
".env.example": "env.example.tpl",
|
|
157
|
+
".gitignore": "gitignore.tpl",
|
|
158
|
+
"README.md": "README.md.tpl",
|
|
159
|
+
"DEVELOPMENT.md": "DEVELOPMENT.md.tpl",
|
|
160
|
+
"CLI.md": "CLI.md.tpl",
|
|
161
|
+
"conftest.py": "conftest.py.tpl",
|
|
162
|
+
"admin_console/__init__.py": "admin_console_init.py.tpl",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# 模块 __init__.py 模板映射
|
|
166
|
+
MODULE_TEMPLATE_MAP = {
|
|
167
|
+
"api": "api.py.tpl",
|
|
168
|
+
"tasks": "tasks.py.tpl",
|
|
169
|
+
"schedules": "schedules.py.tpl",
|
|
170
|
+
"exceptions": "exceptions.py.tpl",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _read_template(name: str) -> str:
|
|
175
|
+
"""读取模板文件。"""
|
|
176
|
+
# 先尝试从映射中查找 .tpl 文件
|
|
177
|
+
tpl_name = TEMPLATE_FILE_MAP.get(name)
|
|
178
|
+
if tpl_name:
|
|
179
|
+
template_path = TEMPLATES_DIR / tpl_name
|
|
180
|
+
if template_path.exists():
|
|
181
|
+
return template_path.read_text(encoding="utf-8")
|
|
182
|
+
|
|
183
|
+
# 尝试直接读取
|
|
184
|
+
template_path = TEMPLATES_DIR / name
|
|
185
|
+
if template_path.exists():
|
|
186
|
+
return template_path.read_text(encoding="utf-8")
|
|
187
|
+
|
|
188
|
+
# 如果模板文件不存在,抛出错误
|
|
189
|
+
raise FileNotFoundError(f"模板文件不存在: {name} (查找路径: {TEMPLATES_DIR})")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _read_module_template(module_name: str) -> str:
|
|
193
|
+
"""读取模块 __init__.py 模板。"""
|
|
194
|
+
tpl_name = MODULE_TEMPLATE_MAP.get(module_name)
|
|
195
|
+
if tpl_name:
|
|
196
|
+
template_path = MODULE_TEMPLATES_DIR / tpl_name
|
|
197
|
+
if template_path.exists():
|
|
198
|
+
return template_path.read_text(encoding="utf-8")
|
|
199
|
+
# 如果模板文件不存在,抛出错误
|
|
200
|
+
raise FileNotFoundError(f"模块模板文件不存在: {module_name} (查找路径: {MODULE_TEMPLATES_DIR})")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ============================================================
|
|
204
|
+
# Admin Console 模块初始化(可复用)
|
|
205
|
+
# ============================================================
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def init_admin_console_module(
|
|
209
|
+
base_path: Path,
|
|
210
|
+
code_root: Path,
|
|
211
|
+
import_prefix: str,
|
|
212
|
+
*,
|
|
213
|
+
force: bool = False,
|
|
214
|
+
enable_env: bool = True,
|
|
215
|
+
) -> dict[str, bool]:
|
|
216
|
+
"""初始化 Admin Console 模块到现有项目。
|
|
217
|
+
|
|
218
|
+
用于 `aury init`(新项目)和 `aury add admin-console`(已有项目)复用。
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
base_path: 项目根目录
|
|
222
|
+
code_root: 代码根目录(包根或平铺)
|
|
223
|
+
import_prefix: 导入前缀(如 "app.")
|
|
224
|
+
force: 强制覆盖已有 admin_console/__init__.py
|
|
225
|
+
enable_env: 是否尝试在 .env.example 中开启 ADMIN_* 配置
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
dict 包含操作结果:
|
|
229
|
+
- file_created: 文件是否创建成功
|
|
230
|
+
- file_existed: 文件是否已存在(且未覆盖)
|
|
231
|
+
- env_updated: 是否更新了 .env.example
|
|
232
|
+
"""
|
|
233
|
+
result = {"file_created": False, "file_existed": False, "env_updated": False}
|
|
234
|
+
|
|
235
|
+
# 1) 创建 admin_console/ 包目录和 __init__.py
|
|
236
|
+
admin_pkg = code_root / "admin_console"
|
|
237
|
+
dest = admin_pkg / "__init__.py"
|
|
238
|
+
content = _read_template("admin_console/__init__.py")
|
|
239
|
+
content = content.replace("{import_prefix}", import_prefix)
|
|
240
|
+
|
|
241
|
+
if dest.exists() and not force:
|
|
242
|
+
result["file_existed"] = True
|
|
243
|
+
else:
|
|
244
|
+
admin_pkg.mkdir(parents=True, exist_ok=True)
|
|
245
|
+
dest.write_text(content, encoding="utf-8")
|
|
246
|
+
result["file_created"] = True
|
|
247
|
+
|
|
248
|
+
# 2) 尝试在 .env.example 中开启 ADMIN_* 配置
|
|
249
|
+
if enable_env:
|
|
250
|
+
env_example = base_path / ".env.example"
|
|
251
|
+
if env_example.exists():
|
|
252
|
+
try:
|
|
253
|
+
s = env_example.read_text(encoding="utf-8")
|
|
254
|
+
s2 = (
|
|
255
|
+
s.replace("# ADMIN_ENABLED=false", "ADMIN_ENABLED=true")
|
|
256
|
+
.replace("# ADMIN_PATH=/api/admin-console", "ADMIN_PATH=/api/admin-console")
|
|
257
|
+
.replace("# ADMIN_AUTH_MODE=basic", "ADMIN_AUTH_MODE=basic")
|
|
258
|
+
.replace(
|
|
259
|
+
"# ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
260
|
+
"ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
261
|
+
)
|
|
262
|
+
.replace("# ADMIN_AUTH_BASIC_USERNAME=admin", "ADMIN_AUTH_BASIC_USERNAME=admin")
|
|
263
|
+
.replace("# ADMIN_AUTH_BASIC_PASSWORD=change_me", "ADMIN_AUTH_BASIC_PASSWORD=change_me")
|
|
264
|
+
)
|
|
265
|
+
if s2 != s:
|
|
266
|
+
env_example.write_text(s2, encoding="utf-8")
|
|
267
|
+
result["env_updated"] = True
|
|
268
|
+
except Exception:
|
|
269
|
+
pass # 静默失败
|
|
270
|
+
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _create_directory_structure(base_path: Path, package_name: str | None = None) -> list[str]:
|
|
275
|
+
"""创建目录结构。
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
base_path: 项目根目录
|
|
279
|
+
package_name: 顶层包名,None 表示平铺结构
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
创建的目录列表
|
|
283
|
+
"""
|
|
284
|
+
created = []
|
|
285
|
+
|
|
286
|
+
# 确定代码根目录
|
|
287
|
+
if package_name:
|
|
288
|
+
code_root = base_path / package_name
|
|
289
|
+
# 创建顶层包目录
|
|
290
|
+
if not code_root.exists():
|
|
291
|
+
code_root.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
created.append(package_name)
|
|
293
|
+
# 创建顶层 __init__.py
|
|
294
|
+
(code_root / "__init__.py").write_text(
|
|
295
|
+
f'"""顶层包 {package_name}。"""\n', encoding="utf-8"
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
code_root = base_path
|
|
299
|
+
|
|
300
|
+
for dir_path in DIRECTORIES:
|
|
301
|
+
full_path = code_root / dir_path
|
|
302
|
+
if not full_path.exists():
|
|
303
|
+
full_path.mkdir(parents=True, exist_ok=True)
|
|
304
|
+
rel_path = f"{package_name}/{dir_path}" if package_name else dir_path
|
|
305
|
+
created.append(rel_path)
|
|
306
|
+
# 创建 __init__.py
|
|
307
|
+
init_file = full_path / "__init__.py"
|
|
308
|
+
if not init_file.exists():
|
|
309
|
+
# 尝试从外部模板读取
|
|
310
|
+
if dir_path in MODULE_TEMPLATE_MAP:
|
|
311
|
+
init_file.write_text(_read_module_template(dir_path), encoding="utf-8")
|
|
312
|
+
else:
|
|
313
|
+
# 普通目录使用简单的 __init__.py
|
|
314
|
+
init_file.write_text('"""模块初始化。"""\n', encoding="utf-8")
|
|
315
|
+
return created
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _create_file_if_not_exists(path: Path, content: str) -> bool:
|
|
319
|
+
"""创建文件(如果不存在)。"""
|
|
320
|
+
if path.exists():
|
|
321
|
+
return False
|
|
322
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
323
|
+
path.write_text(content, encoding="utf-8")
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _append_ruff_config(base_path: Path) -> bool:
|
|
328
|
+
"""追加 ruff 配置到 pyproject.toml。"""
|
|
329
|
+
pyproject_path = base_path / "pyproject.toml"
|
|
330
|
+
if not pyproject_path.exists():
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
content = pyproject_path.read_text(encoding="utf-8")
|
|
334
|
+
|
|
335
|
+
# 检查是否已有 ruff 配置
|
|
336
|
+
if "[tool.ruff]" in content:
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
content += RUFF_CONFIG
|
|
340
|
+
pyproject_path.write_text(content, encoding="utf-8")
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _append_dev_deps_config(base_path: Path) -> bool:
|
|
345
|
+
"""追加开发依赖配置到 pyproject.toml。"""
|
|
346
|
+
pyproject_path = base_path / "pyproject.toml"
|
|
347
|
+
if not pyproject_path.exists():
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
content = pyproject_path.read_text(encoding="utf-8")
|
|
351
|
+
|
|
352
|
+
# 检查是否已有 dependency-groups 配置
|
|
353
|
+
if "[dependency-groups]" in content:
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
content += DEV_DEPS_CONFIG
|
|
357
|
+
pyproject_path.write_text(content, encoding="utf-8")
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _to_snake_case(name: str) -> str:
|
|
362
|
+
"""转换为 snake_case。"""
|
|
363
|
+
import re
|
|
364
|
+
# 处理 PascalCase 和 camelCase
|
|
365
|
+
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
366
|
+
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _init_migrations(base_path: Path, package_name: str | None = None) -> bool:
|
|
370
|
+
"""初始化数据库迁移目录和配置。
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
base_path: 项目根目录
|
|
374
|
+
package_name: 顶层包名,None 表示平铺结构
|
|
375
|
+
|
|
376
|
+
直接调用 MigrationManager 的创建逻辑,保证单一数据源。
|
|
377
|
+
"""
|
|
378
|
+
from aury.boot.application.config.settings import MigrationSettings
|
|
379
|
+
from aury.boot.application.migrations.setup import ensure_migration_setup
|
|
380
|
+
|
|
381
|
+
migration_config = MigrationSettings()
|
|
382
|
+
migrations_dir = base_path / migration_config.script_location
|
|
383
|
+
|
|
384
|
+
if migrations_dir.exists():
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
# 调用统一的创建函数
|
|
388
|
+
ensure_migration_setup(
|
|
389
|
+
base_path=base_path,
|
|
390
|
+
config_path=migration_config.config_path,
|
|
391
|
+
script_location=migration_config.script_location,
|
|
392
|
+
model_modules=migration_config.model_modules,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return True
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# ============================================================
|
|
399
|
+
# 环境检查函数
|
|
400
|
+
# ============================================================
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _check_python_version() -> bool:
|
|
404
|
+
"""检查 Python 版本是否满足最低要求。"""
|
|
405
|
+
return sys.version_info >= MIN_PYTHON_VERSION
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ============================================================
|
|
409
|
+
# 交互式配置收集
|
|
410
|
+
# ============================================================
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _collect_interactive_config() -> dict:
|
|
414
|
+
"""交互式收集项目配置(Vue CLI 风格)。"""
|
|
415
|
+
config = {}
|
|
416
|
+
|
|
417
|
+
# 默认包名使用 app(更符合习惯,也更利于 AI/模板稳定生成)
|
|
418
|
+
# 如需平铺结构请输入 "."
|
|
419
|
+
default_pkg = "app"
|
|
420
|
+
|
|
421
|
+
console.print(Panel.fit(
|
|
422
|
+
"[bold cyan]🎯 Aury Boot[/bold cyan]\n"
|
|
423
|
+
"[dim]交互式项目初始化[/dim]",
|
|
424
|
+
border_style="cyan",
|
|
425
|
+
))
|
|
426
|
+
console.print()
|
|
427
|
+
|
|
428
|
+
# 1. 项目结构(输入包名或留空)
|
|
429
|
+
console.print("[bold]📦 项目结构[/bold]")
|
|
430
|
+
console.print(" [dim]输入包名使用顶层包结构,. 则使用平铺结构[/dim]")
|
|
431
|
+
package_input = Prompt.ask(
|
|
432
|
+
"包名",
|
|
433
|
+
default=default_pkg,
|
|
434
|
+
)
|
|
435
|
+
# "." 表示平铺结构
|
|
436
|
+
if package_input.strip() == ".":
|
|
437
|
+
config["package_name"] = None
|
|
438
|
+
else:
|
|
439
|
+
config["package_name"] = package_input.strip() or None
|
|
440
|
+
|
|
441
|
+
# 2. 数据库类型
|
|
442
|
+
console.print()
|
|
443
|
+
console.print("[bold]🗄️ 数据库[/bold]")
|
|
444
|
+
console.print(" [dim]1. PostgreSQL (推荐)[/dim]")
|
|
445
|
+
console.print(" [dim]2. MySQL[/dim]")
|
|
446
|
+
console.print(" [dim]3. SQLite (开发用)[/dim]")
|
|
447
|
+
db_choice = IntPrompt.ask(
|
|
448
|
+
"选择数据库",
|
|
449
|
+
default=1,
|
|
450
|
+
choices=["1", "2", "3"],
|
|
451
|
+
)
|
|
452
|
+
config["database"] = {
|
|
453
|
+
1: "postgresql",
|
|
454
|
+
2: "mysql",
|
|
455
|
+
3: "sqlite",
|
|
456
|
+
}[db_choice]
|
|
457
|
+
|
|
458
|
+
# 3. 缓存类型
|
|
459
|
+
console.print()
|
|
460
|
+
console.print("[bold]📦 缓存[/bold]")
|
|
461
|
+
console.print(" [dim]1. Redis (推荐)[/dim]")
|
|
462
|
+
console.print(" [dim]2. 内存缓存 (开发用)[/dim]")
|
|
463
|
+
cache_choice = IntPrompt.ask(
|
|
464
|
+
"选择缓存类型",
|
|
465
|
+
default=1,
|
|
466
|
+
choices=["1", "2"],
|
|
467
|
+
)
|
|
468
|
+
config["cache"] = {
|
|
469
|
+
1: "redis",
|
|
470
|
+
2: "memory",
|
|
471
|
+
}[cache_choice]
|
|
472
|
+
|
|
473
|
+
# 4. 服务模式(决定推荐安装的依赖包)
|
|
474
|
+
console.print()
|
|
475
|
+
console.print("[bold]⚙️ 服务模式[/bold] [dim](决定推荐安装的依赖)[/dim]")
|
|
476
|
+
console.print(" [dim]1. api - 纯 API 服务[/dim]")
|
|
477
|
+
console.print(" [dim]2. api+scheduler - API + 定时任务 (APScheduler)[/dim]")
|
|
478
|
+
console.print(" [dim]3. full - API + 定时任务 + 异步任务队列 (Dramatiq)[/dim]")
|
|
479
|
+
mode_choice = IntPrompt.ask(
|
|
480
|
+
"选择服务模式",
|
|
481
|
+
default=2,
|
|
482
|
+
choices=["1", "2", "3"],
|
|
483
|
+
)
|
|
484
|
+
config["service_mode"] = {
|
|
485
|
+
1: "api",
|
|
486
|
+
2: "api+scheduler",
|
|
487
|
+
3: "full",
|
|
488
|
+
}[mode_choice]
|
|
489
|
+
|
|
490
|
+
# 5. 可选功能
|
|
491
|
+
console.print()
|
|
492
|
+
console.print("[bold]📦 可选功能[/bold]")
|
|
493
|
+
features = []
|
|
494
|
+
|
|
495
|
+
# 管理后台(Admin Console)
|
|
496
|
+
config["with_admin_console"] = Confirm.ask(
|
|
497
|
+
" 启用管理后台 Admin Console (SQLAdmin)",
|
|
498
|
+
default=True,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
if Confirm.ask(" 启用对象存储 (S3/本地)", default=True):
|
|
502
|
+
features.append("storage")
|
|
503
|
+
|
|
504
|
+
if Confirm.ask(" 启用事件总线", default=False):
|
|
505
|
+
features.append("events")
|
|
506
|
+
|
|
507
|
+
if Confirm.ask(" 启用国际化 (i18n)", default=False):
|
|
508
|
+
features.append("i18n")
|
|
509
|
+
|
|
510
|
+
config["features"] = features
|
|
511
|
+
|
|
512
|
+
# 6. 开发工具
|
|
513
|
+
console.print()
|
|
514
|
+
config["with_dev"] = Confirm.ask(
|
|
515
|
+
"[bold]🛠️ 安装开发工具[/bold] (pytest, ruff, mypy)",
|
|
516
|
+
default=True,
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# 7. Docker 配置
|
|
520
|
+
console.print()
|
|
521
|
+
config["with_docker"] = Confirm.ask(
|
|
522
|
+
"[bold]🐳 生成 Docker 配置[/bold]",
|
|
523
|
+
default=True,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
return config
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _build_dependency_list(config: dict) -> list[str]:
|
|
530
|
+
"""根据配置构建依赖列表。"""
|
|
531
|
+
extras = set()
|
|
532
|
+
|
|
533
|
+
# 数据库
|
|
534
|
+
db = config.get("database", "postgresql")
|
|
535
|
+
if db == "postgresql":
|
|
536
|
+
extras.add("postgresql")
|
|
537
|
+
elif db == "mysql":
|
|
538
|
+
extras.add("mysql")
|
|
539
|
+
|
|
540
|
+
# 缓存
|
|
541
|
+
if config.get("cache") == "redis":
|
|
542
|
+
extras.add("redis")
|
|
543
|
+
|
|
544
|
+
# 服务模式
|
|
545
|
+
mode = config.get("service_mode", "api")
|
|
546
|
+
if mode in ("api+scheduler", "full"):
|
|
547
|
+
extras.add("scheduler")
|
|
548
|
+
if mode == "full":
|
|
549
|
+
extras.add("tasks")
|
|
550
|
+
|
|
551
|
+
# 可选功能
|
|
552
|
+
for feature in config.get("features", []):
|
|
553
|
+
extras.add(feature)
|
|
554
|
+
|
|
555
|
+
# 管理后台(可选扩展)
|
|
556
|
+
if config.get("with_admin_console", True):
|
|
557
|
+
extras.add("admin")
|
|
558
|
+
|
|
559
|
+
# 开发工具
|
|
560
|
+
if config.get("with_dev"):
|
|
561
|
+
extras.add("dev")
|
|
562
|
+
|
|
563
|
+
# 构建依赖字符串
|
|
564
|
+
if extras:
|
|
565
|
+
extras_str = ",".join(sorted(extras))
|
|
566
|
+
return [f"aury-boot[{extras_str}]"]
|
|
567
|
+
return ["aury-boot"]
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _show_config_summary(config: dict) -> None:
|
|
571
|
+
"""显示配置摘要。"""
|
|
572
|
+
console.print()
|
|
573
|
+
console.print(Panel.fit(
|
|
574
|
+
"[bold]📋 配置摘要[/bold]",
|
|
575
|
+
border_style="blue",
|
|
576
|
+
))
|
|
577
|
+
|
|
578
|
+
items = [
|
|
579
|
+
("项目名称", config.get("project_name", Path.cwd().name)),
|
|
580
|
+
("包结构", config.get("package_name") or "平铺结构"),
|
|
581
|
+
("数据库", config.get("database", "postgresql")),
|
|
582
|
+
("缓存", config.get("cache", "memory")),
|
|
583
|
+
("服务模式", config.get("service_mode", "api")),
|
|
584
|
+
("管理后台", "是" if config.get("with_admin_console", True) else "否"),
|
|
585
|
+
("可选功能", ", ".join(config.get("features", [])) or "无"),
|
|
586
|
+
("开发工具", "是" if config.get("with_dev") else "否"),
|
|
587
|
+
("Docker", "是" if config.get("with_docker") else "否"),
|
|
588
|
+
]
|
|
589
|
+
|
|
590
|
+
for label, value in items:
|
|
591
|
+
console.print(f" [bold]{label}:[/bold] {value}")
|
|
592
|
+
|
|
593
|
+
# 显示依赖
|
|
594
|
+
deps = _build_dependency_list(config)
|
|
595
|
+
console.print(f" [bold]依赖:[/bold] {deps[0]}")
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
# ============================================================
|
|
599
|
+
# 主命令
|
|
600
|
+
# ============================================================
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def init(
|
|
604
|
+
package_name: str = typer.Argument(
|
|
605
|
+
None,
|
|
606
|
+
help="顶层包名(默认 app)。如需平铺结构请输入 '.'",
|
|
607
|
+
),
|
|
608
|
+
no_interactive: bool = typer.Option(
|
|
609
|
+
False,
|
|
610
|
+
"--no-interactive",
|
|
611
|
+
"-y",
|
|
612
|
+
help="跳过交互,使用默认配置",
|
|
613
|
+
),
|
|
614
|
+
force: bool = typer.Option(
|
|
615
|
+
False,
|
|
616
|
+
"--force",
|
|
617
|
+
"-f",
|
|
618
|
+
help="强制覆盖已存在的文件(包括 main.py)",
|
|
619
|
+
),
|
|
620
|
+
with_docker: bool = typer.Option(
|
|
621
|
+
False,
|
|
622
|
+
"--docker",
|
|
623
|
+
help="同时生成 Docker 配置文件",
|
|
624
|
+
),
|
|
625
|
+
) -> None:
|
|
626
|
+
"""初始化 Aury 项目脚手架。
|
|
627
|
+
|
|
628
|
+
前置条件(先执行以下命令):
|
|
629
|
+
mkdir my-service && cd my-service
|
|
630
|
+
uv init . --name my_service --no-package --python 3.13
|
|
631
|
+
uv add "aury-boot[recommended]"
|
|
632
|
+
|
|
633
|
+
示例:
|
|
634
|
+
aury init # 交互式模式(默认)
|
|
635
|
+
aury init -y # 跳过交互,使用默认配置
|
|
636
|
+
aury init my_package # 顶层包结构
|
|
637
|
+
aury init --docker # 包含 Docker 配置
|
|
638
|
+
aury init -f # 强制覆盖
|
|
639
|
+
"""
|
|
640
|
+
base_path = Path.cwd()
|
|
641
|
+
|
|
642
|
+
# 检查 pyproject.toml
|
|
643
|
+
if not (base_path / "pyproject.toml").exists():
|
|
644
|
+
console.print("[red]❌ 未找到 pyproject.toml[/red]")
|
|
645
|
+
console.print()
|
|
646
|
+
console.print("[bold]请先执行以下命令:[/bold]")
|
|
647
|
+
console.print(" [cyan]uv init . --name <project_name> --no-package --python 3.13[/cyan]")
|
|
648
|
+
console.print(' [cyan]uv add "aury-boot[recommended]"[/cyan]')
|
|
649
|
+
raise typer.Exit(1)
|
|
650
|
+
|
|
651
|
+
# 获取项目名称
|
|
652
|
+
project_name = base_path.name
|
|
653
|
+
project_name_snake = _to_snake_case(project_name)
|
|
654
|
+
|
|
655
|
+
# 交互式模式(默认)
|
|
656
|
+
if not no_interactive:
|
|
657
|
+
config = _collect_interactive_config()
|
|
658
|
+
config["project_name"] = project_name # 使用当前目录名
|
|
659
|
+
package_name_snake = _to_snake_case(config.get("package_name")) if config.get("package_name") else None
|
|
660
|
+
with_docker = config.get("with_docker", False)
|
|
661
|
+
with_admin_console = config.get("with_admin_console", True)
|
|
662
|
+
|
|
663
|
+
# 显示配置摘要并确认
|
|
664
|
+
_show_config_summary(config)
|
|
665
|
+
console.print()
|
|
666
|
+
if not Confirm.ask("确认初始化项目", default=True):
|
|
667
|
+
console.print("[yellow]已取消[/yellow]")
|
|
668
|
+
raise typer.Exit(0)
|
|
669
|
+
|
|
670
|
+
# 显示推荐的依赖安装命令
|
|
671
|
+
deps = _build_dependency_list(config)
|
|
672
|
+
console.print()
|
|
673
|
+
console.print("[bold]📦 推荐安装的依赖:[/bold]")
|
|
674
|
+
console.print(f" [cyan]uv add \"{deps[0]}\"[/cyan]")
|
|
675
|
+
console.print()
|
|
676
|
+
else:
|
|
677
|
+
# 非交互模式:默认启用 Admin Console
|
|
678
|
+
with_admin_console = True
|
|
679
|
+
if package_name == ".":
|
|
680
|
+
package_name_snake = None
|
|
681
|
+
elif package_name:
|
|
682
|
+
package_name_snake = _to_snake_case(package_name)
|
|
683
|
+
else:
|
|
684
|
+
package_name_snake = "app"
|
|
685
|
+
|
|
686
|
+
# 显示标题
|
|
687
|
+
title = project_name
|
|
688
|
+
if package_name_snake:
|
|
689
|
+
title += f" (包: {package_name_snake})"
|
|
690
|
+
console.print(Panel.fit(
|
|
691
|
+
f"[bold cyan]🚀 初始化 Aury 项目: {title}[/bold cyan]",
|
|
692
|
+
border_style="cyan",
|
|
693
|
+
))
|
|
694
|
+
|
|
695
|
+
# 1. 创建目录结构
|
|
696
|
+
console.print("\n[bold]📁 创建目录结构...[/bold]")
|
|
697
|
+
_create_directory_structure(base_path, package_name_snake)
|
|
698
|
+
|
|
699
|
+
# 确定代码目录
|
|
700
|
+
code_root = base_path / package_name_snake if package_name_snake else base_path
|
|
701
|
+
|
|
702
|
+
# 2. 生成文件
|
|
703
|
+
console.print("\n[bold]📝 生成文件...[/bold]")
|
|
704
|
+
|
|
705
|
+
# main.py 总是覆盖(因为 uv init 会创建默认的)
|
|
706
|
+
# main.py 始终放在根目录,作为入口文件
|
|
707
|
+
files_to_create = [
|
|
708
|
+
(base_path / "main.py", "main.py", True), # 总是覆盖,放在根目录
|
|
709
|
+
(code_root / "config.py", "config.py", False),
|
|
710
|
+
(base_path / ".env.example", ".env.example", False),
|
|
711
|
+
# 管理后台默认模块(可选)—— 现在是包目录
|
|
712
|
+
(code_root / "admin_console" / "__init__.py", "admin_console/__init__.py", False),
|
|
713
|
+
(base_path / "tests" / "conftest.py", "conftest.py", False), # tests 放在项目根目录
|
|
714
|
+
(base_path / "README.md", "README.md", True), # 覆盖 uv init 创建的默认 README
|
|
715
|
+
(base_path / "DEVELOPMENT.md", "DEVELOPMENT.md", False), # 开发指南
|
|
716
|
+
(base_path / "CLI.md", "CLI.md", False), # CLI 命令参考
|
|
717
|
+
(base_path / ".gitignore", ".gitignore", False), # Git 忽略文件
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
import_prefix = f"{package_name_snake}." if package_name_snake else ""
|
|
721
|
+
template_vars = {
|
|
722
|
+
"project_name": project_name,
|
|
723
|
+
"project_name_snake": project_name_snake,
|
|
724
|
+
"import_prefix": import_prefix,
|
|
725
|
+
"package_name": package_name_snake or "",
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for full_path, template_name, always_overwrite in files_to_create:
|
|
729
|
+
# 若禁用管理后台,则跳过生成 admin_console/
|
|
730
|
+
if template_name == "admin_console/__init__.py" and not with_admin_console:
|
|
731
|
+
continue
|
|
732
|
+
|
|
733
|
+
rel_path = full_path.relative_to(base_path)
|
|
734
|
+
should_write = always_overwrite or force or not full_path.exists()
|
|
735
|
+
|
|
736
|
+
if not should_write:
|
|
737
|
+
console.print(f" [dim]⏭️ {rel_path} 已存在,跳过[/dim]")
|
|
738
|
+
continue
|
|
739
|
+
|
|
740
|
+
content = _read_template(template_name)
|
|
741
|
+
# 临时替换代码块中的字典字面量,避免 str.format() 解析
|
|
742
|
+
import re
|
|
743
|
+
dict_placeholders = {}
|
|
744
|
+
placeholder_counter = [0] # 使用列表以便在嵌套函数中修改
|
|
745
|
+
|
|
746
|
+
def protect_dict(match, *, _dict_placeholders=dict_placeholders, _placeholder_counter=placeholder_counter):
|
|
747
|
+
"""保护字典字面量,用占位符替换"""
|
|
748
|
+
placeholder = f"__DICT_PLACEHOLDER_{_placeholder_counter[0]}__"
|
|
749
|
+
_dict_placeholders[placeholder] = match.group(0)
|
|
750
|
+
_placeholder_counter[0] += 1
|
|
751
|
+
return placeholder
|
|
752
|
+
|
|
753
|
+
def process_code_block(match, *, _protect_dict=protect_dict):
|
|
754
|
+
"""处理代码块,保护其中的字典字面量"""
|
|
755
|
+
code_content = match.group(1)
|
|
756
|
+
protected_code = re.sub(r'\{"[^"]+":\s*[^}]+\}', _protect_dict, code_content, flags=re.DOTALL)
|
|
757
|
+
return '```python' + protected_code + '```'
|
|
758
|
+
|
|
759
|
+
# 在代码块中保护字典字面量(匹配 {"key": value} 格式)
|
|
760
|
+
content = re.sub(r'```python(.*?)```', process_code_block, content, flags=re.DOTALL)
|
|
761
|
+
|
|
762
|
+
# 格式化模板(替换 {project_name} 等占位符)
|
|
763
|
+
content = content.format(**template_vars)
|
|
764
|
+
|
|
765
|
+
# 若启用管理后台,默认在 .env.example 中打开 ADMIN_ENABLED,并给出基础示例
|
|
766
|
+
if template_name == ".env.example" and with_admin_console:
|
|
767
|
+
content = content.replace("# ADMIN_ENABLED=false", "ADMIN_ENABLED=true")
|
|
768
|
+
content = content.replace("# ADMIN_PATH=/api/admin-console", "ADMIN_PATH=/api/admin-console")
|
|
769
|
+
content = content.replace("# ADMIN_AUTH_MODE=basic", "ADMIN_AUTH_MODE=basic")
|
|
770
|
+
content = content.replace(
|
|
771
|
+
"# ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
772
|
+
"ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
773
|
+
)
|
|
774
|
+
content = content.replace("# ADMIN_AUTH_BASIC_USERNAME=admin", "ADMIN_AUTH_BASIC_USERNAME=admin")
|
|
775
|
+
content = content.replace("# ADMIN_AUTH_BASIC_PASSWORD=change_me", "ADMIN_AUTH_BASIC_PASSWORD=change_me")
|
|
776
|
+
|
|
777
|
+
# 恢复字典字面量
|
|
778
|
+
for placeholder, original in dict_placeholders.items():
|
|
779
|
+
content = content.replace(placeholder, original)
|
|
780
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
781
|
+
full_path.write_text(content, encoding="utf-8")
|
|
782
|
+
console.print(f" [green]✅ {rel_path}[/green]")
|
|
783
|
+
|
|
784
|
+
# 3. 配置 pyproject.toml
|
|
785
|
+
console.print("\n[bold]⚙️ 配置 pyproject.toml...[/bold]")
|
|
786
|
+
|
|
787
|
+
# 保存 [tool.aury] 配置(包含 package 与 app)
|
|
788
|
+
proj_config = ProjectConfig(package=package_name_snake, app="main:app")
|
|
789
|
+
if save_project_config(proj_config, base_path):
|
|
790
|
+
if package_name_snake:
|
|
791
|
+
console.print(f" [green]✅ 已保存包配置: [tool.aury] package = \"{package_name_snake}\"[/green]")
|
|
792
|
+
console.print(" [green]✅ 已保存入口配置: [tool.aury] app = \"main:app\"[/green]")
|
|
793
|
+
|
|
794
|
+
if _append_ruff_config(base_path):
|
|
795
|
+
console.print(" [green]✅ 已添加 ruff 和 pytest 配置[/green]")
|
|
796
|
+
|
|
797
|
+
if _append_dev_deps_config(base_path):
|
|
798
|
+
console.print(" [green]✅ 已添加 [dependency-groups] dev 配置[/green]")
|
|
799
|
+
|
|
800
|
+
# 4. 初始化数据库迁移
|
|
801
|
+
console.print("\n[bold]📦 初始化数据库迁移...[/bold]")
|
|
802
|
+
if _init_migrations(base_path, package_name_snake):
|
|
803
|
+
console.print(" [green]✅ 已创建 migrations/ 目录和配置[/green]")
|
|
804
|
+
else:
|
|
805
|
+
console.print(" [dim]ℹ️ migrations/ 目录已存在,跳过[/dim]")
|
|
806
|
+
|
|
807
|
+
# 5. 生成 Docker 配置
|
|
808
|
+
if with_docker:
|
|
809
|
+
console.print("\n[bold]🐳 生成 Docker 配置...[/bold]")
|
|
810
|
+
from .docker import docker_init
|
|
811
|
+
docker_init(force=force)
|
|
812
|
+
|
|
813
|
+
# 6. 显示结果
|
|
814
|
+
console.print("\n")
|
|
815
|
+
|
|
816
|
+
tree = Tree(f"[bold cyan]{project_name}/[/bold cyan]")
|
|
817
|
+
tree.add("[dim].env.example[/dim]")
|
|
818
|
+
tree.add("[dim].gitignore[/dim]")
|
|
819
|
+
tree.add("[dim]alembic.ini[/dim]")
|
|
820
|
+
tree.add("[dim]pyproject.toml[/dim]")
|
|
821
|
+
tree.add("[dim]README.md[/dim]")
|
|
822
|
+
tree.add("[dim]DEVELOPMENT.md[/dim]")
|
|
823
|
+
tree.add("[dim]CLI.md[/dim]")
|
|
824
|
+
if with_docker:
|
|
825
|
+
tree.add("[dim]Dockerfile[/dim]")
|
|
826
|
+
tree.add("[dim]docker-compose.yml[/dim]")
|
|
827
|
+
tree.add("[dim].dockerignore[/dim]")
|
|
828
|
+
|
|
829
|
+
# tests 目录始终在项目根目录
|
|
830
|
+
tests_branch = tree.add("[blue]tests/[/blue]")
|
|
831
|
+
tests_branch.add("[dim]conftest.py[/dim]")
|
|
832
|
+
|
|
833
|
+
# main.py 始终在根目录
|
|
834
|
+
tree.add("[green]main.py[/green]")
|
|
835
|
+
|
|
836
|
+
if package_name_snake:
|
|
837
|
+
pkg_branch = tree.add(f"[bold blue]{package_name_snake}/[/bold blue]")
|
|
838
|
+
pkg_branch.add("[green]config.py[/green]")
|
|
839
|
+
pkg_branch.add("[blue]api/[/blue]")
|
|
840
|
+
pkg_branch.add("[blue]services/[/blue]")
|
|
841
|
+
pkg_branch.add("[blue]models/[/blue]")
|
|
842
|
+
pkg_branch.add("[blue]repositories/[/blue]")
|
|
843
|
+
pkg_branch.add("[blue]schemas/[/blue]")
|
|
844
|
+
pkg_branch.add("[blue]exceptions/[/blue]")
|
|
845
|
+
pkg_branch.add("[blue]tasks/[/blue]")
|
|
846
|
+
pkg_branch.add("[blue]schedules/[/blue]")
|
|
847
|
+
else:
|
|
848
|
+
tree.add("[green]config.py[/green]")
|
|
849
|
+
tree.add("[blue]api/[/blue]")
|
|
850
|
+
tree.add("[blue]services/[/blue]")
|
|
851
|
+
tree.add("[blue]models/[/blue]")
|
|
852
|
+
tree.add("[blue]repositories/[/blue]")
|
|
853
|
+
tree.add("[blue]schemas/[/blue]")
|
|
854
|
+
tree.add("[blue]exceptions/[/blue]")
|
|
855
|
+
tree.add("[blue]tasks/[/blue]")
|
|
856
|
+
tree.add("[blue]schedules/[/blue]")
|
|
857
|
+
|
|
858
|
+
migrations_branch = tree.add("[blue]migrations/[/blue]")
|
|
859
|
+
migrations_branch.add("[dim]env.py[/dim]")
|
|
860
|
+
migrations_branch.add("[dim]versions/[/dim]")
|
|
861
|
+
|
|
862
|
+
console.print(tree)
|
|
863
|
+
|
|
864
|
+
# 下一步提示
|
|
865
|
+
console.print("\n[bold green]✨ 项目初始化完成![/bold green]\n")
|
|
866
|
+
console.print("[bold]下一步:[/bold]")
|
|
867
|
+
console.print(" 1. 安装开发依赖:")
|
|
868
|
+
console.print(" [cyan]uv sync --group dev[/cyan]")
|
|
869
|
+
console.print(" [dim]dev 组包含: pytest, pytest-asyncio, pytest-cov, ruff, mypy, watchfiles(用于稳定热重载)[/dim]")
|
|
870
|
+
console.print(" 2. 复制并编辑环境变量:")
|
|
871
|
+
console.print(" [cyan]cp .env.example .env[/cyan]")
|
|
872
|
+
console.print(" [dim]# 编辑 .env 配置数据库连接等[/dim]")
|
|
873
|
+
console.print(" 3. 启动开发服务器:")
|
|
874
|
+
console.print(" [cyan]aury server dev[/cyan]")
|
|
875
|
+
console.print(" 4. 访问 API 文档:")
|
|
876
|
+
console.print(" [cyan]http://127.0.0.1:8000/docs[/cyan]")
|
|
877
|
+
console.print()
|
|
878
|
+
console.print("[bold]常用命令:[/bold]")
|
|
879
|
+
console.print(" [cyan]aury generate crud user -i[/cyan] # 生成 CRUD(交互式)")
|
|
880
|
+
console.print(" [cyan]aury generate model user -i[/cyan] # 生成模型(交互式)")
|
|
881
|
+
console.print(" [cyan]aury migrate make -m \"xxx\"[/cyan] # 创建迁移")
|
|
882
|
+
console.print(" [cyan]aury migrate up[/cyan] # 执行迁移")
|
|
883
|
+
console.print(" [cyan]aury server prod[/cyan] # 生产模式")
|
|
884
|
+
console.print()
|
|
885
|
+
console.print("[dim]💡 使用 -i 参数可交互式配置字段、类型、约束等[/dim]")
|
|
886
|
+
console.print()
|
|
887
|
+
console.print("[dim]详细文档: https://github.com/AuriMyth/aury-boot[/dim]")
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
__all__ = ["init", "init_admin_console_module"]
|