sycommon-python-lib 0.2.4a5__py3-none-any.whl → 0.2.4a7__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.
@@ -37,6 +37,11 @@ except ImportError:
37
37
  _SSL_CONTEXT = ssl.create_default_context()
38
38
 
39
39
 
40
+ def _read_file_safe(path: str) -> bytes:
41
+ with open(path, "rb") as f:
42
+ return f.read()
43
+
44
+
40
45
  class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
41
46
  """
42
47
  通过 HTTP API 连接远程沙箱容器
@@ -210,6 +215,24 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
210
215
  nacos_group=group,
211
216
  )
212
217
 
218
+ @classmethod
219
+ async def afrom_nacos(
220
+ cls,
221
+ service_name: str,
222
+ user_id: str,
223
+ group: str = "DEFAULT_GROUP",
224
+ version: str = None,
225
+ timeout: int = 180,
226
+ sync_dirs: List[tuple[str, str]] = None,
227
+ auto_sync: bool = False,
228
+ load_balance: bool = True,
229
+ ) -> "HTTPSandboxBackend":
230
+ """异步从 Nacos 服务发现创建后端实例"""
231
+ return await asyncio.to_thread(
232
+ cls.from_nacos,
233
+ service_name, user_id, group, version, timeout, sync_dirs, auto_sync, load_balance,
234
+ )
235
+
213
236
  # ============== 内部方法 - 同步版本 ==============
214
237
 
215
238
  def _refresh_from_nacos_and_switch_sync(self) -> bool:
@@ -565,7 +588,7 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
565
588
  else:
566
589
  # 单文件
567
590
  SYLogger.info(f"[Sandbox] 上传单文件: {local_path}")
568
- content = await asyncio.to_thread(lambda: open(local_dir, "rb").read())
591
+ content = await asyncio.to_thread(lambda p: _read_file_safe(p), local_dir)
569
592
  upload_results = await self.aupload_files([(remote_path, content)], timeout=timeout)
570
593
  if upload_results[0].error:
571
594
  results[local_path] = {"success": 0, "failed": 1, "errors": [{"path": remote_path, "error": upload_results[0].error}]}
@@ -669,7 +692,8 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
669
692
  def _read_batch():
670
693
  items = []
671
694
  for sandbox_path, local_file in batch_files:
672
- content = open(local_file, "rb").read()
695
+ with open(local_file, "rb") as f:
696
+ content = f.read()
673
697
  items.append((sandbox_path, content))
674
698
  return items
675
699
  batch_items = await asyncio.to_thread(_read_batch)
@@ -1037,7 +1061,7 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
1037
1061
  SYLogger.info(f"[Sandbox] 异步目录上传完成: {local_path}, 成功={result['success']}, 失败={result['failed']}")
1038
1062
  else:
1039
1063
  SYLogger.info(f"[Sandbox] 异步上传单文件: {local_path}")
1040
- content = await asyncio.to_thread(lambda: open(local_dir, "rb").read())
1064
+ content = await asyncio.to_thread(lambda p: _read_file_safe(p), local_dir)
1041
1065
  upload_results = await self.aupload_files([(remote_path, content)], timeout=timeout)
1042
1066
  if upload_results[0].error:
1043
1067
  results[local_path] = {"success": 0, "failed": 1, "errors": [{"path": remote_path, "error": upload_results[0].error}]}
@@ -162,7 +162,7 @@ class MinioSyncService(metaclass=SingletonMeta):
162
162
  return None
163
163
 
164
164
  def get_presigned_url(self, object_key: str, expires_days: int = 7) -> Optional[str]:
165
- """生成预签名下载 URL"""
165
+ """生成预签名下载 URL(同步)"""
166
166
  if not self._client:
167
167
  return None
168
168
  try:
@@ -177,8 +177,12 @@ class MinioSyncService(metaclass=SingletonMeta):
177
177
  f"[MinIO] Presigned URL failed: {object_key}, error={e}")
178
178
  return None
179
179
 
180
+ async def aget_presigned_url(self, object_key: str, expires_days: int = 7) -> Optional[str]:
181
+ """异步生成预签名下载 URL"""
182
+ return await asyncio.to_thread(self.get_presigned_url, object_key, expires_days)
183
+
180
184
  def remove_object(self, object_key: str) -> bool:
181
- """从 MinIO 删除文件"""
185
+ """从 MinIO 删除文件(同步)"""
182
186
  if not self._client:
183
187
  return False
184
188
  try:
@@ -189,12 +193,12 @@ class MinioSyncService(metaclass=SingletonMeta):
189
193
  SYLogger.warning(f"[MinIO] Delete failed: {object_key}, error={e}")
190
194
  return False
191
195
 
192
- def remove_prefix(self, prefix: str) -> int:
193
- """删除 MinIO 中指定前缀下的所有对象(用于目录删除)
196
+ async def aremove_object(self, object_key: str) -> bool:
197
+ """异步从 MinIO 删除文件"""
198
+ return await asyncio.to_thread(self.remove_object, object_key)
194
199
 
