sycommon-python-lib 0.1.32__py3-none-any.whl → 0.1.56b5__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 (30) hide show
  1. sycommon/config/Config.py +6 -2
  2. sycommon/config/RerankerConfig.py +1 -0
  3. sycommon/database/async_base_db_service.py +36 -0
  4. sycommon/database/async_database_service.py +96 -0
  5. sycommon/llm/__init__.py +0 -0
  6. sycommon/llm/embedding.py +149 -0
  7. sycommon/llm/get_llm.py +246 -0
  8. sycommon/llm/llm_logger.py +126 -0
  9. sycommon/llm/llm_tokens.py +119 -0
  10. sycommon/logging/async_sql_logger.py +65 -0
  11. sycommon/logging/kafka_log.py +21 -9
  12. sycommon/logging/logger_levels.py +23 -0
  13. sycommon/middleware/context.py +2 -0
  14. sycommon/middleware/traceid.py +155 -32
  15. sycommon/notice/__init__.py +0 -0
  16. sycommon/notice/uvicorn_monitor.py +195 -0
  17. sycommon/rabbitmq/rabbitmq_client.py +385 -626
  18. sycommon/rabbitmq/rabbitmq_pool.py +287 -71
  19. sycommon/rabbitmq/rabbitmq_service.py +345 -191
  20. sycommon/services.py +104 -64
  21. sycommon/synacos/feign.py +71 -18
  22. sycommon/synacos/feign_client.py +26 -8
  23. sycommon/synacos/nacos_service.py +91 -54
  24. sycommon/tools/merge_headers.py +97 -0
  25. sycommon/tools/snowflake.py +290 -23
  26. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/METADATA +17 -12
  27. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/RECORD +30 -18
  28. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/WHEEL +0 -0
  29. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/entry_points.txt +0 -0
  30. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,30 @@
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
-
1
+ import asyncio
2
+ import random
3
+ from typing import Optional, List, Dict, Callable, Tuple
4
+ from aio_pika import connect_robust, RobustChannel, Message
5
+ from aio_pika.abc import (
6
+ AbstractRobustConnection, AbstractQueue, AbstractExchange, AbstractMessage
7
+ )
6
8
  from sycommon.logging.kafka_log import SYLogger
7
9
 
8
10
  logger = SYLogger
9
11
 
10
12
 
13
+ class AsyncProperty:
14
+ """实现 await obj.attr 的支持"""
15
+
16
+ def __init__(self, method):
17
+ self.method = method
18
+
19
+ def __get__(self, obj, objtype=None):
20
+ if obj is None:
21
+ return self
22
+ # 关键:当访问 obj.attr 时,直接返回协程对象,而不是方法本身
23
+ return self.method(obj)
24
+
25
+
11
26
  class RabbitMQConnectionPool:
12
- """RabbitMQ连接池管理,负责创建和管理连接池与通道池"""
27
+ """单连接单通道RabbitMQ客户端 (增强版日志)"""
13
28
 
14
29
  def __init__(
15
30
  self,
@@ -18,87 +33,288 @@ class RabbitMQConnectionPool:
18
33
  username: str,
19
34
  password: str,
20
35
  virtualhost: str = "/",
21
- connection_pool_size: int = 2,
22
- channel_pool_size: int = 10,
23
- heartbeat: int = 10,
24
- app_name: str = ""
36
+ heartbeat: int = 30,
37
+ app_name: str = "",
38
+ connection_timeout: int = 30,
39
+ reconnect_interval: int = 5,
40
+ prefetch_count: int = 2,
25
41
  ):
26
42
  self.hosts = [host.strip() for host in hosts if host.strip()]
27
43
  if not self.hosts:
28
44
  raise ValueError("至少需要提供一个RabbitMQ主机地址")
45
+
29
46
  self.port = port
30
47
  self.username = username
31
48
  self.password = password
32
49
  self.virtualhost = virtualhost
33
50
  self.app_name = app_name or "rabbitmq-client"
34
51
  self.heartbeat = heartbeat
