aury-boot 0.0.4__py3-none-any.whl → 0.0.7__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 (122) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +60 -36
  4. aury/boot/application/adapter/__init__.py +112 -0
  5. aury/boot/application/adapter/base.py +511 -0
  6. aury/boot/application/adapter/config.py +242 -0
  7. aury/boot/application/adapter/decorators.py +259 -0
  8. aury/boot/application/adapter/exceptions.py +202 -0
  9. aury/boot/application/adapter/http.py +325 -0
  10. aury/boot/application/app/__init__.py +12 -8
  11. aury/boot/application/app/base.py +12 -0
  12. aury/boot/application/app/components.py +137 -44
  13. aury/boot/application/app/middlewares.py +9 -4
  14. aury/boot/application/app/startup.py +249 -0
  15. aury/boot/application/config/__init__.py +36 -1
  16. aury/boot/application/config/multi_instance.py +216 -0
  17. aury/boot/application/config/settings.py +398 -149
  18. aury/boot/application/constants/components.py +6 -0
  19. aury/boot/application/errors/handlers.py +17 -3
  20. aury/boot/application/middleware/logging.py +21 -120
  21. aury/boot/application/rpc/__init__.py +2 -2
  22. aury/boot/commands/__init__.py +30 -10
  23. aury/boot/commands/app.py +131 -1
  24. aury/boot/commands/docs.py +104 -17
  25. aury/boot/commands/generate.py +22 -22
  26. aury/boot/commands/init.py +68 -17
  27. aury/boot/commands/server/app.py +2 -3
  28. aury/boot/commands/templates/project/AGENTS.md.tpl +221 -0
  29. aury/boot/commands/templates/project/README.md.tpl +2 -2
  30. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  31. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +184 -0
  32. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  33. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  34. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  35. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  36. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  37. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  38. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  39. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  40. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  41. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +131 -0
  42. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  43. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +104 -0
  44. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  45. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  46. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  47. aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
  48. aury/boot/commands/templates/project/config.py.tpl +10 -10
  49. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  50. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  51. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  52. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  53. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  54. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  55. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  56. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  57. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  58. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  59. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  60. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  61. aury/boot/common/logging/__init__.py +26 -674
  62. aury/boot/common/logging/context.py +132 -0
  63. aury/boot/common/logging/decorators.py +118 -0
  64. aury/boot/common/logging/format.py +315 -0
  65. aury/boot/common/logging/setup.py +214 -0
  66. aury/boot/contrib/admin_console/auth.py +2 -3
  67. aury/boot/contrib/admin_console/install.py +1 -1
  68. aury/boot/domain/models/mixins.py +48 -1
  69. aury/boot/domain/pagination/__init__.py +94 -0
  70. aury/boot/domain/repository/impl.py +1 -1
  71. aury/boot/domain/repository/interface.py +1 -1
  72. aury/boot/domain/transaction/__init__.py +8 -9
  73. aury/boot/infrastructure/__init__.py +86 -29
  74. aury/boot/infrastructure/cache/backends.py +102 -18
  75. aury/boot/infrastructure/cache/base.py +12 -0
  76. aury/boot/infrastructure/cache/manager.py +153 -91
  77. aury/boot/infrastructure/channel/__init__.py +24 -0
  78. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  79. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  80. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  81. aury/boot/infrastructure/channel/base.py +92 -0
  82. aury/boot/infrastructure/channel/manager.py +203 -0
  83. aury/boot/infrastructure/clients/__init__.py +22 -0
  84. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  85. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  86. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  87. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  88. aury/boot/infrastructure/clients/redis/config.py +51 -0
  89. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  90. aury/boot/infrastructure/database/config.py +7 -16
  91. aury/boot/infrastructure/database/manager.py +16 -38
  92. aury/boot/infrastructure/events/__init__.py +18 -21
  93. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  94. aury/boot/infrastructure/events/backends/memory.py +86 -0
  95. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  96. aury/boot/infrastructure/events/backends/redis.py +162 -0
  97. aury/boot/infrastructure/events/base.py +127 -0
  98. aury/boot/infrastructure/events/manager.py +224 -0
  99. aury/boot/infrastructure/mq/__init__.py +24 -0
  100. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  101. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  102. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  103. aury/boot/infrastructure/mq/base.py +143 -0
  104. aury/boot/infrastructure/mq/manager.py +239 -0
  105. aury/boot/infrastructure/scheduler/manager.py +7 -3
  106. aury/boot/infrastructure/storage/__init__.py +9 -9
  107. aury/boot/infrastructure/storage/base.py +17 -5
  108. aury/boot/infrastructure/storage/factory.py +0 -1
  109. aury/boot/infrastructure/tasks/__init__.py +2 -2
  110. aury/boot/infrastructure/tasks/config.py +5 -13
  111. aury/boot/infrastructure/tasks/manager.py +55 -33
  112. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
  113. aury_boot-0.0.7.dist-info/RECORD +197 -0
  114. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  115. aury/boot/commands/templates/project/env.example.tpl +0 -213
  116. aury/boot/infrastructure/events/bus.py +0 -362
  117. aury/boot/infrastructure/events/config.py +0 -52
  118. aury/boot/infrastructure/events/consumer.py +0 -134
  119. aury/boot/infrastructure/events/models.py +0 -63
  120. aury_boot-0.0.4.dist-info/RECORD +0 -137
  121. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  122. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
