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.
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 +892 -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.4.dist-info}/METADATA +3 -2
  135. aury_boot-0.0.4.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.4.dist-info}/WHEEL +0 -0
  138. {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,218 @@
1
+ """服务发现模块。
2
+
3
+ 支持多种服务发现方式:
4
+ - 配置文件(推荐,通过 BaseConfig.rpc_client.services)
5
+ - DNS 解析(统一处理 K8s/Docker Compose,通过服务名自动解析)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from aury.boot.common.logging import logger
13
+
14
+ if TYPE_CHECKING:
15
+ from aury.boot.application.config import BaseConfig
16
+
17
+
18
+ class ServiceDiscovery:
19
+ """服务发现抽象基类。
20
+
21
+ 所有服务发现实现都应该继承此类。
22
+ """
23
+
24
+ def resolve(self, service_name: str) -> str | None:
25
+ """解析服务地址。
26
+
27
+ Args:
28
+ service_name: 服务名称
29
+
30
+ Returns:
31
+ str | None: 服务地址(URL),如果无法解析返回 None
32
+ """
33
+ raise NotImplementedError
34
+
35
+
36
+ class ConfigServiceDiscovery(ServiceDiscovery):
37
+ """基于配置文件的服务发现。
38
+
39
+ 从配置文件中读取服务地址映射。
40
+
41
+ 配置格式:
42
+ ```python
43
+ services = {
44
+ "user-service": "http://user-service:8000",
45
+ "order-service": "http://order-service:8001",
46
+ }
47
+ ```
48
+ """
49
+
50
+ def __init__(self, service_config: dict[str, str] | None = None) -> None:
51
+ """初始化配置服务发现。
52
+
53
+ Args:
54
+ service_config: 服务配置字典 {service_name: url}
55
+ """
56
+ self._services: dict[str, str] = service_config or {}
57
+
58
+ def resolve(self, service_name: str) -> str | None:
59
+ """从配置中解析服务地址。"""
60
+ url = self._services.get(service_name)
61
+ if url:
62
+ logger.debug(f"从配置解析服务: {service_name} -> {url}")
63
+ return url
64
+ logger.warning(f"配置中未找到服务: {service_name}")
65
+ return None
66
+
67
+ def register(self, service_name: str, url: str) -> None:
68
+ """注册服务地址。
69
+
70
+ Args:
71
+ service_name: 服务名称
72
+ url: 服务地址
73
+ """
74
+ self._services[service_name] = url
75
+ logger.info(f"注册服务: {service_name} -> {url}")
76
+
77
+
78
+
79
+
80
+ class DNSServiceDiscovery(ServiceDiscovery):
81
+ """DNS 服务发现(统一处理 K8s/Docker Compose)。
82
+
83
+ 直接使用服务名进行 DNS 解析:
84
+ - K8s: 自动使用 service-name(K8s DNS 会在同一 namespace 内解析)
85
+ - Docker Compose: 直接使用 service-name
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ scheme: str = "http",
91
+ default_port: int = 80,
92
+ ) -> None:
93
+ """初始化 DNS 服务发现。
94
+
95
+ Args:
96
+ scheme: 协议(http 或 https)
97
+ default_port: 默认端口
98
+ """
99
+ self._scheme = scheme
100
+ self._default_port = default_port
101
+
102
+ def resolve(self, service_name: str) -> str | None:
103
+ """使用 DNS 解析服务地址。
104
+
105
+ Args:
106
+ service_name: 服务名称(直接作为主机名,DNS 自动解析)
107
+ """
108
+ # 直接使用服务名作为主机名(DNS 自动处理)
109
+ url = f"{self._scheme}://{service_name}"
110
+
111
+ if self._default_port not in (80, 443):
112
+ url = f"{url}:{self._default_port}"
113
+
114
+ logger.debug(f"使用 DNS 解析服务: {service_name} -> {url}")
115
+ return url
116
+
117
+
118
+ class CompositeServiceDiscovery(ServiceDiscovery):
119
+ """组合服务发现。
120
+
121
+ 按优先级顺序尝试多种服务发现方式:
122
+ 1. 配置文件(最高优先级,通过 BaseConfig.rpc_client.services)
123
+ 2. DNS 解析(统一处理 K8s/Docker Compose,自动检测环境)
124
+ """
125
+
126
+ def __init__(
127
+ self,
128
+ config: "BaseConfig" | None = None,
129
+ ) -> None:
130
+ """初始化组合服务发现。
131
+
132
+ Args:
133
+ config: 应用配置(BaseConfig),如果为 None,则仅使用默认配置
134
+ """
135
+ self._discoveries: list[ServiceDiscovery] = []
136
+
137
+ # 从配置中获取 RPC 客户端设置
138
+ if config:
139
+ rpc_client_settings = config.rpc_client
140
+ else:
141
+ # 如果没有提供配置,使用默认配置
142
+ from aury.boot.application.config import RPCClientSettings
143
+ rpc_client_settings = RPCClientSettings()
144
+
145
+ # 1. 配置文件(最高优先级)
146
+ if rpc_client_settings.services:
147
+ self._discoveries.append(ConfigServiceDiscovery(rpc_client_settings.services))
148
+
149
+ # 2. DNS 解析(如果启用)
150
+ if rpc_client_settings.use_dns_fallback:
151
+ logger.info("启用 DNS 服务发现(统一处理 K8s/Docker Compose)")
152
+ self._discoveries.append(
153
+ DNSServiceDiscovery(
154
+ scheme=rpc_client_settings.dns_scheme,
155
+ default_port=rpc_client_settings.dns_port,
156
+ )
157
+ )
158
+
159
+ def resolve(self, service_name: str) -> str | None:
160
+ """按优先级顺序解析服务地址。"""
161
+ for discovery in self._discoveries:
162
+ url = discovery.resolve(service_name)
163
+ if url:
164
+ return url
165
+
166
+ logger.error(f"无法解析服务地址: {service_name}(已尝试所有服务发现方式)")
167
+ return None
168
+
169
+
170
+ # 全局服务发现实例
171
+ _global_discovery: ServiceDiscovery | None = None
172
+ _global_config: "BaseConfig | None" = None
173
+
174
+
175
+ def get_service_discovery(config: "BaseConfig | None" = None) -> ServiceDiscovery:
176
+ """获取全局服务发现实例。
177
+
178
+ 如果未初始化,使用配置创建组合服务发现。
179
+
180
+ Args:
181
+ config: 应用配置(可选),如果提供则使用此配置初始化
182
+
183
+ Returns:
184
+ ServiceDiscovery: 服务发现实例
185
+ """
186
+ global _global_discovery, _global_config
187
+
188
+ # 如果提供了新配置,更新全局配置
189
+ if config is not None:
190
+ _global_config = config
191
+
192
+ # 如果未初始化或配置已更新,重新创建
193
+ if _global_discovery is None or config is not None:
194
+ _global_discovery = CompositeServiceDiscovery(_global_config)
195
+
196
+ return _global_discovery
197
+
198
+
199
+ def set_service_discovery(discovery: ServiceDiscovery) -> None:
200
+ """设置全局服务发现实例。
201
+
202
+ Args:
203
+ discovery: 服务发现实例
204
+ """
205
+ global _global_discovery
206
+ _global_discovery = discovery
207
+ logger.info("已设置全局服务发现实例")
208
+
209
+
210
+ __all__ = [
211
+ "CompositeServiceDiscovery",
212
+ "ConfigServiceDiscovery",
213
+ "DNSServiceDiscovery",
214
+ "ServiceDiscovery",
215
+ "get_service_discovery",
216
+ "set_service_discovery",
217
+ ]
218
+
@@ -0,0 +1,13 @@
1
+ """调度器启动器模块。
2
+
3
+ 提供调度器独立进程的启动入口。
4
+ """
5
+
6
+ from .runner import run_scheduler, run_scheduler_sync
7
+
8
+ __all__ = [
9
+ "run_scheduler",
10
+ "run_scheduler_sync",
11
+ ]
12
+
13
+
@@ -0,0 +1,123 @@
1
+ """调度器启动器。
2
+
3
+ 提供调度器独立进程的启动入口。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ from collections.abc import Awaitable, Callable
10
+ import signal
11
+
12
+ from aury.boot.application.config import BaseConfig
13
+ from aury.boot.application.constants import ServiceType
14
+ from aury.boot.common.logging import logger
15
+ from aury.boot.infrastructure.scheduler import SchedulerManager
16
+
17
+
18
+ async def run_scheduler(
19
+ config: BaseConfig | None = None,
20
+ *,
21
+ register_jobs: Callable[[], None] | Callable[[], Awaitable[None]] | None = None,
22
+ ) -> None:
23
+ """运行调度器进程(独立模式)。
24
+
25
+ 当 SERVICE_TYPE=scheduler 时,使用此函数启动独立的调度器进程。
26
+
27
+ Args:
28
+ config: 应用配置(可选,默认从环境变量加载)
29
+ register_jobs: 注册任务的回调函数(可选)
30
+
31
+ 使用示例:
32
+ async def setup_jobs():
33
+ scheduler = SchedulerManager.get_instance()
34
+ scheduler.add_job(
35
+ func=my_task,
36
+ trigger="interval",
37
+ seconds=60
38
+ )
39
+
40
+ if __name__ == "__main__":
41
+ asyncio.run(run_scheduler(register_jobs=setup_jobs))
42
+ """
43
+ # 加载配置
44
+ if config is None:
45
+ config = BaseConfig()
46
+
47
+ service_config = config.service
48
+
49
+ # 验证服务类型
50
+ try:
51
+ service_type = ServiceType(service_config.service_type.lower())
52
+ except ValueError as e:
53
+ raise ValueError(f"无效的服务类型: {service_config.service_type}") from e
54
+
55
+ if service_type != ServiceType.SCHEDULER:
56
+ raise ValueError(
57
+ f"此函数仅用于 SERVICE_TYPE=scheduler,当前类型: {service_config.service_type}"
58
+ )
59
+
60
+ logger.info("启动调度器进程(独立模式)")
61
+
62
+ # 初始化调度器
63
+ scheduler_manager = SchedulerManager.get_instance()
64
+ await scheduler_manager.initialize()
65
+
66
+ # 注册任务
67
+ if register_jobs:
68
+ logger.info("注册定时任务...")
69
+ if asyncio.iscoroutinefunction(register_jobs):
70
+ await register_jobs()
71
+ else:
72
+ register_jobs()
73
+ jobs = scheduler_manager.get_jobs()
74
+ logger.info(f"已注册 {len(jobs)} 个定时任务")
75
+ else:
76
+ logger.warning("未提供任务注册函数,调度器将不执行任何任务")
77
+
78
+ # 启动调度器
79
+ scheduler_manager.start()
80
+ logger.info("调度器进程已启动,等待任务执行...")
81
+
82
+ # 设置信号处理,优雅关闭
83
+ def signal_handler(signum, frame):
84
+ logger.info(f"收到信号 {signum},正在关闭调度器...")
85
+ scheduler_manager.shutdown()
86
+ logger.info("调度器已关闭")
87
+ asyncio.get_event_loop().stop()
88
+
89
+ signal.signal(signal.SIGINT, signal_handler)
90
+ signal.signal(signal.SIGTERM, signal_handler)
91
+
92
+ # 保持进程运行
93
+ try:
94
+ # 使用 asyncio.Event 保持进程运行
95
+ stop_event = asyncio.Event()
96
+ await stop_event.wait()
97
+ except KeyboardInterrupt:
98
+ logger.info("收到中断信号,正在关闭...")
99
+ scheduler_manager.shutdown()
100
+ logger.info("调度器已关闭")
101
+
102
+
103
+ def run_scheduler_sync(
104
+ config: BaseConfig | None = None,
105
+ *,
106
+ register_jobs: Callable[[], None] | Callable[[], Awaitable[None]] | None = None,
107
+ ) -> None:
108
+ """运行调度器进程(同步版本)。
109
+
110
+ 这是 run_scheduler 的同步包装,方便直接调用。
111
+
112
+ Args:
113
+ config: 应用配置(可选,默认从环境变量加载)
114
+ register_jobs: 注册任务的回调函数(可选)
115
+ """
116
+ asyncio.run(run_scheduler(config=config, register_jobs=register_jobs))
117
+
118
+
119
+ __all__ = [
120
+ "run_scheduler",
121
+ "run_scheduler_sync",
122
+ ]
123
+
@@ -0,0 +1,296 @@
1
+ """ASGI 服务器管理。
2
+
3
+ 提供 uvicorn 服务器的完整集成。
4
+
5
+ 包含:
6
+ - ApplicationServer 类:服务器管理
7
+ - run_app 函数:快速启动
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import signal
14
+ import sys
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ from aury.boot.common.logging import logger
18
+
19
+ if TYPE_CHECKING:
20
+ from aury.boot.application.app.base import FoundationApp
21
+
22
+
23
+ class ApplicationServer:
24
+ """ASGI 应用服务器。
25
+
26
+ 基于 uvicorn,提供完整的服务器管理功能。
27
+
28
+ 使用示例:
29
+ app = FoundationApp()
30
+ server = ApplicationServer(
31
+ app=app,
32
+ host="0.0.0.0",
33
+ port=8000,
34
+ workers=4,
35
+ )
36
+ server.run()
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ app: FoundationApp,
42
+ *,
43
+ host: str = "127.0.0.1",
44
+ port: int = 8000,
45
+ workers: int = 1,
46
+ reload: bool = False,
47
+ reload_dirs: list[str] | None = None,
48
+ loop: str = "auto",
49
+ http: str = "auto",
50
+ interface: str = "auto",
51
+ debug: bool = False,
52
+ access_log: bool = True,
53
+ use_colors: bool | None = None,
54
+ log_config: dict | None = None,
55
+ ssl_keyfile: str | None = None,
56
+ ssl_certfile: str | None = None,
57
+ ssl_keyfile_password: str | None = None,
58
+ ssl_version: int | None = None,
59
+ ssl_cert_reqs: int | None = None,
60
+ ssl_ca_certs: str | None = None,
61
+ ssl_ciphers: str | None = None,
62
+ **kwargs,
63
+ ) -> None:
64
+ """初始化服务器。
65
+
66
+ Args:
67
+ app: FastAPI/FoundationApp 应用实例
68
+ host: 监听地址,默认 127.0.0.1
69
+ port: 监听端口,默认 8000
70
+ workers: 工作进程数,默认 1
71
+ reload: 是否启用热重载
72
+ reload_dirs: 热重载监控目录
73
+ loop: 事件循环实现 (auto/asyncio/uvloop)
74
+ http: HTTP 协议版本 (auto/h11/httptools)
75
+ interface: ASGI 接口版本 (auto/asgi3/asgi2)
76
+ debug: 是否启用调试模式
77
+ access_log: 是否记录访问日志
78
+ use_colors: 是否使用彩色输出
79
+ log_config: 自定义日志配置
80
+ ssl_keyfile: SSL 密钥文件路径
81
+ ssl_certfile: SSL 证书文件路径
82
+ ssl_keyfile_password: SSL 密钥密码
83
+ ssl_version: SSL 版本
84
+ ssl_cert_reqs: SSL 证书请求模式
85
+ ssl_ca_certs: SSL CA 证书文件
86
+ ssl_ciphers: SSL 密码套件
87
+ **kwargs: 传递给 uvicorn.Config 的其他参数
88
+ """
89
+ self.app = app
90
+ self.host = host
91
+ self.port = port
92
+ self.workers = workers
93
+ self.reload = reload
94
+ self.reload_dirs = reload_dirs
95
+ self.loop = loop
96
+ self.http = http
97
+ self.interface = interface
98
+ self.debug = debug
99
+ self.access_log = access_log
100
+ self.use_colors = use_colors
101
+ self.log_config = log_config or self._get_default_log_config()
102
+ self.ssl_keyfile = ssl_keyfile
103
+ self.ssl_certfile = ssl_certfile
104
+ self.ssl_keyfile_password = ssl_keyfile_password
105
+ self.ssl_version = ssl_version
106
+ self.ssl_cert_reqs = ssl_cert_reqs
107
+ self.ssl_ca_certs = ssl_ca_certs
108
+ self.ssl_ciphers = ssl_ciphers
109
+ self.extra_kwargs = kwargs
110
+
111
+ self.server: Any = None
112
+
113
+ def _get_default_log_config(self) -> dict:
114
+ """获取默认日志配置。
115
+
116
+ 整合 uvicorn 日志和框架日志。
117
+ """
118
+ import uvicorn
119
+ from uvicorn.config import LOGGING_CONFIG
120
+
121
+ config = LOGGING_CONFIG.copy()
122
+
123
+ # 调整日志格式
124
+ config["formatters"]["access"]["format"] = (
125
+ "%(asctime)s | %(client_addr)s - "
126
+ "%(request_line)s | %(status_code)s"
127
+ )
128
+ config["formatters"]["default"]["format"] = (
129
+ "%(asctime)s | %(levelname)s | %(name)s - %(message)s"
130
+ )
131
+
132
+ return config
133
+
134
+ def get_config(self) -> Any:
135
+ """获取 uvicorn 配置对象。
136
+
137
+ Returns:
138
+ uvicorn.Config: 服务器配置
139
+ """
140
+ import uvicorn
141
+
142
+ # 从 uvicorn 0.30.0 开始,Config.__init__ 不再接受 ``debug`` 参数,
143
+ # 这里仅在日志中使用 debug 标志,而不再传递给 uvicorn.Config,
144
+ # 以避免 "unexpected keyword argument 'debug'" 异常。
145
+ return uvicorn.Config(
146
+ app=self.app,
147
+ host=self.host,
148
+ port=self.port,
149
+ workers=self.workers,
150
+ reload=self.reload,
151
+ reload_dirs=self.reload_dirs,
152
+ loop=self.loop,
153
+ http=self.http,
154
+ interface=self.interface,
155
+ access_log=self.access_log,
156
+ use_colors=self.use_colors,
157
+ log_config=self.log_config,
158
+ ssl_keyfile=self.ssl_keyfile,
159
+ ssl_certfile=self.ssl_certfile,
160
+ ssl_keyfile_password=self.ssl_keyfile_password,
161
+ ssl_version=self.ssl_version,
162
+ ssl_cert_reqs=self.ssl_cert_reqs,
163
+ ssl_ca_certs=self.ssl_ca_certs,
164
+ ssl_ciphers=self.ssl_ciphers,
165
+ **self.extra_kwargs,
166
+ )
167
+
168
+ def run(self) -> None:
169
+ """运行服务器。
170
+
171
+ 阻塞调用,服务器会一直运行直到收到中断信号。
172
+ """
173
+ import uvicorn
174
+
175
+ config = self.get_config()
176
+ self.server = uvicorn.Server(config)
177
+
178
+ # 设置信号处理
179
+ self._setup_signal_handlers()
180
+
181
+ # 记录启动信息
182
+ self._log_startup_info()
183
+
184
+ # 运行服务器
185
+ try:
186
+ self.server.run()
187
+ except KeyboardInterrupt:
188
+ logger.info("服务器已停止(Ctrl+C)")
189
+ except Exception as exc:
190
+ logger.error(f"服务器错误: {exc}", exc_info=True)
191
+ sys.exit(1)
192
+
193
+ async def run_async(self) -> None:
194
+ """异步运行服务器。
195
+
196
+ 使用示例:
197
+ server = ApplicationServer(app)
198
+ await server.run_async()
199
+ """
200
+ import uvicorn
201
+
202
+ config = self.get_config()
203
+ self.server = uvicorn.Server(config)
204
+
205
+ # 记录启动信息
206
+ self._log_startup_info()
207
+
208
+ # 运行服务器
209
+ try:
210
+ await self.server.serve()
211
+ except KeyboardInterrupt:
212
+ logger.info("服务器已停止")
213
+ except Exception as exc:
214
+ logger.error(f"服务器错误: {exc}", exc_info=True)
215
+ sys.exit(1)
216
+
217
+ def _setup_signal_handlers(self) -> None:
218
+ """设置信号处理器。
219
+
220
+ 处理 SIGTERM 和 SIGINT 信号,确保优雅关闭。
221
+ """
222
+ def handle_signal(signum, frame):
223
+ logger.info(f"收到信号 {signum},正在关闭...")
224
+ if self.server:
225
+ self.server.should_exit = True
226
+
227
+ signal.signal(signal.SIGINT, handle_signal)
228
+ signal.signal(signal.SIGTERM, handle_signal)
229
+
230
+ def _log_startup_info(self) -> None:
231
+ """记录启动信息。"""
232
+ protocol = "https" if self.ssl_certfile else "http"
233
+ url = f"{protocol}://{self.host}:{self.port}"
234
+
235
+ logger.info(
236
+ f"服务器启动 | "
237
+ f"地址: {url} | "
238
+ f"工作进程: {self.workers} | "
239
+ f"热重载: {self.reload}"
240
+ )
241
+
242
+ if self.debug:
243
+ logger.warning("调试模式已启用")
244
+
245
+ if self.reload:
246
+ default_dirs = [os.getcwd()] if not self.reload_dirs else self.reload_dirs
247
+ logger.info(f"热重载监控目录: {default_dirs}")
248
+
249
+
250
+ def run_app(
251
+ app: FoundationApp,
252
+ *,
253
+ host: str | None = None,
254
+ port: int | None = None,
255
+ workers: int | None = None,
256
+ reload: bool = False,
257
+ **kwargs,
258
+ ) -> None:
259
+ """快速运行应用。
260
+
261
+ 从环境变量或参数读取配置。
262
+
263
+ 使用示例:
264
+ from aury.boot.application.server import run_app
265
+
266
+ app = FoundationApp()
267
+ run_app(app)
268
+
269
+ 环境变量支持:
270
+ SERVER_HOST: 监听地址
271
+ SERVER_PORT: 监听端口
272
+ SERVER_WORKERS: 工作进程数
273
+ SERVER_RELOAD: 是否热重载
274
+ """
275
+ # 从环境变量读取默认值
276
+ host = host or os.getenv("SERVER_HOST", "127.0.0.1")
277
+ port = port or int(os.getenv("SERVER_PORT", "8000"))
278
+ workers = workers or int(os.getenv("SERVER_WORKERS", "1"))
279
+ reload = reload or os.getenv("SERVER_RELOAD", "").lower() in ("true", "1", "yes")
280
+
281
+ server = ApplicationServer(
282
+ app=app,
283
+ host=host,
284
+ port=port,
285
+ workers=workers,
286
+ reload=reload,
287
+ **kwargs,
288
+ )
289
+ server.run()
290
+
291
+
292
+ __all__ = [
293
+ "ApplicationServer",
294
+ "run_app",
295
+ ]
296
+
@@ -0,0 +1,30 @@
1
+ """命令行工具模块。
2
+
3
+ 统一入口: aum
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ # 轻量配置模块可以直接导入
9
+ from .config import ProjectConfig, get_project_config, save_project_config
10
+
11
+
12
+ # 延迟导入 app 和 main,避免加载重型依赖
13
+ def __getattr__(name: str):
14
+ if name in ("app", "main"):
15
+ from .app import main as _main
16
+ if name == "main":
17
+ return _main
18
+ # app 通过 app 模块的 __getattr__ 获取
19
+ from . import app as app_module
20
+ return app_module.app
21
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
22
+
23
+
24
+ __all__ = [
25
+ "app",
26
+ "main",
27
+ "ProjectConfig",
28
+ "get_project_config",
29
+ "save_project_config",
30
+ ]