sycommon-python-lib 0.1.16__py3-none-any.whl → 0.1.17__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.

Potentially problematic release.


This version of sycommon-python-lib might be problematic. Click here for more details.

@@ -0,0 +1,104 @@
1
+ from typing import Optional, List
2
+ from aio_pika import connect_robust, Channel
3
+ from aio_pika.abc import AbstractRobustConnection
4
+ from aio_pika.pool import Pool
5
+
6
+ from sycommon.logging.kafka_log import SYLogger
7
+
8
+ logger = SYLogger
9
+
10
+
11
+ class RabbitMQConnectionPool:
12
+ """RabbitMQ连接池管理,负责创建和管理连接池与通道池"""
13
+
14
+ def __init__(
15
+ self,
16
+ hosts: List[str],
17
+ port: int,
18
+ username: str,
19
+ password: str,
20
+ virtualhost: str = "/",
21
+ connection_pool_size: int = 2,
22
+ channel_pool_size: int = 10,
23
+ heartbeat: int = 10,
24
+ app_name: str = ""
25
+ ):
26
+ self.hosts = [host.strip() for host in hosts if host.strip()]
27
+ if not self.hosts:
28
+ raise ValueError("至少需要提供一个RabbitMQ主机地址")
29
+ self.port = port
30
+ self.username = username
31
+ self.password = password
32
+ self.virtualhost = virtualhost
33
+ self.app_name = app_name or "rabbitmq-client"
34
+ self.heartbeat = heartbeat
35
+
36
+ # 连接池和通道池
37
+ self.connection_pool: Optional[Pool] = None
38
+ self.channel_pool: Optional[Pool] = None
39
+ self.connection_pool_size = connection_pool_size
40
+ self.channel_pool_size = channel_pool_size
41
+
42
+ async def init_pools(self):
43
+ """初始化连接池和通道池"""
44
+ # 连接创建函数(支持集群节点轮询)
45
+ async def create_connection() -> AbstractRobustConnection:
46
+ # 轮询选择主机(简单负载均衡)
47
+ hosts = self.hosts.copy()
48
+ while hosts:
49
+ host = hosts.pop(0)
50
+ try:
51
+ return await connect_robust(
52
+ host=host,
53
+ port=self.port,
54
+ login=self.username,
55
+ password=self.password,
56
+ virtualhost=self.virtualhost,
57
+ heartbeat=self.heartbeat,
58
+ client_properties={
59
+ "connection_name": f"{self.app_name}@{host}"
60
+ }
61
+ )
62
+ except Exception as e:
63
+ logger.warning(
64
+ f"连接主机 {host}:{self.port} 失败,尝试下一个节点: {str(e)}")
65
+ if not hosts:
66
+ raise # 所有节点都失败时抛出异常
67
+
68
+ # 初始化连接池
69
+ self.connection_pool = Pool(
70
+ create_connection,
71
+ max_size=self.connection_pool_size
72
+ )
73
+
74
+ # 通道创建函数
75
+ async def create_channel() -> Channel:
76
+ async with self.connection_pool.acquire() as connection:
77
+ channel = await connection.channel()
78
+ return channel
79
+
80
+ # 初始化通道池
81
+ self.channel_pool = Pool(
82
+ create_channel,
83
+ max_size=self.channel_pool_size
84
+ )
85
+
86
+ logger.info(
87
+ f"RabbitMQ连接池初始化完成 - 连接池大小: {self.connection_pool_size}, "
88
+ f"通道池大小: {self.channel_pool_size}, 集群节点: {self.hosts}"
89
+ )
90
+
91
+ async def close(self):
92
+ """关闭连接池和通道池"""
93
+ if self.channel_pool:
94
+ await self.channel_pool.close()
95
+ if self.connection_pool:
96
+ await self.connection_pool.close()
97
+ logger.info("RabbitMQ连接池已关闭")
98
+
99
+ async def __aenter__(self):
100
+ await self.init_pools()
101
+ return self
102
+
103
+ async def __aexit__(self, exc_type, exc, tb):
104
+ await self.close()
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import logging
3
2
  from typing import (
4
3
  Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set
5
4
  )
@@ -11,15 +10,14 @@ from sycommon.models.mqlistener_config import RabbitMQListenerConfig
11
10
  from sycommon.models.mqsend_config import RabbitMQSendConfig
12
11
  from sycommon.models.sso_user import SsoUser
13
12
  from sycommon.logging.kafka_log import SYLogger
