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.
Files changed (138) hide show
  1. aury/boot/__init__.py +66 -0
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +120 -0
  4. aury/boot/application/app/__init__.py +39 -0
  5. aury/boot/application/app/base.py +511 -0
  6. aury/boot/application/app/components.py +434 -0
  7. aury/boot/application/app/middlewares.py +101 -0
  8. aury/boot/application/config/__init__.py +44 -0
  9. aury/boot/application/config/settings.py +663 -0
  10. aury/boot/application/constants/__init__.py +19 -0
  11. aury/boot/application/constants/components.py +50 -0
  12. aury/boot/application/constants/scheduler.py +28 -0
  13. aury/boot/application/constants/service.py +29 -0
  14. aury/boot/application/errors/__init__.py +55 -0
  15. aury/boot/application/errors/chain.py +80 -0
  16. aury/boot/application/errors/codes.py +67 -0
  17. aury/boot/application/errors/exceptions.py +238 -0
  18. aury/boot/application/errors/handlers.py +320 -0
  19. aury/boot/application/errors/response.py +120 -0
  20. aury/boot/application/interfaces/__init__.py +76 -0
  21. aury/boot/application/interfaces/egress.py +224 -0
  22. aury/boot/application/interfaces/ingress.py +98 -0
  23. aury/boot/application/middleware/__init__.py +22 -0
  24. aury/boot/application/middleware/logging.py +451 -0
  25. aury/boot/application/migrations/__init__.py +13 -0
  26. aury/boot/application/migrations/manager.py +685 -0
  27. aury/boot/application/migrations/setup.py +237 -0
  28. aury/boot/application/rpc/__init__.py +63 -0
  29. aury/boot/application/rpc/base.py +108 -0
  30. aury/boot/application/rpc/client.py +294 -0
  31. aury/boot/application/rpc/discovery.py +218 -0
  32. aury/boot/application/scheduler/__init__.py +13 -0
  33. aury/boot/application/scheduler/runner.py +123 -0
  34. aury/boot/application/server/__init__.py +296 -0
  35. aury/boot/commands/__init__.py +30 -0
  36. aury/boot/commands/add.py +76 -0
  37. aury/boot/commands/app.py +105 -0
  38. aury/boot/commands/config.py +177 -0
  39. aury/boot/commands/docker.py +367 -0
  40. aury/boot/commands/docs.py +284 -0
  41. aury/boot/commands/generate.py +1277 -0
  42. aury/boot/commands/init.py +890 -0
  43. aury/boot/commands/migrate/__init__.py +37 -0
  44. aury/boot/commands/migrate/app.py +54 -0
  45. aury/boot/commands/migrate/commands.py +303 -0
  46. aury/boot/commands/scheduler.py +124 -0
  47. aury/boot/commands/server/__init__.py +21 -0
  48. aury/boot/commands/server/app.py +541 -0
  49. aury/boot/commands/templates/generate/api.py.tpl +105 -0
  50. aury/boot/commands/templates/generate/model.py.tpl +17 -0
  51. aury/boot/commands/templates/generate/repository.py.tpl +19 -0
  52. aury/boot/commands/templates/generate/schema.py.tpl +29 -0
  53. aury/boot/commands/templates/generate/service.py.tpl +48 -0
  54. aury/boot/commands/templates/project/CLI.md.tpl +92 -0
  55. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +1397 -0
  56. aury/boot/commands/templates/project/README.md.tpl +111 -0
  57. aury/boot/commands/templates/project/admin_console_init.py.tpl +50 -0
  58. aury/boot/commands/templates/project/config.py.tpl +30 -0
  59. aury/boot/commands/templates/project/conftest.py.tpl +26 -0
  60. aury/boot/commands/templates/project/env.example.tpl +213 -0
  61. aury/boot/commands/templates/project/gitignore.tpl +128 -0
  62. aury/boot/commands/templates/project/main.py.tpl +41 -0
  63. aury/boot/commands/templates/project/modules/api.py.tpl +19 -0
  64. aury/boot/commands/templates/project/modules/exceptions.py.tpl +84 -0
  65. aury/boot/commands/templates/project/modules/schedules.py.tpl +18 -0
  66. aury/boot/commands/templates/project/modules/tasks.py.tpl +20 -0
  67. aury/boot/commands/worker.py +143 -0
  68. aury/boot/common/__init__.py +35 -0
  69. aury/boot/common/exceptions/__init__.py +114 -0
  70. aury/boot/common/i18n/__init__.py +16 -0
  71. aury/boot/common/i18n/translator.py +272 -0
  72. aury/boot/common/logging/__init__.py +716 -0
  73. aury/boot/contrib/__init__.py +10 -0
  74. aury/boot/contrib/admin_console/__init__.py +18 -0
  75. aury/boot/contrib/admin_console/auth.py +137 -0
  76. aury/boot/contrib/admin_console/discovery.py +69 -0
  77. aury/boot/contrib/admin_console/install.py +172 -0
  78. aury/boot/contrib/admin_console/utils.py +44 -0
  79. aury/boot/domain/__init__.py +79 -0
  80. aury/boot/domain/exceptions/__init__.py +132 -0
  81. aury/boot/domain/models/__init__.py +51 -0
  82. aury/boot/domain/models/base.py +69 -0
  83. aury/boot/domain/models/mixins.py +135 -0
  84. aury/boot/domain/models/models.py +96 -0
  85. aury/boot/domain/pagination/__init__.py +279 -0
  86. aury/boot/domain/repository/__init__.py +23 -0
  87. aury/boot/domain/repository/impl.py +423 -0
  88. aury/boot/domain/repository/interceptors.py +47 -0
  89. aury/boot/domain/repository/interface.py +106 -0
  90. aury/boot/domain/repository/query_builder.py +348 -0
  91. aury/boot/domain/service/__init__.py +11 -0
  92. aury/boot/domain/service/base.py +73 -0
  93. aury/boot/domain/transaction/__init__.py +404 -0
  94. aury/boot/infrastructure/__init__.py +104 -0
  95. aury/boot/infrastructure/cache/__init__.py +31 -0
  96. aury/boot/infrastructure/cache/backends.py +348 -0
  97. aury/boot/infrastructure/cache/base.py +68 -0
  98. aury/boot/infrastructure/cache/exceptions.py +37 -0
  99. aury/boot/infrastructure/cache/factory.py +94 -0
  100. aury/boot/infrastructure/cache/manager.py +274 -0
  101. aury/boot/infrastructure/database/__init__.py +39 -0
  102. aury/boot/infrastructure/database/config.py +71 -0
  103. aury/boot/infrastructure/database/exceptions.py +44 -0
  104. aury/boot/infrastructure/database/manager.py +317 -0
  105. aury/boot/infrastructure/database/query_tools/__init__.py +164 -0
  106. aury/boot/infrastructure/database/strategies/__init__.py +198 -0
  107. aury/boot/infrastructure/di/__init__.py +15 -0
  108. aury/boot/infrastructure/di/container.py +393 -0
  109. aury/boot/infrastructure/events/__init__.py +33 -0
  110. aury/boot/infrastructure/events/bus.py +362 -0
  111. aury/boot/infrastructure/events/config.py +52 -0
  112. aury/boot/infrastructure/events/consumer.py +134 -0
  113. aury/boot/infrastructure/events/middleware.py +51 -0
  114. aury/boot/infrastructure/events/models.py +63 -0
  115. aury/boot/infrastructure/monitoring/__init__.py +529 -0
  116. aury/boot/infrastructure/scheduler/__init__.py +19 -0
  117. aury/boot/infrastructure/scheduler/exceptions.py +37 -0
  118. aury/boot/infrastructure/scheduler/manager.py +478 -0
  119. aury/boot/infrastructure/storage/__init__.py +38 -0
  120. aury/boot/infrastructure/storage/base.py +164 -0
  121. aury/boot/infrastructure/storage/exceptions.py +37 -0
  122. aury/boot/infrastructure/storage/factory.py +88 -0
  123. aury/boot/infrastructure/tasks/__init__.py +24 -0
  124. aury/boot/infrastructure/tasks/config.py +45 -0
  125. aury/boot/infrastructure/tasks/constants.py +37 -0
  126. aury/boot/infrastructure/tasks/exceptions.py +37 -0
  127. aury/boot/infrastructure/tasks/manager.py +490 -0
  128. aury/boot/testing/__init__.py +24 -0
  129. aury/boot/testing/base.py +122 -0
  130. aury/boot/testing/client.py +163 -0
  131. aury/boot/testing/factory.py +154 -0
  132. aury/boot/toolkit/__init__.py +21 -0
  133. aury/boot/toolkit/http/__init__.py +367 -0
  134. {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/METADATA +3 -2
  135. aury_boot-0.0.3.dist-info/RECORD +137 -0
  136. aury_boot-0.0.2.dist-info/RECORD +0 -5
  137. {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/WHEEL +0 -0
  138. {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,541 @@
1
+ """服务器运行命令实现。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ import sys
8
+ from typing import TYPE_CHECKING
9
+
10
+ import typer
11
+
12
+ if TYPE_CHECKING:
13
+ from aury.boot.application.app.base import FoundationApp
14
+
15
+ # 创建 Typer 应用
16
+ app = typer.Typer(
17
+ name="server",
18
+ help="ASGI 服务器管理工具",
19
+ add_completion=False,
20
+ )
21
+
22
+
23
+ def _detect_app_module() -> str:
24
+ """自动检测应用模块路径。
25
+
26
+ 检测顺序:
27
+ 1. 环境变量 APP_MODULE
28
+ 2. pyproject.toml 的 [tool.aury].app
29
+ 3. 安装包的 entry points: [project.entry-points."aury.app"]
30
+ 4. 默认 main:app
31
+
32
+ 注意:main.py 默认在项目根目录。
33
+ """
34
+ # 1. 环境变量优先
35
+ if env_app := os.environ.get("APP_MODULE"):
36
+ return env_app
37
+
38
+ # 2. 读取 pyproject.toml 配置
39
+ try:
40
+ from ..config import get_project_config
41
+ cfg = get_project_config()
42
+ if cfg.app:
43
+ return cfg.app
44
+ except Exception:
45
+ pass
46
+
47
+ # 3. 读取安装包 entry points(生产环境常用)
48
+ try:
49
+ from importlib.metadata import entry_points
50
+ eps = entry_points(group="aury.app")
51
+ # 优先名为 default 的项,否则取第一个
52
+ if eps:
53
+ ep = next((e for e in eps if e.name == "default"), eps[0])
54
+ return ep.value
55
+ except Exception:
56
+ pass
57
+
58
+ # 4. 默认
59
+ return "main:app"
60
+
61
+
62
+ def _get_app_instance(app_path: str | None = None) -> FoundationApp:
63
+ """动态导入并获取应用实例。
64
+
65
+ Args:
66
+ app_path: 应用模块路径,格式为 "module.path:variable"
67
+ 例如: "main:app", "myproject.main:application"
68
+ 如果不提供,会自动检测
69
+
70
+ Returns:
71
+ FoundationApp: 应用实例
72
+
73
+ Raises:
74
+ SystemExit: 如果无法找到应用
75
+ """
76
+ import importlib
77
+
78
+ # 自动检测应用模块
79
+ if app_path is None:
80
+ app_path = _detect_app_module()
81
+
82
+ # 解析模块路径
83
+ if ":" not in app_path:
84
+ typer.echo(f"❌ 错误:无效的 app 路径格式: {app_path}", err=True)
85
+ typer.echo("格式应为: module.path:variable,例如: main:app", err=True)
86
+ raise typer.Exit(1)
87
+
88
+ module_path, var_name = app_path.rsplit(":", 1)
89
+
90
+ try:
91
+ # 添加当前工作目录到 sys.path
92
+ cwd = os.getcwd()
93
+ if cwd not in sys.path:
94
+ sys.path.insert(0, cwd)
95
+
96
+ # 导入模块
97
+ module = importlib.import_module(module_path)
98
+
99
+ # 获取 app 实例
100
+ if not hasattr(module, var_name):
101
+ typer.echo(f"❌ 错误:模块 {module_path} 中找不到变量 {var_name}", err=True)
102
+ raise typer.Exit(1)
103
+
104
+ app_instance = getattr(module, var_name)
105
+ return app_instance
106
+
107
+ except ImportError as e:
108
+ typer.echo(f"❌ 错误:无法导入模块 {module_path}", err=True)
109
+ typer.echo(f" {e}", err=True)
110
+ typer.echo("请确保在项目根目录运行此命令", err=True)
111
+ raise typer.Exit(1) from e
112
+
113
+
114
+ @app.command()
115
+ def run(
116
+ app_path: str | None = typer.Option(
117
+ None,
118
+ "--app",
119
+ "-a",
120
+ envvar="APP_MODULE",
121
+ help="应用模块路径,格式: module.path:variable(默认自动检测)",
122
+ ),
123
+ host: str | None = typer.Option(
124
+ None,
125
+ "--host",
126
+ "-h",
127
+ help="监听地址(默认使用配置文件中的 SERVER_HOST)",
128
+ ),
129
+ port: int | None = typer.Option(
130
+ None,
131
+ "--port",
132
+ "-p",
133
+ help="监听端口(默认使用配置文件中的 SERVER_PORT)",
134
+ ),
135
+ workers: int | None = typer.Option(
136
+ None,
137
+ "--workers",
138
+ "-w",
139
+ help="工作进程数(默认使用配置文件中的 SERVER_WORKERS)",
140
+ ),
141
+ reload: bool = typer.Option(
142
+ False,
143
+ "--reload",
144
+ help="启用热重载(开发模式)",
145
+ ),
146
+ reload_dir: list[str] | None = typer.Option(
147
+ None,
148
+ "--reload-dir",
149
+ help="热重载监控目录(可以指定多次)",
150
+ ),
151
+ debug: bool = typer.Option(
152
+ False,
153
+ "--debug",
154
+ help="启用调试模式",
155
+ ),
156
+ loop: str = typer.Option(
157
+ "auto",
158
+ "--loop",
159
+ help="事件循环实现",
160
+ ),
161
+ http: str = typer.Option(
162
+ "auto",
163
+ "--http",
164
+ help="HTTP 协议版本",
165
+ ),
166
+ ssl_keyfile: str | None = typer.Option(
167
+ None,
168
+ "--ssl-keyfile",
169
+ help="SSL 密钥文件路径",
170
+ ),
171
+ ssl_certfile: str | None = typer.Option(
172
+ None,
173
+ "--ssl-certfile",
174
+ help="SSL 证书文件路径",
175
+ ),
176
+ no_access_log: bool = typer.Option(
177
+ False,
178
+ "--no-access-log",
179
+ help="禁用访问日志",
180
+ ),
181
+ ) -> None:
182
+ """运行开发/生产服务器。
183
+
184
+ 配置优先级: 命令行参数 > .env/环境变量 > 默认值
185
+
186
+ 示例:
187
+
188
+ # 指定 app 模块
189
+ aury server run --app myproject.main:app
190
+
191
+ # 开发模式(热重载)
192
+ aury server run --reload
193
+
194
+ # 生产模式(多进程)
195
+ aury server run --workers 4
196
+
197
+ # HTTPS
198
+ aury server run --ssl-keyfile key.pem --ssl-certfile cert.pem
199
+ """
200
+ from aury.boot.application.server import ApplicationServer
201
+
202
+ app_instance = _get_app_instance(app_path)
203
+
204
+ # 优先使用命令行参数,否则使用 app 配置
205
+ server_host = host if host is not None else app_instance.config.server.host
206
+ server_port = port if port is not None else app_instance.config.server.port
207
+ server_workers = workers if workers is not None else app_instance.config.server.workers
208
+
209
+ # 创建服务器配置
210
+ reload_dirs = reload_dir if reload_dir else None
211
+
212
+ typer.echo("🚀 启动服务器...")
213
+ typer.echo(f" 地址: http://{server_host}:{server_port}")
214
+ typer.echo(f" 工作进程: {server_workers}")
215
+ typer.echo(f" 热重载: {'✅' if reload else '❌'}")
216
+ typer.echo(f" 调试模式: {'✅' if debug else '❌'}")
217
+
218
+ if reload:
219
+ typer.echo(f" 监控目录: {reload_dirs or ['./']}")
220
+
221
+ # 创建并运行服务器
222
+ try:
223
+ if reload:
224
+ # 热重载模式:直接使用 uvicorn,传递 app 字符串路径
225
+ import uvicorn
226
+ app_module_path = app_path or _detect_app_module()
227
+ uvicorn.run(
228
+ app=app_module_path,
229
+ host=server_host,
230
+ port=server_port,
231
+ reload=True,
232
+ reload_dirs=reload_dirs,
233
+ log_level="debug" if debug else "info",
234
+ )
235
+ else:
236
+ # 非热重载模式:使用 ApplicationServer
237
+ server = ApplicationServer(
238
+ app=app_instance,
239
+ host=server_host,
240
+ port=server_port,
241
+ workers=server_workers,
242
+ reload=False,
243
+ loop=loop,
244
+ http=http,
245
+ debug=debug,
246
+ access_log=not no_access_log,
247
+ ssl_keyfile=ssl_keyfile,
248
+ ssl_certfile=ssl_certfile,
249
+ )
250
+ server.run()
251
+ except KeyboardInterrupt:
252
+ typer.echo("\n👋 服务器已停止")
253
+ except Exception as e:
254
+ typer.echo(f"❌ 错误:{e}", err=True)
255
+ raise typer.Exit(1) from e
256
+
257
+
258
+ @app.command()
259
+ def dev(
260
+ app_path: str | None = typer.Option(
261
+ None,
262
+ "--app",
263
+ "-a",
264
+ envvar="APP_MODULE",
265
+ help="应用模块路径,格式: module.path:variable(默认自动检测)",
266
+ ),
267
+ host: str | None = typer.Option(
268
+ None,
269
+ "--host",
270
+ "-h",
271
+ help="监听地址(默认使用配置文件中的 SERVER_HOST)",
272
+ ),
273
+ port: int | None = typer.Option(
274
+ None,
275
+ "--port",
276
+ "-p",
277
+ help="监听端口(默认使用配置文件中的 SERVER_PORT)",
278
+ ),
279
+ reload_include: list[str] | None = typer.Option(
280
+ None,
281
+ "--include",
282
+ help="追加监控的文件模式(可多次指定,如 --include '*.jinja2')",
283
+ ),
284
+ reload_exclude: list[str] | None = typer.Option(
285
+ None,
286
+ "--exclude",
287
+ help="追加排除的文件模式(可多次指定,如 --exclude 'static/*')",
288
+ ),
289
+ ) -> None:
290
+ """启动开发服务器(热重载)。
291
+
292
+ 快捷命令,相当于 run --reload --debug
293
+
294
+ 配置优先级: 命令行参数 > .env/环境变量 > 默认值
295
+
296
+ 示例:
297
+ aury server dev
298
+ aury server dev --app myproject.main:app
299
+ aury server dev --port 9000
300
+ """
301
+ app_instance = _get_app_instance(app_path)
302
+
303
+ # 优先使用命令行参数,否则使用 app 配置
304
+ server_host = host if host is not None else app_instance.config.server.host
305
+ server_port = port if port is not None else app_instance.config.server.port
306
+
307
+ # 构建默认监控目录:优先仅监控项目包目录,避免监控根目录导致日志等文件触发重载
308
+ cwd = Path.cwd()
309
+ reload_dirs: list[str] = []
310
+
311
+ cfg = None
312
+ try:
313
+ from ..config import get_project_config
314
+ cfg = get_project_config()
315
+ if cfg.has_package:
316
+ pkg_path = cwd / cfg.package
317
+ if pkg_path.exists():
318
+ reload_dirs.append(str(pkg_path))
319
+ except Exception:
320
+ pass
321
+
322
+ # 如果没有检测到包目录,则退回到当前目录(单文件/平铺项目)
323
+ if not reload_dirs:
324
+ reload_dirs = [str(cwd)]
325
+
326
+ # 去重
327
+ seen = set()
328
+ reload_dirs = [d for d in reload_dirs if not (d in seen or seen.add(d))]
329
+
330
+ # 获取 app 模块路径(热重载需要字符串格式)
331
+ app_module_path = app_path or _detect_app_module()
332
+
333
+ typer.echo("🚀 启动开发服务器...")
334
+ typer.echo(f" 地址: http://{server_host}:{server_port}")
335
+ typer.echo(" 工作进程: 1")
336
+ typer.echo(" 热重载: ✅")
337
+ typer.echo(" 调试模式: ✅")
338
+ typer.echo(f" 监控目录: {reload_dirs}")
339
+ typer.echo(f" 应用模块: {app_module_path}")
340
+
341
+ # 在应用启动完成后打印一次服务地址
342
+ try:
343
+ app_instance.add_event_handler(
344
+ "startup",
345
+ lambda: typer.echo(f"✅ 服务已就绪: http://{server_host}:{server_port}"),
346
+ )
347
+ except Exception:
348
+ pass
349
+
350
+ # 默认包含/排除规则(watchfiles 支持)
351
+ reload_includes = [
352
+ "*.py",
353
+ "*.pyi",
354
+ "*.ini",
355
+ "*.toml",
356
+ "*.yaml",
357
+ "*.yml",
358
+ "*.json",
359
+ "*.env",
360
+ "*.cfg",
361
+ "*.conf",
362
+ # 常见模板与静态资源(如需更少重载,可通过 --exclude 精确排除)
363
+ "*.jinja2",
364
+ "*.jinja",
365
+ "*.j2",
366
+ "*.html",
367
+ "*.htm",
368
+ "*.sql",
369
+ "*.graphql",
370
+ # 前端常见类型(node_modules 已排除)
371
+ "*.ts",
372
+ "*.tsx",
373
+ "*.js",
374
+ "*.jsx",
375
+ "*.vue",
376
+ "*.css",
377
+ "*.scss",
378
+ "*.sass",
379
+ ]
380
+ reload_excludes = [
381
+ "logs/*",
382
+ "*.log",
383
+ "*.log.*",
384
+ "migrations/versions/*",
385
+ "alembic.ini",
386
+ "__pycache__/*",
387
+ ".pytest_cache/*",
388
+ ".mypy_cache/*",
389
+ ".ruff_cache/*",
390
+ ".git/*",
391
+ ".venv/*",
392
+ "dist/*",
393
+ "build/*",
394
+ "coverage/*",
395
+ "node_modules/*",
396
+ ]
397
+
398
+ # 追加用户自定义模式
399
+ if reload_include:
400
+ reload_includes.extend(reload_include)
401
+ if reload_exclude:
402
+ reload_excludes.extend(reload_exclude)
403
+
404
+ typer.echo(f" 监控包含: {reload_includes}")
405
+ typer.echo(f" 监控排除: {reload_excludes}")
406
+
407
+ # 提示将使用的热重载实现
408
+ try:
409
+ import importlib
410
+ importlib.import_module("watchfiles")
411
+ typer.echo(" 重载引擎: watchfiles ✅")
412
+ except Exception:
413
+ typer.echo(" 重载引擎: watchdog/stat ❌ (建议安装: uv add watchfiles)")
414
+
415
+ try:
416
+ import os as os_module
417
+ os_module.environ["AURIMYTH_RELOAD"] = "1"
418
+
419
+ # 热重载模式下,直接使用 uvicorn,传递 app 字符串路径
420
+ import uvicorn
421
+ uvicorn.run(
422
+ app=app_module_path,
423
+ host=server_host,
424
+ port=server_port,
425
+ reload=True,
426
+ reload_dirs=reload_dirs,
427
+ reload_includes=reload_includes,
428
+ reload_excludes=reload_excludes,
429
+ log_level="info",
430
+ )
431
+ except KeyboardInterrupt:
432
+ typer.echo("\n👋 服务器已停止")
433
+ except Exception as e:
434
+ typer.echo(f"❌ 错误:{e}", err=True)
435
+ raise typer.Exit(1) from e
436
+
437
+
438
+ @app.command()
439
+ def prod(
440
+ app_path: str | None = typer.Option(
441
+ None,
442
+ "--app",
443
+ "-a",
444
+ envvar="APP_MODULE",
445
+ help="应用模块路径,格式: module.path:variable(默认自动检测)",
446
+ ),
447
+ host: str | None = typer.Option(
448
+ None,
449
+ "--host",
450
+ "-h",
451
+ help="监听地址(默认使用配置文件中的 SERVER_HOST,或 0.0.0.0)",
452
+ ),
453
+ port: int | None = typer.Option(
454
+ None,
455
+ "--port",
456
+ "-p",
457
+ help="监听端口(默认使用配置文件中的 SERVER_PORT)",
458
+ ),
459
+ workers: int | None = typer.Option(
460
+ None,
461
+ "--workers",
462
+ "-w",
463
+ help="工作进程数(默认使用配置文件中的 SERVER_WORKERS,或 CPU 核心数)",
464
+ ),
465
+ ) -> None:
466
+ """启动生产服务器(多进程)。
467
+
468
+ 快捷命令,相当于 run --workers <cpu_count>
469
+
470
+ 配置优先级: 命令行参数 > .env/环境变量 > 默认值
471
+
472
+ 示例:
473
+ aury server prod
474
+ aury server prod --app myproject.main:app
475
+ aury server prod --workers 8
476
+ """
477
+ import os as os_module
478
+
479
+ from aury.boot.application.server import ApplicationServer
480
+
481
+ app_instance = _get_app_instance(app_path)
482
+
483
+ # 优先使用命令行参数,否则使用 app 配置
484
+ server_host = host if host is not None else app_instance.config.server.host
485
+
486
+ # 生产模式:如果是默认的 127.0.0.1,自动改成 0.0.0.0(适合 Docker/生产环境)
487
+ # 用户如果明确通过命令行指定 --host 127.0.0.1,则会尊重
488
+ if host is None and server_host == "127.0.0.1":
489
+ server_host = "0.0.0.0"
490
+
491
+ server_port = port if port is not None else app_instance.config.server.port
492
+ server_workers = workers if workers is not None else app_instance.config.server.workers
493
+
494
+ # 如果配置中 workers 也是默认值 1,则使用 CPU 核心数
495
+ if server_workers <= 1:
496
+ server_workers = os_module.cpu_count() or 4
497
+
498
+ typer.echo("🚀 启动生产服务器...")
499
+ typer.echo(f" 地址: http://{server_host}:{server_port}")
500
+ typer.echo(f" 工作进程: {server_workers}")
501
+ typer.echo(" 热重载: ❌")
502
+ typer.echo(" 调试模式: ❌")
503
+
504
+ try:
505
+ server = ApplicationServer(
506
+ app=app_instance,
507
+ host=server_host,
508
+ port=server_port,
509
+ workers=server_workers,
510
+ reload=False,
511
+ loop="auto",
512
+ http="auto",
513
+ debug=False,
514
+ access_log=True,
515
+ )
516
+ server.run()
517
+ except KeyboardInterrupt:
518
+ typer.echo("\n👋 服务器已停止")
519
+ except Exception as e:
520
+ typer.echo(f"❌ 错误:{e}", err=True)
521
+ raise typer.Exit(1) from e
522
+
523
+
524
+ def server_cli() -> None:
525
+ """CLI 入口点。
526
+
527
+ 使用示例:
528
+ if __name__ == "__main__":
529
+ server_cli()
530
+ """
531
+ app()
532
+
533
+
534
+ __all__ = [
535
+ "app",
536
+ "dev",
537
+ "prod",
538
+ "run",
539
+ "server_cli",
540
+ ]
541
+
@@ -0,0 +1,105 @@
1
+ """{class_name} API 路由。"""
2
+
3
+ {uuid_import}from fastapi import APIRouter, Depends
4
+ from sqlalchemy.ext.asyncio import AsyncSession
5
+
6
+ from aury.boot.application.interfaces.egress import (
7
+ BaseResponse,
8
+ Pagination,
9
+ PaginationResponse,
10
+ )
11
+ from aury.boot.infrastructure.database import DatabaseManager
12
+
13
+ from {import_prefix}schemas.{file_name} import (
14
+ {class_name}Create,
15
+ {class_name}Response,
16
+ {class_name}Update,
17
+ )
18
+ from {import_prefix}services.{file_name}_service import {class_name}Service
19
+
20
+ router = APIRouter(prefix="/v1/{var_name_plural}", tags=["{class_name}"])
21
+ db_manager = DatabaseManager.get_instance()
22
+
23
+
24
+ async def get_service(
25
+ session: AsyncSession = Depends(db_manager.get_session),
26
+ ) -> {class_name}Service:
27
+ """获取服务实例。"""
28
+ return {class_name}Service(session)
29
+
30
+
31
+ @router.get("", response_model=PaginationResponse[{class_name}Response])
32
+ async def list_{var_name_plural}(
33
+ skip: int = 0,
34
+ limit: int = 20,
35
+ service: {class_name}Service = Depends(get_service),
36
+ ) -> PaginationResponse[{class_name}Response]:
37
+ """获取 {class_name} 列表。"""
38
+ items = await service.list(skip=skip, limit=limit)
39
+ return PaginationResponse(
40
+ code=200,
41
+ message="获取成功",
42
+ data=Pagination(
43
+ total=len(items),
44
+ items=[{class_name}Response.model_validate(item) for item in items],
45
+ page=skip // limit + 1,
46
+ size=limit,
47
+ ),
48
+ )
49
+
50
+
51
+ @router.get("/{{id}}", response_model=BaseResponse[{class_name}Response])
52
+ async def get_{var_name}(
53
+ id: {id_type},
54
+ service: {class_name}Service = Depends(get_service),
55
+ ) -> BaseResponse[{class_name}Response]:
56
+ """获取 {class_name} 详情。"""
57
+ entity = await service.get(id)
58
+ return BaseResponse(
59
+ code=200,
60
+ message="获取成功",
61
+ data={class_name}Response.model_validate(entity),
62
+ )
63
+
64
+
65
+ @router.post("", response_model=BaseResponse[{class_name}Response])
66
+ async def create_{var_name}(
67
+ data: {class_name}Create,
68
+ service: {class_name}Service = Depends(get_service),
69
+ ) -> BaseResponse[{class_name}Response]:
70
+ """创建 {class_name}。"""
71
+ entity = await service.create(data)
72
+ return BaseResponse(
73
+ code=200,
74
+ message="创建成功",
75
+ data={class_name}Response.model_validate(entity),
76
+ )
77
+
78
+
79
+ @router.put("/{{id}}", response_model=BaseResponse[{class_name}Response])
80
+ async def update_{var_name}(
81
+ id: {id_type},
82
+ data: {class_name}Update,
83
+ service: {class_name}Service = Depends(get_service),
84
+ ) -> BaseResponse[{class_name}Response]:
85
+ """更新 {class_name}。"""
86
+ entity = await service.update(id, data)
87
+ return BaseResponse(
88
+ code=200,
89
+ message="更新成功",
90
+ data={class_name}Response.model_validate(entity),
91
+ )
92
+
93
+
94
+ @router.delete("/{{id}}", response_model=BaseResponse[None])
95
+ async def delete_{var_name}(
96
+ id: {id_type},
97
+ service: {class_name}Service = Depends(get_service),
98
+ ) -> BaseResponse[None]:
99
+ """删除 {class_name}。"""
100
+ await service.delete(id)
101
+ return BaseResponse(
102
+ code=200,
103
+ message="删除成功",
104
+ data=None,
105
+ )
@@ -0,0 +1,17 @@
1
+ """{class_name} 数据模型。"""
2
+
3
+ from sqlalchemy import {imports_str}
4
+ from sqlalchemy.orm import Mapped, mapped_column
5
+
6
+ from aury.boot.domain.models import {base_class}
7
+
8
+
9
+ class {class_name}({base_class}):
10
+ """{class_name} 模型。
11
+
12
+ {base_doc}
13
+ """
14
+
15
+ __tablename__ = "{table_name}"
16
+
17
+ {fields_str}
@@ -0,0 +1,19 @@
1
+ """{class_name} 数据访问层。"""
2
+
3
+ from aury.boot.domain.repository.impl import BaseRepository
4
+
5
+ from {import_prefix}models.{file_name} import {class_name}
6
+
7
+
8
+ class {class_name}Repository(BaseRepository[{class_name}]):
9
+ """{class_name} 仓储。
10
+
11
+ 继承 BaseRepository 自动获得:
12
+ - get(id): 按 ID 获取
13
+ - get_by(**filters): 按条件获取单个
14
+ - list(skip, limit, **filters): 获取列表
15
+ - paginate(params, **filters): 分页获取
16
+ - create(data): 创建
17
+ - update(entity, data): 更新
18
+ - delete(entity, soft=True): 删除
19
+ """{methods_str}
@@ -0,0 +1,29 @@
1
+ """{class_name} Pydantic 模型。"""
2
+
3
+ {imports_str}from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class {class_name}Base(BaseModel):
7
+ """{class_name} 基础模型。"""
8
+
9
+ {base_fields_str}
10
+
11
+
12
+ class {class_name}Create({class_name}Base):
13
+ """创建 {class_name} 请求。"""
14
+
15
+ pass
16
+
17
+
18
+ class {class_name}Update(BaseModel):
19
+ """更新 {class_name} 请求。"""
20
+
21
+ {update_fields_str}
22
+
23
+
24
+ class {class_name}Response({class_name}Base):
25
+ """{class_name} 响应。"""
26
+
27
+ {response_extra}
28
+
29
+ model_config = ConfigDict(from_attributes=True)