195
- Returns:
196
- 删除的对象数量
197
- """
200
+ def remove_prefix(self, prefix: str) -> int:
201
+ """删除 MinIO 中指定前缀下的所有对象(同步,用于目录删除)"""
198
202
  if not self._client:
199
203
  return 0
200
204
  try:
@@ -210,6 +214,10 @@ class MinioSyncService(metaclass=SingletonMeta):
210
214
  SYLogger.warning(f"[MinIO] Delete prefix failed: {prefix}, error={e}")
211
215
  return 0
212
216
 
217
+ async def aremove_prefix(self, prefix: str) -> int:
218
+ """异步删除 MinIO 中指定前缀下的所有对象"""
219
+ return await asyncio.to_thread(self.remove_prefix, prefix)
220
+
213
221
  # ============== 查找最近副本 ==============
214
222
 
215
223
  def find_latest_object_key(self, user_id: str, file_path: str) -> Optional[str]:
@@ -252,7 +260,7 @@ class MinioSyncService(metaclass=SingletonMeta):
252
260
  SYLogger.warning(f"[MinIO] find_latest failed: {e}")
253
261
  return None
254
262
 
255
- async def afinf_latest_object_key(self, user_id: str, file_path: str) -> Optional[str]:
263
+ async def afind_latest_object_key(self, user_id: str, file_path: str) -> Optional[str]:
256
264
  """异步查找某个文件最近一天的 object key"""
257
265
  return await asyncio.to_thread(self.find_latest_object_key, user_id, file_path)
258
266
 
@@ -473,12 +481,17 @@ class MinioSyncService(metaclass=SingletonMeta):
473
481
  files_to_upload = []
474
482
  for rel_path in batch:
475
483
  try:
476
- response = await asyncio.to_thread(
477
- self._client.get_object, self._bucket, f"{prefix}{rel_path}"
484
+ def _download_object(bucket, key):
485
+ resp = self._client.get_object(bucket, key)
486
+ try:
487
+ return resp.read()
488
+ finally:
489
+ resp.close()
490
+ resp.release_conn()
491
+
492
+ content = await asyncio.to_thread(
493
+ _download_object, self._bucket, f"{prefix}{rel_path}"
478
494
  )
479
- content = response.read()
480
- response.close()
481
- response.release_conn()
482
495
  files_to_upload.append((f"/{rel_path}", content))
483
496
  except Exception as e:
484
497
  SYLogger.warning(f"[MinIO] 下载文件失败: {rel_path}, error={e}")
@@ -123,7 +123,7 @@ class SandboxRecoveryManager:
123
123
  bool: 切换成功返回 True,否则返回 False
124
124
  """
125
125
  # 优先使用底层已有的切换逻辑(会尝试迁移工作空间文件)
126
- switched = await self.backend._refresh_from_nacos_and_switch()
126
+ switched = await asyncio.to_thread(self.backend._refresh_from_nacos_and_switch_sync)
127
127
 
128
128
  if switched:
129
129
  self._consecutive_failures = 0
@@ -149,7 +149,8 @@ class SandboxRecoveryManager:
149
149
  SYLogger.error("[Sandbox] NacosService 未初始化")
150
150
  return False
151
151
 
