aury-boot 0.0.2__py3-none-any.whl → 0.0.4__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 +892 -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.4.dist-info}/METADATA +3 -2
- aury_boot-0.0.4.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.4.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Docker 相关命令。
|
|
2
|
+
|
|
3
|
+
生成 Docker 配置文件:
|
|
4
|
+
- Dockerfile
|
|
5
|
+
- docker-compose.yml
|
|
6
|
+
- .dockerignore
|
|
7
|
+
|
|
8
|
+
使用示例:
|
|
9
|
+
aury docker init
|
|
10
|
+
aury docker init --force
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
from .config import get_project_config
|
|
22
|
+
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
# 创建 docker 子应用
|
|
26
|
+
app = typer.Typer(
|
|
27
|
+
name="docker",
|
|
28
|
+
help="Docker 配置文件生成",
|
|
29
|
+
no_args_is_help=True,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ============================================================
|
|
34
|
+
# 模板
|
|
35
|
+
# ============================================================
|
|
36
|
+
|
|
37
|
+
DOCKERFILE_TEMPLATE = '''# =============================================================================
|
|
38
|
+
# {project_name} Dockerfile
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# 多阶段构建优化镜像大小
|
|
41
|
+
|
|
42
|
+
# 基础镜像
|
|
43
|
+
FROM python:3.13-slim AS base
|
|
44
|
+
|
|
45
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \\
|
|
46
|
+
PYTHONUNBUFFERED=1 \\
|
|
47
|
+
PIP_NO_CACHE_DIR=1 \\
|
|
48
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
49
|
+
|
|
50
|
+
WORKDIR /app
|
|
51
|
+
|
|
52
|
+
# 安装系统依赖
|
|
53
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
54
|
+
curl \\
|
|
55
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
56
|
+
|
|
57
|
+
# -----------------------------------------------------------------------------
|
|
58
|
+
# 构建阶段:安装依赖
|
|
59
|
+
# -----------------------------------------------------------------------------
|
|
60
|
+
FROM base AS builder
|
|
61
|
+
|
|
62
|
+
# 安装 uv
|
|
63
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
64
|
+
|
|
65
|
+
# 复制依赖文件
|
|
66
|
+
COPY pyproject.toml uv.lock* ./
|
|
67
|
+
|
|
68
|
+
# 安装依赖到虚拟环境
|
|
69
|
+
RUN uv sync --frozen --no-dev --no-install-project
|
|
70
|
+
|
|
71
|
+
# -----------------------------------------------------------------------------
|
|
72
|
+
# 运行阶段
|
|
73
|
+
# -----------------------------------------------------------------------------
|
|
74
|
+
FROM base AS runtime
|
|
75
|
+
|
|
76
|
+
# 从 builder 复制虚拟环境
|
|
77
|
+
COPY --from=builder /app/.venv /app/.venv
|
|
78
|
+
|
|
79
|
+
# 设置 PATH
|
|
80
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
81
|
+
|
|
82
|
+
# 复制应用代码
|
|
83
|
+
COPY . .
|
|
84
|
+
|
|
85
|
+
# 创建非 root 用户
|
|
86
|
+
RUN useradd --create-home --shell /bin/bash appuser && \\
|
|
87
|
+
chown -R appuser:appuser /app
|
|
88
|
+
USER appuser
|
|
89
|
+
|
|
90
|
+
# 健康检查
|
|
91
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
92
|
+
CMD curl -f http://localhost:8000/health || exit 1
|
|
93
|
+
|
|
94
|
+
# 默认命令
|
|
95
|
+
CMD ["aury", "server", "prod"]
|
|
96
|
+
'''
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
DOCKER_COMPOSE_TEMPLATE = '''# =============================================================================
|
|
100
|
+
# {project_name} Docker Compose
|
|
101
|
+
# =============================================================================
|
|
102
|
+
# 服务编排配置
|
|
103
|
+
|
|
104
|
+
services:
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# 基础服务(共享配置)
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
base: &base
|
|
109
|
+
build:
|
|
110
|
+
context: .
|
|
111
|
+
dockerfile: Dockerfile
|
|
112
|
+
env_file:
|
|
113
|
+
- .env
|
|
114
|
+
volumes:
|
|
115
|
+
- ./logs:/app/logs
|
|
116
|
+
restart: unless-stopped
|
|
117
|
+
networks:
|
|
118
|
+
- {project_name_snake}_network
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# API 服务
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
api:
|
|
124
|
+
<<: *base
|
|
125
|
+
container_name: {project_name_snake}_api
|
|
126
|
+
environment:
|
|
127
|
+
- SERVICE_NAME=api
|
|
128
|
+
- SERVICE_TYPE=api
|
|
129
|
+
- SCHEDULER_ENABLED=false # API 服务不启动内嵌调度器
|
|
130
|
+
ports:
|
|
131
|
+
- "${{API_PORT:-8000}}:8000"
|
|
132
|
+
command: ["aury", "server", "prod"]
|
|
133
|
+
depends_on:
|
|
134
|
+
- redis
|
|
135
|
+
- postgres
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Scheduler 服务(定时任务)
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
scheduler:
|
|
141
|
+
<<: *base
|
|
142
|
+
container_name: {project_name_snake}_scheduler
|
|
143
|
+
environment:
|
|
144
|
+
- SERVICE_NAME=scheduler
|
|
145
|
+
command: ["aury", "scheduler"]
|
|
146
|
+
depends_on:
|
|
147
|
+
- redis
|
|
148
|
+
- postgres
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Worker 服务(异步任务)
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
worker:
|
|
154
|
+
<<: *base
|
|
155
|
+
container_name: {project_name_snake}_worker
|
|
156
|
+
environment:
|
|
157
|
+
- SERVICE_NAME=worker
|
|
158
|
+
command: ["aury", "worker", "-c", "8"]
|
|
159
|
+
depends_on:
|
|
160
|
+
- redis
|
|
161
|
+
- postgres
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# 基础设施服务
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
postgres:
|
|
167
|
+
image: postgres:16-alpine
|
|
168
|
+
container_name: {project_name_snake}_postgres
|
|
169
|
+
environment:
|
|
170
|
+
POSTGRES_USER: ${{POSTGRES_USER:-postgres}}
|
|
171
|
+
POSTGRES_PASSWORD: ${{POSTGRES_PASSWORD:-postgres}}
|
|
172
|
+
POSTGRES_DB: ${{POSTGRES_DB:-{project_name_snake}}}
|
|
173
|
+
volumes:
|
|
174
|
+
- postgres_data:/var/lib/postgresql/data
|
|
175
|
+
ports:
|
|
176
|
+
- "${{POSTGRES_PORT:-5432}}:5432"
|
|
177
|
+
networks:
|
|
178
|
+
- {project_name_snake}_network
|
|
179
|
+
restart: unless-stopped
|
|
180
|
+
|
|
181
|
+
redis:
|
|
182
|
+
image: redis:7-alpine
|
|
183
|
+
container_name: {project_name_snake}_redis
|
|
184
|
+
command: redis-server --appendonly yes
|
|
185
|
+
volumes:
|
|
186
|
+
- redis_data:/data
|
|
187
|
+
ports:
|
|
188
|
+
- "${{REDIS_PORT:-6379}}:6379"
|
|
189
|
+
networks:
|
|
190
|
+
- {project_name_snake}_network
|
|
191
|
+
restart: unless-stopped
|
|
192
|
+
|
|
193
|
+
# -----------------------------------------------------------------------------
|
|
194
|
+
# 网络和卷
|
|
195
|
+
# -----------------------------------------------------------------------------
|
|
196
|
+
networks:
|
|
197
|
+
{project_name_snake}_network:
|
|
198
|
+
driver: bridge
|
|
199
|
+
|
|
200
|
+
volumes:
|
|
201
|
+
postgres_data:
|
|
202
|
+
redis_data:
|
|
203
|
+
'''
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
DOCKERIGNORE_TEMPLATE = '''# =============================================================================
|
|
207
|
+
# Docker 忽略文件
|
|
208
|
+
# =============================================================================
|
|
209
|
+
|
|
210
|
+
# Python
|
|
211
|
+
__pycache__/
|
|
212
|
+
*.py[cod]
|
|
213
|
+
*$py.class
|
|
214
|
+
*.so
|
|
215
|
+
.Python
|
|
216
|
+
.venv/
|
|
217
|
+
venv/
|
|
218
|
+
ENV/
|
|
219
|
+
env/
|
|
220
|
+
.eggs/
|
|
221
|
+
*.egg-info/
|
|
222
|
+
*.egg
|
|
223
|
+
|
|
224
|
+
# 开发工具
|
|
225
|
+
.git/
|
|
226
|
+
.gitignore
|
|
227
|
+
.idea/
|
|
228
|
+
.vscode/
|
|
229
|
+
*.swp
|
|
230
|
+
*.swo
|
|
231
|
+
|
|
232
|
+
# 测试
|
|
233
|
+
.pytest_cache/
|
|
234
|
+
.coverage
|
|
235
|
+
htmlcov/
|
|
236
|
+
.tox/
|
|
237
|
+
.nox/
|
|
238
|
+
|
|
239
|
+
# 构建
|
|
240
|
+
dist/
|
|
241
|
+
build/
|
|
242
|
+
*.egg-info/
|
|
243
|
+
|
|
244
|
+
# 文档
|
|
245
|
+
docs/
|
|
246
|
+
*.md
|
|
247
|
+
!README.md
|
|
248
|
+
|
|
249
|
+
# 本地配置
|
|
250
|
+
.env.local
|
|
251
|
+
.env.*.local
|
|
252
|
+
*.local.yml
|
|
253
|
+
|
|
254
|
+
# 日志
|
|
255
|
+
logs/
|
|
256
|
+
*.log
|
|
257
|
+
|
|
258
|
+
# 缓存
|
|
259
|
+
.cache/
|
|
260
|
+
.mypy_cache/
|
|
261
|
+
.ruff_cache/
|
|
262
|
+
|
|
263
|
+
# IDE
|
|
264
|
+
.idea/
|
|
265
|
+
.vscode/
|
|
266
|
+
*.sublime-*
|
|
267
|
+
|
|
268
|
+
# macOS
|
|
269
|
+
.DS_Store
|
|
270
|
+
|
|
271
|
+
# 其他
|
|
272
|
+
Makefile
|
|
273
|
+
docker-compose.override.yml
|
|
274
|
+
'''
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ============================================================
|
|
278
|
+
# 命令
|
|
279
|
+
# ============================================================
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _to_snake_case(name: str) -> str:
|
|
283
|
+
"""转换为 snake_case。"""
|
|
284
|
+
import re
|
|
285
|
+
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
286
|
+
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@app.command(name="init")
|
|
290
|
+
def docker_init(
|
|
291
|
+
force: bool = typer.Option(
|
|
292
|
+
False,
|
|
293
|
+
"--force",
|
|
294
|
+
"-f",
|
|
295
|
+
help="强制覆盖已存在的文件",
|
|
296
|
+
),
|
|
297
|
+
) -> None:
|
|
298
|
+
"""生成 Docker 配置文件。
|
|
299
|
+
|
|
300
|
+
生成:
|
|
301
|
+
- Dockerfile
|
|
302
|
+
- docker-compose.yml
|
|
303
|
+
- .dockerignore
|
|
304
|
+
|
|
305
|
+
示例:
|
|
306
|
+
aury docker init
|
|
307
|
+
aury docker init --force
|
|
308
|
+
"""
|
|
309
|
+
base_path = Path.cwd()
|
|
310
|
+
|
|
311
|
+
# 获取项目名称
|
|
312
|
+
project_name = base_path.name
|
|
313
|
+
project_name_snake = _to_snake_case(project_name)
|
|
314
|
+
|
|
315
|
+
# 读取项目配置,获取包名
|
|
316
|
+
config = get_project_config(base_path)
|
|
317
|
+
package_or_dot = f"{config.package}." if config.has_package else ""
|
|
318
|
+
|
|
319
|
+
console.print(Panel.fit(
|
|
320
|
+
f"[bold cyan]🐳 生成 Docker 配置: {project_name}[/bold cyan]",
|
|
321
|
+
border_style="cyan",
|
|
322
|
+
))
|
|
323
|
+
|
|
324
|
+
created_files = []
|
|
325
|
+
|
|
326
|
+
# 模板变量
|
|
327
|
+
template_vars = {
|
|
328
|
+
"project_name": project_name,
|
|
329
|
+
"project_name_snake": project_name_snake,
|
|
330
|
+
"package_or_dot": package_or_dot,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# 生成文件
|
|
334
|
+
files_to_create = [
|
|
335
|
+
("Dockerfile", DOCKERFILE_TEMPLATE),
|
|
336
|
+
("docker-compose.yml", DOCKER_COMPOSE_TEMPLATE),
|
|
337
|
+
(".dockerignore", DOCKERIGNORE_TEMPLATE),
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
for file_name, template in files_to_create:
|
|
341
|
+
file_path = base_path / file_name
|
|
342
|
+
|
|
343
|
+
if file_path.exists() and not force:
|
|
344
|
+
console.print(f" [dim]⏭️ {file_name} 已存在,跳过[/dim]")
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
content = template.format(**template_vars)
|
|
348
|
+
file_path.write_text(content, encoding="utf-8")
|
|
349
|
+
created_files.append(file_name)
|
|
350
|
+
console.print(f" [green]✅ {file_name}[/green]")
|
|
351
|
+
|
|
352
|
+
if created_files:
|
|
353
|
+
console.print("\n[bold green]✨ Docker 配置生成完成![/bold green]\n")
|
|
354
|
+
console.print("[bold]使用方法:[/bold]")
|
|
355
|
+
console.print(" 1. 启动所有服务:")
|
|
356
|
+
console.print(" [cyan]docker-compose up -d[/cyan]")
|
|
357
|
+
console.print(" 2. 只启动 API:")
|
|
358
|
+
console.print(" [cyan]docker-compose up -d api[/cyan]")
|
|
359
|
+
console.print(" 3. 查看日志:")
|
|
360
|
+
console.print(" [cyan]docker-compose logs -f api[/cyan]")
|
|
361
|
+
console.print(" 4. 停止服务:")
|
|
362
|
+
console.print(" [cyan]docker-compose down[/cyan]")
|
|
363
|
+
else:
|
|
364
|
+
console.print("\n[dim]所有文件已存在,使用 --force 覆盖[/dim]")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""文档生成命令。
|
|
2
|
+
|
|
3
|
+
提供命令行工具用于在现有项目中生成/更新文档:
|
|
4
|
+
- aury docs dev 生成/更新 DEVELOPMENT.md
|
|
5
|
+
- aury docs cli 生成/更新 CLI.md
|
|
6
|
+
- aury docs env 生成/更新 .env.example
|
|
7
|
+
- aury docs all 生成/更新所有文档
|
|
8
|
+
|
|
9
|
+
使用示例:
|
|
10
|
+
aury docs dev # 生成开发文档
|
|
11
|
+
aury docs cli # 生成 CLI 文档
|
|
12
|
+
aury docs env # 生成环境变量示例
|
|
13
|
+
aury docs all # 生成所有文档
|
|
14
|
+
aury docs all --force # 强制覆盖已存在的文件
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
import typer
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
name="docs",
|
|
26
|
+
help="📚 生成/更新项目文档",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
# 模板目录
|
|
33
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates" / "project"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _detect_project_info(project_dir: Path) -> dict[str, str]:
|
|
37
|
+
"""检测项目信息。
|
|
38
|
+
|
|
39
|
+
从 pyproject.toml 或目录结构中推断项目名称和包名。
|
|
40
|
+
"""
|
|
41
|
+
# 尝试从 pyproject.toml 读取
|
|
42
|
+
pyproject_path = project_dir / "pyproject.toml"
|
|
43
|
+
if pyproject_path.exists():
|
|
44
|
+
try:
|
|
45
|
+
import tomllib
|
|
46
|
+
with open(pyproject_path, "rb") as f:
|
|
47
|
+
data = tomllib.load(f)
|
|
48
|
+
project_name = data.get("project", {}).get("name", "")
|
|
49
|
+
if project_name:
|
|
50
|
+
# 转换为 snake_case
|
|
51
|
+
project_name_snake = project_name.replace("-", "_").lower()
|
|
52
|
+
return {
|
|
53
|
+
"project_name": project_name,
|
|
54
|
+
"project_name_snake": project_name_snake,
|
|
55
|
+
"package_name": project_name_snake,
|
|
56
|
+
"import_prefix": project_name_snake,
|
|
57
|
+
}
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# 尝试从目录名推断
|
|
62
|
+
dir_name = project_dir.name
|
|
63
|
+
project_name_snake = dir_name.replace("-", "_").lower()
|
|
64
|
+
|
|
65
|
+
# 检查是否有匹配的 Python 包目录
|
|
66
|
+
package_name = project_name_snake
|
|
67
|
+
for candidate in [project_name_snake, "app", "src"]:
|
|
68
|
+
candidate_path = project_dir / candidate
|
|
69
|
+
if candidate_path.is_dir() and (candidate_path / "__init__.py").exists():
|
|
70
|
+
package_name = candidate
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"project_name": dir_name,
|
|
75
|
+
"project_name_snake": project_name_snake,
|
|
76
|
+
"package_name": package_name,
|
|
77
|
+
"import_prefix": package_name,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _render_template(template_name: str, context: dict[str, str]) -> str:
|
|
82
|
+
"""渲染模板。"""
|
|
83
|
+
template_path = TEMPLATES_DIR / template_name
|
|
84
|
+
if not template_path.exists():
|
|
85
|
+
raise FileNotFoundError(f"模板文件不存在: {template_path}")
|
|
86
|
+
|
|
87
|
+
content = template_path.read_text(encoding="utf-8")
|
|
88
|
+
return content.format(**context)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _write_file(
|
|
92
|
+
output_path: Path,
|
|
93
|
+
content: str,
|
|
94
|
+
force: bool = False,
|
|
95
|
+
dry_run: bool = False,
|
|
96
|
+
) -> bool:
|
|
97
|
+
"""写入文件。
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
bool: 是否成功写入
|
|
101
|
+
"""
|
|
102
|
+
if output_path.exists() and not force:
|
|
103
|
+
console.print(f"[yellow]⚠️ 文件已存在,跳过: {output_path}[/yellow]")
|
|
104
|
+
console.print(" 使用 --force 覆盖已存在的文件")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
if dry_run:
|
|
108
|
+
console.print(f"[dim]🔍 预览模式,将生成: {output_path}[/dim]")
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
output_path.write_text(content, encoding="utf-8")
|
|
113
|
+
|
|
114
|
+
action = "覆盖" if output_path.exists() else "创建"
|
|
115
|
+
console.print(f"[green]✅ {action}: {output_path}[/green]")
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command(name="dev")
|
|
120
|
+
def generate_dev_doc(
|
|
121
|
+
project_dir: Path = typer.Argument(
|
|
122
|
+
Path("."),
|
|
123
|
+
help="项目目录路径",
|
|
124
|
+
exists=True,
|
|
125
|
+
file_okay=False,
|
|
126
|
+
dir_okay=True,
|
|
127
|
+
resolve_path=True,
|
|
128
|
+
),
|
|
129
|
+
force: bool = typer.Option(
|
|
130
|
+
False,
|
|
131
|
+
"--force",
|
|
132
|
+
"-f",
|
|
133
|
+
help="强制覆盖已存在的文件",
|
|
134
|
+
),
|
|
135
|
+
dry_run: bool = typer.Option(
|
|
136
|
+
False,
|
|
137
|
+
"--dry-run",
|
|
138
|
+
"-n",
|
|
139
|
+
help="预览模式,不实际写入文件",
|
|
140
|
+
),
|
|
141
|
+
) -> None:
|
|
142
|
+
"""生成/更新 DEVELOPMENT.md 开发文档。"""
|
|
143
|
+
context = _detect_project_info(project_dir)
|
|
144
|
+
|
|
145
|
+
console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
content = _render_template("DEVELOPMENT.md.tpl", context)
|
|
149
|
+
output_path = project_dir / "DEVELOPMENT.md"
|
|
150
|
+
_write_file(output_path, content, force=force, dry_run=dry_run)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
console.print(f"[red]❌ 生成失败: {e}[/red]")
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.command(name="cli")
|
|
157
|
+
def generate_cli_doc(
|
|
158
|
+
project_dir: Path = typer.Argument(
|
|
159
|
+
Path("."),
|
|
160
|
+
help="项目目录路径",
|
|
161
|
+
exists=True,
|
|
162
|
+
file_okay=False,
|
|
163
|
+
dir_okay=True,
|
|
164
|
+
resolve_path=True,
|
|
165
|
+
),
|
|
166
|
+
force: bool = typer.Option(
|
|
167
|
+
False,
|
|
168
|
+
"--force",
|
|
169
|
+
"-f",
|
|
170
|
+
help="强制覆盖已存在的文件",
|
|
171
|
+
),
|
|
172
|
+
dry_run: bool = typer.Option(
|
|
173
|
+
False,
|
|
174
|
+
"--dry-run",
|
|
175
|
+
"-n",
|
|
176
|
+
help="预览模式,不实际写入文件",
|
|
177
|
+
),
|
|
178
|
+
) -> None:
|
|
179
|
+
"""生成/更新 CLI.md 命令行文档。"""
|
|
180
|
+
context = _detect_project_info(project_dir)
|
|
181
|
+
|
|
182
|
+
console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
content = _render_template("CLI.md.tpl", context)
|
|
186
|
+
output_path = project_dir / "CLI.md"
|
|
187
|
+
_write_file(output_path, content, force=force, dry_run=dry_run)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
console.print(f"[red]❌ 生成失败: {e}[/red]")
|
|
190
|
+
raise typer.Exit(1)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@app.command(name="env")
|
|
194
|
+
def generate_env_example(
|
|
195
|
+
project_dir: Path = typer.Argument(
|
|
196
|
+
Path("."),
|
|
197
|
+
help="项目目录路径",
|
|
198
|
+
exists=True,
|
|
199
|
+
file_okay=False,
|
|
200
|
+
dir_okay=True,
|
|
201
|
+
resolve_path=True,
|
|
202
|
+
),
|
|
203
|
+
force: bool = typer.Option(
|
|
204
|
+
False,
|
|
205
|
+
"--force",
|
|
206
|
+
"-f",
|
|
207
|
+
help="强制覆盖已存在的文件",
|
|
208
|
+
),
|
|
209
|
+
dry_run: bool = typer.Option(
|
|
210
|
+
False,
|
|
211
|
+
"--dry-run",
|
|
212
|
+
"-n",
|
|
213
|
+
help="预览模式,不实际写入文件",
|
|
214
|
+
),
|
|
215
|
+
) -> None:
|
|
216
|
+
"""生成/更新 .env.example 环境变量示例。"""
|
|
217
|
+
context = _detect_project_info(project_dir)
|
|
218
|
+
|
|
219
|
+
console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
content = _render_template("env.example.tpl", context)
|
|
223
|
+
output_path = project_dir / ".env.example"
|
|
224
|
+
_write_file(output_path, content, force=force, dry_run=dry_run)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
console.print(f"[red]❌ 生成失败: {e}[/red]")
|
|
227
|
+
raise typer.Exit(1)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@app.command(name="all")
|
|
231
|
+
def generate_all_docs(
|
|
232
|
+
project_dir: Path = typer.Argument(
|
|
233
|
+
Path("."),
|
|
234
|
+
help="项目目录路径",
|
|
235
|
+
exists=True,
|
|
236
|
+
file_okay=False,
|
|
237
|
+
dir_okay=True,
|
|
238
|
+
resolve_path=True,
|
|
239
|
+
),
|
|
240
|
+
force: bool = typer.Option(
|
|
241
|
+
False,
|
|
242
|
+
"--force",
|
|
243
|
+
"-f",
|
|
244
|
+
help="强制覆盖已存在的文件",
|
|
245
|
+
),
|
|
246
|
+
dry_run: bool = typer.Option(
|
|
247
|
+
False,
|
|
248
|
+
"--dry-run",
|
|
249
|
+
"-n",
|
|
250
|
+
help="预览模式,不实际写入文件",
|
|
251
|
+
),
|
|
252
|
+
) -> None:
|
|
253
|
+
"""生成/更新所有文档(DEVELOPMENT.md, CLI.md, .env.example)。"""
|
|
254
|
+
context = _detect_project_info(project_dir)
|
|
255
|
+
|
|
256
|
+
console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
|
|
257
|
+
console.print()
|
|
258
|
+
|
|
259
|
+
docs_to_generate = [
|
|
260
|
+
("DEVELOPMENT.md.tpl", "DEVELOPMENT.md", "开发文档"),
|
|
261
|
+
("CLI.md.tpl", "CLI.md", "CLI 文档"),
|
|
262
|
+
("env.example.tpl", ".env.example", "环境变量示例"),
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
success_count = 0
|
|
266
|
+
for template_name, output_name, description in docs_to_generate:
|
|
267
|
+
try:
|
|
268
|
+
content = _render_template(template_name, context)
|
|
269
|
+
output_path = project_dir / output_name
|
|
270
|
+
if _write_file(output_path, content, force=force, dry_run=dry_run):
|
|
271
|
+
success_count += 1
|
|
272
|
+
except FileNotFoundError:
|
|
273
|
+
console.print(f"[yellow]⚠️ 模板不存在,跳过: {template_name}[/yellow]")
|
|
274
|
+
except Exception as e:
|
|
275
|
+
console.print(f"[red]❌ 生成 {description} 失败: {e}[/red]")
|
|
276
|
+
|
|
277
|
+
console.print()
|
|
278
|
+
if dry_run:
|
|
279
|
+
console.print(f"[dim]🔍 预览模式完成,将生成 {success_count} 个文件[/dim]")
|
|
280
|
+
else:
|
|
281
|
+
console.print(f"[green]✨ 完成!成功生成 {success_count} 个文档[/green]")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
__all__ = ["app"]
|