aury-boot 0.0.22__py3-none-any.whl → 0.0.23__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.
aury/boot/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.22'
32
- __version_tuple__ = version_tuple = (0, 0, 22)
31
+ __version__ = version = '0.0.23'
32
+ __version_tuple__ = version_tuple = (0, 0, 23)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -71,12 +71,51 @@ async def sse_stream(user_id: str):
71
71
 
72
72
  @router.post("/notify/{{user_id}}")
73
73
  async def send_notification(user_id: str, message: str):
74
- \"\"\"\u53d1\u9001\u901a\u77e5\u3002\"\"\"
74
+ \"\"\"发送通知。\"\"\"
75
75
  await sse_channel.publish(f"user:{{user_id}}", {{"message": message}})
76
76
  return {{"status": "sent"}}
77
77
  ```
78
78
 
79
- ## 13.4 应用场景示例
79
+ ## 13.4 模式订阅(psubscribe)
80
+
81
+ 使用通配符订阅多个通道,适合一个 SSE 连接接收多种事件的场景。
82
+
83
+ ```python
84
+ @router.get("/spaces/{{space_id}}/events")
85
+ async def space_events(space_id: str):
86
+ \"\"\"订阅空间下所有事件。\"\"\"
87
+ async def event_generator():
88
+ # 使用 psubscribe 订阅 space:{id}:* 下所有事件
89
+ async for msg in sse_channel.psubscribe(f"space:{{space_id}}:*"):
90
+ yield msg.to_sse() # 自动转换为 SSE 格式
91
+
92
+ return StreamingResponse(
93
+ event_generator(),
94
+ media_type="text/event-stream"
95
+ )
96
+
97
+
98
+ # 后端发布不同类型的事件
99
+ await sse_channel.publish(f"space:{{space_id}}:file_analyzed", {{
100
+ "file_id": "abc",
101
+ "status": "done"
102
+ }}, event="file_analyzed")
103
+
104
+ await sse_channel.publish(f"space:{{space_id}}:comment_added", {{
105
+ "comment_id": "xyz",
106
+ "content": "..."
107
+ }}, event="comment_added")
108
+ ```
109
+
110
+ **通配符说明**:
111
+ - `*` 匹配任意字符
112
+ - `?` 匹配单个字符
113
+ - `[seq]` 匹配 seq 中的任意字符
114
+ - 示例:`space:123:*` 匹配 `space:123:file_analyzed`、`space:123:comment_added` 等
115
+
116
+ Redis 后端使用 Redis 原生 `PSUBSCRIBE`,内存后端使用 `fnmatch` 实现。
117
+
118
+ ## 13.5 应用场景示例
80
119
 
81
120
  ```python
82
121
  # 不同业务场景使用不同的命名实例
@@ -90,7 +129,7 @@ await chat_channel.initialize(backend="redis", redis_client=redis_client) # 需
90
129
  await notify_channel.initialize(backend="redis", redis_client=redis_client)
91
130
  ```
92
131
 
93
- ## 13.5 环境变量
132
+ ## 13.6 环境变量
94
133
 
95
134
  ```bash
96
135
  # 默认实例
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  import asyncio
9
9
  from collections.abc import AsyncIterator
10
10
  import contextlib
11
+ import fnmatch
11
12
 
12
13
  from aury.boot.common.logging import logger
13
14
 
@@ -31,12 +32,15 @@ class MemoryChannel(IChannel):
31
32
  self._max_subscribers = max_subscribers
32
33
  # channel -> list of queues
33
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]]] = {}
34
37
  self._lock = asyncio.Lock()
35
38
 
36
39
  async def publish(self, channel: str, message: ChannelMessage) -> None:
37
40
  """发布消息到通道。"""
38
41
  message.channel = channel
39
42
  async with self._lock:
43
+ # 精确匹配订阅者
40
44
  subscribers = self._subscribers.get(channel, [])
41
45
  for queue in subscribers:
42
46
  try:
@@ -44,6 +48,15 @@ class MemoryChannel(IChannel):
44
48
  except asyncio.QueueFull:
45
49
  logger.warning(f"通道 [{channel}] 订阅者队列已满,消息被丢弃")
46
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
+
47
60
  async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
48
61
  """订阅通道。"""
49
62
  queue: asyncio.Queue[ChannelMessage] = asyncio.Queue(maxsize=100)
