veadk-python 0.2.7__py3-none-any.whl → 0.2.9__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 veadk-python might be problematic. Click here for more details.

Files changed (75) hide show
  1. veadk/agent.py +3 -2
  2. veadk/auth/veauth/opensearch_veauth.py +75 -0
  3. veadk/auth/veauth/postgresql_veauth.py +75 -0
  4. veadk/cli/cli.py +3 -1
  5. veadk/cli/cli_eval.py +160 -0
  6. veadk/cli/cli_prompt.py +9 -2
  7. veadk/cli/cli_web.py +6 -1
  8. veadk/configs/database_configs.py +43 -0
  9. veadk/configs/model_configs.py +32 -0
  10. veadk/consts.py +11 -4
  11. veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
  12. veadk/evaluation/base_evaluator.py +95 -68
  13. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +23 -15
  14. veadk/evaluation/eval_set_recorder.py +2 -2
  15. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +9 -3
  16. veadk/integrations/ve_tls/utils.py +1 -2
  17. veadk/integrations/ve_tls/ve_tls.py +9 -5
  18. veadk/integrations/ve_tos/ve_tos.py +542 -68
  19. veadk/knowledgebase/backends/base_backend.py +59 -0
  20. veadk/knowledgebase/backends/in_memory_backend.py +82 -0
  21. veadk/knowledgebase/backends/opensearch_backend.py +136 -0
  22. veadk/knowledgebase/backends/redis_backend.py +144 -0
  23. veadk/knowledgebase/backends/utils.py +91 -0
  24. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +524 -0
  25. veadk/{database/__init__.py → knowledgebase/entry.py} +10 -2
  26. veadk/knowledgebase/knowledgebase.py +120 -139
  27. veadk/memory/__init__.py +22 -0
  28. veadk/memory/long_term_memory.py +124 -41
  29. veadk/{database/base_database.py → memory/long_term_memory_backends/base_backend.py} +10 -22
  30. veadk/memory/long_term_memory_backends/in_memory_backend.py +65 -0
  31. veadk/memory/long_term_memory_backends/mem0_backend.py +129 -0
  32. veadk/memory/long_term_memory_backends/opensearch_backend.py +120 -0
  33. veadk/memory/long_term_memory_backends/redis_backend.py +127 -0
  34. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +148 -0
  35. veadk/memory/short_term_memory.py +80 -72
  36. veadk/memory/short_term_memory_backends/base_backend.py +31 -0
  37. veadk/memory/short_term_memory_backends/mysql_backend.py +41 -0
  38. veadk/memory/short_term_memory_backends/postgresql_backend.py +41 -0
  39. veadk/memory/short_term_memory_backends/sqlite_backend.py +48 -0
  40. veadk/runner.py +12 -19
  41. veadk/tools/builtin_tools/generate_image.py +355 -0
  42. veadk/tools/builtin_tools/image_edit.py +56 -16
  43. veadk/tools/builtin_tools/image_generate.py +51 -15
  44. veadk/tools/builtin_tools/video_generate.py +41 -41
  45. veadk/tools/builtin_tools/web_scraper.py +1 -1
  46. veadk/tools/builtin_tools/web_search.py +7 -7
  47. veadk/tools/load_knowledgebase_tool.py +2 -8
  48. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +21 -3
  49. veadk/tracing/telemetry/exporters/apmplus_exporter.py +24 -6
  50. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +2 -0
  51. veadk/tracing/telemetry/exporters/inmemory_exporter.py +22 -8
  52. veadk/tracing/telemetry/exporters/tls_exporter.py +2 -0
  53. veadk/tracing/telemetry/opentelemetry_tracer.py +13 -10
  54. veadk/tracing/telemetry/telemetry.py +66 -63
  55. veadk/utils/misc.py +15 -0
  56. veadk/version.py +1 -1
  57. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/METADATA +28 -5
  58. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/RECORD +65 -56
  59. veadk/database/database_adapter.py +0 -533
  60. veadk/database/database_factory.py +0 -80
  61. veadk/database/kv/redis_database.py +0 -159
  62. veadk/database/local_database.py +0 -62
  63. veadk/database/relational/mysql_database.py +0 -173
  64. veadk/database/vector/opensearch_vector_database.py +0 -263
  65. veadk/database/vector/type.py +0 -50
  66. veadk/database/viking/__init__.py +0 -13
  67. veadk/database/viking/viking_database.py +0 -638
  68. veadk/database/viking/viking_memory_db.py +0 -525
  69. /veadk/{database/kv → knowledgebase/backends}/__init__.py +0 -0
  70. /veadk/{database/relational → memory/long_term_memory_backends}/__init__.py +0 -0
  71. /veadk/{database/vector → memory/short_term_memory_backends}/__init__.py +0 -0
  72. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/WHEEL +0 -0
  73. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/entry_points.txt +0 -0
  74. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/licenses/LICENSE +0 -0
  75. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/top_level.txt +0 -0
