scalebox-sdk 0.1.25__py3-none-any.whl → 1.0.2__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 (78) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +3 -1
  3. scalebox/api/client/api/sandboxes/get_sandboxes.py +1 -1
  4. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  5. scalebox/api/client/models/connect_sandbox.py +59 -0
  6. scalebox/api/client/models/error.py +2 -2
  7. scalebox/api/client/models/listed_sandbox.py +24 -3
  8. scalebox/api/client/models/new_sandbox.py +10 -0
  9. scalebox/api/client/models/sandbox.py +13 -0
  10. scalebox/api/client/models/sandbox_detail.py +24 -0
  11. scalebox/cli.py +125 -125
  12. scalebox/client/aclient.py +57 -57
  13. scalebox/client/client.py +102 -102
  14. scalebox/code_interpreter/__init__.py +12 -12
  15. scalebox/code_interpreter/charts.py +230 -230
  16. scalebox/code_interpreter/code_interpreter_async.py +3 -1
  17. scalebox/code_interpreter/code_interpreter_sync.py +3 -1
  18. scalebox/code_interpreter/constants.py +3 -3
  19. scalebox/code_interpreter/exceptions.py +13 -13
  20. scalebox/code_interpreter/models.py +485 -485
  21. scalebox/connection_config.py +36 -1
  22. scalebox/csx_connect/__init__.py +1 -1
  23. scalebox/csx_connect/client.py +485 -485
  24. scalebox/csx_desktop/main.py +651 -651
  25. scalebox/exceptions.py +83 -83
  26. scalebox/generated/api.py +61 -61
  27. scalebox/generated/api_pb2.py +203 -203
  28. scalebox/generated/api_pb2.pyi +956 -956
  29. scalebox/generated/api_pb2_connect.py +1407 -1407
  30. scalebox/generated/rpc.py +50 -50
  31. scalebox/sandbox/main.py +146 -139
  32. scalebox/sandbox/sandbox_api.py +105 -91
  33. scalebox/sandbox/signature.py +40 -40
  34. scalebox/sandbox/utils.py +34 -34
  35. scalebox/sandbox_async/main.py +226 -44
  36. scalebox/sandbox_async/sandbox_api.py +124 -3
  37. scalebox/sandbox_sync/main.py +205 -130
  38. scalebox/sandbox_sync/sandbox_api.py +119 -3
  39. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  40. scalebox/test/README.md +329 -329
  41. scalebox/test/bedrock_openai_adapter.py +73 -0
  42. scalebox/test/code_interpreter_test.py +34 -34
  43. scalebox/test/code_interpreter_test_sync.py +34 -34
  44. scalebox/test/run_stress_code_interpreter_sync.py +178 -0
  45. scalebox/test/simple_upload_example.py +131 -0
  46. scalebox/test/stabitiy_test.py +323 -0
  47. scalebox/test/test_browser_use.py +27 -0
  48. scalebox/test/test_browser_use_scalebox.py +62 -0
  49. scalebox/test/test_code_interpreter_execcode.py +289 -211
  50. scalebox/test/test_code_interpreter_sync_comprehensive.py +116 -69
  51. scalebox/test/test_connect_pause_async.py +300 -0
  52. scalebox/test/test_connect_pause_sync.py +300 -0
  53. scalebox/test/test_csx_desktop_examples.py +3 -3
  54. scalebox/test/test_desktop_sandbox_sf.py +112 -0
  55. scalebox/test/test_download_url.py +41 -0
  56. scalebox/test/test_existing_sandbox.py +1037 -0
  57. scalebox/test/test_sandbox_async_comprehensive.py +5 -3
  58. scalebox/test/test_sandbox_object_storage_example.py +151 -0
  59. scalebox/test/test_sandbox_object_storage_example_async.py +159 -0
  60. scalebox/test/test_sandbox_sync_comprehensive.py +1 -1
  61. scalebox/test/test_sf.py +141 -0
  62. scalebox/test/test_watch_dir_async.py +58 -0
  63. scalebox/test/testacreate.py +1 -1
  64. scalebox/test/testagetinfo.py +1 -3
  65. scalebox/test/testcomputeuse.py +243 -243
  66. scalebox/test/testsandbox_api.py +5 -5
  67. scalebox/test/testsandbox_async.py +17 -47
  68. scalebox/test/testsandbox_sync.py +19 -15
  69. scalebox/test/upload_100mb_example.py +377 -0
  70. scalebox/utils/httpcoreclient.py +297 -297
  71. scalebox/utils/httpxclient.py +403 -403
  72. scalebox/version.py +2 -2
  73. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/METADATA +1 -1
  74. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/RECORD +78 -60
  75. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/WHEEL +1 -1
  76. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/entry_points.txt +0 -0
  77. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/licenses/LICENSE +0 -0
  78. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test cases for connect and pause functionality (synchronous version).