152
- instances = nacos_manager.get_service_instances(
152
+ instances = await asyncio.to_thread(
153
+ nacos_manager.get_service_instances,
153
154
  self.backend._nacos_service_name,
154
155
  group=self.backend._nacos_group
155
156
  )
@@ -1,12 +1,18 @@
1
1
  from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
2
2
  from sqlalchemy import text
3
3
 
4
- # Fix: aiomysql's AsyncAdapt ping() requires 'reconnect' arg,
4
+ # Fix: aiomysql's AsyncAdapt ping() requires 'reconnect' positional arg,
5
5
  # but SQLAlchemy's pymysql dialect calls ping() without it.
6
+ # aiomysql asserts reconnect must be False (async reconnection is handled by pool).
6
7
  from sqlalchemy.dialects.mysql.pymysql import MySQLDialect_pymysql
7
8
 
9
+ _original_do_ping = MySQLDialect_pymysql.do_ping
10
+
8
11
  def _patched_do_ping(self, dbapi_connection):
9
- dbapi_connection.ping(reconnect=True)
12
+ try:
13
+ return _original_do_ping(self, dbapi_connection)
14
+ except TypeError:
15
+ dbapi_connection.ping(reconnect=False)
10
16
 
11
17
  MySQLDialect_pymysql.do_ping = _patched_do_ping
12
18
 
sycommon/synacos/feign.py CHANGED
@@ -203,8 +203,11 @@ async def _feign_internal(service_name, api_path, method='GET', params=None, hea
203
203
  raise TypeError(f"files 参数必须是字典或列表,实际为 {type(files)}")
204
204
  if file_path:
205
205
  filename = os.path.basename(file_path)
206
- with open(file_path, 'rb') as f:
207
- data.add_field('file', f, filename=filename)
206
+ def _read_file():
207
+ with open(file_path, 'rb') as f:
208
+ return f.read()
209
+ content = await asyncio.to_thread(_read_file)
210
+ data.add_field('file', content, filename=filename)
208
211
  # 移除Content-Type,让aiohttp自动处理
209
212
  headers.pop('Content-Type', None)
210
213
  async with session.request(
@@ -303,10 +303,13 @@ def feign_client(
303
303
  for path in file_paths:
304
304
  if not os.path.exists(path):
305
305
  raise FileNotFoundError(f"文件不存在: {path}")
306
- with open(path, "rb") as f:
307
- form_data.add_field(
308
- meta.field_name, f.read(), filename=os.path.basename(path)
309
- )
306
+ def _read_file(p: str) -> bytes:
307
+ with open(p, "rb") as f:
308
+ return f.read()
309
+ content = await asyncio.to_thread(_read_file, path)
310
+ form_data.add_field(
311
+ meta.field_name, content, filename=os.path.basename(path)
312
+ )
310
313
  # 处理表单字段(支持 Pydantic 模型)
311
314
  form_params = {
312
315
  n: m for n, m in param_meta.items() if isinstance(m, Form)}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.4a5
3
+ Version: 0.2.4a7
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -137,10 +137,10 @@ sycommon/agent/mcp/models.py,sha256=RBAIbGETNXkqD3wQZT7eKS4ozkgE9DQEneF1WKZf1C0,
137
137
  sycommon/agent/mcp/tool_loader.py,sha256=SEny14f7Bm9I17pT-9PJWMbhi9Ki77wvCR0KRNEJmyM,6428
138
138
  sycommon/agent/sandbox/__init__.py,sha256=jR7LlkD4J4Y6QYyRXQClkwmqDBCCPmycV_hQV9p9YHw,4621
139
139
  sycommon/agent/sandbox/file_ops.py,sha256=6ymRMM0WchM7G_YmF1ckrLjf5s_JCh1wrAp2g_-sg8k,23162
140
- sycommon/agent/sandbox/http_sandbox_backend.py,sha256=mjiTZnADvUq_rO05ewllo_eGDS4uTdD2e2GGYvBpF-Q,56150
141
- sycommon/agent/sandbox/minio_sync.py,sha256=r6tjoQA8AHNVG_hcHS3enfFnw-eTkW4r7jA7bwatsWc,19241
140
+ sycommon/agent/sandbox/http_sandbox_backend.py,sha256=kwuPEmrOMyxfrRu20AEGqWD9t38L-DrtKSFp6CWt44o,56877
141
+ sycommon/agent/sandbox/minio_sync.py,sha256=d1kuWllvyAvAMsFZCP0OdHEQtXN9BEIgHbupC31BjSk,20000
142
142
  sycommon/agent/sandbox/sandbox_pool.py,sha256=eMn8sLakCWf90l6ni2-333QM8oBdX1CflV-WzneFp_k,9133
143
- sycommon/agent/sandbox/sandbox_recovery.py,sha256=VDhFI1q9DzSs5B3s2gee1mTmXQoxs0UCXzDrqNQ7VBY,7295
143
+ sycommon/agent/sandbox/sandbox_recovery.py,sha256=X-eDODx1tmGMh_iTngV6e1ppfDBHpTdkPreJusN5MHY,7358
144
144
  sycommon/agent/sandbox/session.py,sha256=TjzC3yFC-VaJ75UwCyL26QX4PRTGNNfQae1FKFuOsYI,2365
145
145
  sycommon/auth/__init__.py,sha256=W814cfHlLXFymmxeTi3pIreFb4nhKnQ7NY1H38x1Gic,974
146
146
  sycommon/auth/ldap_service.py,sha256=fOcpVov5LWJkBk62qbTaltks1c4la7JsbD104KfdBOI,10102
@@ -159,7 +159,7 @@ sycommon/config/SentryConfig.py,sha256=OsLb3G9lTsCSZ7tWkcXWJHmvfILQopBxje5pjnkFJ
159
159
  sycommon/config/XxlJobConfig.py,sha256=VSG6dn9ysfUVunOs7PqugyZUGJWmX_cEePz2ZCfqHtU,392
160
160
  sycommon/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
161
161
  sycommon/database/async_base_db_service.py,sha256=w6ONUiTtF4-bXRnkBt9QpL9BAy0XUDbQG7F9Hf2rfjw,1337
162
- sycommon/database/async_database_service.py,sha256=cDu8Ssv5TVxOT4qOeG9hCbl7r_UbMtASL18ANYkUutw,4635
162
+ sycommon/database/async_database_service.py,sha256=Cf3RaO3skP6IAJrkta-CRE-Q1NtjWLPLUe9hazB8LRM,4873
163
163
  sycommon/database/base_db_service.py,sha256=J5ELHMNeGfzA6zVcASPSPZ0XNKrRY3_gdGmVkZw3Mto,946
164
164
  sycommon/database/database_service.py,sha256=IMoJ9554dYkr6QfRofvNa0VR24U1WQDz_ATrg0-6EQ0,3857
165
165
  sycommon/database/elasticsearch_service.py,sha256=qm490GRlxZlYsQgyfyclSbARRP1-Tc4Lwav3lbPINvQ,3092
@@ -239,8 +239,8 @@ sycommon/sse/sse.py,sha256=OQ3ElV8WCi-AD3-e0nbiUF28Syf6GRpGztneWTn77EM,10356
239
239
  sycommon/synacos/__init__.py,sha256=Re9YKVjL62AZURejgSQ3-OvIiMXY-KeAAjIcRJ8PsO0,329
240
240
  sycommon/synacos/example.py,sha256=FOnBkvodR8WF_jf-RovM3ngVmvZQX6wKwMLscUTGn2M,8707
241
241
  sycommon/synacos/example2.py,sha256=yYuQscfHUIl1HLZ8kSRBuZpHUcNWZMi5H3Mb-LjYnvk,8136
242
- sycommon/synacos/feign.py,sha256=kWnKPyWNUGOTIsmdJvn_Ch7Nm5essjsnX4bS7udmlQQ,12468
243
- sycommon/synacos/feign_client.py,sha256=nD8Ar8n0NOy5wzaWKe_8MxV76-UuuoaBV_Q5BxOriTI,19736
242
+ sycommon/synacos/feign.py,sha256=RU6p2gRP3LZoHYBBEUUY9z5KKzGiUmwj4qdo7aF9UNA,12610
243
+ sycommon/synacos/feign_client.py,sha256=i6O20JWl1g2fjD-et7olFaO-0z2yEbKT8-pKluAHCgs,19917
244
244
  sycommon/synacos/nacos_client_base.py,sha256=iP5kLkBD2VOrx6X8v6_RnC9NWiBWmL6-Bgf493QOtxc,6899
245
245
  sycommon/synacos/nacos_config_manager.py,sha256=Swqsd9X2xO5-x2VfKUrq8HjzRJn8JBPDqyXazWlF-T4,6859
246
246
  sycommon/synacos/nacos_heartbeat_manager.py,sha256=LfimUKpG4KaqsVQl150sg3MLK8psanuUwQ07tjL3uBE,10963
@@ -265,8 +265,8 @@ sycommon/tools/syemail.py,sha256=BDFhgf7WDOQeTcjxJEQdu0dQhnHFPO_p3eI0-Ni3LhQ,561
265
265
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
266
266
  sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
267
267
  sycommon/xxljob/xxljob_service.py,sha256=JIEJaGXhqrTLcyxlyynSrsHg9bBnDNzX-D4qIWLRPUE,6815
268
- sycommon_python_lib-0.2.4a5.dist-info/METADATA,sha256=TrKuiToYcZfjlvBiK3pOPBwg6aTw7Iy6gsZEoyrQMtY,7879
269
- sycommon_python_lib-0.2.4a5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
270
- sycommon_python_lib-0.2.4a5.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
271
- sycommon_python_lib-0.2.4a5.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
272
- sycommon_python_lib-0.2.4a5.dist-info/RECORD,,
268
+ sycommon_python_lib-0.2.4a7.dist-info/METADATA,sha256=ReaCw3hM57OWF1Gso6vD-CXssScGQMrJl3f7u78z0uM,7879
269
+ sycommon_python_lib-0.2.4a7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
270
+ sycommon_python_lib-0.2.4a7.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
271
+ sycommon_python_lib-0.2.4a7.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
272
+ sycommon_python_lib-0.2.4a7.dist-info/RECORD,,