@@ -15,11 +15,13 @@
15
15
  import asyncio
16
16
  import os
17
17
  from datetime import datetime
18
- from typing import TYPE_CHECKING, Union
18
+ from io import StringIO
19
+ from typing import TYPE_CHECKING, List, Optional, Union
19
20
  from urllib.parse import urlparse
20
21
 
21
22
  from veadk.consts import DEFAULT_TOS_BUCKET_NAME
22
23
  from veadk.utils.logger import get_logger
24
+ from veadk.utils.misc import getenv
23
25
 
24
26
  if TYPE_CHECKING:
25
27
  pass
@@ -39,8 +41,17 @@ class VeTOS:
39
41
  ) -> None:
40
42
  self.ak = ak if ak else os.getenv("VOLCENGINE_ACCESS_KEY", "")
41
43
  self.sk = sk if sk else os.getenv("VOLCENGINE_SECRET_KEY", "")
44
+ # Add empty value validation
45
+ if not self.ak or not self.sk:
46
+ raise ValueError(
47
+ "VOLCENGINE_ACCESS_KEY and VOLCENGINE_SECRET_KEY must be provided "
48
+ "either via parameters or environment variables."
49
+ )
50
+
42
51
  self.region = region
43
- self.bucket_name = bucket_name
52
+ self.bucket_name = (
53
+ bucket_name if bucket_name else getenv("", DEFAULT_TOS_BUCKET_NAME)
54
+ )
44
55
  self._tos_module = None
45
56
 
46
57
  try:
@@ -65,7 +76,7 @@ class VeTOS:
65
76
  )
66
77
  logger.info("Init TOS client.")
67
78
  except Exception as e:
68
- logger.error(f"Client initialization failed:{e}")
79
+ logger.error(f"Client initialization failed: {e}")
69
80
 
70
81
  def _refresh_client(self):
71
82
  try:
@@ -82,38 +93,74 @@ class VeTOS:
82
93
  logger.error(f"Failed to refresh client: {str(e)}")
83
94
  self._client = None
84
95
 
85
- def create_bucket(self) -> bool:
86
- """If the bucket does not exist, create it and set CORS rules"""
96
+ def _check_bucket_name(self, bucket_name: str = "") -> str:
97
+ return bucket_name or self.bucket_name
98
+
99
+ def bucket_exists(self, bucket_name: str) -> bool:
100
+ """Check if bucket exists
101
+
102
+ Args:
103
+ bucket_name: Bucket name
104
+
105
+ Returns:
106
+ bool: True if bucket exists, False otherwise
107
+ """
108
+ bucket_name = self._check_bucket_name(bucket_name)
87
109
  if not self._client:
88
110
  logger.error("TOS client is not initialized")
89
111
  return False
112
+
90
113
  try:
91
- self._client.head_bucket(self.bucket_name)
92
- logger.info(f"Bucket {self.bucket_name} already exists")
93
- except self._tos_module.exceptions.TosServerError as e:
94
- if e.status_code == 404:
95
- try:
96
- self._client.create_bucket(
97
- bucket=self.bucket_name,
98
- storage_class=self._tos_module.StorageClassType.Storage_Class_Standard,
99
- acl=self._tos_module.ACLType.ACL_Public_Read,
100
- )
101
- logger.info(f"Bucket {self.bucket_name} created successfully")
102
- self._refresh_client()
103
- except Exception as create_error:
104
- logger.error(f"Bucket creation failed: {str(create_error)}")
105
- return False
106
- else:
107
- logger.error(f"Bucket check failed: {str(e)}")
108
- return False
114
+ self._client.head_bucket(bucket_name)
115
+ logger.debug(f"Bucket {bucket_name} exists")
116
+ return True
109
117
  except Exception as e:
110
- logger.error(f"Bucket check failed: {str(e)}")
118
+ logger.error(
119
+ f"Unexpected error when checking bucket {bucket_name}: {str(e)}"
120
+ )
121
+ return False
122
+
123
+ def create_bucket(self, bucket_name: str = "") -> bool:
124
+ """Create bucket (if not exists)
125
+
126
+ Args:
127
+ bucket_name: Bucket name
128
+
129
+ Returns:
130
+ bool: True if bucket exists or created successfully, False otherwise
131
+ """
132
+ bucket_name = self._check_bucket_name(bucket_name)
133
+ if not self._client:
134
+ logger.error("TOS client is not initialized")
135
+ return False
136
+
137
+ # Check if bucket already exists
138
+ if self.bucket_exists(bucket_name):
139
+ logger.info(f"Bucket {bucket_name} already exists, no need to create")
140
+ return True
141
+
142
+ # Try to create bucket
143
+ try:
144
+ logger.info(f"Attempting to create bucket: {bucket_name}")
145
+ self._client.create_bucket(
146
+ bucket=bucket_name,
147
+ storage_class=self._tos_module.StorageClassType.Storage_Class_Standard,
148
+ acl=self._tos_module.ACLType.ACL_Public_Read,
149
+ )
150
+ logger.info(f"Bucket {bucket_name} created successfully")
151
+ self._refresh_client()
152
+ except self._tos_module.exceptions.TosServerError as e:
153
+ logger.error(
154
+ f"Failed to create bucket {bucket_name}: status_code={e.status_code}, {str(e)}"
155
+ )
111
156
  return False
112
157
 
113
- # ensure return bool type
114
- return self._set_cors_rules()
158
+ # Set CORS rules
159
+ return self._set_cors_rules(bucket_name)
160
+
161
+ def _set_cors_rules(self, bucket_name: str) -> bool:
162
+ bucket_name = self._check_bucket_name(bucket_name)
115
163
 
116
- def _set_cors_rules(self) -> bool:
117
164
  if not self._client:
118
165
  logger.error("TOS client is not initialized")
119
166
  return False
@@ -124,91 +171,519 @@ class VeTOS:
124
171
  allowed_headers=["*"],
125
172
  max_age_seconds=1000,
126
173
  )
127
- self._client.put_bucket_cors(self.bucket_name, [rule])
128
- logger.info(f"CORS rules for bucket {self.bucket_name} set successfully")
174
+ self._client.put_bucket_cors(bucket_name, [rule])
175
+ logger.info(f"CORS rules for bucket {bucket_name} set successfully")
129
176
  return True
130
177
  except Exception as e:
131
- logger.error(
132
- f"Failed to set CORS rules for bucket {self.bucket_name}: {str(e)}"
133
- )
178
+ logger.error(f"Failed to set CORS rules for bucket {bucket_name}: {str(e)}")
134
179
  return False
135
180
 
136
- def build_tos_url(
137
- self, user_id: str, app_name: str, session_id: str, data_path: str
138
- ) -> tuple[str, str]:
139
- """generate TOS object key"""
181
+ def _build_object_key_for_file(self, data_path: str) -> str:
182
+ """Builds the TOS object key and URL for the given parameters.
183
+
184
+ Args:
185
+ user_id (str): User ID
186
+ app_name (str): App name
187
+ session_id (str): Session ID
188
+ data_path (str): Data path
189
+
190
+ Returns:
191
+ tuple[str, str]: Object key and TOS URL.
192
+ """
193
+
140
194
  parsed_url = urlparse(data_path)
141
195
 