@@ -67,6 +80,35 @@ class MemoryChannel(IChannel):
67
80
  if not self._subscribers[channel]:
68
81
  del self._subscribers[channel]
69
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
+
70
112
  async def unsubscribe(self, channel: str) -> None:
71
113
  """取消订阅通道(清除所有订阅者)。"""
72
114
  async with self._lock:
@@ -77,6 +119,7 @@ class MemoryChannel(IChannel):
77
119
  """关闭通道,清理所有订阅。"""
78
120
  async with self._lock:
79
121
  self._subscribers.clear()
122
+ self._pattern_subscribers.clear()
80
123
  logger.debug("内存通道已关闭")
81
124
 
82
125
 
@@ -72,6 +72,48 @@ class RedisChannel(IChannel):
72
72
  await pubsub.unsubscribe(channel)
73
73
  await pubsub.close()
74
74
 
75
+ async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
76
+ """模式订阅(通配符)。
77
+
78
+ Args:
79
+ pattern: 通道模式,支持 * 和 ? 通配符
80
+ - * 匹配任意字符
81
+ - ? 匹配单个字符
82
+ - 示例: "space:123:*" 订阅 space:123 下所有事件
83
+
84
+ Yields:
85
+ ChannelMessage: 接收到的消息
86
+ """
87
+ pubsub = self._client.connection.pubsub()
88
+ await pubsub.psubscribe(pattern)
89
+
90
+ try:
91
+ async for raw_message in pubsub.listen():
92
+ # psubscribe 的消息类型是 "pmessage"
93
+ if raw_message["type"] == "pmessage":
94
+ try:
95
+ data = json.loads(raw_message["data"])
96
+ # pmessage 包含实际匹配的通道名
97
+ actual_channel = raw_message.get("channel")
98
+ if isinstance(actual_channel, bytes):
99
+ actual_channel = actual_channel.decode("utf-8")
100
+
101
+ message = ChannelMessage(
102
+ data=data.get("data"),
103
+ event=data.get("event"),
104
+ id=data.get("id"),
105
+ channel=actual_channel or data.get("channel"),
106
+ timestamp=datetime.fromisoformat(data["timestamp"])
107
+ if data.get("timestamp")
108
+ else datetime.now(),
109
+ )
110
+ yield message
111
+ except (json.JSONDecodeError, KeyError) as e:
112
+ logger.warning(f"解析通道消息失败: {e}")
113
+ finally:
114
+ await pubsub.punsubscribe(pattern)
115
+ await pubsub.close()
116
+
75
117
  async def unsubscribe(self, channel: str) -> None:
76
118
  """取消订阅通道。"""
77
119
  if self._pubsub:
@@ -70,6 +70,21 @@ class IChannel(ABC):
70
70
  """
71
71
  ...
72
72
 
73
+ async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
74
+ """模式订阅(通配符)。
75
+
76
+ Args:
77
+ pattern: 通道模式,如 "space:123:*" 订阅 space:123 下所有事件
78
+
79
+ Yields:
80
+ ChannelMessage: 接收到的消息
81
+
82
+ 注意:内存后端默认回退到普通 subscribe,Redis 后端支持真正的模式匹配。
83
+ """
84
+ # 默认实现:回退到普通订阅(子类可覆盖)
85
+ async for msg in self.subscribe(pattern):
86
+ yield msg
87
+
73
88
  @abstractmethod
74
89
  async def unsubscribe(self, channel: str) -> None:
75
90
  """取消订阅通道。
@@ -178,6 +178,29 @@ class ChannelManager:
178
178
  async for message in self.backend.subscribe(channel):
179
179
  yield message
180
180
 
181
+ async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
182
+ """模式订阅(通配符)。
183
+
184
+ Args:
185
+ pattern: 通道模式,支持 * 和 ? 通配符
186
+ - "space:123:*" 订阅 space:123 下所有事件
187
+ - "user:*:notification" 订阅所有用户的通知
188
+
189
+ Yields:
190
+ ChannelMessage: 接收到的消息
191
+
192
+ 示例:
193
+ # SSE 路由中订阅某个空间的所有事件
194
+ async for msg in channel.psubscribe(f"space:{{space_id}}:*"):
195
+ yield msg.to_sse()
196
+
197
+ # 发布不同类型的事件
198
+ await channel.publish(f"space:{{space_id}}:file_analyzed", {{...}})
199
+ await channel.publish(f"space:{{space_id}}:comment_added", {{...}})
200
+ """
201
+ async for message in self.backend.psubscribe(pattern):
202
+ yield message
203
+
181
204
  async def unsubscribe(self, channel: str) -> None:
182
205
  """取消订阅通道。
183
206
 
@@ -17,10 +17,17 @@ if TYPE_CHECKING:
17
17
  from aury.boot.infrastructure.clients.redis import RedisClient
18
18
 
19
19
 
20
+ # 框架默认前缀
21
+ DEFAULT_CHANNEL_PREFIX = "aury:event:"
22
+
23
+
20
24
  class RedisEventBus(IEventBus):
21
25
  """Redis 事件总线实现。
22
26
 
23
27
  使用 Redis Pub/Sub 实现跨进程的事件发布/订阅。
28
+
29
+ 频道命名格式:{channel_prefix}{event_name}
30
+ 默认:aury:event:user.created
24
31
  """
25
32
 
26
33
  def __init__(
@@ -28,14 +35,14 @@ class RedisEventBus(IEventBus):
28
35
  url: str | None = None,
29
36
  *,
30
37
  redis_client: RedisClient | None = None,
31
- channel_prefix: str = "events:",
38
+ channel_prefix: str = DEFAULT_CHANNEL_PREFIX,
32
39
  ) -> None:
33
40
  """初始化 Redis 事件总线。
34
41
 
35
42
  Args:
36
43
  url: Redis 连接 URL(当 redis_client 为 None 时必须提供)
37
44
  redis_client: RedisClient 实例(可选,优先使用)
38
- channel_prefix: 频道名称前缀
45
+ channel_prefix: 频道名称前缀,默认 "aury:event:"
39
46
 
40
47
  Raises:
41
48
  ValueError: 当 url 和 redis_client 都为 None 时
@@ -97,8 +97,8 @@ class EventBusManager:
97
97
  config: EventInstanceConfig | None = None,
98
98
  redis_client: RedisClient | None = None,
99
99
  url: str | None = None,
100
- channel_prefix: str = "events:",
101
- exchange_name: str = "events",
100
+ channel_prefix: str | None = None,
101
+ exchange_name: str = "aury.events",
102
102
  ) -> EventBusManager:
103
103
  """初始化事件总线(链式调用)。
104
104
 
@@ -107,8 +107,8 @@ class EventBusManager:
107
107
  config: Event 实例配置(推荐,自动根据 backend 初始化)
108
108
  redis_client: Redis 客户端(当 backend=redis 且 config=None 时需要)
109
109
  url: 连接 URL(当 config=None 时需要)
110
- channel_prefix: Redis 频道前缀
111
- exchange_name: RabbitMQ 交换机名称
110
+ channel_prefix: Redis 频道前缀,默认 "aury:event:"
111
+ exchange_name: RabbitMQ 交换机名称,默认 "aury.events"
112
112
 
113
113
  Returns:
114
114
  self: 支持链式调用
@@ -136,7 +136,11 @@ class EventBusManager:
136
136
  if backend == EventBackend.MEMORY:
137
137
  self._backend = MemoryEventBus()
138
138
  elif backend == EventBackend.REDIS:
139
- self._backend = RedisEventBus(url=url, redis_client=redis_client, channel_prefix=channel_prefix)
139
+ # channel_prefix 为 None 时使用 RedisEventBus 的默认值
140
+ kwargs = {"url": url, "redis_client": redis_client}
141
+ if channel_prefix is not None:
142
+ kwargs["channel_prefix"] = channel_prefix
143
+ self._backend = RedisEventBus(**kwargs)
140
144
  elif backend == EventBackend.RABBITMQ:
141
145
  self._backend = RabbitMQEventBus(url=url, exchange_name=exchange_name)
142
146
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aury-boot
3
- Version: 0.0.22
3
+ Version: 0.0.23
4
4
  Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: alembic>=1.17.2
@@ -1,5 +1,5 @@
1
1
  aury/boot/__init__.py,sha256=pCno-EInnpIBa1OtxNYF-JWf9j95Cd2h6vmu0xqa_-4,1791
2
- aury/boot/_version.py,sha256=ppqXAvMQkDCf5uPK5J4O6hozIheDimMJFhFAaY9euV0,706
2
+ aury/boot/_version.py,sha256=ibKwfdtUKJ8ftEJ-ANHeQVfItYjoZhYBzigrJx5AdJU,706
3
3
  aury/boot/application/__init__.py,sha256=0o_XmiwFCeAu06VHggS8I1e7_nSMoRq0Hcm0fYfCywU,3071
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
@@ -81,7 +81,7 @@ aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl,sha256=swHOQ_pWPt
81
81
  aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl,sha256=mhe0j0S51ndPJLjaQ6yD8OPYBEO02NHumJVbBvz2qkw,4320
82
82
  aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl,sha256=bwxFCGQsO9cTEbwqJF1xcjsZKP82HRWhIMRUS0c9_ZI,2435
83
83
  aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl,sha256=6z3mN54qP2jtpTFOJBLVexvEv0ZHXYKjncvpZG4yOdw,1883
84
- aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl,sha256=rdtlog3ajcSsT0fq9a_US3MPcZhTvDo72eT6hetg4aI,3376
84
+ aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl,sha256=xdWsIO4vT-0V1BKvPgWiG7DaQwKQSFdv7CeS16iS0cQ,4578
85
85
  aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl,sha256=4bxLQBbCi0Fue0VQWOPt6acZ5P00BoLkCoLPQe_8k4U,2396
86
86
  aury/boot/commands/templates/project/aury_docs/15-events.md.tpl,sha256=a4wQRgVPuYUGTGmw_lX1HJH_yFTbD30mBz7Arc4zgfs,3361
87
87
  aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl,sha256=pkmJkZw2Ca6_uYk2jZvAb8DozjBa2tWq_t3gtq1lFSk,11456
@@ -139,11 +139,11 @@ aury/boot/infrastructure/cache/exceptions.py,sha256=KZsFIHXW3_kOh_KB93EVZJKbiDvD
139
139
  aury/boot/infrastructure/cache/factory.py,sha256=aF74JoiiSKFgctqqh2Z8OtGRS2Am_ou-I40GyygLzC0,2489
140
140
  aury/boot/infrastructure/cache/manager.py,sha256=GGoOgYyIdWKMmhej5cRvEfpNeMN1GaSaU9hc0dy8_sA,12106
141
141
  aury/boot/infrastructure/channel/__init__.py,sha256=Ztcfn1-TomgV91qhePpFK-3_nKgBt862yEFYUzIwPlo,566
142
- aury/boot/infrastructure/channel/base.py,sha256=lBpP6vQB2AReoE7pJorkj9mAylXgC31B9Iwhyy2XKlk,2087
143
- aury/boot/infrastructure/channel/manager.py,sha256=aZ-5lVn2lVRnq_tyCcEgBjZngrt_B6tuoWxlDOf3ykw,6260
142
+ aury/boot/infrastructure/channel/base.py,sha256=kRjvoOQ0X5ilUAdz03BNBfbZJgdqcsjUviK4_dJatBs,2642
143
+ aury/boot/infrastructure/channel/manager.py,sha256=kHdeVzo7btivJs8TyGDu2QgucnAlLqr-hL1dJegD72U,7166
144
144
  aury/boot/infrastructure/channel/backends/__init__.py,sha256=zrOhrzkhEIgsO7Armhgda1ruJQ6a9ZK7GPZuzvEiuN8,151
145
- aury/boot/infrastructure/channel/backends/memory.py,sha256=bQuuCU1fHRYdzLRsGwtqObI6U6VYlfyfT_gEwsZcujQ,2808
146
- aury/boot/infrastructure/channel/backends/redis.py,sha256=lJEPyUxKazjcNa-RU5GyIT2BzyOPLAw3KBTijHslrEo,2810
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
147
147
  aury/boot/infrastructure/clients/__init__.py,sha256=1ANMejb3RrBgaR-jq-dsxJ0kQDRHz5jV-QvdUNcf_ok,435
148
148
  aury/boot/infrastructure/clients/rabbitmq/__init__.py,sha256=cnU-W7jOcAgp_FvsY9EipNCeJzeA9gHLRuZ0yQZE2DI,200
149
149
  aury/boot/infrastructure/clients/rabbitmq/config.py,sha256=YmvNiISpqNt-LE2CrpzmxCgaEgYna7IbOfUSnA0B4T0,1239
@@ -161,12 +161,12 @@ aury/boot/infrastructure/di/__init__.py,sha256=qFYlk265d6_rS8OiX37_wOc7mBFw8hk3y
161
161
  aury/boot/infrastructure/di/container.py,sha256=14FVbafGXea-JEAYeOEBxB6zAwndLCZJvprKiD_1IOQ,12524
162
162
  aury/boot/infrastructure/events/__init__.py,sha256=D5JNFkGHCH79nYbqUil0Z8eW2p6Gx8P0vBbNnHqnTgM,698
163
163
  aury/boot/infrastructure/events/base.py,sha256=oS6aNUWRvpXlbh7L3_4vzlwUumYmg44HKS1S4m_zOFo,3019
164
- aury/boot/infrastructure/events/manager.py,sha256=HIHcTMs9lz9pFIcR7S1F2iKBUKGeLaD_4P5rlLjp1vw,7334
164
+ aury/boot/infrastructure/events/manager.py,sha256=Hnua9x_Ppmo99JUEmjuZcKIPv2IEZQ3Du9ziPg-C8Fo,7570
165
165
  aury/boot/infrastructure/events/middleware.py,sha256=Ck3qNMTtLuFFKsJuEUeOMG9nu3qK1N_aqt6wH5JoAtw,1336
166
166
  aury/boot/infrastructure/events/backends/__init__.py,sha256=V_hPtdjVUkYU4Uf8hTPVBUcnNYG9OfkjRPDnjp_5_zA,224
167
167
  aury/boot/infrastructure/events/backends/memory.py,sha256=Up7vAxdJvIqkcqpnKNCu81ec6iCfNIhcQ-jKM3M2hZc,2623
168
168
  aury/boot/infrastructure/events/backends/rabbitmq.py,sha256=XCuI9mc3GR-t0zht4yZ3e2nnyFl8UuTDir_0nsDbfxM,6495
169
- aury/boot/infrastructure/events/backends/redis.py,sha256=ZCcA6ZvJFvRuwW9IbBfEUy9yp1IKiIrhJ6eDnp2ikKs,5596
169
+ aury/boot/infrastructure/events/backends/redis.py,sha256=i8jPCtR7ITPVTl9DVFDbNbjypnWoeSpar6z4lJJlOD8,5790
170
170
  aury/boot/infrastructure/monitoring/__init__.py,sha256=VgElCdCVcgERTIn3oRoSNslR82W9gRX5vgJcYDeloak,16149
171
171
  aury/boot/infrastructure/mq/__init__.py,sha256=Q7kBk_GeQnxnqkyp29Bh1yFH3Q8xxxjs8oDYLeDj8C0,498
172
172
  aury/boot/infrastructure/mq/base.py,sha256=kHrWUysWflMj3qyOnioLZ90it8d9Alq1Wb4PYhpBW4k,3396
@@ -192,7 +192,7 @@ aury/boot/testing/client.py,sha256=KOg1EemuIVsBG68G5y0DjSxZGcIQVdWQ4ASaHE3o1R0,4
192
192
  aury/boot/testing/factory.py,sha256=8GvwX9qIDu0L65gzJMlrWB0xbmJ-7zPHuwk3eECULcg,5185
193
193
  aury/boot/toolkit/__init__.py,sha256=AcyVb9fDf3CaEmJPNkWC4iGv32qCPyk4BuFKSuNiJRQ,334
194
194
  aury/boot/toolkit/http/__init__.py,sha256=zIPmpIZ9Qbqe25VmEr7jixoY2fkRbLm7NkCB9vKpg6I,11039
195
- aury_boot-0.0.22.dist-info/METADATA,sha256=ni5jJsNSu861N3iwbJndJrniPtKLU3U7kEa1CVNDNwA,7695
196
- aury_boot-0.0.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
197
- aury_boot-0.0.22.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
198
- aury_boot-0.0.22.dist-info/RECORD,,
195
+ aury_boot-0.0.23.dist-info/METADATA,sha256=019uIaM7hlX1CuRtQZpXpEFzDNkBowKBsad1PRyXcBc,7695
196
+ aury_boot-0.0.23.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
197
+ aury_boot-0.0.23.dist-info/entry_points.txt,sha256=f9KXEkDIGc0BGkgBvsNx_HMz9VhDjNxu26q00jUpDwQ,49
198
+ aury_boot-0.0.23.dist-info/RECORD,,