52
+ self.connection_timeout = connection_timeout
53
+ self.reconnect_interval = reconnect_interval
54
+ self.prefetch_count = prefetch_count
55
+
56
+ self._current_host: str = random.choice(self.hosts)
57
+ logger.info(f"[INIT] 随机选择RabbitMQ主机: {self._current_host}")
58
+
59
+ # 核心资源
60
+ self._connection: Optional[AbstractRobustConnection] = None
61
+ self._channel: Optional[RobustChannel] = None
62
+ self._consumer_channels: Dict[str, RobustChannel] = {}
63
+
64
+ # 状态控制
65
+ self._lock = asyncio.Lock()
66
+ self._initialized = False
67
+ self._is_shutdown = False
68
+
69
+ @AsyncProperty
70
+ async def is_alive(self) -> bool:
71
+ """对外暴露的连接存活状态(原子化判断)"""
72
+ async with self._lock:
73
+ if self._is_shutdown:
74
+ return False
75
+
76
+ if not self._initialized:
77
+ return False
78
+
79
+ if self._connection is None or self._connection.is_closed:
80
+ return False
35
81
 
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
82
+ # 可选:检查主通道是否存活
83
+ if self._channel is None or self._channel.is_closed:
84
+ # 如果你认为通道断了连接也算死,就保留这行;否则删除
85
+ return False
86
+
87
+ return True
88
+
89
+ async def _create_connection_impl(self) -> AbstractRobustConnection:
90
+ """
91
+ 连接创建入口
92
+ """
93
+ conn_url = (
94
+ f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/"
95
+ f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
96
+ f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
97
+ )
98
+ logger.info(
99
+ f"🔌 [CONNECT_START] 尝试创建连接 -> {self._current_host}:{self.port}")
100
+ try:
101
+ conn = await connect_robust(conn_url, timeout=self.connection_timeout)
102
+ # 注意:connect_robust 返回时,底层 TCP 可能还在握手,但对象已创建
103
+ logger.info(f"✅ [CONNECT_OK] 连接对象创建成功: {id(conn)}")
104
+ return conn
105
+ except Exception as e:
106
+ logger.error(f"❌ [CONNECT_FAIL] 连接创建失败: {str(e)}")
107
+ raise ConnectionError(f"无法连接RabbitMQ {self._current_host}") from e
108
+
109
+ async def _create_channel_impl(self, connection: AbstractRobustConnection) -> RobustChannel:
110
+ """创建通道"""
111
+ try:
112
+ channel = await connection.channel()
113
+ await channel.set_qos(prefetch_count=self.prefetch_count)
114
+ logger.debug(f"✅ [CHANNEL_OK] 通道创建成功: {id(channel)}")
115
+ return channel
116
+ except Exception as e:
117
+ logger.error(f"❌ [CHANNEL_FAIL] 通道创建失败: {str(e)}")
118
+ raise
119
+
120
+ async def _ensure_main_channel(self) -> RobustChannel:
121
+ """确保主通道有效 (原子操作)"""
122
+ async with self._lock:
123
+ if self._is_shutdown:
124
+ raise RuntimeError("客户端已关闭")
125
+
126
+ # 1. 确保底层连接存在
127
+ conn = self._connection
128
+ if conn is None or conn.is_closed:
129
+ logger.info("⚠️ [RECONNECT] 检测到连接不存在或已关闭,开始重建...")
130
+ conn = await self._create_connection_impl()
131
+ self._connection = conn
132
+ logger.info(f"🔗 [UPDATE] 连接对象已更新: {id(conn)}")
133
+
134
+ # 2. 确保主通道存在
135
+ if self._channel is None or self._channel.is_closed:
136
+ logger.info("⚠️ [RECOVER_CHANNEL] 检测到主通道不存在或已关闭,开始恢复...")
137
+ self._channel = await self._create_channel_impl(conn)
138
+
139
+ return self._channel
41
140
 
42
141
  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)
