aury-boot 0.0.4__py3-none-any.whl → 0.0.5__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 (98) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +45 -36
  4. aury/boot/application/app/__init__.py +12 -8
  5. aury/boot/application/app/base.py +12 -0
  6. aury/boot/application/app/components.py +137 -44
  7. aury/boot/application/app/middlewares.py +2 -0
  8. aury/boot/application/app/startup.py +249 -0
  9. aury/boot/application/config/__init__.py +36 -1
  10. aury/boot/application/config/multi_instance.py +200 -0
  11. aury/boot/application/config/settings.py +341 -12
  12. aury/boot/application/constants/components.py +6 -0
  13. aury/boot/application/errors/handlers.py +17 -3
  14. aury/boot/application/middleware/logging.py +8 -120
  15. aury/boot/application/rpc/__init__.py +2 -2
  16. aury/boot/commands/__init__.py +30 -10
  17. aury/boot/commands/app.py +131 -1
  18. aury/boot/commands/docs.py +104 -17
  19. aury/boot/commands/init.py +27 -8
  20. aury/boot/commands/server/app.py +2 -3
  21. aury/boot/commands/templates/project/AGENTS.md.tpl +217 -0
  22. aury/boot/commands/templates/project/README.md.tpl +2 -2
  23. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  24. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +183 -0
  25. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  26. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  27. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  28. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  29. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  30. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  31. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  32. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  33. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  34. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +92 -0
  35. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  36. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +92 -0
  37. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  38. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  39. aury/boot/commands/templates/project/config.py.tpl +1 -1
  40. aury/boot/commands/templates/project/env.example.tpl +73 -5
  41. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  42. aury/boot/contrib/admin_console/auth.py +2 -3
  43. aury/boot/contrib/admin_console/install.py +1 -1
  44. aury/boot/domain/models/mixins.py +48 -1
  45. aury/boot/domain/pagination/__init__.py +94 -0
  46. aury/boot/domain/repository/impl.py +1 -1
  47. aury/boot/domain/repository/interface.py +1 -1
  48. aury/boot/domain/transaction/__init__.py +8 -9
  49. aury/boot/infrastructure/__init__.py +86 -29
  50. aury/boot/infrastructure/cache/backends.py +102 -18
  51. aury/boot/infrastructure/cache/base.py +12 -0
  52. aury/boot/infrastructure/cache/manager.py +153 -91
  53. aury/boot/infrastructure/channel/__init__.py +24 -0
  54. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  55. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  56. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  57. aury/boot/infrastructure/channel/base.py +92 -0
  58. aury/boot/infrastructure/channel/manager.py +203 -0
  59. aury/boot/infrastructure/clients/__init__.py +22 -0
  60. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  61. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  62. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  63. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  64. aury/boot/infrastructure/clients/redis/config.py +51 -0
  65. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  66. aury/boot/infrastructure/database/config.py +1 -2
  67. aury/boot/infrastructure/database/manager.py +16 -38
  68. aury/boot/infrastructure/events/__init__.py +18 -21
  69. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  70. aury/boot/infrastructure/events/backends/memory.py +86 -0
  71. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  72. aury/boot/infrastructure/events/backends/redis.py +162 -0
  73. aury/boot/infrastructure/events/base.py +127 -0
  74. aury/boot/infrastructure/events/manager.py +224 -0
  75. aury/boot/infrastructure/mq/__init__.py +24 -0
  76. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  77. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  78. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  79. aury/boot/infrastructure/mq/base.py +143 -0
  80. aury/boot/infrastructure/mq/manager.py +239 -0
  81. aury/boot/infrastructure/scheduler/manager.py +7 -3
  82. aury/boot/infrastructure/storage/__init__.py +9 -9
  83. aury/boot/infrastructure/storage/base.py +17 -5
  84. aury/boot/infrastructure/storage/factory.py +0 -1
  85. aury/boot/infrastructure/tasks/__init__.py +2 -2
  86. aury/boot/infrastructure/tasks/manager.py +47 -29
  87. aury/boot/testing/base.py +2 -2
  88. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/METADATA +19 -2
  89. aury_boot-0.0.5.dist-info/RECORD +176 -0
  90. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  91. aury/boot/infrastructure/events/bus.py +0 -362
  92. aury/boot/infrastructure/events/config.py +0 -52
  93. aury/boot/infrastructure/events/consumer.py +0 -134
  94. aury/boot/infrastructure/events/models.py +0 -63
  95. aury_boot-0.0.4.dist-info/RECORD +0 -137
  96. /aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +0 -0
  97. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/WHEEL +0 -0
  98. {aury_boot-0.0.4.dist-info → aury_boot-0.0.5.dist-info}/entry_points.txt +0 -0