14
- from .rabbitmq_client import RabbitMQClient
13
+ from sycommon.rabbitmq.rabbitmq_client import RabbitMQClient, RabbitMQConnectionPool
15
14
 
16
15
  logger = SYLogger
17
16
 
18
17
 
19
18
  class RabbitMQService:
20
19
  """
21
- RabbitMQ服务封装,管理多个客户端实例,提供发送和接收消息的高级接口
22
- 负责客户端的创建、配置、生命周期管理和错误处理
20
+ RabbitMQ服务封装,管理多个客户端实例,基于连接池实现资源复用
23
21
  """
24
22
 
25
23
  # 保存多个客户端实例
@@ -45,11 +43,13 @@ class RabbitMQService:
45
43
  _has_senders: bool = False
46
44
  # 消费启动超时设置
47
45
  CONSUMER_START_TIMEOUT = 30 # 30秒超时
46
+ # 连接池实例
47
+ _connection_pool: Optional[RabbitMQConnectionPool] = None
48
48
 
49
49
  @classmethod
50
50
  def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
51
51
  """
52
- 初始化RabbitMQ服务(支持集群配置)
52
+ 初始化RabbitMQ服务(支持集群配置),同时创建连接池
53
53
  """
54
54
  from sycommon.synacos.nacos_service import NacosService
55
55
 
@@ -70,13 +70,63 @@ class RabbitMQService:
70
70
  cls._has_listeners = has_listeners
71
71
  cls._has_senders = has_senders
72
72
 
73
+ # 初始化连接池(在单独的异步方法中启动)
74
+ asyncio.create_task(cls._init_connection_pool())
75
+
73
76
  return cls
74
77
 
75
78
  @classmethod
76
- async def _create_client(cls, mq_config: dict, queue_name: str, **kwargs) -> RabbitMQClient:
79
+ async def _init_connection_pool(cls):
80
+ """初始化连接池(异步操作)"""
81
+ if cls._connection_pool or not cls._config:
82
+ return
83
+
84
+ try:
85
+ # 解析集群节点
86
+ hosts_str = cls._config.get('host', "")
87
+ hosts_list = [host.strip()
88
+ for host in hosts_str.split(',') if host.strip()]
89
+ if not hosts_list:
90
+ raise ValueError("RabbitMQ集群配置为空,请检查host参数")
91
+
92
+ # 创建连接池
93
+ cls._connection_pool = RabbitMQConnectionPool(
94
+ hosts=hosts_list,
95
+ port=cls._config.get('port', 5672),
96
+ username=cls._config.get('username', ""),
97
+ password=cls._config.get('password', ""),
98
+ virtualhost=cls._config.get('virtual-host', "/"),
99
+ connection_pool_size=cls._config.get(
100
+ 'connection_pool_size', 2), # 连接池大小
101
+ channel_pool_size=cls._config.get(
102
+ 'channel_pool_size', 10), # 通道池大小
103
+ heartbeat=cls._config.get('heartbeat', 10),
104
+ app_name=cls._config.get("APP_NAME", "")
105
+ )
106
+
107
+ # 初始化连接池
108
+ await cls._connection_pool.init_pools()
109
+ logger.info("RabbitMQ连接池初始化成功")
110
+
111
+ except Exception as e:
112
+ logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
113
+ # 连接池初始化失败时重试
114
+ await asyncio.sleep(1)
115
+ asyncio.create_task(cls._init_connection_pool())
116
+
117
+ @classmethod
118
+ async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
77
119
  """
78
- 创建RabbitMQ客户端实例(支持集群节点列表)
120
+ 创建RabbitMQ客户端实例(基于连接池)
79
121
  """
122
+ if not cls._connection_pool:
123
+ # 等待连接池初始化
124
+ start_time = asyncio.get_event_loop().time()
125
+ while not cls._connection_pool:
126
+ if asyncio.get_event_loop().time() - start_time > 30:
127
+ raise TimeoutError("等待连接池初始化超时")
128
+ await asyncio.sleep(1)
129
+
80
130
  app_name = kwargs.get('app_name', cls._config.get(
81
131
  "APP_NAME", "")) if cls._config else ""
82
132
 
@@ -100,20 +150,9 @@ class RabbitMQService:
100
150
  f"允许创建: {create_if_not_exists}"
101
151
  )
102
152
 