4
+
5
+ This test suite validates:
6
+ - Creating a sandbox
7
+ - Adding and deleting files before pause
8
+ - Pausing the sandbox
9
+ - Connecting to the paused sandbox
10
+ - Verifying file operations after connect
11
+ """
12
+
13
+ import time
14
+
15
+ from scalebox.sandbox.filesystem.filesystem import WriteInfo, FileType
16
+ from scalebox.sandbox_sync.main import Sandbox
17
+
18
+
19
+ class ConnectPauseSyncTest:
20
+ """Test suite for connect and pause functionality (sync)."""
21
+
22
+ def __init__(self):
23
+ self.sandbox = None
24
+ self.sandbox_id = None
25
+ self.test_results = []
26
+
27
+ def log_test_result(self, test_name: str, success: bool, message: str = ""):
28
+ """记录测试结果"""
29
+ status = "✅ PASS" if success else "❌ FAIL"
30
+ result = {
31
+ "test": test_name,
32
+ "success": success,
33
+ "message": message,
34
+ }
35
+ self.test_results.append(result)
36
+ print(f"{status} {test_name}: {message}")
37
+
38
+ def test_connect_pause_with_files(self):
39
+ """测试connect和pause功能,包含文件操作"""
40
+ try:
41
+ # 1. 创建沙箱
42
+ print("Step 1: Creating sandbox...")
43
+ self.sandbox = Sandbox.create(
44
+ template="code-interpreter",
45
+ timeout=3600,
46
+ metadata={"test": "connect_pause_sync"},
47
+ envs={"TEST_ENV": "sync_test"},
48
+ )
49
+ self.sandbox_id = self.sandbox.sandbox_id
50
+ assert self.sandbox is not None
51
+ assert self.sandbox_id is not None
52
+ print(f"Created sandbox with ID: {self.sandbox_id}")
53
+
54
+ # 2. 在pause之前增加文件
55
+ print("Step 2: Adding files before pause...")
56
+ test_files = {
57
+ "/tmp/test_file1.txt": "Content of test file 1",
58
+ "/tmp/test_file2.txt": "Content of test file 2",
59
+ "/tmp/test_file3.bin": b"Binary content for test file 3",
60
+ }
61
+
62
+ for file_path, content in test_files.items():
63
+ result = self.sandbox.files.write(file_path, content)
64
+ print(result)
65
+ assert isinstance(result, WriteInfo)
66
+ assert self.sandbox.files.exists(file_path)
67
+ print(f"Created file: {file_path}")
68
+
69
+ # 3. 在pause之前删除一个文件
70
+ print("Step 3: Deleting a file before pause...")
71
+ file_to_delete = "/tmp/test_file2.txt"
72
+ self.sandbox.files.remove(file_to_delete)
73
+ assert not self.sandbox.files.exists(file_to_delete)
74
+ print(f"Deleted file: {file_to_delete}")
75
+
76
+ # 4. 创建目录和文件
77
+ print("Step 4: Creating directory and nested files...")
78
+ self.sandbox.files.make_dir("/tmp/test_dir")
79
+ self.sandbox.files.write(
80
+ "/tmp/test_dir/nested_file.txt", "Nested file content"
81
+ )
82
+ assert self.sandbox.files.exists("/tmp/test_dir")
83
+ assert self.sandbox.files.exists("/tmp/test_dir/nested_file.txt")
84
+ print("Created directory and nested file")
85
+
86
+ # 5. Pause沙箱
87
+ print("Step 5: Pausing sandbox...")
88
+ self.sandbox.beta_pause()
89
+ print("Sandbox paused successfully")
90
+
91
+ # 等待一小段时间确保pause完成
92
+ time.sleep(1)
93
+
94
+ # 6. Connect到暂停的沙箱
95
+ print("Step 6: Connecting to paused sandbox...")
96
+ connected_sandbox = Sandbox.connect(self.sandbox_id)
97
+ print(connected_sandbox)
98
+ assert connected_sandbox.sandbox_id == self.sandbox_id
99
+ print("Connected to sandbox successfully")
100
+
101
+ # 等待一小段时间确保connect完成
102
+ time.sleep(2)
103
+
104
+ # 7. 在connect之后校验文件操作结果
105
+ print("Step 7: Verifying file operations after connect...")
106
+
107
+ # 校验应该存在的文件
108
+ assert connected_sandbox.files.exists(
109
+ "/tmp/test_file1.txt"
110
+ ), "test_file1.txt should exist"
111
+ file1_content = connected_sandbox.files.read(
112
+ "/tmp/test_file1.txt", format="text"
113
+ )
114
+ assert "Content of test file 1" in file1_content
115
+ print("✅ Verified test_file1.txt exists and content is correct")
116
+
117
+ assert connected_sandbox.files.exists(
118
+ "/tmp/test_file3.bin"
119
+ ), "test_file3.bin should exist"
120
+ file3_content = connected_sandbox.files.read(
121
+ "/tmp/test_file3.bin", format="bytes"
122
+ )
123
+ assert bytes(file3_content) == b"Binary content for test file 3"
124
+ print("✅ Verified test_file3.bin exists and content is correct")
125
+
126
+ # 校验应该不存在的文件(被删除的)
127
+ assert not connected_sandbox.files.exists(
128
+ "/tmp/test_file2.txt"
129
+ ), "test_file2.txt should not exist"
130
+ print("✅ Verified test_file2.txt was deleted correctly")
131
+
132
+ # 校验目录和嵌套文件
133
+ assert connected_sandbox.files.exists(
134
+ "/tmp/test_dir"
135
+ ), "test_dir should exist"
136
+ assert connected_sandbox.files.exists(
137
+ "/tmp/test_dir/nested_file.txt"
138
+ ), "nested_file.txt should exist"
139
+ nested_content = connected_sandbox.files.read(
140
+ "/tmp/test_dir/nested_file.txt", format="text"
141
+ )
142
+ assert "Nested file content" in nested_content
143
+ print("✅ Verified directory and nested file exist and content is correct")
144
+
145
+ # 8. 在connect后的沙箱中执行一些操作验证功能正常
146
+ print("Step 8: Verifying sandbox functionality after connect...")
147
+ result = connected_sandbox.commands.run("echo 'Test command after connect'")
148
+ assert result.exit_code == 0
149
+ assert "Test command after connect" in result.stdout
150
+ print("✅ Verified sandbox commands work after connect")
151
+
152
+ # 9. 创建新文件验证写入功能正常
153
+ print("Step 9: Testing file write after connect...")
154
+ new_file_path = "/tmp/new_file_after_connect.txt"
155
+ connected_sandbox.files.write(
156
+ new_file_path, "New file created after connect"
157
+ )
158
+ assert connected_sandbox.files.exists(new_file_path)
159
+ new_content = connected_sandbox.files.read(new_file_path, format="text")
160
+ assert "New file created after connect" in new_content
161
+ print("✅ Verified file write works after connect")
162
+
163
+ self.log_test_result(
164
+ "Connect and Pause with Files", True, "All verifications passed"
165
+ )
166
+ return True
167
+
168
+ except Exception as e:
169
+ self.log_test_result("Connect and Pause with Files", False, str(e))
170
+ print(f"Test failed with error: {e}")
171
+ import traceback
172
+
173
+ traceback.print_exc()
174
+ return False
175
+
176
+ def test_connect_pause_multiple_operations(self):
177
+ """测试多次pause和connect操作"""
178
+ try:
179
+ # 1. 创建沙箱
180
+ print("Test 2: Creating sandbox for multiple operations test...")
181
+ sandbox = Sandbox.create(
182
+ template="base",
183
+ timeout=3600,
184
+ metadata={"test": "connect_pause_multiple_sync"},
185
+ )
186
+ sandbox_id = sandbox.sandbox_id
187
+ print(f"Created sandbox with ID: {sandbox_id}")
188
+
189
+ # 2. 第一次操作:创建文件
190
+ print("First operation: Creating files...")
191
+ sandbox.files.write("/tmp/multi_test1.txt", "First batch")
192
+ sandbox.files.write("/tmp/multi_test2.txt", "First batch")
193
+
194
+ # 3. 第一次pause
195
+ print("First pause...")
196
+ sandbox.beta_pause()
197
+ time.sleep(2)
198
+
199
+ # 4. 第一次connect
200
+ print("First connect...")
201
+ sandbox = Sandbox.connect(sandbox_id)
202
+ time.sleep(2)
203
+
204
+ # 5. 验证第一次的文件存在
205
+ assert sandbox.files.exists("/tmp/multi_test1.txt")
206
+ assert sandbox.files.exists("/tmp/multi_test2.txt")
207
+
208
+ # 6. 第二次操作:删除一个文件,创建新文件
209
+ print("Second operation: Deleting and creating files...")
210
+ sandbox.files.remove("/tmp/multi_test1.txt")
211
+ sandbox.files.write("/tmp/multi_test3.txt", "Second batch")
212
+
213
+ # 7. 第二次pause
214
+ print("Second pause...")
215
+ sandbox.beta_pause()
216
+ time.sleep(2)
217
+
218
+ # 8. 第二次connect
219
+ print("Second connect...")
220
+ sandbox = Sandbox.connect(sandbox_id)
221
+ time.sleep(2)
222
+
223
+ # 9. 验证第二次操作的结果
224
+ assert not sandbox.files.exists(
225
+ "/tmp/multi_test1.txt"
226
+ ), "multi_test1.txt should be deleted"
227
+ assert sandbox.files.exists(
228
+ "/tmp/multi_test2.txt"
229
+ ), "multi_test2.txt should exist"
230
+ assert sandbox.files.exists(
231
+ "/tmp/multi_test3.txt"
232
+ ), "multi_test3.txt should exist"
233
+ content = sandbox.files.read("/tmp/multi_test3.txt", format="text")
234
+ assert "Second batch" in content
235
+
236
+ # 清理
237
+ sandbox.kill()
238
+
239
+ self.log_test_result(
240
+ "Multiple Connect and Pause Operations",
241
+ True,
242
+ "All verifications passed",
243
+ )
244
+ return True
245
+
246
+ except Exception as e:
247
+ self.log_test_result("Multiple Connect and Pause Operations", False, str(e))
248
+ print(f"Test failed with error: {e}")
249
+ import traceback
250
+
251
+ traceback.print_exc()
252
+ return False
253
+
254
+ def cleanup(self):
255
+ """清理资源"""
256
+ if self.sandbox:
257
+ try:
258
+ self.sandbox.kill()
259
+ print("Sandbox cleaned up successfully")
260
+ except Exception as e:
261
+ print(f"Error cleaning up sandbox: {e}")
262
+
263
+ def print_summary(self):
264
+ """打印测试摘要"""
265
+ total_tests = len(self.test_results)
266
+ passed_tests = sum(1 for r in self.test_results if r["success"])
267
+ failed_tests = total_tests - passed_tests
268
+
269
+ print("\n" + "=" * 60)
270
+ print("Connect and Pause Test Report (Sync)")
271
+ print("=" * 60)
272
+ print(f"总测试数: {total_tests}")
273
+ print(f"通过数: {passed_tests}")
274
+ print(f"失败数: {failed_tests}")
275
+ print(f"成功率: {(passed_tests/total_tests*100):.1f}%")
276
+
277
+ if failed_tests > 0:
278
+ print(f"\n失败的测试:")
279
+ for result in self.test_results:
280
+ if not result["success"]:
281
+ print(f" ❌ {result['test']}: {result['message']}")
282
+
283
+ print("=" * 60)
284
+
285
+
286
+ def main():
287
+ """主函数"""
288
+ test_suite = ConnectPauseSyncTest()
289
+
290
+ try:
291
+ # 运行测试
292
+ test_suite.test_connect_pause_with_files()
293
+ # test_suite.test_connect_pause_multiple_operations()
294
+ finally:
295
+ test_suite.cleanup()
296
+ test_suite.print_summary()
297
+
298
+
299
+ if __name__ == "__main__":
300
+ main()
@@ -18,7 +18,9 @@ class CsxDesktopValidator:
18
18
  self.success_count = 0
19
19
  self.fail_count = 0
20
20
 
21
- def log_test_result(self, name: str, success: bool, error: Optional[Exception] = None) -> None:
21
+ def log_test_result(
22
+ self, name: str, success: bool, error: Optional[Exception] = None
23
+ ) -> None:
22
24
  if success:
23
25
  self.success_count += 1
24
26
  print(f"✅ {name}")
@@ -126,5 +128,3 @@ if __name__ == "__main__":
126
128
  finally:
127
129
  validator.cleanup()
128
130
  validator.print_summary()
129
-
130
-
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 测试用例:基于desktop创建sandbox,上传并执行test_sf.py
4
+ """
5
+
6
+ import os
7
+ import re
8
+ import sys
9
+ import time
10
+
11
+ # 确保可以从项目根导入 scalebox
12
+ from scalebox.csx_desktop.main import Sandbox
13
+
14
+
15
+ def main():
16
+ """主测试函数"""
17
+ print("=" * 60)
18
+ print("开始测试:基于desktop创建sandbox并执行test_sf.py")
19
+ print("=" * 60)
20
+
21
+ # 1. 基于desktop创建sandbox
22
+ print("\n[步骤 1] 正在创建desktop sandbox...")
23
+ sandbox = Sandbox.create(
24
+ template="browser-use",
25
+ timeout=3600, # 1小时超时
26
+ )
27
+ print(f"✓ Sandbox创建成功,ID: {sandbox.sandbox_id}")
28
+
29
+ # 2. 获取network_proxy
30
+ print("\n[步骤 2] 获取network_proxy...")
31
+ network_proxy = sandbox.network_proxy()
32
+ print(f"✓ Network Proxy: {network_proxy}")
33
+
34
+ # 3. 获取VNC URL(不启动,直接获取)
35
+ print("\n[步骤 3] 获取VNC URL...")
36
+ vnc_url = sandbox.stream.get_url(auto_connect=True)
37
+ print(f"✓ VNC URL: {vnc_url}")
38
+
39
+ # 4. 读取test_sf.py并修改username和password
40
+ print("\n[步骤 4] 读取并修改test_sf.py...")
41
+ test_sf_path = os.path.join(os.path.dirname(__file__), "test_sf.py")
42
+
43
+ with open(test_sf_path, "r", encoding="utf-8") as f:
44
+ test_sf_content = f.read()
45
+
46
+ # 从network_proxy中提取username和password
47
+ if network_proxy and isinstance(network_proxy, dict):
48
+ # network_proxy结构: {'proxy_configs': {'username': '...', 'password': '...', ...}, ...}
49
+ proxy_configs = network_proxy.get("proxy_configs", {})
50
+ if isinstance(proxy_configs, dict):
51
+ proxy_username = proxy_configs.get("username", "")
52
+ proxy_password = proxy_configs.get("password", "")
53
+ else:
54
+ # 兼容旧格式:直接在network_proxy中
55
+ proxy_username = network_proxy.get("username", "")
56
+ proxy_password = network_proxy.get("password", "")
57
+
58
+ if not proxy_username or not proxy_password:
59
+ print("⚠ 警告: network_proxy中缺少username或password字段")
60
+ print(f" network_proxy内容: {network_proxy}")
61
+ else:
62
+ # 替换test_sf.py中的username和password
63
+ # 匹配proxy字典中的username和password(处理可能的引号差异)
64
+ pattern_username = r'"username"\s*:\s*"[^"]*"'
65
+ pattern_password = r'"password"\s*:\s*"[^"]*"'
66
+
67
+ test_sf_content = re.sub(
68
+ pattern_username, f'"username": "{proxy_username}"', test_sf_content
69
+ )
70
+ test_sf_content = re.sub(
71
+ pattern_password, f'"password": "{proxy_password}"', test_sf_content
72
+ )
73
+
74
+ print(f"✓ 已更新username: {proxy_username}")
75
+ print(f"✓ 已更新password: {proxy_password[:20]}...")
76
+ else:
77
+ print("⚠ 警告: network_proxy为空或格式不正确,跳过修改")
78
+ print(f" network_proxy类型: {type(network_proxy)}, 值: {network_proxy}")
79
+
80
+ # 5. 上传test_sf.py到sandbox的/目录
81
+ print("\n[步骤 5] 上传test_sf.py到sandbox的/目录...")
82
+ result = sandbox.files.write("/test_sf.py", test_sf_content.encode("utf-8"))
83
+ print(f"✓ 文件上传成功: {result.path}")
84
+
85
+ # 6. 在sandbox中执行命令
86
+ print("\n[步骤 6] 在sandbox中执行: source /venv/bin/activate && python /test_sf.py")
87
+ try:
88
+ # 使用bash -c确保source命令和环境变量正确加载
89
+ result = sandbox.commands.run(
90
+ 'bash -c "source /venv/bin/activate && python /test_sf.py"',
91
+ timeout=600, # 10分钟超时
92
+ )
93
+ print(f"✓ 命令执行完成")
94
+ print(f"退出码: {result.exit_code}")
95
+ print(f"标准输出:\n{result.stdout}")
96
+ if result.stderr:
97
+ print(f"标准错误:\n{result.stderr}")
98
+ except Exception as e:
99
+ print(f"✗ 命令执行失败: {e}")
100
+ raise
101
+
102
+ print("\n" + "=" * 60)
103
+ print("测试完成!")
104
+ print("=" * 60)
105
+
106
+ # 清理(可选,注释掉以保留sandbox)
107
+ # sandbox.kill()
108
+ # print("Sandbox已清理")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()
@@ -0,0 +1,41 @@
1
+ from scalebox.code_interpreter import Sandbox
2
+ import json
3
+
4
+ # 1. 准备要上传的文件内容
5
+ print("正在准备文件内容...")
6
+ results_data = {
7
+ "experiment_id": "exp_001",
8
+ "status": "completed",
9
+ "metrics": {"accuracy": 0.952, "precision": 0.94, "recall": 0.96},
10
+ "timestamp": "2024-01-01T12:00:00Z",
11
+ "details": [
12
+ {"epoch": 1, "loss": 0.45},
13
+ {"epoch": 2, "loss": 0.32},
14
+ {"epoch": 3, "loss": 0.18},
15
+ ],
16
+ }
17
+
18
+ # 转换为 JSON 字符串
19
+ json_content = json.dumps(results_data, indent=2)
20
+
21
+ # 2. 创建沙箱并写入文件
22
+ print("\n正在创建沙箱并写入文件...")
23
+ sandbox = Sandbox.create(timeout=1800)
24
+ remote_path = "/home/user/results.json"
25
+
26
+ # 关键修改:使用 sandbox.write() 写入文件
27
+ sandbox.files.write(remote_path, json_content)
28
+ print(f"✅ 文件已写入沙箱: {remote_path}")
29
+
30
+ # 3. 获取预签名下载 URL
31
+ print("\n正在生成下载链接...")
32
+ download_url = sandbox.download_url(
33
+ path=remote_path, use_signature_expiration=180 # 180秒有效期
34
+ )
35
+
36
+ print(f"\n📥 下载 URL: {download_url}")
37
+ print(f"⏰ 链接将在 3 分钟后过期")
38
+
39
+ upload_url = sandbox.upload_url(path="/home/user", use_signature_expiration=360)
40
+ print(f"\n📥 上传 URL: {upload_url}")
41
+ print(f"⏰ 链接将在 6 分钟后过期")