142
+ """
143
+ 初始化入口与异常处理 (修复泄漏的关键)
144
+ """
145
+ async with self._lock:
146
+ if self._is_shutdown:
147
+ raise RuntimeError("客户端已关闭")
148
+ if self._initialized:
149
+ return
150
+
151
+ conn_created_in_this_try = None
152
+ try:
153
+ # 步骤 A: 创建连接 (在锁外进行,避免阻塞其他操作)
154
+ conn = await self._create_connection_impl()
155
+ conn_created_in_this_try = conn # 记录本次创建的对象,用于失败回滚
156
+
157
+ # 步骤 B: 更新状态和初始化通道 (在锁内进行,保证原子性)
158
+ async with self._lock:
159
+ if self._is_shutdown:
160
+ # 如果在创建连接期间,外部调用了 close,则必须立即清理刚创建的连接
161
+ logger.warning("⚠️ [ABORT] 检测到关闭信号,放弃初始化并清理资源")
162
+ raise RuntimeError("客户端已关闭")
163
+
164
+ self._connection = conn
165
+ self._channel = await self._create_channel_impl(conn)
166
+ self._initialized = True
167
+ logger.info(
168
+ f"🚀 [INIT_SUCCESS] 客户端初始化完成. ConnID: {id(self._connection)}")
169
+
170
+ except Exception as e:
171
+ logger.error(f"💥 [INIT_ERROR] 初始化流程异常: {str(e)}", exc_info=True)
172
+ # 如果步骤A成功但步骤B失败(例如通道创建失败),或者步骤B中出错,
173
+ # 必须显式关闭在步骤A中创建的连接,否则它会变成“游离连接”。
174
+ if conn_created_in_this_try:
175
+ logger.warning(
176
+ f"🧹 [LEAK_PREVENTION] 检测到初始化失败,正在显式关闭刚创建的连接: {id(conn_created_in_this_try)}")
50
177
  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
- )
178
+ await conn_created_in_this_try.close()
179
+ logger.info(
180
+ f"✅ [CLOSE_OK] 泄漏连接已关闭: {id(conn_created_in_this_try)}")
181
+ except Exception as close_err:
182
+ logger.error(f"❌ [CLOSE_ERR] 关闭泄漏连接时出错: {str(close_err)}")
73
183
 
74
- # 通道创建函数
75
- async def create_channel() -> Channel:
76
- async with self.connection_pool.acquire() as connection:
77
- channel = await connection.channel()
78
- return channel
184
+ # 如果是因为中途关闭导致的错误,不需要再次调用全局 close,否则调用
185
+ if not self._is_shutdown:
186
+ await self.close()
187
+ raise
79
188
 
80
- # 初始化通道池
81
- self.channel_pool = Pool(
82
- create_channel,
83
- max_size=self.channel_pool_size
84
- )
189
+ async def acquire_channel(self) -> Tuple[RobustChannel, AbstractRobustConnection]:
190
+ """获取主通道"""
191
+ if not self._initialized and not self._is_shutdown:
192
+ await self.init_pools()
193
+ return await self._ensure_main_channel(), self._connection
85
194
 