@@ -1,213 +0,0 @@
1
- # =============================================================================
2
- # {project_name} 环境变量配置
3
- # =============================================================================
4
- # 复制此文件为 .env 并根据实际情况修改
5
- # 所有配置项均有默认值,只需取消注释并修改需要覆盖的项
6
-
7
- # =============================================================================
8
- # 服务配置 (SERVICE_)
9
- # =============================================================================
10
- # 服务名称,用于日志目录区分
11
- SERVICE_NAME={project_name_snake}
12
- # 服务类型: api / worker
13
- # SERVICE_TYPE=api
14
-
15
- # =============================================================================
16
- # 服务器配置 (SERVER_)
17
- # =============================================================================
18
- # SERVER_HOST=127.0.0.1
19
- # SERVER_PORT=8000
20
- # 工作进程数(生产环境建议设为 CPU 核心数)
21
- # SERVER_WORKERS=1
22
- # 是否启用热重载(生产环境应设为 false)
23
- # SERVER_RELOAD=true
24
-
25
- # =============================================================================
26
- # 数据库配置 (DATABASE_)
27
- # =============================================================================
28
- # 数据库连接字符串(默认 SQLite)
29
- # DATABASE_URL=sqlite+aiosqlite:///./dev.db
30
- # PostgreSQL: postgresql+asyncpg://user:pass@localhost:5432/{project_name_snake}
31
- # MySQL: mysql+aiomysql://user:pass@localhost:3306/{project_name_snake}
32
-
33
- # 连接池大小
34
- # DATABASE_POOL_SIZE=5
35
- # 连接池最大溢出连接数
36
- # DATABASE_MAX_OVERFLOW=10
37
- # 连接回收时间(秒)
38
- # DATABASE_POOL_RECYCLE=3600
39
- # 获取连接超时时间(秒)
40
- # DATABASE_POOL_TIMEOUT=30
41
- # 是否在获取连接前 PING
42
- # DATABASE_POOL_PRE_PING=true
43
- # 是否输出 SQL 语句(调试用)
44
- # DATABASE_ECHO=false
45
-
46
- # =============================================================================
47
- # 缓存配置 (CACHE_)
48
- # =============================================================================
49
- # 缓存类型: memory / redis / memcached
50
- # CACHE_TYPE=memory
51
- # Redis/Memcached URL
52
- # CACHE_URL=redis://localhost:6379/0
53
- # 内存缓存最大大小
54
- # CACHE_MAX_SIZE=1000
55
-
56
- # =============================================================================
57
- # 日志配置 (LOG_)
58
- # =============================================================================
59
- # 日志级别: DEBUG / INFO / WARNING / ERROR / CRITICAL
60
- # LOG_LEVEL=INFO
61
- # 日志文件目录
62
- # LOG_DIR=logs
63
- # 日志文件轮转时间 (HH:MM 格式)
64
- # LOG_ROTATION_TIME=00:00
65
- # 日志文件轮转大小阈值
66
- # LOG_ROTATION_SIZE=100 MB
67
- # 日志文件保留天数
68
- # LOG_RETENTION_DAYS=7
69
- # 是否启用日志文件轮转
70
- # LOG_ENABLE_FILE_ROTATION=true
71
- # 是否输出日志到控制台
72
- # LOG_ENABLE_CONSOLE=true
73
-
74
- # =============================================================================
75
- # 健康检查配置 (HEALTH_CHECK_)
76
- # =============================================================================
77
- # 健康检查端点路径
78
- # HEALTH_CHECK_PATH=/api/health
79
- # 是否启用健康检查端点
80
- # HEALTH_CHECK_ENABLED=true
81
-
82
- # =============================================================================
83
- # 管理后台配置 (ADMIN_) - SQLAdmin Admin Console
84
- # =============================================================================
85
- # 是否启用管理后台(生产建议仅内网或配合反向代理)
86
- # ADMIN_ENABLED=false
87
- # 管理后台路径(默认 /api/admin-console,避免与业务 URL 冲突)
88
- # ADMIN_PATH=/api/admin-console
89
- #
90
- # SQLAdmin 通常要求同步 SQLAlchemy Engine:
91
- # - 若 DATABASE_URL 使用的是异步驱动(如 postgresql+asyncpg),建议显式提供同步 URL 覆盖
92
- # ADMIN_DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name_snake}
93
- #
94
- # 可选:显式指定项目侧模块(用于注册 views/auth)
95
- # ADMIN_VIEWS_MODULE={project_name_snake}.admin_console
96
- #
97
- # 认证(默认建议 basic / bearer;jwt/custom 通常需要自定义 backend)
98
- # ADMIN_AUTH_MODE=basic
99
- # ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET
100
- #
101
- # basic:登录页用户名/密码
102
- # ADMIN_AUTH_BASIC_USERNAME=admin
103
- # ADMIN_AUTH_BASIC_PASSWORD=change_me
104
- #
105
- # bearer:token 白名单(也支持在登录页输入 token)
106
- # ADMIN_AUTH_BEARER_TOKENS=["change_me_token"]
107
- #
108
- # custom/jwt:自定义认证后端(动态导入)
109
- # ADMIN_AUTH_BACKEND=yourpkg.admin_auth:backend
110
-
111
- # =============================================================================
112
- # CORS 配置 (CORS_)
113
- # =============================================================================
114
- # 允许的 CORS 源(生产环境应设置具体域名)
115
- # CORS_ORIGINS=["*"]
116
- # 是否允许 CORS 凭据
117
- # CORS_ALLOW_CREDENTIALS=true
118
- # 允许的 CORS 方法
119
- # CORS_ALLOW_METHODS=["*"]
120
- # 允许的 CORS 头
121
- # CORS_ALLOW_HEADERS=["*"]
122
-
123
- # =============================================================================
124
- # 调度器配置 (SCHEDULER_)
125
- # =============================================================================
126
- # 是否在 API 服务中启用内嵌调度器
127
- # SCHEDULER_ENABLED=true
128
- # 定时任务模块列表(为空时自动发现 schedules 模块)
129
- # SCHEDULER_SCHEDULE_MODULES=[]
130
-
131
- # =============================================================================
132
- # 任务队列配置 (TASK_)
133
- # =============================================================================
134
- # 任务队列代理 URL(如 Redis 或 RabbitMQ)
135
- # TASK_BROKER_URL=redis://localhost:6379/1
136
- # 最大重试次数
137
- # TASK_MAX_RETRIES=3
138
- # 任务超时时间(秒)
139
- # TASK_TIMEOUT=3600
140
-
141
- # =============================================================================
142
- # 事件总线配置 (EVENT_)
143
- # =============================================================================
144
- # 事件总线代理 URL(如 RabbitMQ)
145
- # EVENT_BROKER_URL=amqp://guest:guest@localhost:5672/
146
- # 事件交换机名称
147
- # EVENT_EXCHANGE_NAME=aury.events
148
-
149
- # =============================================================================
150
- # 数据库迁移配置 (MIGRATION_)
151
- # =============================================================================
152
- # Alembic 配置文件路径
153
- # MIGRATION_CONFIG_PATH=alembic.ini
154
- # Alembic 迁移脚本目录
155
- # MIGRATION_SCRIPT_LOCATION=migrations
156
- # 是否自动创建迁移配置和目录
157
- # MIGRATION_AUTO_CREATE=true
158
-
159
- # =============================================================================
160
- # 对象存储配置 (STORAGE_) - 基于 aury-sdk-storage
161
- # =============================================================================
162
- # 是否启用存储组件
163
- # STORAGE_ENABLED=true
164
- # 存储类型: local / s3 / cos / oss
165
- # STORAGE_TYPE=local
166
- #
167
- # 本地存储(开发环境)
168
- # STORAGE_BASE_PATH=./storage
169
- #
170
- # S3/COS/OSS 通用配置
171
- # STORAGE_ACCESS_KEY_ID=AKIDxxxxx
172
- # STORAGE_ACCESS_KEY_SECRET=xxxxx
173
- # STORAGE_SESSION_TOKEN=
174
- # STORAGE_ENDPOINT=https://cos.ap-guangzhou.myqcloud.com
175
- # STORAGE_REGION=ap-guangzhou
176
- # STORAGE_BUCKET_NAME=my-bucket-1250000000
177
- # STORAGE_ADDRESSING_STYLE=virtual
178
- #
179
- # STS AssumeRole(可选,服务端自动刷新凭证)
180
- # STORAGE_ROLE_ARN=
181
- # STORAGE_ROLE_SESSION_NAME=aury-storage
182
- # STORAGE_EXTERNAL_ID=
183
- # STORAGE_STS_ENDPOINT=
184
- # STORAGE_STS_REGION=
185
- # STORAGE_STS_DURATION_SECONDS=3600
186
-
187
- # =============================================================================
188
- # RPC 客户端配置 (RPC_CLIENT_)
189
- # =============================================================================
190
- # 服务地址映射 {{service_name: url}}
191
- # RPC_CLIENT_SERVICES={{"user-service": "http://localhost:8001"}}
192
- # 默认超时时间(秒)
193
- # RPC_CLIENT_DEFAULT_TIMEOUT=30
194
- # 默认重试次数
195
- # RPC_CLIENT_DEFAULT_RETRY_TIMES=3
196
- # DNS 解析使用的协议
197
- # RPC_CLIENT_DNS_SCHEME=http
198
- # DNS 解析默认端口
199
- # RPC_CLIENT_DNS_PORT=80
200
- # 是否使用 DNS 回退(K8s/Docker Compose 自动 DNS)
201
- # RPC_CLIENT_USE_DNS_FALLBACK=true
202
-
203
- # =============================================================================
204
- # RPC 服务注册配置 (RPC_SERVICE_)
205
- # =============================================================================
206
- # 服务名称(用于注册)
207
- # RPC_SERVICE_NAME={project_name_snake}
208
- # 服务地址(用于注册)
209
- # RPC_SERVICE_URL=http://localhost:8000
210
- # 健康检查 URL(用于注册)
211
- # RPC_SERVICE_HEALTH_CHECK_URL=http://localhost:8000/api/health
212
- # 是否自动注册到服务注册中心
213
- # RPC_SERVICE_AUTO_REGISTER=false
@@ -1,362 +0,0 @@
1
- """事件总线实现。
2
-
3
- 提供事件发布/订阅机制,支持本地和分布式模式。
4
-
5
- **架构说明**:
6
- 本模块不依赖具体的 domain.events 实现,而是通过接口和类型变量来保持通用性。
7
- 事件总线作为 infrastructure 组件只需要事件对象能够被序列化。
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import asyncio
13
- from collections.abc import Callable
14
- from typing import Any
15
-
16
- from kombu import Connection, Exchange
17
-
18
- from aury.boot.common.logging import logger
19
-
20
- from .config import EventConfig
21
- from .consumer import EventConsumer
22
- from .models import Event, EventHandler, EventType
23
-
24
-
25
- class EventBus:
26
- """事件总线(单例模式)。
27
-
28
- 职责:
29
- 1. 管理事件订阅者
30
- 2. 分发事件(支持本地和分布式模式)
31
- 3. 异步事件处理
32
-
33
- 模式:
34
- - 本地模式:内存中的观察者模式(默认,无需消息队列)
35
- - 分布式模式:使用 Kombu 消息队列(需要配置 broker_url)
36
-
37
- 使用示例:
38
- # 本地模式(内存)
39
- event_bus = EventBus.get_instance()
40
-
41
- @event_bus.subscribe(MyEvent)
42
- async def on_my_event(event: MyEvent):
43
- print(f"Event {event.event_name} received!")
44
-
45
- await event_bus.publish(MyEvent(...))
46
-
47
- # 分布式模式(Kombu)
48
- await event_bus.initialize(broker_url="redis://localhost:6379/0")
49
- await event_bus.publish(MyEvent(...)) # 通过消息队列发布
50
- """
51
-
52
- _instance: EventBus | None = None
53
-
54
- def __init__(self, settings: EventConfig | None = None) -> None:
55
- """私有构造函数,使用 get_instance() 获取实例。
56
-
57
- Args:
58
- settings: 事件总线配置(如果为 None 则使用默认配置)
59
- """
60
- if EventBus._instance is not None:
61
- raise RuntimeError("EventBus 是单例类,请使用 get_instance() 获取实例")
62
-
63
- self._settings = settings or EventConfig()
64
- self._handlers: dict[type[Event], list[EventHandler]] = {}
65
- self._event_history: list[Event] = []
66
-
67
- # Kombu 相关
68
- self._connection: Any = None # Connection | None
69
- self._exchange: Any = None # Exchange | None
70
- self._producer: Any = None # Producer | None
71
- self._consumer: Any = None # EventConsumer | None
72
- self._consumer_task: Any = None # asyncio.Task | None
73
- self._initialized: bool = False
74
- self._use_distributed: bool = False
75
- self._broker_url: str | None = None
76
-
77
- logger.debug("事件总线已创建(本地模式)")
78
-
79
- @classmethod
80
- def get_instance(cls) -> EventBus:
81
- """获取单例实例。"""
82
- if cls._instance is None:
83
- cls._instance = cls()
84
- return cls._instance
85
-
86
- async def initialize(
87
- self,
88
- broker_url: str | None = None,
89
- *,
90
- exchange_name: str | None = None,
91
- queue_prefix: str | None = None,
92
- ) -> None:
93
- """初始化分布式事件总线(使用 Kombu)。
94
-
95
- Args:
96
- broker_url: 消息队列 URL(如 "redis://localhost:6379/0" 或 "amqp://guest:guest@localhost:5672//")
97
- 如果不指定则使用配置中的值
98
- exchange_name: 交换机名称,如果不指定则使用配置中的值
99
- queue_prefix: 队列名称前缀,如果不指定则使用配置中的值
100
- """
101
- if self._initialized:
102
- logger.warning("事件总线已初始化,跳过")
103
- return
104
-
105
- # 使用提供的参数或配置中的值
106
- final_broker_url = broker_url or self._settings.broker_url
107
- final_exchange_name = exchange_name or self._settings.exchange_name
108
- final_queue_prefix = queue_prefix or self._settings.queue_prefix
109
-
110
- if not final_broker_url:
111
- logger.info("未提供 broker_url,使用本地模式(内存)")
112
- self._use_distributed = False
113
- self._initialized = True
114
- return
115
-
116
- try:
117
- self._broker_url = final_broker_url
118
- self._connection = Connection(final_broker_url)
119
- self._exchange = Exchange(final_exchange_name, type="topic", durable=True)
120
-
121
- # 创建生产者
122
- self._producer = self._connection.Producer(
123
- exchange=self._exchange,
124
- serializer="json",
125
- )
126
-
127
- # 启动消费者(在后台任务中)
128
- self._consumer = EventConsumer(
129
- connection=self._connection,
130
- exchange=self._exchange,
131
- queue_prefix=final_queue_prefix,
132
- handlers=self._handlers,
133
- )
134
-
135
- # 在后台启动消费者
136
- self._consumer_task = asyncio.create_task(self._start_consumer())
137
-
138
- self._use_distributed = True
139
- self._initialized = True
140
- logger.info(f"事件总线已初始化(分布式模式,broker: {broker_url})")
141
- except Exception as exc:
142
- logger.error(f"事件总线初始化失败: {exc}")
143
- raise
144
-
145
- async def _start_consumer(self) -> None:
146
- """启动消费者(在后台运行)。"""
147
- if self._consumer:
148
- try:
149
- # 在事件循环中运行消费者
150
- loop = asyncio.get_event_loop()
151
- await loop.run_in_executor(None, self._consumer.run)
152
- except Exception as exc:
153
- logger.error(f"事件消费者运行失败: {exc}")
154
-
155
- def subscribe(
156
- self,
157
- event_type: type[EventType],
158
- handler: EventHandler | None = None,
159
- ) -> EventHandler | Callable[[EventHandler], EventHandler]:
160
- """订阅事件。
161
-
162
- 可以作为装饰器使用:
163
- @event_bus.subscribe(MyEvent)
164
- async def handler(event):
165
- pass
166
-
167
- 或直接调用:
168
- event_bus.subscribe(MyEvent, handler)
169
-
170
- Args:
171
- event_type: 事件类型
172
- handler: 事件处理器
173
-
174
- Returns:
175
- EventHandler | Callable: 处理器或装饰器
176
- """
177
- def decorator(fn: EventHandler) -> EventHandler:
178
- if event_type not in self._handlers:
179
- self._handlers[event_type] = []
180
-
181
- self._handlers[event_type].append(fn)
182
- logger.debug(f"订阅事件: {event_type.__name__} -> {fn.__name__}")
183
-
184
- # 如果使用分布式模式,需要注册队列
185
- if self._use_distributed and self._consumer:
186
- self._consumer.register_event_type(event_type)
187
-
188
- return fn
189
-
190
- if handler is not None:
191
- return decorator(handler)
192
- return decorator
193
-
194
- def unsubscribe(self, event_type: type[EventType], handler: EventHandler) -> None:
195
- """取消订阅事件。
196
-
197
- Args:
198
- event_type: 事件类型
199
- handler: 事件处理器
200
- """
201
- if event_type in self._handlers:
202
- try:
203
- self._handlers[event_type].remove(handler)
204
- logger.debug(f"取消订阅事件: {event_type.__name__} -> {handler.__name__}")
205
- except ValueError:
206
- pass
207
-
208
- async def publish(self, event: EventType) -> None:
209
- """发布事件。
210
-
211
- Args:
212
- event: 事件对象
213
- """
214
- logger.info(f"发布事件: {event}")
215
-
216
- # 记录事件历史
217
- self._add_to_history(event)
218
-
219
- if self._use_distributed:
220
- # 分布式模式:通过消息队列发布
221
- await self._publish_distributed(event)
222
- else:
223
- # 本地模式:直接调用处理器
224
- await self._publish_local(event)
225
-
226
- async def _publish_local(self, event: EventType) -> None:
227
- """本地模式:直接调用处理器。"""
228
- handlers = self._handlers.get(type(event), [])
229
- if not handlers:
230
- logger.debug(f"事件 {event.event_name} 没有订阅者")
231
- return
232
-
233
- # 执行处理器
234
- for handler in handlers:
235
- try:
236
- if asyncio.iscoroutinefunction(handler):
237
- await handler(event)
238
- else:
239
- handler(event)
240
- logger.debug(f"事件处理成功: {handler.__name__}")
241
- except Exception as exc:
242
- logger.error(f"事件处理失败: {handler.__name__}, 错误: {exc}")
243
-
244
- async def _publish_distributed(self, event: EventType) -> None:
245
- """分布式模式:通过消息队列发布。"""
246
- if not self._producer:
247
- logger.warning("生产者未初始化,回退到本地模式")
248
- await self._publish_local(event)
249
- return
250
-
251
- try:
252
- # 序列化事件(使用 model_dump 而不是 model_dump_json,因为 kombu 会自动序列化)
253
- event_data = event.model_dump()
254
- routing_key = event.event_name.lower().replace("event", "")
255
-
256
- # 发布到消息队列
257
- self._producer.publish(
258
- event_data,
259
- routing_key=routing_key,
260
- declare=[self._exchange],
261
- )
262
- logger.debug(f"事件已发布到消息队列: {routing_key}")
263
- except Exception as exc:
264
- logger.error(f"发布事件到消息队列失败: {exc}")
265
- # 失败时回退到本地模式
266
- await self._publish_local(event)
267
-
268
- async def publish_many(self, events: list[Event]) -> None:
269
- """批量发布事件。
270
-
271
- Args:
272
- events: 事件列表
273
- """
274
- for event in events:
275
- await self.publish(event)
276
-
277
- def _add_to_history(self, event: Event) -> None:
278
- """添加事件到历史记录。
279
-
280
- Args:
281
- event: 事件对象
282
- """
283
- # 检查是否启用了历史记录
284
- if not self._settings.enable_history:
285
- return
286
-
287
- self._event_history.append(event)
288
-
289
- # 限制历史记录大小
290
- if (
291
- self._settings.max_history_size is not None
292
- and len(self._event_history) > self._settings.max_history_size
293
- ):
294
- self._event_history = self._event_history[-self._settings.max_history_size:]
295
-
296
- def get_history(
297
- self,
298
- event_type: type[EventType] | None = None,
299
- limit: int = 100,
300
- ) -> list[Event]:
301
- """获取事件历史。
302
-
303
- Args:
304
- event_type: 事件类型(可选,不指定则返回所有事件)
305
- limit: 返回数量限制
306
-
307
- Returns:
308
- list[Event]: 事件列表
309
- """
310
- if event_type is None:
311
- return self._event_history[-limit:]
312
-
313
- filtered = [e for e in self._event_history if isinstance(e, event_type)]
314
- return filtered[-limit:]
315
-
316
- def clear_history(self) -> None:
317
- """清空事件历史。"""
318
- self._event_history.clear()
319
- logger.debug("事件历史已清空")
320
-
321
- def clear_handlers(self) -> None:
322
- """清空所有事件处理器。"""
323
- self._handlers.clear()
324
- logger.debug("事件处理器已清空")
325
-
326
- def get_handler_count(self, event_type: type[EventType] | None = None) -> int:
327
- """获取处理器数量。
328
-
329
- Args:
330
- event_type: 事件类型(可选)
331
-
332
- Returns:
333
- int: 处理器数量
334
- """
335
- if event_type is None:
336
- return sum(len(handlers) for handlers in self._handlers.values())
337
- return len(self._handlers.get(event_type, []))
338
-
339
- async def cleanup(self) -> None:
340
- """清理资源。"""
341
- if self._consumer:
342
- self._consumer.should_stop = True
343
-
344
- if self._connection:
345
- self._connection.close()
346
-
347
- self._initialized = False
348
- self._use_distributed = False
349
- logger.info("事件总线已清理")
350
-
351
- def __repr__(self) -> str:
352
- """字符串表示。"""
353
- event_count = len(self._handlers)
354
- history_size = len(self._event_history)
355
- mode = "distributed" if self._use_distributed else "local"
356
- return f"<EventBus mode={mode} events={event_count} history={history_size}>"
357
-
358
-
359
- __all__ = [
360
- "EventBus",
361
- ]
362
-
@@ -1,52 +0,0 @@
1
- """事件总线配置。
2
-
3
- 提供事件系统的配置管理。
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from pydantic import Field
9
- from pydantic_settings import BaseSettings, SettingsConfigDict
10
-
11
-
12
- class EventConfig(BaseSettings):
13
- """事件总线基础设施配置。
14
-
15
- Infrastructure 层直接使用的事件总线配置。
16
-
17
- 环境变量前缀: EVENT_
18
- 示例: EVENT_BROKER_URL, EVENT_EXCHANGE_NAME, EVENT_QUEUE_PREFIX, EVENT_MAX_HISTORY_SIZE
19
- """
20
-
21
- broker_url: str | None = Field(
22
- default=None,
23
- description="消息队列 URL(如 redis://localhost:6379/0 或 amqp://guest:guest@localhost:5672//),为空时使用本地模式"
24
- )
25
- exchange_name: str = Field(
26
- default="events",
27
- description="交换机名称"
28
- )
29
- queue_prefix: str = Field(
30
- default="event",
31
- description="队列名称前缀"
32
- )
33
- max_history_size: int | None = Field(
34
- default=1000,
35
- description="事件历史记录最大条数(None 表示不限制,生产环境建议设置或禁用历史记录)"
36
- )
37
- enable_history: bool = Field(
38
- default=True,
39
- description="是否启用事件历史记录(生产环境建议关闭以节省内存)"
40
- )
41
-
42
- model_config = SettingsConfigDict(
43
- env_prefix="EVENT_",
44
- case_sensitive=False,
45
- )
46
-
47
-
48
- __all__ = [
49
- "EventConfig",
50
- ]
51
-
52
-