tamar-file-hub-client 0.0.4__py3-none-any.whl → 0.0.6__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.
file_hub_client/client.py CHANGED
@@ -23,6 +23,8 @@ from .services import (
23
23
  )
24
24
  from .schemas.context import UserContext, RequestContext
25
25
 
26
+ MAX_MESSAGE_LENGTH = 2 ** 31 - 1 # 对于32位系统
27
+
26
28
 
27
29
  class AsyncTamarFileHubClient:
28
30
  """异步文件管理系统客户端"""
@@ -41,6 +43,7 @@ class AsyncTamarFileHubClient:
41
43
  request_context: Optional[RequestContext] = None,
42
44
  enable_logging: bool = True,
43
45
  log_level: str = "INFO",
46
+ options: Optional[list] = None,
44
47
  ):
45
48
  """
46
49
  初始化客户端
@@ -56,6 +59,13 @@ class AsyncTamarFileHubClient:
56
59
  default_metadata: 默认的元数据(如 org_id, user_id 等)
57
60
  user_context: 用户上下文
58
61
  request_context: 请求上下文
62
+ enable_logging: 是否启用日志
63
+ log_level: 日志级别
64
+ options: gRPC 通道选项列表,例如:
65
+ [
66
+ ('grpc.max_send_message_length', MAX_MESSAGE_LENGTH),
67
+ ('grpc.max_receive_message_length', MAX_MESSAGE_LENGTH),
68
+ ]
59
69
 
60
70
  环境变量:
61
71
  FILE_HUB_HOST: gRPC 服务器地址
@@ -86,11 +96,19 @@ class AsyncTamarFileHubClient:
86
96
  if default_metadata is None:
87
97
  default_metadata = {}
88
98
 
99
+ # 设置默认的 gRPC 选项(增加消息大小限制)
100
+ if options is None:
101
+ options = [
102
+ ('grpc.max_send_message_length', MAX_MESSAGE_LENGTH),
103
+ ('grpc.max_receive_message_length', MAX_MESSAGE_LENGTH),
104
+ ]
105
+
89
106
  self._client = AsyncGrpcClient(
90
107
  self._host,
91
108
  self._port,
92
109
  self._secure,
93
110
  credentials,
111
+ options=options,
94
112
  retry_count=self._retry_count,
95
113
  retry_delay=self._retry_delay,
96
114
  default_metadata=default_metadata,
@@ -243,6 +261,13 @@ class TamarFileHubClient:
243
261
  default_metadata: 默认的元数据(如 org_id, user_id 等)
244
262
  user_context: 用户上下文
245
263
  request_context: 请求上下文
264
+ enable_logging: 是否启用日志
265
+ log_level: 日志级别
266
+ options: gRPC 通道选项列表,例如:
267
+ [
268
+ ('grpc.max_send_message_length', 100 * 1024 * 1024), # 100MB
269
+ ('grpc.max_receive_message_length', 100 * 1024 * 1024), # 100MB
270
+ ]
246
271
 
247
272
  环境变量:
248
273
  FILE_HUB_HOST: gRPC 服务器地址
@@ -273,11 +298,19 @@ class TamarFileHubClient:
273
298
  if default_metadata is None:
274
299
  default_metadata = {}
275
300
 
301
+ # 设置默认的 gRPC 选项(增加消息大小限制)
302
+ if options is None:
303
+ options = [
304
+ ('grpc.max_send_message_length', 100 * 1024 * 1024), # 100MB
305
+ ('grpc.max_receive_message_length', 100 * 1024 * 1024), # 100MB
306
+ ]
307
+
276
308
  self._client = SyncGrpcClient(
277
309
  self._host,
278
310
  self._port,
279
311
  self._secure,
280
312
  credentials,
313
+ options=options,
281
314
  retry_count=self._retry_count,
282
315
  retry_delay=self._retry_delay,
283
316
  default_metadata=default_metadata,
@@ -86,8 +86,29 @@ def _sanitize_request_data(data: Any, max_string_length: int = 200, max_binary_p
86
86
  # 递归处理字典
87
87
  result = {}
88
88
  for key, value in data.items():
89
- # 检查是否是常见的二进制/内容字段
90
- if key.lower() in ['content', 'data', 'file', 'file_content', 'binary', 'blob', 'bytes', 'image', 'attachment']:
89
+ # 检查是否是需要特殊处理的字段
90
+ if key.lower() in ['operations'] and isinstance(value, list):
91
+ # 对于 operations 字段,特殊处理以显示操作类型和数量
92
+ if len(value) > 5:
93
+ ops_summary = []
94
+ # 统计操作类型
95
+ op_types = {}
96
+ for op in value:
97
+ if isinstance(op, dict):
98
+ for op_type in ['edit', 'create', 'update', 'delete', 'clear']:
99
+ if op_type in op:
100
+ op_types[op_type] = op_types.get(op_type, 0) + 1
101
+
102
+ # 显示前3个操作
103
+ for i in range(min(3, len(value))):
104
+ ops_summary.append(_sanitize_request_data(value[i], max_string_length, max_binary_preview))
105
+
106
+ # 添加统计信息
107
+ ops_summary.append(f"... 总计 {len(value)} 个操作: {', '.join(f'{k}={v}' for k, v in op_types.items())}")
108
+ result[key] = ops_summary
109
+ else:
110
+ result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
111
+ elif key.lower() in ['content', 'data', 'file', 'file_content', 'binary', 'blob', 'bytes', 'image', 'attachment']:
91
112
  if isinstance(value, (bytes, bytearray)):
92
113
  # 二进制内容,显示长度和预览
93
114
  preview = base64.b64encode(value[:max_binary_preview]).decode('utf-8')
@@ -106,8 +127,18 @@ def _sanitize_request_data(data: Any, max_string_length: int = 200, max_binary_p
106
127
  result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
107
128
  return result
108
129
  elif isinstance(data, list):
109
- # 递归处理列表
110
- return [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data]
130
+ # 递归处理列表,限制列表长度以避免日志过长
131
+ max_list_items = 10 # 最多显示10个元素
132
+ if len(data) > max_list_items:
133
+ # 显示前5个和后5个元素
134
+ preview_items = (
135
+ [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[:5]] +
136
+ [f"... {len(data) - max_list_items} more items ..."] +
137
+ [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[-5:]]
138
+ )
139
+ return preview_items
140
+ else:
141
+ return [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data]
111
142
  elif isinstance(data, tuple):
112
143
  # 递归处理元组
113
144
  return tuple(_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data)
@@ -387,7 +387,6 @@ class SyncBlobService(BaseFileService):
387
387
  expire_seconds: 过期秒数
388
388
  url: 要下载并上传的URL(可选)
389
389
  file_name: 当使用url参数时指定的文件名(可选)
390
- mime_type: 文件mine-type,当传入的是二进制内容时必传
391
390
  request_id: 请求ID(可选,如果不提供则自动生成)
392
391
  **metadata: 额外的元数据
393
392
 
@@ -512,6 +511,7 @@ class SyncBlobService(BaseFileService):
512
511
 
513
512
  Args:
514
513
  file_id: 文件ID
514
+ expire_seconds: 过期时间(秒)
515
515
  request_id: 请求ID(可选,如果不提供则自动生成)
516
516
  **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
517
517
 
@@ -109,6 +109,10 @@ def setup_logging(
109
109
  # 防止日志传播到根日志记录器 - 保持SDK日志独立
110
110
  logger.propagate = False
111
111
 
112
+ # 对整个 file_hub_client 包设置隔离,确保所有子模块的日志都不会传播
113
+ parent_logger = logging.getLogger('file_hub_client')
114
+ parent_logger.propagate = False
115
+
112
116
  # 初始化日志(使用JSON格式)
113
117
  if enable_grpc_logging:
114
118
  log_record = logging.LogRecord(
@@ -67,10 +67,20 @@ def retry_with_backoff(
67
67
  raise
68
68
 
69
69
  if attempt < max_retries:
70
+ # 提取更详细的错误信息
71
+ error_details = str(e)
72
+ if hasattr(e, 'code') and hasattr(e, 'details'):
73
+ # gRPC 错误
74
+ error_details = f"gRPC {e.code().name}: {e.details()}"
75
+ elif hasattr(e, 'response') and hasattr(e.response, 'status_code'):
76
+ # HTTP 错误
77
+ error_details = f"HTTP {e.response.status_code}: {str(e)}"
78
+
70
79
  logger.warning(
71
80
  f"🔄 触发重试 | 操作: {func.__name__} | "
72
81
  f"尝试: {attempt + 1}/{max_retries + 1} | "
73
- f"错误: {type(e).__name__}: {str(e)} | "
82
+ f"错误类型: {type(e).__name__} | "
83
+ f"错误详情: {error_details} | "
74
84
  f"延迟: {delay:.1f}秒"
75
85
  )
76
86
  await asyncio.sleep(delay)
@@ -109,10 +119,20 @@ def retry_with_backoff(
109
119
  raise
110
120
 
111
121
  if attempt < max_retries:
122
+ # 提取更详细的错误信息
123
+ error_details = str(e)
124
+ if hasattr(e, 'code') and hasattr(e, 'details'):
125
+ # gRPC 错误
126
+ error_details = f"gRPC {e.code().name}: {e.details()}"
127
+ elif hasattr(e, 'response') and hasattr(e.response, 'status_code'):
128
+ # HTTP 错误
129
+ error_details = f"HTTP {e.response.status_code}: {str(e)}"
130
+
112
131
  logger.warning(
113
132
  f"🔄 触发重试 | 操作: {func.__name__} | "
114
133
  f"尝试: {attempt + 1}/{max_retries + 1} | "
115
- f"错误: {type(e).__name__}: {str(e)} | "
134
+ f"错误类型: {type(e).__name__} | "
135
+ f"错误详情: {error_details} | "
116
136
  f"延迟: {delay:.1f}秒"
117
137
  )
118
138
  time.sleep(delay)
@@ -162,10 +182,21 @@ def retry_on_lock_conflict(
162
182
  if _is_lock_conflict(result):
163
183
  last_result = result
164
184
  if attempt < max_retries:
185
+ # 提取冲突详细信息
186
+ conflict_details = "lock_conflict"
187
+ if isinstance(result, dict):
188
+ conflict_info = result.get('conflict_info', {})
189
+ if conflict_info:
190
+ conflict_details = f"{conflict_info.get('conflict_type', 'lock_conflict')}"
191
+ if 'resolution_suggestion' in conflict_info:
192
+ conflict_details += f" - {conflict_info['resolution_suggestion']}"
193
+ if 'error_message' in result:
194
+ conflict_details += f" - {result['error_message']}"
195
+
165
196
  logger.warning(
166
197
  f"🔒 锁冲突重试 | 操作: {func.__name__} | "
167
198
  f"尝试: {attempt + 1}/{max_retries + 1} | "
168
- f"冲突类型: lock_conflict | "
199
+ f"冲突详情: {conflict_details} | "
169
200
  f"延迟: {delay:.1f}秒"
170
201
  )
171
202
  await asyncio.sleep(delay)
@@ -196,10 +227,21 @@ def retry_on_lock_conflict(
196
227
  if _is_lock_conflict(result):
197
228
  last_result = result
198
229
  if attempt < max_retries:
230
+ # 提取冲突详细信息
231
+ conflict_details = "lock_conflict"
232
+ if isinstance(result, dict):
233
+ conflict_info = result.get('conflict_info', {})
234
+ if conflict_info:
235
+ conflict_details = f"{conflict_info.get('conflict_type', 'lock_conflict')}"
236
+ if 'resolution_suggestion' in conflict_info:
237
+ conflict_details += f" - {conflict_info['resolution_suggestion']}"
238
+ if 'error_message' in result:
239
+ conflict_details += f" - {result['error_message']}"
240
+
199
241
  logger.warning(
200
242
  f"🔒 锁冲突重试 | 操作: {func.__name__} | "
201
243
  f"尝试: {attempt + 1}/{max_retries + 1} | "
202
- f"冲突类型: lock_conflict | "
244
+ f"冲突详情: {conflict_details} | "
203
245
  f"延迟: {delay:.1f}秒"
204
246
  )
205
247
  time.sleep(delay)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tamar-file-hub-client
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: A Python SDK for gRPC-based file management system
5
5
  Home-page: https://github.com/Tamar-Edge-AI/file-hub-client
6
6
  Author: Oscar Ou
@@ -1,5 +1,5 @@
1
1
  file_hub_client/__init__.py,sha256=5pOOgw8YLzsQ49o81j5xCFtiH7VHFObNlbAYZwsU7ts,2918
2
- file_hub_client/client.py,sha256=FXYHibyzgtvmIG1NKyHTAjkLutdn5fbAc9XSqrCEAFs,15895
2
+ file_hub_client/client.py,sha256=OJ7tveaGkSc4gP4b58Qecu3Yw4NBhTCjZZEj59Xcx30,17294
3
3
  file_hub_client/py.typed,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
4
4
  file_hub_client/enums/__init__.py,sha256=e3Io6-IC9Nezbdc8H4TzuWqiDw0T1fsdbSi-fsgSQM4,203
5
5
  file_hub_client/enums/export_format.py,sha256=C9P0q8v5eTszUBqKBmERxoA9UrTgLuX3a_bZeUxkdlQ,296
@@ -10,7 +10,7 @@ file_hub_client/errors/exceptions.py,sha256=0RDkAv4t2GeVefm1H5eaXGxd6Od-drXzPIVJ
10
10
  file_hub_client/rpc/__init__.py,sha256=dhRt0D_US3j2Dam47nhd23RowVZ-nvVowhPsNFf4GZo,204
11
11
  file_hub_client/rpc/async_client.py,sha256=vMi4Aqc_Ajg2sdOgXHiLjP_4FkPtg5tDpSDPK6hg-cM,14038
12
12
  file_hub_client/rpc/generate_grpc.py,sha256=opzstxWdW7vqR9OxrgUCSUkZe8IqgcOdruqWGIzCneI,2158
13
- file_hub_client/rpc/interceptors.py,sha256=AkeU6PxURIUuhHrCta5LYP09oOXCKsGwD98J7dx3gJo,20258
13
+ file_hub_client/rpc/interceptors.py,sha256=UeQ8u-olMiYwScXJrlzqPS-odGomH-h3xJqOwPznRUo,22071
14
14
  file_hub_client/rpc/sync_client.py,sha256=ZWBmP-TU8eeAfl2lV8sLu9nsxo_aOPAVmt9ww_T8Hwg,14054
15
15
  file_hub_client/rpc/gen/__init__.py,sha256=NJLqr9ezUXeOyy1J0sMPn3Kl_8IyGw7GAzzzewO3MIw,45
16
16
  file_hub_client/rpc/gen/file_service_pb2.py,sha256=7zEL8RFMp9gcigEqWnZFREX8ylljfQzBHwg43SbKt-A,8354
@@ -32,7 +32,7 @@ file_hub_client/services/file/__init__.py,sha256=aJygo_AzYk5NN-ezp-a9YlugJ82wVIP
32
32
  file_hub_client/services/file/async_blob_service.py,sha256=x4Xmxc5FqeiYZ7yKb5_fDjJhgxpq79g7dJuGbHIWMow,20484
33
33
  file_hub_client/services/file/async_file_service.py,sha256=QxVfwPoJe_oj8t7EOLHMQF3PQf4E1-HctQR7yvY9D3g,8585
34
34
  file_hub_client/services/file/base_file_service.py,sha256=wTxplrTk9klfwIHOPfTL0TQd6gX4nEmkYtIhpiZ3GVo,4791
35
- file_hub_client/services/file/sync_blob_service.py,sha256=ZHNEwoB97G3mQrGOCJ491xxrPS7XMa2kSPDpKU18WKE,20296
35
+ file_hub_client/services/file/sync_blob_service.py,sha256=pnUciPJ0oOLOLK1WDvigJ5gdL5lfCDw-qJX1WMGZvew,20265
36
36
  file_hub_client/services/file/sync_file_service.py,sha256=i1pLCcGNWMlWQfAW4dlhLsEiV3oc1jXKmKax35k0CGw,8439
37
37
  file_hub_client/services/folder/__init__.py,sha256=vGbMOlNiEBdnWZB1xE74RJtoroI28hKHCWfQV1GqKQc,210
38
38
  file_hub_client/services/folder/async_folder_service.py,sha256=uFEmtW8EXYvaKYT2JCitWbdTGR1EtHlx_eBN5P3JUZg,7293
@@ -47,11 +47,11 @@ file_hub_client/utils/converter.py,sha256=TX69Bqk-PwNdv2hYQ07_tW6HQnQycHcJkGeRns
47
47
  file_hub_client/utils/download_helper.py,sha256=Mc8TQSWjHxIglJMkKlGy9r3LZe8e_Mwe6D3sfn6IOnY,13338
48
48
  file_hub_client/utils/file_utils.py,sha256=Ly8R5KJS_3lbgJxNZkc4sSBKuGgn-fYeh17GEY4pyy8,4359
49
49
  file_hub_client/utils/idempotency.py,sha256=zuXDlpAc9VTkTsarlnkO0VuJ77yON6j1TX0GvL9Xd9k,6029
50
- file_hub_client/utils/logging.py,sha256=W32_goFF7_tVN_hCfe85smPu2lHW0jPs_AGlSIYSRxY,11543
51
- file_hub_client/utils/retry.py,sha256=PybdlGEdYdjeCPBg5JfKoKHsRxAIy24v5oyIeYQAg9U,12693
50
+ file_hub_client/utils/logging.py,sha256=IxcvWkA0G9s9BMiXIeFAdJX5G-Lc5-JFlS2yxOX1Swo,11741
51
+ file_hub_client/utils/retry.py,sha256=MyEAYHEGljoHvVs4Kh1hX_Y3iDGcppVCSUvVkNGbC28,15609
52
52
  file_hub_client/utils/smart_retry.py,sha256=RjBhyG6SNDfMXxNxKU_qayWDD6Ihp7ow6_BPjhgflM0,16465
53
53
  file_hub_client/utils/upload_helper.py,sha256=gEtn9OXVJiGUpVev_fqrDnRQ6AFiiP9goLzFrVpqXmU,22569
54
- tamar_file_hub_client-0.0.4.dist-info/METADATA,sha256=Xonh4NRJcoFB5vbw7bK64RpZSC2OZRbB2p-_lyQtIa8,64873
55
- tamar_file_hub_client-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
56
- tamar_file_hub_client-0.0.4.dist-info/top_level.txt,sha256=9wcR7hyAJQdJg_kuH6WR3nmpJ8O-j8aJNK8f_kcFy6U,16
57
- tamar_file_hub_client-0.0.4.dist-info/RECORD,,
54
+ tamar_file_hub_client-0.0.6.dist-info/METADATA,sha256=JVRL8Uyrc-y1XnLZeaBFJxxQ-MzUPLaEKVqLRXhScWk,64873
55
+ tamar_file_hub_client-0.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
56
+ tamar_file_hub_client-0.0.6.dist-info/top_level.txt,sha256=9wcR7hyAJQdJg_kuH6WR3nmpJ8O-j8aJNK8f_kcFy6U,16
57
+ tamar_file_hub_client-0.0.6.dist-info/RECORD,,