103
- # 将逗号分隔的host字符串拆分为集群节点列表
104
- hosts_str = mq_config.get('host', "")
105
- hosts_list = [host.strip()
106
- for host in hosts_str.split(',') if host.strip()]
107
- if not hosts_list:
108
- raise ValueError("RabbitMQ集群配置为空,请检查host参数")
109
-
110
153
  return RabbitMQClient(
111
- hosts=hosts_list,
112
- port=mq_config.get('port', 5672),
113
- username=mq_config.get('username', ""),
114
- password=mq_config.get('password', ""),
115
- virtualhost=mq_config.get('virtual-host', "/"),
116
- exchange_name=mq_config.get(
154
+ connection_pool=cls._connection_pool, # 传入连接池实例
155
+ exchange_name=cls._config.get(
117
156
  'exchange_name', "system.topic.exchange"),
118
157
  exchange_type=kwargs.get('exchange_type', "topic"),
119
158
  queue_name=processed_queue_name,
@@ -125,11 +164,9 @@ class RabbitMQService:
125
164
  create_if_not_exists=create_if_not_exists,
126
165
  connection_timeout=kwargs.get('connection_timeout', 10),
127
166
  rpc_timeout=kwargs.get('rpc_timeout', 5),
128
- app_name=app_name,
129
167
  reconnection_delay=kwargs.get('reconnection_delay', 1),
130
168
  max_reconnection_attempts=kwargs.get(
131
169
  'max_reconnection_attempts', 5),
132
- heartbeat=kwargs.get('heartbeat', 10),
133
170
  prefetch_count=kwargs.get('prefetch_count', 2),
134
171
  consumption_stall_threshold=kwargs.get(
135
172
  'consumption_stall_threshold', 10)
@@ -141,15 +178,19 @@ class RabbitMQService:
141
178
  client_name: str = "default", ** kwargs
142
179
  ) -> RabbitMQClient:
143
180
  """
144
- 获取或创建RabbitMQ客户端
145
-
146
- :param client_name: 客户端名称
147
- :param kwargs: 客户端参数
148
- :return: RabbitMQClient实例
181
+ 获取或创建RabbitMQ客户端(基于连接池)
149
182
  """
150
183
  if not cls._config:
151
184
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
152
185
 
186
+ # 等待连接池就绪
187
+ if not cls._connection_pool:
188
+ start_time = asyncio.get_event_loop().time()
189
+ while not cls._connection_pool:
190
+ if asyncio.get_event_loop().time() - start_time > 30:
191
+ raise TimeoutError("等待连接池初始化超时")
192
+ await asyncio.sleep(1)
193
+
153
194
  # 确保锁存在
154
195
  if client_name not in cls._init_locks:
155
196
  cls._init_locks[client_name] = asyncio.Lock()
@@ -184,7 +225,6 @@ class RabbitMQService:
184
225
  if is_sender:
185
226
  kwargs['create_if_not_exists'] = False
186
227
  client = await cls._create_client(
187
- cls._config,
188
228
  initial_queue_name,
189
229
  app_name=cls._config.get("APP_NAME", ""),
190
230
  **kwargs
@@ -200,7 +240,6 @@ class RabbitMQService:
200
240
  if initial_queue_name in cls._initialized_queues:
201
241
  logger.debug(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
202
242
  client = await cls._create_client(
203
- cls._config,
204
243
  initial_queue_name, ** kwargs
205
244
  )
206
245
  await client.connect(declare_queue=True)
@@ -209,7 +248,6 @@ class RabbitMQService:
209
248
 
210
249
  # 创建并连接客户端
211
250
  client = await cls._create_client(
212
- cls._config,
213
251
  initial_queue_name,
214
252
  app_name=cls._config.get("APP_NAME", ""),
215
253
  **kwargs
@@ -234,16 +272,11 @@ class RabbitMQService:
234
272
  cls._clients[client_name] = client
235
273
  return client
236
274
 
275
+ # 以下方法逻辑与原有保持一致(无需修改)
237
276
  @classmethod
238
277
  async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False) -> None:
239
- """
240
- 设置消息发送器
241
-
242
- :param senders: 发送器配置列表
243
- :param has_listeners: 是否同时存在监听器
244
- """
278
+ """设置消息发送器"""
245
279
  cls._has_senders = True
246
- # 保存监听器存在状态
247
280
  cls._has_listeners = has_listeners
248
281
  logger.info(f"开始设置 {len(senders)} 个消息发送器")
249
282
 
@@ -300,14 +333,8 @@ class RabbitMQService:
300
333
 
301
334
  @classmethod
302
335
  async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False) -> None:
303
- """
304
- 设置消息监听器
305
-
306
- :param listeners: 监听器配置列表
307
- :param has_senders: 是否同时存在发送器
308
- """
336
+ """设置消息监听器"""
309
337
  cls._has_listeners = True
310
- # 保存发送器存在状态
311
338
  cls._has_senders = has_senders
312
339
  logger.info(f"开始设置 {len(listeners)} 个消息监听器")
313
340
 
@@ -337,16 +364,11 @@ class RabbitMQService:
337
364
 
338
365
  @classmethod
339
366
  async def _verify_consumers_started(cls, timeout: int = 30) -> None:
340
- """
341
- 验证消费者是否成功启动
342
-
343
- :param timeout: 超时时间(秒)
344
- """
367
+ """验证消费者是否成功启动"""
345
368
  start_time = asyncio.get_event_loop().time()
346
369
  required_clients = list(cls._message_handlers.keys())
347
370
  running_clients = []
348
371
 
349
- # 等待所有消费者启动或超时
350
372
  while len(running_clients) < len(required_clients) and \
351
373
  (asyncio.get_event_loop().time() - start_time) < timeout:
352
374
 
@@ -357,14 +379,12 @@ class RabbitMQService:
357
379
 
358
380
  logger.info(
359
381
  f"消费者启动验证: {len(running_clients)}/{len(required_clients)} 已启动")
360
- await asyncio.sleep(2)
382
+ await asyncio.sleep(1)
361
383
 
362
- # 检查未成功启动的消费者
363
384
  failed_clients = [
364
385
  name for name in required_clients if name not in running_clients]
365
386
  if failed_clients:
366
387
  logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
367
- # 尝试重新启动失败的消费者
368
388
  for client_name in failed_clients:
369
389
  logger.info(f"尝试重新启动消费者: {client_name}")
370
390
  asyncio.create_task(cls.start_consumer(client_name))
@@ -375,13 +395,7 @@ class RabbitMQService:
375
395
  queue_name: str,
376
396
  handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], ** kwargs
377
397
  ) -> None:
378
- """
379
- 添加消息监听器
380
-
381
- :param queue_name: 队列名称
382
- :param handler: 消息处理函数
383
- :param kwargs: 其他参数
384
- """
398
+ """添加消息监听器"""
385
399
  if not cls._config:
386
400
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
387
401
 
@@ -408,11 +422,7 @@ class RabbitMQService:
408
422
 
409
423
  @classmethod
410
424
  async def start_consumer(cls, client_name: str) -> None:
411
- """
412
- 启动指定客户端的消费者
413
-
414
- :param client_name: 客户端名称
415
- """
425
+ """启动指定客户端的消费者"""
416
426
  if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
417
427
  logger.debug(f"消费者 '{client_name}' 已在运行中,无需重复启动")
418
428
  return
@@ -461,7 +471,7 @@ class RabbitMQService:
461
471
  logger.warning(
462
472
  f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
463
473
  if attempt < max_attempts:
464
- await asyncio.sleep(2)
474
+ await asyncio.sleep(1)
465
475
 
466
476
  if not consumer_tag:
467
477
  raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
@@ -528,12 +538,7 @@ class RabbitMQService:
528
538
 
529
539
  @classmethod
530
540
  def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
531
- """
532
- 获取发送客户端
533
-
534
- :param queue_name: 队列名称
535
- :return: RabbitMQClient实例或None
536
- """
541
+ """获取发送客户端"""
537
542
  if not queue_name:
538
543
  logger.warning("发送器名称不能为空")
539
544
  return None
@@ -556,13 +561,7 @@ class RabbitMQService:
556
561
  data: Union[BaseModel, str, Dict[str, Any], None],
557
562
  queue_name: str, ** kwargs
558
563
  ) -> None:
559
- """
560
- 发送消息到指定队列
561
-
562
- :param data: 消息数据
563
- :param queue_name: 队列名称
564
- :param kwargs: 其他参数
565
- """
564
+ """发送消息到指定队列"""
566
565
  # 获取发送客户端
567
566
  sender = cls.get_sender(queue_name)
568
567
  if not sender:
@@ -628,11 +627,7 @@ class RabbitMQService:
628
627
 
629
628
  @classmethod
630
629
  async def shutdown(cls, timeout: float = 10.0) -> None:
631
- """
632
- 优雅关闭所有资源
633
-
634
- :param timeout: 超时时间(秒)
635
- """
630
+ """优雅关闭所有资源(新增连接池关闭逻辑)"""
636
631
  start_time = asyncio.get_event_loop().time()
637
632
  logger.info("开始关闭RabbitMQ服务...")
638
633
 
@@ -646,7 +641,6 @@ class RabbitMQService:
646
641
  0.0, timeout - (asyncio.get_event_loop().time() - start_time))
647
642
  if remaining_time > 0 and cls._consumer_tasks:
648
643
  try:
649
- # 等待所有消费者任务完成或超时
650
644
  done, pending = await asyncio.wait(
651
645
  list(cls._consumer_tasks.values()),
652
646
  timeout=remaining_time,
@@ -680,6 +674,14 @@ class RabbitMQService:
680
674
  logger.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
681
675
  logger.info(f"客户端 '{name}' 已关闭")
682
676
 
677
+ # 关闭连接池
678
+ if cls._connection_pool:
679
+ try:
680
+ await cls._connection_pool.close()
681
+ logger.info("RabbitMQ连接池已关闭")
682
+ except Exception as e:
683
+ logger.warning(f"关闭连接池时出错: {str(e)}")
684
+
683
685
  # 清理所有状态
684
686
  cls._clients.clear()
685
687
  cls._consumer_tasks.clear()
@@ -691,5 +693,6 @@ class RabbitMQService:
691
693
  cls._init_locks.clear()
692
694
  cls._has_listeners = False
693
695
  cls._has_senders = False
696
+ cls._connection_pool = None
694
697
 
695
698
  logger.info("RabbitMQ服务已完全关闭")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.16
3
+ Version: 0.1.17
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -33,8 +33,9 @@ sycommon/models/mqlistener_config.py,sha256=PPwhAVJ2AWvVAvNox_1t0fuBKTyRH3Ui9cuu
33
33
  sycommon/models/mqmsg_model.py,sha256=cxn0M5b0utQK6crMYmL-1waeGYHvK3AlGaRy23clqTE,277
34
34
  sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6D7I,268
35
35
  sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
36
- sycommon/rabbitmq/rabbitmq_client.py,sha256=BJa5_CkBzI7LdZL7ozs4zc2qVgIVV5Oqg3AxVg3qiNM,37165
37
- sycommon/rabbitmq/rabbitmq_service.py,sha256=pBApkWWcLyvwuk7av_8owtJVXjsJ28Mb9et8nzkxYGQ,27794
36
+ sycommon/rabbitmq/rabbitmq_client.py,sha256=1mnccWeqBHHTPy__8kGEBX77UtAPL3_ffVoAZbbyPf4,26429
37
+ sycommon/rabbitmq/rabbitmq_pool.py,sha256=_NMOO4CZy-I_anMqpzfYinz-8373_rg5FM9eqzdjGyU,3598
38
+ sycommon/rabbitmq/rabbitmq_service.py,sha256=lpYSavAwl-UOmz5gTgS3yj2ulfF-s7boL3lsMibN-JM,28881
38
39
  sycommon/sse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
40
  sycommon/sse/event.py,sha256=k_rBJy23R7crtzQeetT0Q73D8o5-5p-eESGSs_BPOj0,2797
40
41
  sycommon/sse/sse.py,sha256=__CfWEcYxOxQ-HpLor4LTZ5hLWqw9-2X7CngqbVHsfw,10128
@@ -45,8 +46,8 @@ sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
46
  sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
46
47
  sycommon/tools/snowflake.py,sha256=rc-VUjBMMpdAvbnHroVwfVt1xzApJbTCthUy9mglAuw,237
47
48
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
48
- sycommon_python_lib-0.1.16.dist-info/METADATA,sha256=Ng_7vxoqZThJVeSOmS2AMsNbgtfKJTvIcn2qqbUvYzc,7005
49
- sycommon_python_lib-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
- sycommon_python_lib-0.1.16.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
51
- sycommon_python_lib-0.1.16.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
52
- sycommon_python_lib-0.1.16.dist-info/RECORD,,
49
+ sycommon_python_lib-0.1.17.dist-info/METADATA,sha256=Yz-MTXIYKGwCFWX3SRSsNQpSiPXZ3vAhsSLig9fl890,7005
50
+ sycommon_python_lib-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ sycommon_python_lib-0.1.17.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
52
+ sycommon_python_lib-0.1.17.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
53
+ sycommon_python_lib-0.1.17.dist-info/RECORD,,