142
- if parsed_url.scheme and parsed_url.scheme in ("http", "https", "ftp", "ftps"):
143
- file_name = os.path.basename(parsed_url.path)
196
+ # Generate object key
197
+ if parsed_url.scheme in ("http", "https", "ftp", "ftps"):
198
+ # For URL, remove protocol part, keep domain and path
199
+ object_key = f"{parsed_url.netloc}{parsed_url.path}"
144
200
  else:
145
- file_name = os.path.basename(data_path)
201
+ # For local files, use path relative to current working directory
202
+ abs_path = os.path.abspath(data_path)
203
+ cwd = os.getcwd()
204
+ # If file is in current working directory or its subdirectories, use relative path
205
+ try:
206
+ rel_path = os.path.relpath(abs_path, cwd)
207
+ # Check if path contains relative path symbols (../, ./ etc.)
208
+ if (
209
+ not rel_path.startswith("../")
210
+ and not rel_path.startswith("..\\")
211
+ and not rel_path.startswith("./")
212
+ and not rel_path.startswith(".\\")
213
+ ):
214
+ object_key = rel_path
215
+ else:
216
+ # If path contains relative path symbols, use only filename
217
+ object_key = os.path.basename(data_path)
218
+ except ValueError:
219
+ # If unable to calculate relative path (cross-volume), use filename
220
+ object_key = os.path.basename(data_path)
146
221
 
147
- timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
148
- object_key: str = f"{app_name}-{user_id}-{session_id}/{timestamp}-{file_name}"
222
+ # Remove leading slash to avoid signature errors
223
+ if object_key.startswith("/"):
224
+ object_key = object_key[1:]
225
+
226
+ # If object key is empty or contains unsafe path symbols, use filename
227
+ if (
228
+ not object_key
229
+ or "../" in object_key
230
+ or "..\\" in object_key
231
+ or "./" in object_key
232
+ or ".\\" in object_key
233
+ ):
234
+ object_key = os.path.basename(data_path)
235
+
236
+ return object_key
237
+
238
+ def _build_object_key_for_text(self) -> str:
239
+ """generate TOS object key"""
240
+
241
+ object_key: str = f"{datetime.now().strftime('%Y%m%d%H%M%S')}.txt"
242
+
243
+ return object_key
244
+
245
+ def _build_object_key_for_bytes(self) -> str:
246
+ object_key: str = f"{datetime.now().strftime('%Y%m%d%H%M%S')}"
247
+
248
+ return object_key
249
+
250
+ def build_tos_url(self, object_key: str, bucket_name: str = "") -> str:
251
+ bucket_name = self._check_bucket_name(bucket_name)
149
252
  tos_url: str = (
150
- f"https://{self.bucket_name}.tos-{self.region}.volces.com/{object_key}"
253
+ f"https://{bucket_name}.tos-{self.region}.volces.com/{object_key}"
151
254
  )
255
+ return tos_url
152
256
 
153
- return object_key, tos_url
257
+ def build_tos_signed_url(self, object_key: str, bucket_name: str = "") -> str:
258
+ bucket_name = self._check_bucket_name(bucket_name)
154
259
 
260
+ out = self._client.pre_signed_url(
261
+ self._tos_module.HttpMethodType.Http_Method_Get,
262
+ bucket=bucket_name,
263
+ key=object_key,
264
+ expires=604800,
265
+ )
266
+ tos_url = out.signed_url
267
+ return tos_url
268
+
269
+ # deprecated
155
270
  def upload(
156
271
  self,
157
- object_key: str,
158
272
  data: Union[str, bytes],
273
+ bucket_name: str = "",
274
+ object_key: str = "",
275
+ metadata: dict | None = None,
159
276
  ):
277
+ """Uploads data to TOS.
278
+
279
+ Args:
280
+ data (Union[str, bytes]): The data to upload, either as a file path or raw bytes.
281
+ bucket_name (str): The name of the TOS bucket to upload to.
282
+ object_key (str): The object key for the uploaded data.
283
+ metadata (dict | None, optional): Metadata to associate with the object. Defaults to None.
284
+
285
+ Raises:
286
+ ValueError: If the data type is unsupported.
287
+ """
160
288
  if isinstance(data, str):
