aury-boot 0.0.29__py3-none-any.whl → 0.0.31__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 (54) hide show
  1. aury/boot/_version.py +2 -2
  2. aury/boot/application/__init__.py +2 -4
  3. aury/boot/application/app/base.py +126 -2
  4. aury/boot/application/app/components.py +226 -1
  5. aury/boot/application/config/settings.py +201 -3
  6. aury/boot/application/constants/components.py +3 -0
  7. aury/boot/application/middleware/logging.py +45 -6
  8. aury/boot/commands/docs.py +40 -0
  9. aury/boot/commands/init.py +2 -0
  10. aury/boot/commands/templates/project/AGENTS.md.tpl +59 -0
  11. aury/boot/commands/templates/project/alert_rules.example.yaml.tpl +85 -0
  12. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +3 -0
  13. aury/boot/commands/templates/project/aury_docs/17-alerting.md.tpl +210 -0
  14. aury/boot/commands/templates/project/env_templates/messaging.tpl +21 -13
  15. aury/boot/commands/templates/project/env_templates/monitoring.tpl +63 -0
  16. aury/boot/common/logging/context.py +17 -1
  17. aury/boot/common/logging/format.py +4 -0
  18. aury/boot/infrastructure/__init__.py +4 -8
  19. aury/boot/infrastructure/channel/__init__.py +9 -8
  20. aury/boot/infrastructure/channel/backends/__init__.py +2 -6
  21. aury/boot/infrastructure/channel/backends/broadcaster.py +141 -0
  22. aury/boot/infrastructure/channel/base.py +11 -4
  23. aury/boot/infrastructure/channel/manager.py +25 -24
  24. aury/boot/infrastructure/database/query_tools/__init__.py +3 -5
  25. aury/boot/infrastructure/events/__init__.py +4 -6
  26. aury/boot/infrastructure/events/backends/__init__.py +2 -4
  27. aury/boot/infrastructure/events/backends/broadcaster.py +189 -0
  28. aury/boot/infrastructure/events/base.py +9 -4
  29. aury/boot/infrastructure/events/manager.py +24 -20
  30. aury/boot/infrastructure/monitoring/__init__.py +210 -6
  31. aury/boot/infrastructure/monitoring/alerting/__init__.py +50 -0
  32. aury/boot/infrastructure/monitoring/alerting/aggregator.py +193 -0
  33. aury/boot/infrastructure/monitoring/alerting/events.py +141 -0
  34. aury/boot/infrastructure/monitoring/alerting/manager.py +430 -0
  35. aury/boot/infrastructure/monitoring/alerting/notifiers/__init__.py +16 -0
  36. aury/boot/infrastructure/monitoring/alerting/notifiers/base.py +60 -0
  37. aury/boot/infrastructure/monitoring/alerting/notifiers/feishu.py +209 -0
  38. aury/boot/infrastructure/monitoring/alerting/notifiers/webhook.py +110 -0
  39. aury/boot/infrastructure/monitoring/alerting/rules.py +179 -0
  40. aury/boot/infrastructure/monitoring/health/__init__.py +231 -0
  41. aury/boot/infrastructure/monitoring/tracing/__init__.py +55 -0
  42. aury/boot/infrastructure/monitoring/tracing/context.py +43 -0
  43. aury/boot/infrastructure/monitoring/tracing/logging.py +73 -0
  44. aury/boot/infrastructure/monitoring/tracing/processor.py +357 -0
  45. aury/boot/infrastructure/monitoring/tracing/provider.py +322 -0
  46. aury/boot/infrastructure/monitoring/tracing/tracing.py +235 -0
  47. {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/METADATA +14 -1
  48. {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/RECORD +50 -33
  49. aury/boot/infrastructure/channel/backends/memory.py +0 -126
  50. aury/boot/infrastructure/channel/backends/redis.py +0 -130
  51. aury/boot/infrastructure/events/backends/memory.py +0 -86
  52. aury/boot/infrastructure/events/backends/redis.py +0 -169
  53. {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/WHEEL +0 -0
  54. {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,235 @@
1
+ """便捷的链路追踪 API。
2
+
3
+ 提供简洁的 span 创建方式,用于手动追踪自定义业务逻辑。
4
+
5
+ 用法:
6
+ # 上下文管理器方式(kind 可自定义,如 "llm"、"tool" 等)
7
+ with span("llm.chat", kind="llm", model="gpt-4") as s:
8
+ response = await call_openai()
9
+ s.set_attribute("tokens", response.usage.total_tokens)
10
+
11
+ # 装饰器方式
12
+ @trace_span("tool.search", kind="tool")
13
+ async def search(query: str):
14
+ ...
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+ from collections.abc import Callable
21
+ from contextlib import contextmanager
22
+ from functools import wraps
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ # OTel 可选依赖
26
+ try:
27
+ from opentelemetry import trace as otel_trace
28
+ from opentelemetry.trace import SpanKind as OTelSpanKind
29
+ from opentelemetry.trace import StatusCode
30
+ except ImportError:
31
+ otel_trace = None # type: ignore[assignment]
32
+ OTelSpanKind = None # type: ignore[assignment, misc]
33
+ StatusCode = None # type: ignore[assignment, misc]
34
+
35
+ if TYPE_CHECKING:
36
+ from opentelemetry.trace import Span
37
+
38
+
39
+ class SpanKind:
40
+ """OTel 标准 Span 类型常量。
41
+
42
+ 开发者也可传入任意字符串作为 kind(如 "llm"、"tool"),
43
+ 会作为属性记录,并默认映射为 INTERNAL。
44
+ """
45
+
46
+ INTERNAL = "internal" # 内部操作
47
+ CLIENT = "client" # 外部调用(HTTP client、DB)
48
+ SERVER = "server" # 处理请求
49
+ PRODUCER = "producer" # 消息生产者
50
+ CONSUMER = "consumer" # 消息消费者
51
+
52
+
53
+ def _get_tracer():
54
+ """获取 tracer 实例。"""
55
+ if otel_trace is None:
56
+ return None
57
+ return otel_trace.get_tracer("aury")
58
+
59
+
60
+ def _to_otel_span_kind(kind: str):
61
+ """转换为 OTel SpanKind。
62
+
63
+ 标准类型(internal/client/server/producer/consumer)映射到对应的 OTel SpanKind,
64
+ 其他自定义类型(如 llm/tool 等)默认映射为 INTERNAL。
65
+ """
66
+ if OTelSpanKind is None:
67
+ return None
68
+
69
+ mapping = {
70
+ "internal": OTelSpanKind.INTERNAL,
71
+ "client": OTelSpanKind.CLIENT,
72
+ "server": OTelSpanKind.SERVER,
73
+ "producer": OTelSpanKind.PRODUCER,
74
+ "consumer": OTelSpanKind.CONSUMER,
75
+ }
76
+ return mapping.get(kind, OTelSpanKind.INTERNAL)
77
+
78
+
79
+ @contextmanager
80
+ def span(
81
+ name: str,
82
+ kind: str = "internal",
83
+ **attributes: Any,
84
+ ):
85
+ """创建追踪 span(上下文管理器)。
86
+
87
+ Args:
88
+ name: span 名称,建议格式 "{category}.{operation}"
89
+ kind: span 类型,可自由定义(如 "llm"、"tool"、"task" 等),
90
+ 标准类型(internal/client/server)会映射到 OTel SpanKind
91
+ **attributes: 附加属性
92
+
93
+ Yields:
94
+ Span: OTel span 对象,可调用 set_attribute() 添加更多属性
95
+
96
+ 用法:
97
+ # 追踪 LLM 调用
98
+ with span("llm.chat", kind="llm", model="gpt-4") as s:
99
+ response = await openai.chat.completions.create(...)
100
+ s.set_attribute("llm.tokens", response.usage.total_tokens)
101
+
102
+ # 追踪工具调用
103
+ with span("tool.web_search", kind="tool", query=query):
104
+ result = await search(query)
105
+
106
+ # 追踪自定义业务
107
+ with span("payment.process", order_id=order_id):
108
+ await process_payment(order_id)
109
+ """
110
+ tracer = _get_tracer()
111
+
112
+ if tracer is None:
113
+ # OTel 未安装,使用空上下文
114
+ yield _DummySpan()
115
+ return
116
+
117
+ otel_kind = _to_otel_span_kind(kind)
118
+
119
+ with tracer.start_as_current_span(name, kind=otel_kind) as s:
120
+ # 设置自定义类型标识(用于 AlertingSpanProcessor 识别)
121
+ s.set_attribute("aury.span_kind", kind)
122
+
123
+ # 设置用户传入的属性
124
+ for key, value in attributes.items():
125
+ if value is not None:
126
+ s.set_attribute(key, _safe_attribute_value(value))
127
+
128
+ yield s
129
+
130
+
131
+ def trace_span(
132
+ name: str | None = None,
133
+ kind: str = "internal",
134
+ **attributes: Any,
135
+ ) -> Callable:
136
+ """追踪装饰器。
137
+
138
+ Args:
139
+ name: span 名称,默认使用函数名
140
+ kind: span 类型,可自由定义
141
+ **attributes: 附加属性
142
+
143
+ 用法:
144
+ @trace_span(kind="llm", model="gpt-4")
145
+ async def chat(prompt: str):
146
+ return await openai.chat.completions.create(...)
147
+
148
+ @trace_span("custom.operation")
149
+ def sync_operation():
150
+ ...
151
+ """
152
+ def decorator(func: Callable) -> Callable:
153
+ span_name = name or f"{func.__module__}.{func.__qualname__}"
154
+
155
+ @wraps(func)
156
+ async def async_wrapper(*args, **kwargs):
157
+ with span(span_name, kind=kind, **attributes):
158
+ return await func(*args, **kwargs)
159
+
160
+ @wraps(func)
161
+ def sync_wrapper(*args, **kwargs):
162
+ with span(span_name, kind=kind, **attributes):
163
+ return func(*args, **kwargs)
164
+
165
+ # 根据函数类型返回对应包装器
166
+ if asyncio.iscoroutinefunction(func):
167
+ return async_wrapper
168
+ return sync_wrapper
169
+
170
+ return decorator
171
+
172
+
173
+ def set_span_error(error: Exception, message: str | None = None) -> None:
174
+ """在当前 span 上记录错误。
175
+
176
+ Args:
177
+ error: 异常对象
178
+ message: 错误消息(可选)
179
+ """
180
+ if otel_trace is None or StatusCode is None:
181
+ return
182
+
183
+ current_span = otel_trace.get_current_span()
184
+ if current_span and current_span.is_recording():
185
+ current_span.set_status(StatusCode.ERROR, message or str(error))
186
+ current_span.record_exception(error)
187
+
188
+
189
+ def set_span_attribute(key: str, value: Any) -> None:
190
+ """在当前 span 上设置属性。
191
+
192
+ Args:
193
+ key: 属性名
194
+ value: 属性值
195
+ """
196
+ if otel_trace is None:
197
+ return
198
+
199
+ current_span = otel_trace.get_current_span()
200
+ if current_span and current_span.is_recording():
201
+ current_span.set_attribute(key, _safe_attribute_value(value))
202
+
203
+
204
+ def _safe_attribute_value(value: Any) -> Any:
205
+ """转换属性值为 OTel 支持的类型。"""
206
+ if isinstance(value, str | int | float | bool):
207
+ return value
208
+ if isinstance(value, list | tuple):
209
+ return [_safe_attribute_value(v) for v in value]
210
+ return str(value)
211
+
212
+
213
+ class _DummySpan:
214
+ """占位 span,用于 OTel 未安装时。"""
215
+
216
+ def set_attribute(self, key: str, value: Any) -> None:
217
+ pass
218
+
219
+ def set_status(self, status, description: str | None = None) -> None:
220
+ pass
221
+
222
+ def record_exception(self, exception: Exception) -> None:
223
+ pass
224
+
225
+ def add_event(self, name: str, attributes: dict | None = None) -> None:
226
+ pass
227
+
228
+
229
+ __all__ = [
230
+ "SpanKind",
231
+ "set_span_attribute",
232
+ "set_span_error",
233
+ "span",
234
+ "trace_span",
235
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aury-boot
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: alembic>=1.17.2
@@ -42,6 +42,14 @@ Requires-Dist: mkdocs-material>=9.7.0; extra == 'docs'
42
42
  Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
43
43
  Provides-Extra: mysql
44
44
  Requires-Dist: aiomysql>=0.3.2; extra == 'mysql'
45
+ Provides-Extra: otel
46
+ Requires-Dist: opentelemetry-api>=1.25.0; extra == 'otel'
47
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.46b0; extra == 'otel'
48
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.46b0; extra == 'otel'
49
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.46b0; extra == 'otel'
50
+ Requires-Dist: opentelemetry-sdk>=1.25.0; extra == 'otel'
51
+ Provides-Extra: otel-exporter
52
+ Requires-Dist: opentelemetry-exporter-otlp>=1.25.0; extra == 'otel-exporter'
45
53
  Provides-Extra: postgres
46
54
  Requires-Dist: asyncpg>=0.31.0; extra == 'postgres'
47
55
  Provides-Extra: rabbitmq
@@ -52,6 +60,11 @@ Requires-Dist: apscheduler>=3.11.1; extra == 'recommended'
52
60
  Requires-Dist: asyncpg>=0.31.0; extra == 'recommended'
53
61
  Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'recommended'
54
62
  Requires-Dist: dramatiq>=1.18.0; extra == 'recommended'
63
+ Requires-Dist: opentelemetry-api>=1.25.0; extra == 'recommended'
64
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.46b0; extra == 'recommended'
65
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.46b0; extra == 'recommended'
66
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.46b0; extra == 'recommended'
67
+ Requires-Dist: opentelemetry-sdk>=1.25.0; extra == 'recommended'
55
68
  Requires-Dist: redis>=7.1.0; extra == 'recommended'
56
69
  Provides-Extra: redis
57
70
  Requires-Dist: redis>=7.1.0; extra == 'redis'
@@ -1,6 +1,6 @@
1
1
  aury/boot/__init__.py,sha256=pCno-EInnpIBa1OtxNYF-JWf9j95Cd2h6vmu0xqa_-4,1791
2
- aury/boot/_version.py,sha256=p4kcB0BmpcnphBIseH7188jNCCOqFZSly7Pka9LrUDM,706
3
- aury/boot/application/__init__.py,sha256=0o_XmiwFCeAu06VHggS8I1e7_nSMoRq0Hcm0fYfCywU,3071
2
+ aury/boot/_version.py,sha256=nmSHmaFWTuD8SY4lecbyWq4JEPaihzn-zXy2K9lJGYo,706
3
+ aury/boot/application/__init__.py,sha256=I2KqNVdYg2q5nlOXr0TtFGyHmhj4oWdaR6ZB73Mwg7Y,3041
4
4
  aury/boot/application/adapter/__init__.py,sha256=e1bcSb1bxUMfofTwiCuHBZJk5-STkMCWPF2EJXHQ7UU,3976
5
5
  aury/boot/application/adapter/base.py,sha256=Ar_66fiHPDEmV-1DKnqXKwc53p3pozG31bgTJTEUriY,15763
6
6
  aury/boot/application/adapter/config.py,sha256=X6ppQMldyJbEdG1GcQSc2SulLtyeBTr8OAboYIjkSu0,8153
@@ -8,15 +8,15 @@ aury/boot/application/adapter/decorators.py,sha256=yyGu_16bWWUiO36gxCeQWgG0DN19p
8
8
  aury/boot/application/adapter/exceptions.py,sha256=Kzm-ytRxdUnSMIcWCSOHPxo4Jh_A6YbyxlOVIUs-5F4,6183
9
9
  aury/boot/application/adapter/http.py,sha256=4TADsSzdSRU63307dmmo-2U_JpVP12mwTFy66B5Ps-w,10759
10
10
  aury/boot/application/app/__init__.py,sha256=I8FfCKDuDQsGzAK6BevyfdtAwieMUVYu6qgVQzBazpE,830
11
- aury/boot/application/app/base.py,sha256=MYfkrb2zZgumLrGWhtT71neGncUfVHyUeAHRnetoSXk,17668
12
- aury/boot/application/app/components.py,sha256=qBAd8hJ4Ap3cfCYJUJu9v_IvC16U9Q8O5dEZDtjDveY,24188
11
+ aury/boot/application/app/base.py,sha256=7n7uFAVmIylr9YZannWKeQSOLSifNosIJUMbL-nmJe4,21897
12
+ aury/boot/application/app/components.py,sha256=Ub7NlfxSPXSDcxUajQ5ed42kNmsBSol-UttcBfnx64Y,33473
13
13
  aury/boot/application/app/middlewares.py,sha256=BXe2H14FHzJUVpQM6DZUm-zfZRXSXIi1QIZ4_3izfHw,3306
14
14
  aury/boot/application/app/startup.py,sha256=DHKt3C2G7V5XfFr1SQMl14tNzcuDd9MqUVAxi274HDQ,7873
15
15
  aury/boot/application/config/__init__.py,sha256=Dd-myRSBCM18DXXsi863h0cJG5VFrI10xMRtjnvelGo,1894
16
16
  aury/boot/application/config/multi_instance.py,sha256=RXSp-xP8-bKMDEhq3SeL7T3lS8-vpRlvBEVBuZVjVK4,6475
17
- aury/boot/application/config/settings.py,sha256=-afn5p9rIS4_63OLqfG-mrTl2UergOZb6z8I5GyYOAY,31621
17
+ aury/boot/application/config/settings.py,sha256=MPOjUyPxvWOrw878FOs0gnUPkqSUMyLpQ-MZz9yMwls,37866
18
18
  aury/boot/application/constants/__init__.py,sha256=DCXs13_VVaQWHqO-qpJoZwRd7HIexiirtw_nu8msTXE,340
19
- aury/boot/application/constants/components.py,sha256=VBCgxJ_iZXjEZyUkC13ZCdqPmdKq-lQmU7jQpXnTa2Y,1056
19
+ aury/boot/application/constants/components.py,sha256=I4SlsF2DpSzMiLsi1wVrEmdHn4yV5J2h3ikMQqufPmM,1120
20
20
  aury/boot/application/constants/scheduler.py,sha256=S77FBIvHlyruvlabRWZJ2J1YAs2xWXPQI2yuGdGUDNA,471
21
21
  aury/boot/application/constants/service.py,sha256=_BjSNP83m1KuLcGs1oqciDU9Nk1mO45COucRYubuFkM,513
22
22
  aury/boot/application/errors/__init__.py,sha256=aYhGqjHayYr7sv9kM22y0sOo9R-8RK0r3Jf5_tgm7TQ,1217
@@ -29,7 +29,7 @@ aury/boot/application/interfaces/__init__.py,sha256=EGbiCL8IoGseylLVZO29Lkt3luyg
29
29
  aury/boot/application/interfaces/egress.py,sha256=t8FK17V649rsm65uAeBruYr2mhfcqJiIzkS8UPsOzlc,5346
30
30
  aury/boot/application/interfaces/ingress.py,sha256=rlflJ4nxAZ2Mc3Iy8ZX__GRgfAWcMYYzLhHL2NSk4_U,2425
31
31
  aury/boot/application/middleware/__init__.py,sha256=T01fmbcdO0Sm6JE74g23uuDyebBGYA4DMZMDBl0L00w,258
32
- aury/boot/application/middleware/logging.py,sha256=d3wbprbCxFJ6TpeEDomTSnjQ7sIadYxXnnKS6b_Wj38,11899
32
+ aury/boot/application/middleware/logging.py,sha256=kStfLskA_srNvWIuNMs0ptZ4Wr9-ke6hYl8kESxbhxc,13509
33
33
  aury/boot/application/migrations/__init__.py,sha256=Z5Gizx7f3AImRcl3cooiIDAZcNi5W-6GvB7mK5w1TNA,204
34
34
  aury/boot/application/migrations/manager.py,sha256=G7mzkNA3MFjyQmM2UwY0ZFNgGGVS4W5GoG2Sbj5AUXk,23685
35
35
  aury/boot/application/migrations/setup.py,sha256=cxzBIHGzRXhlIYs4_ZW6P85Z9A7uVnTq_dmQFKp3ha4,6485
@@ -45,9 +45,9 @@ aury/boot/commands/add.py,sha256=JRJir92oFHwIBtIKKEjQ7trUhfb9-kCH84x_7saV2gI,264
45
45
  aury/boot/commands/app.py,sha256=k0zHzR3ckt17laAYk6WwwS-lqzS-N8811XjKC-7lerg,7857
46
46
  aury/boot/commands/config.py,sha256=gPkG_jSWrXidjpyVdzABH7uRhoCgX5yrOcdKabtX5wY,4928
47
47
  aury/boot/commands/docker.py,sha256=7mKorZCPZgxH1XFslzo6W-uzpe61hGXz86JKOhOeBlo,9006
48
- aury/boot/commands/docs.py,sha256=7VaZyEuezmL28aM9y13ni-y-VYP-eNKff5-qb-9q1RQ,13066
48
+ aury/boot/commands/docs.py,sha256=Hz1W-2TW8DzaPxARqEF4UncPhGMI9h97jJ962dlox3U,14327
49
49
  aury/boot/commands/generate.py,sha256=WZieSXuofxJOC7NBiVGpBigB9NZ4GMcF2F1ReTNun1I,44420
50
- aury/boot/commands/init.py,sha256=vHg2Zhdoar1ANug9J8pT1tNGdH5S0GO19Rhsy-04mXQ,32262
50
+ aury/boot/commands/init.py,sha256=W_eCL3wydWaMSLqTpadREDnzC0w-LGgNnj3IBjuQAfA,32348
51
51
  aury/boot/commands/pkg.py,sha256=bw0QPptKscNgQ4I1SfSehTio9Q5KrvxgvkYx4tbZ7Vs,14495
52
52
  aury/boot/commands/scheduler.py,sha256=BCIGQcGryXpsYNF-mncP6v5kNoz6DZ10DMzMKVDiXxA,3516
53
53
  aury/boot/commands/worker.py,sha256=qAcPdoKpMBLYoi45X_y2-nobuYKxscJpooEB_0HhM4o,4163
@@ -61,14 +61,15 @@ aury/boot/commands/templates/generate/model.py.tpl,sha256=knFwMyGZ7wMpzH4_bQD_V1
61
61
  aury/boot/commands/templates/generate/repository.py.tpl,sha256=xoEg6lPAaLIRDeFy4I0FBsPPVLSy91h6xosAlaCL_mM,590
62
62
  aury/boot/commands/templates/generate/schema.py.tpl,sha256=HIaY5B0UG_S188nQLrZDEJ0q73WPdb7BmCdc0tseZA4,545
63
63
  aury/boot/commands/templates/generate/service.py.tpl,sha256=2hwQ8e4a5d_bIMx_jGDobdmKPMFLBlfQrQVQH4Ym5k4,1842
64
- aury/boot/commands/templates/project/AGENTS.md.tpl,sha256=SpqoXZXCyWmitBV6Mt0VvXWdPZAZXsLoU-vDEyJyQDc,8269
64
+ aury/boot/commands/templates/project/AGENTS.md.tpl,sha256=sp5qyzU-SGhgQCobpMW4EXRzpGsEsVdmJvspnKAP4AQ,10059
65
65
  aury/boot/commands/templates/project/README.md.tpl,sha256=oCeBiukk6Pa3hrCKybkfM2sIRHsPZ15nlwuFTUSFDwY,2459
66
66
  aury/boot/commands/templates/project/admin_console_init.py.tpl,sha256=K81L14thyEhRA8lFCQJVZL_NU22-sBz0xS68MJPeoCo,1541
67
+ aury/boot/commands/templates/project/alert_rules.example.yaml.tpl,sha256=QZH6SC5TcUhgX_2JRXk0k0g26wJf9xNwsdquiEIgg-I,2492
67
68
  aury/boot/commands/templates/project/config.py.tpl,sha256=H_B05FypBJxTjb7qIL91zC1C9e37Pk7C9gO0-b3CqNs,1009
68
69
  aury/boot/commands/templates/project/conftest.py.tpl,sha256=chbETK81Hy26cWz6YZ2cFgy7HbnABzYCqeyMzgpa3eI,726
69
70
  aury/boot/commands/templates/project/gitignore.tpl,sha256=OI0nt9u2E9EC-jAMoh3gpqamsWo18uDgyPybgee_snQ,3053
70
71
  aury/boot/commands/templates/project/main.py.tpl,sha256=Q61ve3o1VkNPv8wcQK7lUosne18JWYeItxoXVNNoYJM,1070
71
- aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl,sha256=8Aept3yEAe9cVdRvkddr_oEF-lr2riPXYRzBuw_6DBA,2138
72
+ aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl,sha256=eOjtqMeKqZ8OgijrOwcpfpHhrhUvt_CiHPUtRG0dilA,2251
72
73
  aury/boot/commands/templates/project/aury_docs/01-model.md.tpl,sha256=1mQ3hGDxqEZjev4CD5-3dzYRFVonPNcAaStI1UBEUyM,6811
73
74
  aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl,sha256=JfUVdrIgW7_J6JGCcB-_uP_x-gCtjKiewwGv4Xr44QI,7803
74
75
  aury/boot/commands/templates/project/aury_docs/03-service.md.tpl,sha256=SgfPAgLVi_RbMjSAe-m49jQOIr6vfUT8VF2V1E-qa3w,15098
@@ -85,13 +86,15 @@ aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl,sha256=aGpf2phQ
85
86
  aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl,sha256=4bxLQBbCi0Fue0VQWOPt6acZ5P00BoLkCoLPQe_8k4U,2396
86
87
  aury/boot/commands/templates/project/aury_docs/15-events.md.tpl,sha256=a4wQRgVPuYUGTGmw_lX1HJH_yFTbD30mBz7Arc4zgfs,3361
87
88
  aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl,sha256=pkmJkZw2Ca6_uYk2jZvAb8DozjBa2tWq_t3gtq1lFSk,11456
89
+ aury/boot/commands/templates/project/aury_docs/17-alerting.md.tpl,sha256=2MosApSAuGerBw7SOO-ihk4NTp2qEkgOUyu6pS2m0UY,5709
88
90
  aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl,sha256=9JSdiAbu3SGmmF_iCslw5LBPlks2DYf4WYw05pA_2_I,5673
89
91
  aury/boot/commands/templates/project/env_templates/_header.tpl,sha256=Pt0X_I25o1th3CLR228L2-nlcC-lIkN8cPailohBEkU,513
90
92
  aury/boot/commands/templates/project/env_templates/admin.tpl,sha256=wWt3iybOpBHtuw6CkoUJ1bzEL0aNgOzKDEkMKhI2oag,2032
91
93
  aury/boot/commands/templates/project/env_templates/cache.tpl,sha256=_sK-p_FECj4mVvggNvgb4Wu0yGii0Ocz560syG7DU2c,498
92
94
  aury/boot/commands/templates/project/env_templates/database.tpl,sha256=2lWzTKt4X0SpeBBCkrDV90Di4EfoAuqYzhVsh74vTUI,907
93
95
  aury/boot/commands/templates/project/env_templates/log.tpl,sha256=x5rkrEFJISH0gaCcr-wTCbDYtyFnlLNJpY789fqjZgc,754
94
- aury/boot/commands/templates/project/env_templates/messaging.tpl,sha256=ZaNBM4pq0pgkTXbCBx_sgjm8QKEBfW1nSnsKZfHu-HM,1771
96
+ aury/boot/commands/templates/project/env_templates/messaging.tpl,sha256=SzPRKwN0wO5e1kpjkSwpPJfVmiUDzZkK4Qm-qNsCvVE,2178
97
+ aury/boot/commands/templates/project/env_templates/monitoring.tpl,sha256=Zq0xQzDrCRtbeLCQB3pkEE2p8FFED6IjQo4TqMyd_P8,2584
95
98
  aury/boot/commands/templates/project/env_templates/rpc.tpl,sha256=FhweCFakawGLSs01a_BkmZo11UhWax2-VCBudHj68WA,1163
96
99
  aury/boot/commands/templates/project/env_templates/scheduler.tpl,sha256=c8Grcs1rgBB58RHlxqmDMPHQl8BnbcqNW473ctmsojU,752
97
100
  aury/boot/commands/templates/project/env_templates/service.tpl,sha256=b-a2GyRyoaunbDj_2kaSw3OFxcugscmPvUBG7w0XO8c,710
@@ -106,9 +109,9 @@ aury/boot/common/exceptions/__init__.py,sha256=aS3rIXWc5qNNJbfMs_PNmBlFsyNdKUMEr
106
109
  aury/boot/common/i18n/__init__.py,sha256=2cy4kteU-1YsAHkuMDTr2c5o4G33fvtYUGKtzEy1Q6c,394
107
110
  aury/boot/common/i18n/translator.py,sha256=_vEDL2SjEI1vwMNHbnJb0xErKUPLm7VmhyOuMBeCqRM,8412
108
111
  aury/boot/common/logging/__init__.py,sha256=Z6pMdjXZMWXz6w-1ev0h6QN3G3c8Cz7EpOqZh42kgQ0,1612
109
- aury/boot/common/logging/context.py,sha256=U78XwjVcDP7Y96VTGclCcP09p0ZWCmiEYzVhp1Obytw,2058
112
+ aury/boot/common/logging/context.py,sha256=ndml3rUokEIt5-845E5aW8jI8b4N93ZtukyqsjqzuNE,2566
110
113
  aury/boot/common/logging/decorators.py,sha256=UaGMhRJdARNJ2VgCuRwaNX0DD5wIc1gAl6NDj7u8K2c,3354
111
- aury/boot/common/logging/format.py,sha256=V1VoZCPFAaAXy20n3WehOsZGTHuboYMHnSFGa0GxhdU,9648
114
+ aury/boot/common/logging/format.py,sha256=ZEqLagTdyGadywTamybcEh1fAZng3Wfx7DC952TFU30,9782
112
115
  aury/boot/common/logging/setup.py,sha256=ZPxOHJD8Fw4KxKPgsf8ZHQ2mWuXxKh4EJtXXvGY7Hgo,9422
113
116
  aury/boot/contrib/__init__.py,sha256=fyk_St9VufIx64hsobv9EsOYzb_T5FbJHxjqtPds4g8,198
114
117
  aury/boot/contrib/admin_console/__init__.py,sha256=HEesLFrtYtBFWTDrh5H3mR-4V4LRg5N4a2a1C4-Whgs,445
@@ -131,19 +134,18 @@ aury/boot/domain/repository/query_builder.py,sha256=pFErMzsBql-T6gBX0S4FxIheCkNa
131
134
  aury/boot/domain/service/__init__.py,sha256=ZRotaBlqJXn7ebPTQjjoHtorpQREk8AgTD69UCcRd1k,118
132
135
  aury/boot/domain/service/base.py,sha256=6sN0nf8r5yUZsE6AcZOiOXFCqzb61oCxTfrWlqjIo9I,2035
133
136
  aury/boot/domain/transaction/__init__.py,sha256=EKnjJ235SYjMCvGIuLVlTdYRzU35RxNMejRGUExYqqE,15488
134
- aury/boot/infrastructure/__init__.py,sha256=ppP1-suaDICMNvBSXj_4DVSH3h0D8e0qZhtENCr16m8,3007
137
+ aury/boot/infrastructure/__init__.py,sha256=DDEr_BIL5OyMJjNlI05jGIUrSHn6MPdnW9xnCS4eHfg,2949
135
138
  aury/boot/infrastructure/cache/__init__.py,sha256=G40uCkpJ1jSs2fc_CBDem73iQQzCcp-4GG1WpDJzwaA,658
136
139
  aury/boot/infrastructure/cache/backends.py,sha256=9QMQ8G9DtZgzVXZ_Ng7n1gXRu-_OQZgw4FHPOfr1qco,13585
137
140
  aury/boot/infrastructure/cache/base.py,sha256=Yn-h_SGcOoGGZW1unOnz_zgcuHaMKOEmwiUP0P7_pIM,1624
138
141
  aury/boot/infrastructure/cache/exceptions.py,sha256=KZsFIHXW3_kOh_KB93EVZJKbiDvDw8aloAefJ3kasP8,622
139
142
  aury/boot/infrastructure/cache/factory.py,sha256=aF74JoiiSKFgctqqh2Z8OtGRS2Am_ou-I40GyygLzC0,2489
140
143
  aury/boot/infrastructure/cache/manager.py,sha256=GGoOgYyIdWKMmhej5cRvEfpNeMN1GaSaU9hc0dy8_sA,12106
141
- aury/boot/infrastructure/channel/__init__.py,sha256=Ztcfn1-TomgV91qhePpFK-3_nKgBt862yEFYUzIwPlo,566
142
- aury/boot/infrastructure/channel/base.py,sha256=kRjvoOQ0X5ilUAdz03BNBfbZJgdqcsjUviK4_dJatBs,2642
143
- aury/boot/infrastructure/channel/manager.py,sha256=wOkO018Ch2694z9vOGgojaGOdCXHi0zrekkfLezn8bo,7444
144
- aury/boot/infrastructure/channel/backends/__init__.py,sha256=zrOhrzkhEIgsO7Armhgda1ruJQ6a9ZK7GPZuzvEiuN8,151
145
- aury/boot/infrastructure/channel/backends/memory.py,sha256=QQyZxIdYtA_pWqdKgUIMkvnbBwvv45-vX-qostDHyfg,4699
146
- aury/boot/infrastructure/channel/backends/redis.py,sha256=_UL7wE-bO147CPXKDjJgYGjj09Lg9x9U2PLYa37q5yQ,4666
144
+ aury/boot/infrastructure/channel/__init__.py,sha256=NmjddenZPz1Dcl0glwIF1Xn9gxBzvGvlOlzhV3eEnEQ,664
145
+ aury/boot/infrastructure/channel/base.py,sha256=TDiP7pXyd2ixiOM3cbxqCSOluGLTkmLCa8pv-KyQ0jo,2941
146
+ aury/boot/infrastructure/channel/manager.py,sha256=GT6eG6PglduKAr23i1PSmjjTQsALvGGoLjYiQ33aZiw,7488
147
+ aury/boot/infrastructure/channel/backends/__init__.py,sha256=NcXG8_KAqy1SiGUs2z_KvkS90jMfLJ6bzyYK4Jw4qCg,107
148
+ aury/boot/infrastructure/channel/backends/broadcaster.py,sha256=y8eKx6X6Iy9a_5vnLMm5gjqkq05SmJEWESw1-x0lIFg,4771
147
149
  aury/boot/infrastructure/clients/__init__.py,sha256=1ANMejb3RrBgaR-jq-dsxJ0kQDRHz5jV-QvdUNcf_ok,435
148
150
  aury/boot/infrastructure/clients/rabbitmq/__init__.py,sha256=cnU-W7jOcAgp_FvsY9EipNCeJzeA9gHLRuZ0yQZE2DI,200
149
151
  aury/boot/infrastructure/clients/rabbitmq/config.py,sha256=YmvNiISpqNt-LE2CrpzmxCgaEgYna7IbOfUSnA0B4T0,1239
@@ -155,19 +157,34 @@ aury/boot/infrastructure/database/__init__.py,sha256=MsHNyrJ2CZJT-lbVZzOAJ0nFfFE
155
157
  aury/boot/infrastructure/database/config.py,sha256=5LYy4DuLL0XNjVnX2HUcrMh3c71eeZa-vWGM8QCkL0U,1408
156
158
  aury/boot/infrastructure/database/exceptions.py,sha256=hUjsU23c0eMwogSDrKq_bQ6zvnY7PQSGaitbCEhhDZQ,766
157
159
  aury/boot/infrastructure/database/manager.py,sha256=lRSKL9jDkSdaA99DjD1k4EoQuQsn2vbh9XQQBOt9dM0,10317
158
- aury/boot/infrastructure/database/query_tools/__init__.py,sha256=D-8Wxm8x48rg9G95aH_b4re7S4_IGJO9zznArYXldFo,5500
160
+ aury/boot/infrastructure/database/query_tools/__init__.py,sha256=pOFuyDDNpkY5cSMJ-O6UA0-5Lq96-Zvt4FaYMhiwaMg,5488
159
161
  aury/boot/infrastructure/database/strategies/__init__.py,sha256=foj_2xEsgLZxshpK65YAhdJ2UZyh1tKvGRq6sre8pQY,5909
160
162
  aury/boot/infrastructure/di/__init__.py,sha256=qFYlk265d6_rS8OiX37_wOc7mBFw8hk3yipDYNkyjQg,231
161
163
  aury/boot/infrastructure/di/container.py,sha256=14FVbafGXea-JEAYeOEBxB6zAwndLCZJvprKiD_1IOQ,12524
162
- aury/boot/infrastructure/events/__init__.py,sha256=D5JNFkGHCH79nYbqUil0Z8eW2p6Gx8P0vBbNnHqnTgM,698
163
- aury/boot/infrastructure/events/base.py,sha256=oS6aNUWRvpXlbh7L3_4vzlwUumYmg44HKS1S4m_zOFo,3019
164
- aury/boot/infrastructure/events/manager.py,sha256=Hnua9x_Ppmo99JUEmjuZcKIPv2IEZQ3Du9ziPg-C8Fo,7570
164
+ aury/boot/infrastructure/events/__init__.py,sha256=-fXZiTKkwh2olyw8BCGO-Qv67ZDCS0enioY005vPhrI,676
165
+ aury/boot/infrastructure/events/base.py,sha256=m5sXe7rpdvq6pcgCAn2PX7lLXZgCpujteRqHXQ0oaqs,3238
166
+ aury/boot/infrastructure/events/manager.py,sha256=aQNzyPuz1oWAMkXz4UQqCRSPiGTcm8HmSE1lGWovhWE,7754
165
167
  aury/boot/infrastructure/events/middleware.py,sha256=Ck3qNMTtLuFFKsJuEUeOMG9nu3qK1N_aqt6wH5JoAtw,1336
166
- aury/boot/infrastructure/events/backends/__init__.py,sha256=V_hPtdjVUkYU4Uf8hTPVBUcnNYG9OfkjRPDnjp_5_zA,224
167
- aury/boot/infrastructure/events/backends/memory.py,sha256=Up7vAxdJvIqkcqpnKNCu81ec6iCfNIhcQ-jKM3M2hZc,2623
168
+ aury/boot/infrastructure/events/backends/__init__.py,sha256=1mj0rDauHdoRm4kXOg87l2f9jnMbj_jKZdVnIZMj9XM,185
169
+ aury/boot/infrastructure/events/backends/broadcaster.py,sha256=FnxO62LUXWLs1ZEiaYmNiMaL3ccXNtuc3DFzLe02eK0,6700
168
170
  aury/boot/infrastructure/events/backends/rabbitmq.py,sha256=XCuI9mc3GR-t0zht4yZ3e2nnyFl8UuTDir_0nsDbfxM,6495
169
- aury/boot/infrastructure/events/backends/redis.py,sha256=i8jPCtR7ITPVTl9DVFDbNbjypnWoeSpar6z4lJJlOD8,5790
170
- aury/boot/infrastructure/monitoring/__init__.py,sha256=VgElCdCVcgERTIn3oRoSNslR82W9gRX5vgJcYDeloak,16149
171
+ aury/boot/infrastructure/monitoring/__init__.py,sha256=KGtJU0slbRvFzzUv60LQHB12sX7eNNvGDu8Lyk9Owy8,22415
172
+ aury/boot/infrastructure/monitoring/alerting/__init__.py,sha256=gBZ23JnCjqglyYTTUxfkmilZ4mY_ZkrkKMDo--3COGE,1363
173
+ aury/boot/infrastructure/monitoring/alerting/aggregator.py,sha256=fiI-lBSqWxXv1eVPfaDNjcigX-81w41fcmhD_vN_XSs,5805
174
+ aury/boot/infrastructure/monitoring/alerting/events.py,sha256=zJvTevQ-9JflIDyYVo1BRzOVyAGhdgEfRlMsD0NcBgM,4056
175
+ aury/boot/infrastructure/monitoring/alerting/manager.py,sha256=ZcIMhBgHmrSGn8FeJOIBIiwKOcdWvT6NBm-6wvRmGc4,14525
176
+ aury/boot/infrastructure/monitoring/alerting/rules.py,sha256=XcXJXWVrPpdZKKz63BiVWmwkKitIaNQWBfJATrSzG1M,6116
177
+ aury/boot/infrastructure/monitoring/alerting/notifiers/__init__.py,sha256=dsfxThPHO_Ofb3Wo_dYlL8HvP_N63pb_S_UXm_qSxF8,321
178
+ aury/boot/infrastructure/monitoring/alerting/notifiers/base.py,sha256=_RXZMzWX-YeTG0Up1U8CwK8ADfX34dd0Sh56ugfqOWM,1462
179
+ aury/boot/infrastructure/monitoring/alerting/notifiers/feishu.py,sha256=JAMJiCNRYoDeJrYn29ew_ZVXDGq8OLgiFApRWd4iPY0,7134
180
+ aury/boot/infrastructure/monitoring/alerting/notifiers/webhook.py,sha256=EiGtLCztHoyjRTR3cvhguRMXbMkScfwY_mXRy9AD5Vw,3514
181
+ aury/boot/infrastructure/monitoring/health/__init__.py,sha256=nqwFFXl6J9yTfQa1JLjQDs4hS4qSVEeA4w07JWdt4jM,7305
182
+ aury/boot/infrastructure/monitoring/tracing/__init__.py,sha256=YizkpnhY-bcUUcd8YaDzUsluMflhNOH1dAKdVtkW05U,1287
183
+ aury/boot/infrastructure/monitoring/tracing/context.py,sha256=s_k2MzNl4LDDpei9xUP6TFW5BwZneoQg44RPaw95jac,978
184
+ aury/boot/infrastructure/monitoring/tracing/logging.py,sha256=gzuKa1ZiyY4z06fHNTbjgZasS6mLftSEaZQQ-Z6J_RE,2041
185
+ aury/boot/infrastructure/monitoring/tracing/processor.py,sha256=qc37YmS8rslpwqAYHrBDzVvNWmXRIFEwpld34NMmByk,12640
186
+ aury/boot/infrastructure/monitoring/tracing/provider.py,sha256=AnPHUDHnfrCB48WHjp9vLBhCh9BpyfWb3DHGRh6Din4,11553
187
+ aury/boot/infrastructure/monitoring/tracing/tracing.py,sha256=BeWL-FYtlQ05r05wGJ6qjTSpypgCp-7OzdNnZ3uunB0,6890
171
188
  aury/boot/infrastructure/mq/__init__.py,sha256=Q7kBk_GeQnxnqkyp29Bh1yFH3Q8xxxjs8oDYLeDj8C0,498
172
189
  aury/boot/infrastructure/mq/base.py,sha256=kHrWUysWflMj3qyOnioLZ90it8d9Alq1Wb4PYhpBW4k,3396
173
190
  aury/boot/infrastructure/mq/manager.py,sha256=DVXOQhoqx9dz9INajWiAxLnKjLaP-otKmdiBUzxgsAY,7502
@@ -192,7 +209,7 @@ aury/boot/testing/client.py,sha256=KOg1EemuIVsBG68G5y0DjSxZGcIQVdWQ4ASaHE3o1R0,4
192
209
  aury/boot/testing/factory.py,sha256=8GvwX9qIDu0L65gzJMlrWB0xbmJ-7zPHuwk3eECULcg,5185
193
210
  aury/boot/toolkit/__init__.py,sha256=AcyVb9fDf3CaEmJPNkWC4iGv32qCPyk4BuFKSuNiJRQ,334
194
211
  aury/boot/toolkit/http/__init__.py,sha256=zIPmpIZ9Qbqe25VmEr7jixoY2fkRbLm7NkCB9vKpg6I,11039
195
- aury_boot-0.0.29.dist-info/METADATA,sha256=EaL1z6pE7pRmQ6YSAytT91uPj34ejAyuzeTpWWpAI-g,7695
196
- aury_boot-0.0.29.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
197
- aury_boot-0.0.29.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
198
- aury_boot-0.0.29.dist-info/RECORD,,
212
+ aury_boot-0.0.31.dist-info/METADATA,sha256=UPuRX_9BPO-OPlk78XK1ynbzHq-je4mrKTKrAvsouzA,8560
213
+ aury_boot-0.0.31.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
214
+ aury_boot-0.0.31.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
215
+ aury_boot-0.0.31.dist-info/RECORD,,
@@ -1,126 +0,0 @@
1
- """内存通道后端。
2
-
3
- 适用于单进程场景,如开发环境或简单应用。
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import asyncio
9
- from collections.abc import AsyncIterator
10
- import contextlib
11
- import fnmatch
12
-
13
- from aury.boot.common.logging import logger
14
-
15
- from ..base import ChannelMessage, IChannel
16
-
17
-
18
- class MemoryChannel(IChannel):
19
- """内存通道实现。
20
-
21
- 使用 asyncio.Queue 实现进程内的发布/订阅。
22
-
23
- 注意:仅适用于单进程,不支持跨进程通信。
24
- """
25
-
26
- def __init__(self, max_subscribers: int = 1000) -> None:
27
- """初始化内存通道。
28
-
29
- Args:
30
- max_subscribers: 每个通道最大订阅者数量
31
- """
32
- self._max_subscribers = max_subscribers
33
- # channel -> list of queues
34
- self._subscribers: dict[str, list[asyncio.Queue[ChannelMessage]]] = {}
35
- # pattern -> list of queues (用于 psubscribe)
36
- self._pattern_subscribers: dict[str, list[asyncio.Queue[ChannelMessage]]] = {}
37
- self._lock = asyncio.Lock()
38
-
39
- async def publish(self, channel: str, message: ChannelMessage) -> None:
40
- """发布消息到通道。"""
41
- message.channel = channel
42
- async with self._lock:
43
- # 精确匹配订阅者
44
- subscribers = self._subscribers.get(channel, [])
45
- for queue in subscribers:
46
- try:
47
- queue.put_nowait(message)
48
- except asyncio.QueueFull:
49
- logger.warning(f"通道 [{channel}] 订阅者队列已满,消息被丢弃")
50
-
51
- # 模式匹配订阅者
52
- for pattern, queues in self._pattern_subscribers.items():
53
- if fnmatch.fnmatch(channel, pattern):
54
- for queue in queues:
55
- try:
56
- queue.put_nowait(message)
57
- except asyncio.QueueFull:
58
- logger.warning(f"模式 [{pattern}] 订阅者队列已满,消息被丢弃")
59
-
60
- async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
61
- """订阅通道。"""
62
- queue: asyncio.Queue[ChannelMessage] = asyncio.Queue(maxsize=100)
63
-
64
- async with self._lock:
65
- if channel not in self._subscribers:
66
- self._subscribers[channel] = []
67
- if len(self._subscribers[channel]) >= self._max_subscribers:
68
- raise RuntimeError(f"通道 [{channel}] 订阅者数量已达上限")
69
- self._subscribers[channel].append(queue)
70
-
71
- try:
72
- while True:
73
- message = await queue.get()
74
- yield message
75
- finally:
76
- async with self._lock:
77
- if channel in self._subscribers:
78
- with contextlib.suppress(ValueError):
79
- self._subscribers[channel].remove(queue)
80
- if not self._subscribers[channel]:
81
- del self._subscribers[channel]
82
-
83
- async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
84
- """模式订阅通道。
85
-
86
- 使用 fnmatch 风格的通配符:
87
- - `*` 匹配任意字符
88
- - `?` 匹配单个字符
89
- - `[seq]` 匹配 seq 中的任意字符
90
- """
91
- queue: asyncio.Queue[ChannelMessage] = asyncio.Queue(maxsize=100)
92
-
93
- async with self._lock:
94
- if pattern not in self._pattern_subscribers:
95
- self._pattern_subscribers[pattern] = []
96
- if len(self._pattern_subscribers[pattern]) >= self._max_subscribers:
97
- raise RuntimeError(f"模式 [{pattern}] 订阅者数量已达上限")
98
- self._pattern_subscribers[pattern].append(queue)
99
-
100
- try:
101
- while True:
102
- message = await queue.get()
103
- yield message
104
- finally:
105
- async with self._lock:
106
- if pattern in self._pattern_subscribers:
107
- with contextlib.suppress(ValueError):
108
- self._pattern_subscribers[pattern].remove(queue)
109
- if not self._pattern_subscribers[pattern]:
110
- del self._pattern_subscribers[pattern]
111
-
112
- async def unsubscribe(self, channel: str) -> None:
113
- """取消订阅通道(清除所有订阅者)。"""
114
- async with self._lock:
115
- if channel in self._subscribers:
116
- del self._subscribers[channel]
117
-
118
- async def close(self) -> None:
119
- """关闭通道,清理所有订阅。"""
120
- async with self._lock:
121
- self._subscribers.clear()
122
- self._pattern_subscribers.clear()
123
- logger.debug("内存通道已关闭")
124
-
125
-
126
- __all__ = ["MemoryChannel"]