86
- logger.info(
87
- f"RabbitMQ连接池初始化完成 - 连接池大小: {self.connection_pool_size}, "
88
- f"通道池大小: {self.channel_pool_size}, 集群节点: {self.hosts}"
89
- )
195
+ async def publish_message(self, routing_key: str, message_body: bytes, exchange_name: str = "", **kwargs):
196
+ """发布消息"""
197
+ channel, _ = await self.acquire_channel()
198
+ try:
199
+ exchange = channel.default_exchange if not exchange_name else await channel.get_exchange(exchange_name)
200
+ message = Message(body=message_body, **kwargs)
201
+ await exchange.publish(message, routing_key=routing_key)
202
+ logger.debug(f"📤 [PUBLISH] 消息发布成功 - RK: {routing_key}")
203
+ except Exception as e:
204
+ logger.error(f"❌ [PUBLISH_FAIL] 发布失败: {str(e)}")
205
+ raise
206
+
207
+ async def consume_queue(self, queue_name: str, callback: Callable[[AbstractMessage], asyncio.Future], auto_ack: bool = False, **kwargs):
208
+ """消费队列"""
209
+ if not self._initialized:
210
+ await self.init_pools()
211
+
212
+ async with self._lock:
213
+ if self._is_shutdown:
214
+ raise RuntimeError("客户端已关闭")
215
+ if queue_name in self._consumer_channels:
216
+ logger.warning(f"⚠️ [CONSUMER_EXISTS] 队列 {queue_name} 已在消费中")
217
+ return
218
+ if not self._connection or self._connection.is_closed:
219
+ raise RuntimeError("连接不可用,无法启动消费")
220
+
221
+ await self.declare_queue(queue_name, **kwargs)
222
+
223
+ try:
224
+ # 获取原始连接对象创建新通道
225
+ conn = self._connection
226
+ consumer_channel = await conn.channel()
227
+ await consumer_channel.set_qos(prefetch_count=self.prefetch_count)
228
+ logger.info(
229
+ f"✅ [CONSUMER_CHANNEL_OK] 消费者通道创建: {id(consumer_channel)}")
230
+
231
+ async with self._lock:
232
+ if self._is_shutdown:
233
+ await consumer_channel.close()
234
+ return
235
+ self._consumer_channels[queue_name] = consumer_channel
236
+
237
+ async def consume_callback_wrapper(message: AbstractMessage):
238
+ try:
239
+ await callback(message)
240
+ if not auto_ack:
241
+ await message.ack()
242
+ except Exception as e:
243
+ logger.error(
244
+ f"❌ [CALLBACK_ERR] 消费回调异常 {queue_name}: {str(e)}")
245
+ if not auto_ack:
246
+ await message.nack(requeue=True)
247
+
248
+ await consumer_channel.basic_consume(
249
+ queue_name, consumer_callback=consume_callback_wrapper, auto_ack=auto_ack, **kwargs
250
+ )
251
+ logger.info(f"🎧 [CONSUME_START] 开始消费队列: {queue_name}")
252
+
253
+ except Exception as e:
254
+ logger.error(f"💥 [CONSUME_ERR] 启动消费失败 {queue_name}: {str(e)}")
255
+ async with self._lock:
256
+ if queue_name in self._consumer_channels:
257
+ del self._consumer_channels[queue_name]
258
+ raise
90
259
 
91
260
  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()
261
+ """
262
+ 资源销毁入口
263
+ """
264
+ async with self._lock:
265
+ if self._is_shutdown:
266
+ return
267
+ self._is_shutdown = True
268
+ self._initialized = False
269
+ # 记录即将关闭的连接ID
270
+ conn_to_close_id = id(
271
+ self._connection) if self._connection else None
272
+
273
+ logger.info(f"🛑 [CLOSE_START] 开始关闭客户端... (准备关闭连接: {conn_to_close_id})")
274
+
275
+ # 1. 关闭消费者通道
276
+ channels_to_close = []
277
+ async with self._lock:
278
+ channels_to_close = list(self._consumer_channels.values())
279
+ self._consumer_channels.clear()
280
+
281
+ for ch in channels_to_close:
282
+ try:
283
+ if not ch.is_closed:
284
+ await ch.close()
285
+ logger.debug(f"✅ [CLOSE_CHANNEL] 消费者通道已关闭")
286
+ except Exception as e:
287
+ logger.warning(f"❌ [CLOSE_CHANNEL_ERR] 关闭消费者通道失败: {str(e)}")
288
+
289
+ # 2. 关闭主通道
290
+ if self._channel:
291
+ try:
292
+ if not self._channel.is_closed:
293
+ await self._channel.close()
294
+ logger.info(f"✅ [CLOSE_CHANNEL] 主通道已关闭")
295
+ except Exception:
296
+ pass
297
+ self._channel = None
298
+
299
+ # 3. 关闭连接
300
+ # 确保在 finally 或显式 close 中调用 connection.close()
301
+ if self._connection:
302
+ try:
303
+ # 打印关闭操作
304
+ logger.info(f"🔌 [CLOSE_CONN] 正在关闭连接: {id(self._connection)}")
305
+ await self._connection.close()
306
+ logger.info(f"✅ [CLOSE_OK] 连接已成功关闭: {id(self._connection)}")
307
+ except Exception as e:
308
+ logger.warning(f"❌ [CLOSE_ERR] 关闭连接失败: {str(e)}")
309
+ self._connection = None
310
+
311
+ logger.info("🏁 [CLOSE_DONE] RabbitMQ客户端已完全关闭")
312
+
313
+ # --- 辅助方法省略 (declare_queue 等) ---
314
+ async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
315
+ channel, _ = await self.acquire_channel()
316
+ return await channel.declare_queue(queue_name, **kwargs)
317
+
318
+ async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
319
+ channel, _ = await self.acquire_channel()
320
+ return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)