161
289
  # data is a file path
162
- return asyncio.to_thread(self._do_upload_file, object_key, data)
290
+ return asyncio.to_thread(
291
+ self.upload_file, data, bucket_name, object_key, metadata
292
+ )
163
293
  elif isinstance(data, bytes):
164
294
  # data is bytes content
165
- return asyncio.to_thread(self._do_upload_bytes, object_key, data)
295
+ return asyncio.to_thread(
296
+ self.upload_bytes, data, bucket_name, object_key, metadata
297
+ )
166
298
  else:
167
299
  error_msg = f"Upload failed: data type error. Only str (file path) and bytes are supported, got {type(data)}"
168
300
  logger.error(error_msg)
169
301
  raise ValueError(error_msg)
170
302
 
171
- def _do_upload_bytes(self, object_key: str, data: bytes) -> None:
303
+ def _ensure_client_and_bucket(self, bucket_name: str) -> bool:
304
+ """Ensure TOS client is initialized and bucket exists
305
+
306
+ Args:
307
+ bucket_name: Bucket name
308
+
309
+ Returns:
310
+ bool: True if client is initialized and bucket exists, False otherwise
311
+ """
312
+ if not self._client:
313
+ logger.error("TOS client is not initialized")
314
+ return False
315
+ if not self.create_bucket(bucket_name):
316
+ logger.error(f"Failed to create or access bucket: {bucket_name}")
317
+ return False
318
+ return True
319
+
320
+ def upload_text(
321
+ self,
322
+ text: str,
323
+ bucket_name: str = "",
324
+ object_key: str = "",
325
+ metadata: dict | None = None,
326
+ ) -> None:
327
+ """Upload text content to TOS bucket
328
+
329
+ Args:
330
+ text: Text content to upload
331
+ bucket_name: TOS bucket name
332
+ object_key: Object key, auto-generated if None
333
+ metadata: Metadata to associate with the object
334
+ """
335
+ bucket_name = self._check_bucket_name(bucket_name)
336
+ if not object_key:
337
+ object_key = self._build_object_key_for_text()
338
+
339
+ if not self._ensure_client_and_bucket(bucket_name):
340
+ return
341
+ data = StringIO(text)
342
+ try:
343
+ self._client.put_object(
344
+ bucket=bucket_name, key=object_key, content=data, meta=metadata
345
+ )
346
+ logger.debug(f"Upload success, object_key: {object_key}")
347
+ return
348
+ except Exception as e:
349
+ logger.error(f"Upload failed: {e}")
350
+ return
351
+ finally:
352
+ data.close()
353
+
354
+ async def async_upload_text(
355
+ self,
356
+ text: str,
357
+ bucket_name: str = "",
358
+ object_key: str = "",
359
+ metadata: dict | None = None,
360
+ ) -> None:
361
+ """Asynchronously upload text content to TOS bucket
362
+
363
+ Args:
364
+ text: Text content to upload
365
+ bucket_name: TOS bucket name
366
+ object_key: Object key, auto-generated if None
367
+ metadata: Metadata to associate with the object
368
+ """
369
+ bucket_name = self._check_bucket_name(bucket_name)
370
+ if not object_key:
371
+ object_key = self._build_object_key_for_text()
372
+ # Use common function to check client and bucket
373
+ if not self._ensure_client_and_bucket(bucket_name):
374
+ return
375
+ data = StringIO(text)
376
+ try:
377
+ # Use asyncio.to_thread to execute blocking TOS operations in thread
378
+ await asyncio.to_thread(
379
+ self._client.put_object,
380
+ bucket=bucket_name,
381
+ key=object_key,
382
+ content=data,
383
+ meta=metadata,
384
+ )
385
+ logger.debug(f"Async upload success, object_key: {object_key}")
386
+ return
387
+ except Exception as e:
388
+ logger.error(f"Async upload failed: {e}")
389
+ return
390
+ finally:
391
+ data.close()
392
+
393
+ def upload_bytes(
394
+ self,
395
+ data: bytes,
396
+ bucket_name: str = "",
397
+ object_key: str = "",
398
+ metadata: dict | None = None,
399
+ ) -> None:
400
+ """Upload byte data to TOS bucket
401
+
402
+ Args:
403
+ data: Byte data to upload
404
+ bucket_name: TOS bucket name
405
+ object_key: Object key, auto-generated if None
406
+ metadata: Metadata to associate with the object
407
+ """
408
+ bucket_name = self._check_bucket_name(bucket_name)
409
+ if not object_key:
410
+ object_key = self._build_object_key_for_bytes()
411
+ # Use common function to check client and bucket
412
+ if not self._ensure_client_and_bucket(bucket_name):
413
+ return
172
414
  try:
173
- if not self._client:
174
- return
175
- if not self.create_bucket():
176
- return
177
415
  self._client.put_object(
178
- bucket=self.bucket_name, key=object_key, content=data
416
+ bucket=bucket_name, key=object_key, content=data, meta=metadata
179
417
  )
180
- logger.debug(f"Upload success, url: {object_key}")
181
- self._close()
418
+ logger.debug(f"Upload success, object_key: {object_key}")
182
419
  return
183
420
  except Exception as e:
184
421
  logger.error(f"Upload failed: {e}")
185
- self._close()
186
422
  return
187
423
 
188
- def _do_upload_file(self, object_key: str, file_path: str) -> None:
424
+ async def async_upload_bytes(
425
+ self,
426
+ data: bytes,
427
+ bucket_name: str = "",
428
+ object_key: str = "",
429
+ metadata: dict | None = None,
430
+ ) -> None:
431
+ """Asynchronously upload byte data to TOS bucket
432
+
433
+ Args:
434
+ data: Byte data to upload
435
+ bucket_name: TOS bucket name
436
+ object_key: Object key, auto-generated if None
437
+ metadata: Metadata to associate with the object
438
+ """
439
+ bucket_name = self._check_bucket_name(bucket_name)
440
+ if not object_key:
441
+ object_key = self._build_object_key_for_bytes()
442
+ # Use common function to check client and bucket
443
+ if not self._ensure_client_and_bucket(bucket_name):
444
+ return
445
+ try:
446
+ # Use asyncio.to_thread to execute blocking TOS operations in thread
447
+ await asyncio.to_thread(
448
+ self._client.put_object,
449
+ bucket=bucket_name,
450
+ key=object_key,
451
+ content=data,
452
+ meta=metadata,
453
+ )
454
+ logger.debug(f"Async upload success, object_key: {object_key}")
455
+ return
456
+ except Exception as e:
457
+ logger.error(f"Async upload failed: {e}")
458
+ return
459
+
460
+ def upload_file(
461
+ self,
462
+ file_path: str,
463
+ bucket_name: str = "",
464
+ object_key: str = "",
465
+ metadata: dict | None = None,
466
+ ) -> None:
467
+ """Upload file to TOS bucket
468
+
469
+ Args:
470
+ file_path: Local file path
471
+ bucket_name: TOS bucket name
472
+ object_key: Object key, auto-generated if None
473
+ metadata: Metadata to associate with the object
474
+ """
475
+ bucket_name = self._check_bucket_name(bucket_name)
476
+ if not object_key:
477
+ object_key = self._build_object_key_for_file(file_path)
478
+ # Use common function to check client and bucket
479
+ if not self._ensure_client_and_bucket(bucket_name):
480
+ return
189
481
  try:
190
- if not self._client:
191
- return
192
- if not self.create_bucket():
193
- return
194
482
  self._client.put_object_from_file(
195
- bucket=self.bucket_name, key=object_key, file_path=file_path
483
+ bucket=bucket_name, key=object_key, file_path=file_path, meta=metadata
196
484
  )
197
- self._close()
198
485
  logger.debug(f"Upload success, object_key: {object_key}")
199
486
  return
200
487
  except Exception as e:
201
488
  logger.error(f"Upload failed: {e}")
202
- self._close()
203
489
  return
204
490
 