@@ -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
-
@@ -1,134 +0,0 @@
1
- """事件消费者实现。
2
-
3
- 基于 Kombu ConsumerMixin 实现消息队列事件消费。
4
-
5
- **架构说明**:
6
- 本模块使用通用的 Any 类型而非直接导入 domain.events,保持 infrastructure 层的独立性。
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import asyncio
12
- from typing import Any
13
-
14
- from kombu import Queue
15
- from kombu.mixins import ConsumerMixin
16
-
17
- from aury.boot.common.logging import logger
18
-
19
-
20
- class EventConsumer(ConsumerMixin):
21
- """事件消费者(基于 Kombu ConsumerMixin)。
22
-
23
- 在后台消费消息队列中的事件并调用本地处理器。
24
- """
25
-
26
- def __init__(
27
- self,
28
- connection: Any, # Connection
29
- exchange: Any, # Exchange
30
- queue_prefix: str,
31
- handlers: dict[Any, list[Any]], # dict[type[EventType], list[EventHandler]]
32
- ) -> None:
33
- """初始化事件消费者。
34
-
35
- Args:
36
- connection: Kombu 连接
37
- exchange: 交换机
38
- queue_prefix: 队列名称前缀
39
- handlers: 事件处理器字典 {事件类型: [处理器列表]}
40
- """
41
- self.connection = connection
42
- self._exchange = exchange
43
- self._queue_prefix = queue_prefix
44
- self._handlers = handlers
45
- self._queues: dict[str, Any] = {} # dict[str, Queue]
46
- self._event_types: dict[str, Any] = {} # dict[str, type[EventType]]
47
-
48
- def register_event_type(self, event_type: Any) -> None:
49
- """注册事件类型(创建对应的队列)。
50
-
51
- Args:
52
- event_type: 事件类型(应实现 __name__ 和 model_validate())
53
- """
54
- event_name = event_type.__name__
55
- routing_key = event_name.lower().replace("event", "")
56
-
57
- if routing_key not in self._queues:
58
- queue_name = f"{self._queue_prefix}.{routing_key}"
59
- queue = Queue(
60
- queue_name,
61
- exchange=self._exchange,
62
- routing_key=routing_key,
63
- durable=True,
64
- )
65
- self._queues[routing_key] = queue
66
- self._event_types[routing_key] = event_type
67
- logger.debug(f"注册事件队列: {queue_name} -> {routing_key}")
68
-
69
- def get_consumers(self, consumer_class: type, channel: Any) -> list:
70
- """获取消费者列表。
71
-
72
- Args:
73
- consumer_class: Kombu Consumer 类
74
- channel: 消息通道
75
-
76
- Returns:
77
- list: 消费者列表
78
- """
79
- consumers = []
80
- for _routing_key, queue in self._queues.items():
81
- consumer = consumer_class(
82
- queues=[queue],
83
- callbacks=[self.on_message],
84
- accept=["json"],
85
- )
86
- consumers.append(consumer)
87
- return consumers
88
-
89
- def on_message(self, body: dict[str, Any], message: Any) -> None:
90
- """处理接收到的消息。
91
-
92
- Args:
93
- body: 消息体
94
- message: 消息对象
95
- """
96
- try:
97
- routing_key = message.delivery_info.get("routing_key", "")
98
- event_type = self._event_types.get(routing_key)
99
-
100
- if not event_type:
101
- logger.warning(f"未知的事件类型: {routing_key}")
102
- message.ack()
103
- return
104
-
105
- # 反序列化事件
106
- event = event_type.model_validate(body)
107
-
108
- # 获取处理器
109
- handlers = self._handlers.get(event_type, [])
110
-
111
- # 在事件循环中执行处理器
112
- loop = asyncio.get_event_loop()
113
- for handler in handlers:
114
- try:
115
- if asyncio.iscoroutinefunction(handler):
116
- task = loop.create_task(handler(event))
117
- # 保存任务引用以避免警告
118
- _ = task
119
- else:
120
- handler(event)
121
- logger.debug(f"事件处理成功: {handler.__name__}")
122
- except Exception as exc:
123
- logger.error(f"事件处理失败: {handler.__name__}, 错误: {exc}")
124
-
125
- message.ack()
126
- except Exception as exc:
127
- logger.error(f"处理事件消息失败: {exc}")
128
- message.reject(requeue=False)
129
-
130
-
131
- __all__ = [
132
- "EventConsumer",
133
- ]
134
-
@@ -1,63 +0,0 @@
1
- """事件模型定义 - 独立文件避免循环导入。
2
-
3
- 提供事件基类、类型变量、处理器定义等基础数据结构。
4
- 此模块不依赖任何其他 infrastructure 模块,可被安全导入。
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from abc import ABC, abstractmethod
10
- from collections.abc import Callable, Coroutine
11
- from datetime import datetime
12
- from typing import Any, ClassVar, TypeVar
13
- from uuid import uuid4
14
-
15
- from pydantic import BaseModel, Field
16
-
17
- # 事件类型定义(基础数据结构)
18
- EventType = TypeVar("EventType", bound="Event")
19
- EventHandler = (
20
- Callable[[EventType], None] |
21
- Callable[[EventType], Coroutine[Any, Any, None]]
22
- )
23
-
24
-
25
- class Event(BaseModel, ABC):
26
- """事件基类(Pydantic)。
27
-
28
- 所有业务事件应继承此类。
29
- Pydantic 提供自动验证和序列化功能。
30
-
31
- Attributes:
32
- event_id: 事件ID(自动生成UUID)
33
- timestamp: 事件时间戳
34
- metadata: 事件元数据
35
- """
36
-
37
- event_id: str = Field(default_factory=lambda: str(uuid4()), description="事件ID")
38
- timestamp: datetime = Field(default_factory=datetime.now, description="事件时间戳")
39
- metadata: dict[str, Any] = Field(default_factory=dict, description="事件元数据")
40
-
41
- @property
42
- @abstractmethod
43
- def event_name(self) -> str:
44
- """事件名称(子类必须实现)。"""
45
- pass
46
-
47
- class Config:
48
- """Pydantic配置。"""
49
- json_encoders: ClassVar[dict] = {
50
- datetime: lambda v: v.isoformat(),
51
- }
52
-
53
- def __repr__(self) -> str:
54
- """字符串表示。"""
55
- return f"<{self.__class__.__name__} id={self.event_id} time={self.timestamp}>"
56
-
57
-
58
- __all__ = [
59
- "Event",
60
- "EventHandler",
61
- "EventType",
62
- ]
63
-