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