205
- def download(self, object_key: str, save_path: str) -> bool:
206
- """download image from TOS"""
491
+ async def async_upload_file(
492
+ self,
493
+ file_path: str,
494
+ bucket_name: str = "",
495
+ object_key: str = "",
496
+ metadata: dict | None = None,
497
+ ) -> None:
498
+ """Asynchronously upload file to TOS bucket
499
+
500
+ Args:
501
+ file_path: Local file path
502
+ bucket_name: TOS bucket name
503
+ object_key: Object key, auto-generated if None
504
+ metadata: Metadata to associate with the object
505
+ """
506
+ bucket_name = self._check_bucket_name(bucket_name)
507
+ if not object_key:
508
+ object_key = self._build_object_key_for_file(file_path)
509
+ # Use common function to check client and bucket
510
+ if not self._ensure_client_and_bucket(bucket_name):
511
+ return
512
+ try:
513
+ # Use asyncio.to_thread to execute blocking TOS operations in thread
514
+ await asyncio.to_thread(
515
+ self._client.put_object_from_file,
516
+ bucket=bucket_name,
517
+ key=object_key,
518
+ file_path=file_path,
519
+ meta=metadata,
520
+ )
521
+ logger.debug(f"Async upload success, object_key: {object_key}")
522
+ return
523
+ except Exception as e:
524
+ logger.error(f"Async upload failed: {e}")
525
+ return
526
+
527
+ def upload_files(
528
+ self,
529
+ file_paths: List[str],
530
+ bucket_name: str = "",
531
+ object_keys: Optional[List[str]] = None,
532
+ metadata: dict | None = None,
533
+ ) -> None:
534
+ """Upload multiple files to TOS bucket
535
+
536
+ Args:
537
+ file_paths: List of local file paths
538
+ bucket_name: TOS bucket name
539
+ object_keys: List of object keys, auto-generated if empty or length mismatch
540
+ metadata: Metadata to associate with the object
541
+ """
542
+ bucket_name = self._check_bucket_name(bucket_name)
543
+
544
+ # If object_keys is None, create empty list
545
+ if object_keys is None:
546
+ object_keys = []
547
+
548
+ # If object_keys length doesn't match file_paths, generate object key for each file
549
+ if len(object_keys) != len(file_paths):
550
+ object_keys = []
551
+ for file_path in file_paths:
552
+ object_key = self._build_object_key_for_file(file_path)
553
+ object_keys.append(object_key)
554
+ logger.debug(f"Generated object keys: {object_keys}")
555
+
556
+ # Upload each file
557
+ try:
558
+ for file_path, object_key in zip(file_paths, object_keys):
559
+ # Note: upload_file method doesn't return value, we use exceptions to determine success
560
+ self.upload_file(file_path, bucket_name, object_key, metadata=metadata)
561
+ return
562
+ except Exception as e:
563
+ logger.error(f"Upload files failed: {str(e)}")
564
+ return
565
+
566
+ async def async_upload_files(
567
+ self,
568
+ file_paths: List[str],
569
+ bucket_name: str = "",
570
+ object_keys: Optional[List[str]] = None,
571
+ metadata: dict | None = None,
572
+ ) -> None:
573
+ """Asynchronously upload multiple files to TOS bucket
574
+
575
+ Args:
576
+ file_paths: List of local file paths
577
+ bucket_name: TOS bucket name
578
+ object_keys: List of object keys, auto-generated if empty or length mismatch
579
+ metadata: Metadata to associate with the object
580
+ """
581
+ bucket_name = self._check_bucket_name(bucket_name)
582
+
583
+ # If object_keys is None, create empty list
584
+ if object_keys is None:
585
+ object_keys = []
586
+
587
+ # If object_keys length doesn't match file_paths, generate object key for each file
588
+ if len(object_keys) != len(file_paths):
589
+ object_keys = []
590
+ for file_path in file_paths:
591
+ object_key = self._build_object_key_for_file(file_path)
592
+ object_keys.append(object_key)
593
+ logger.debug(f"Generated object keys: {object_keys}")
594
+
595
+ # Upload each file
596
+ try:
597
+ for file_path, object_key in zip(file_paths, object_keys):
598
+ # Use asyncio.to_thread to execute blocking TOS operations in thread
599
+ await asyncio.to_thread(
600
+ self._client.put_object_from_file,
601
+ bucket=bucket_name,
602
+ key=object_key,
603
+ file_path=file_path,
604
+ metadata=metadata,
605
+ )
606
+ logger.debug(f"Async upload success, object_key: {object_key}")
607
+ return
608
+ except Exception as e:
609
+ logger.error(f"Async upload files failed: {str(e)}")
610
+ return
611
+
612
+ def upload_directory(
613
+ self, directory_path: str, bucket_name: str = "", metadata: dict | None = None
614
+ ) -> None:
615
+ """Upload entire directory to TOS bucket
616
+
617
+ Args:
618
+ directory_path: Local directory path
619
+ bucket_name: TOS bucket name
620
+ metadata: Metadata to associate with the objects
621
+ """
622
+ bucket_name = self._check_bucket_name(bucket_name)
623
+
624
+ def _upload_dir(root_dir):
625
+ items = os.listdir(root_dir)
626
+ for item in items:
627
+ path = os.path.join(root_dir, item)
628
+ if os.path.isdir(path):
629
+ _upload_dir(path)
630
+ if os.path.isfile(path):
631
+ # Use relative path of file as object key
632
+ object_key = os.path.relpath(path, directory_path)
633
+ # upload_file method doesn't return value, use exceptions to determine success
634
+ self.upload_file(path, bucket_name, object_key, metadata=metadata)
635
+
636
+ try:
637
+ _upload_dir(directory_path)
638
+ logger.debug(f"Upload directory success: {directory_path}")
639
+ return
640
+ except Exception as e:
641
+ logger.error(f"Upload directory failed: {str(e)}")
642
+ raise
643
+
644
+ async def async_upload_directory(
645
+ self, directory_path: str, bucket_name: str = "", metadata: dict | None = None
646
+ ) -> None:
647
+ """Asynchronously upload entire directory to TOS bucket
648
+
649
+ Args:
650
+ directory_path: Local directory path
651
+ bucket_name: TOS bucket name
652
+ metadata: Metadata to associate with the objects
653
+ """
654
+ bucket_name = self._check_bucket_name(bucket_name)
655
+
656
+ async def _aupload_dir(root_dir):
657
+ items = os.listdir(root_dir)
658
+ for item in items:
659
+ path = os.path.join(root_dir, item)
660
+ if os.path.isdir(path):
661
+ await _aupload_dir(path)
662
+ if os.path.isfile(path):
663
+ # Use relative path of file as object key
664
+ object_key = os.path.relpath(path, directory_path)
665
+ # Asynchronously upload single file
666
+ await self.async_upload_file(
667
+ path, bucket_name, object_key, metadata=metadata
668
+ )
669
+
670
+ try:
671
+ await _aupload_dir(directory_path)
672
+ logger.debug(f"Async upload directory success: {directory_path}")
673
+ return
674
+ except Exception as e:
675
+ logger.error(f"Async upload directory failed: {str(e)}")
676
+ raise
677
+
678
+ def download(self, bucket_name: str, object_key: str, save_path: str) -> bool:
679
+ """download object from TOS"""
680
+ bucket_name = self._check_bucket_name(bucket_name)
681
+
207
682
  if not self._client:
208
683
  logger.error("TOS client is not initialized")
209
684
  return False
210
685
  try:
211
- object_stream = self._client.get_object(self.bucket_name, object_key)
686
+ object_stream = self._client.get_object(bucket_name, object_key)
212
687
 
213
688
  save_dir = os.path.dirname(save_path)
214
689
  if save_dir and not os.path.exists(save_dir):
@@ -223,9 +698,8 @@ class VeTOS:
223
698
 
224
699
  except Exception as e:
225
700
  logger.error(f"Image download failed: {str(e)}")
226
-
227
701
  return False
228
702
 
229
- def _close(self):
703
+ def close(self):
230
704
  if self._client:
231
705
  self._client.close()