scalebox-sdk 0.1.25__py3-none-any.whl → 1.0.1__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.
- scalebox/__init__.py +2 -2
- scalebox/api/__init__.py +3 -1
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- scalebox/api/client/models/connect_sandbox.py +59 -0
- scalebox/api/client/models/error.py +2 -2
- scalebox/api/client/models/listed_sandbox.py +19 -1
- scalebox/api/client/models/new_sandbox.py +10 -0
- scalebox/api/client/models/sandbox.py +13 -0
- scalebox/api/client/models/sandbox_detail.py +24 -0
- scalebox/cli.py +125 -125
- scalebox/client/aclient.py +57 -57
- scalebox/client/client.py +102 -102
- scalebox/code_interpreter/__init__.py +12 -12
- scalebox/code_interpreter/charts.py +230 -230
- scalebox/code_interpreter/constants.py +3 -3
- scalebox/code_interpreter/exceptions.py +13 -13
- scalebox/code_interpreter/models.py +485 -485
- scalebox/connection_config.py +34 -1
- scalebox/csx_connect/__init__.py +1 -1
- scalebox/csx_connect/client.py +485 -485
- scalebox/csx_desktop/main.py +651 -651
- scalebox/exceptions.py +83 -83
- scalebox/generated/api.py +61 -61
- scalebox/generated/api_pb2.py +203 -203
- scalebox/generated/api_pb2.pyi +956 -956
- scalebox/generated/api_pb2_connect.py +1407 -1407
- scalebox/generated/rpc.py +50 -50
- scalebox/sandbox/main.py +146 -139
- scalebox/sandbox/sandbox_api.py +105 -91
- scalebox/sandbox/signature.py +40 -40
- scalebox/sandbox/utils.py +34 -34
- scalebox/sandbox_async/main.py +226 -44
- scalebox/sandbox_async/sandbox_api.py +124 -3
- scalebox/sandbox_sync/main.py +205 -130
- scalebox/sandbox_sync/sandbox_api.py +119 -3
- scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
- scalebox/test/README.md +329 -329
- scalebox/test/bedrock_openai_adapter.py +67 -0
- scalebox/test/code_interpreter_test.py +34 -34
- scalebox/test/code_interpreter_test_sync.py +34 -34
- scalebox/test/run_stress_code_interpreter_sync.py +166 -0
- scalebox/test/simple_upload_example.py +123 -0
- scalebox/test/stabitiy_test.py +310 -0
- scalebox/test/test_browser_use.py +25 -0
- scalebox/test/test_browser_use_scalebox.py +61 -0
- scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
- scalebox/test/test_connect_pause_async.py +277 -0
- scalebox/test/test_connect_pause_sync.py +267 -0
- scalebox/test/test_desktop_sandbox_sf.py +117 -0
- scalebox/test/test_download_url.py +49 -0
- scalebox/test/test_sandbox_async_comprehensive.py +1 -1
- scalebox/test/test_sandbox_object_storage_example.py +146 -0
- scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
- scalebox/test/test_sf.py +137 -0
- scalebox/test/test_watch_dir_async.py +56 -0
- scalebox/test/testacreate.py +1 -1
- scalebox/test/testagetinfo.py +1 -1
- scalebox/test/testcomputeuse.py +243 -243
- scalebox/test/testsandbox_api.py +1 -3
- scalebox/test/testsandbox_sync.py +1 -1
- scalebox/test/upload_100mb_example.py +355 -0
- scalebox/utils/httpcoreclient.py +297 -297
- scalebox/utils/httpxclient.py +403 -403
- scalebox/version.py +2 -2
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +70 -53
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
使用 sandbox_sync.filesystem.filesystem.py 上传 100MB 本地文件的示例
|
|
4
|
+
|
|
5
|
+
这个示例演示了如何:
|
|
6
|
+
1. 创建一个 100MB 的本地测试文件
|
|
7
|
+
2. 连接到沙箱
|
|
8
|
+
3. 使用 Filesystem 类上传文件到沙箱
|
|
9
|
+
4. 验证上传结果
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
import logging
|
|
15
|
+
from io import BytesIO
|
|
16
|
+
from scalebox.sandbox_sync.main import Sandbox
|
|
17
|
+
from scalebox.connection_config import ConnectionConfig
|
|
18
|
+
|
|
19
|
+
# 配置日志
|
|
20
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
def create_100mb_test_file(file_path: str = "/home/test_100mb.bin") -> str:
|
|
24
|
+
"""
|
|
25
|
+
创建一个 100MB 的测试文件
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
file_path: 本地文件路径
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
创建的文件路径
|
|
32
|
+
"""
|
|
33
|
+
logger.info(f"开始创建 100MB 测试文件: {file_path}")
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
|
|
36
|
+
# 创建 100MB 的数据 (100 * 1024 * 1024 字节)
|
|
37
|
+
file_size = 1000 * 1024 * 1024 # 100MB
|
|
38
|
+
chunk_size = 1024 * 1024 # 1MB 块大小
|
|
39
|
+
|
|
40
|
+
with open(file_path, 'wb') as f:
|
|
41
|
+
written = 0
|
|
42
|
+
while written < file_size:
|
|
43
|
+
# 生成 1MB 的数据块,包含一些模式以便验证
|
|
44
|
+
chunk_data = bytes([(i + written) % 256 for i in range(chunk_size)])
|
|
45
|
+
f.write(chunk_data)
|
|
46
|
+
written += chunk_size
|
|
47
|
+
|
|
48
|
+
# 显示进度
|
|
49
|
+
if written % (10 * 1024 * 1024) == 0: # 每 10MB 显示一次
|
|
50
|
+
progress = (written / file_size) * 100
|
|
51
|
+
logger.info(f"创建进度: {progress:.1f}% ({written // (1024*1024)}MB)")
|
|
52
|
+
|
|
53
|
+
creation_time = time.time() - start_time
|
|
54
|
+
actual_size = os.path.getsize(file_path)
|
|
55
|
+
logger.info(f"文件创建完成: {actual_size / (1024*1024):.2f}MB, 耗时: {creation_time:.2f}秒")
|
|
56
|
+
|
|
57
|
+
return file_path
|
|
58
|
+
|
|
59
|
+
def upload_file_to_sandbox(local_file_path: str, sandbox_path: str = "/tmp/uploaded_100mb.bin"):
|
|
60
|
+
"""
|
|
61
|
+
将本地文件上传到沙箱
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
local_file_path: 本地文件路径
|
|
65
|
+
sandbox_path: 沙箱中的目标路径
|
|
66
|
+
"""
|
|
67
|
+
logger.info("开始连接沙箱...")
|
|
68
|
+
|
|
69
|
+
# 创建沙箱连接配置
|
|
70
|
+
sandbox = Sandbox.create(
|
|
71
|
+
timeout=3600,
|
|
72
|
+
# debug=True,
|
|
73
|
+
metadata={"test": "code_interpreter_validation"},
|
|
74
|
+
envs={"CI_TEST": "sync_test"},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# 检查沙箱是否运行
|
|
79
|
+
if not sandbox.is_running():
|
|
80
|
+
logger.error("沙箱未运行,无法上传文件")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
logger.info(f"沙箱连接成功,ID: {sandbox.sandbox_id}")
|
|
84
|
+
|
|
85
|
+
# 读取本地文件
|
|
86
|
+
logger.info(f"读取本地文件: {local_file_path}")
|
|
87
|
+
start_time = time.time()
|
|
88
|
+
|
|
89
|
+
with open(local_file_path, 'rb') as f:
|
|
90
|
+
file_data = f.read()
|
|
91
|
+
|
|
92
|
+
read_time = time.time() - start_time
|
|
93
|
+
logger.info(f"文件读取完成: {len(file_data) / (1024*1024):.2f}MB, 耗时: {read_time:.2f}秒")
|
|
94
|
+
|
|
95
|
+
# 上传文件到沙箱
|
|
96
|
+
logger.info(f"开始上传文件到沙箱: {sandbox_path}")
|
|
97
|
+
upload_start = time.time()
|
|
98
|
+
|
|
99
|
+
# 使用 filesystem.write 方法上传文件
|
|
100
|
+
result = sandbox.files.write(sandbox_path, file_data,"root",3600)
|
|
101
|
+
|
|
102
|
+
upload_time = time.time() - upload_start
|
|
103
|
+
logger.info(f"文件上传完成: {result.path}, 耗时: {upload_time:.2f}秒")
|
|
104
|
+
logger.info(f"上传速度: {len(file_data) / (1024*1024) / upload_time:.2f} MB/s")
|
|
105
|
+
|
|
106
|
+
# 验证上传结果
|
|
107
|
+
logger.info("验证上传结果...")
|
|
108
|
+
verify_start = time.time()
|
|
109
|
+
|
|
110
|
+
# 检查文件是否存在
|
|
111
|
+
if sandbox.files.exists(sandbox_path):
|
|
112
|
+
logger.info("✅ 文件在沙箱中存在")
|
|
113
|
+
|
|
114
|
+
# 获取文件信息
|
|
115
|
+
file_info = sandbox.files.get_info(sandbox_path)
|
|
116
|
+
logger.info(f"文件信息: {file_info}")
|
|
117
|
+
|
|
118
|
+
# 读取文件内容进行验证(只读取前1MB进行快速验证)
|
|
119
|
+
logger.info("验证文件内容...")
|
|
120
|
+
with open(local_file_path, 'rb') as f:
|
|
121
|
+
local_sample = f.read(1024 * 1024) # 读取前1MB
|
|
122
|
+
|
|
123
|
+
sandbox_sample = sandbox.files.read(sandbox_path, format="bytes")[:1024 * 1024]
|
|
124
|
+
|
|
125
|
+
if local_sample == sandbox_sample:
|
|
126
|
+
logger.info("✅ 文件内容验证成功")
|
|
127
|
+
else:
|
|
128
|
+
logger.error("❌ 文件内容验证失败")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
else:
|
|
132
|
+
logger.error("❌ 文件在沙箱中不存在")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
verify_time = time.time() - verify_start
|
|
136
|
+
total_time = time.time() - upload_start
|
|
137
|
+
logger.info(f"验证完成,耗时: {verify_time:.2f}秒")
|
|
138
|
+
logger.info(f"总上传时间: {total_time:.2f}秒")
|
|
139
|
+
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"上传过程中发生错误: {e}")
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
finally:
|
|
147
|
+
# 清理沙箱
|
|
148
|
+
try:
|
|
149
|
+
sandbox.kill()
|
|
150
|
+
logger.info("沙箱已清理")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.warning(f"清理沙箱时发生错误: {e}")
|
|
153
|
+
|
|
154
|
+
def upload_file_streaming(local_file_path: str, sandbox_path: str = "/tmp/uploaded_100mb_streaming.bin"):
|
|
155
|
+
"""
|
|
156
|
+
使用真正的流式方式上传文件(不将整个文件读入内存)。
|
|
157
|
+
|
|
158
|
+
通过沙箱已配置的 HTTP 客户端发起 multipart/form-data 请求,直接传入文件对象,
|
|
159
|
+
由 HTTPX 以流的方式读取并上传内容。
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
local_file_path: 本地文件路径
|
|
163
|
+
sandbox_path: 沙箱中的目标路径
|
|
164
|
+
"""
|
|
165
|
+
logger.info("开始流式上传文件...")
|
|
166
|
+
|
|
167
|
+
sandbox = Sandbox.create(
|
|
168
|
+
timeout=3600,
|
|
169
|
+
# debug=True,
|
|
170
|
+
metadata={"test": "code_interpreter_validation"},
|
|
171
|
+
envs={"CI_TEST": "sync_test"},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
if not sandbox.is_running():
|
|
176
|
+
logger.error("沙箱未运行,无法上传文件")
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
logger.info(f"沙箱连接成功,ID: {sandbox.sandbox_id}")
|
|
180
|
+
|
|
181
|
+
file_size = os.path.getsize(local_file_path)
|
|
182
|
+
logger.info(f"文件大小: {file_size / (1024*1024):.2f}MB")
|
|
183
|
+
|
|
184
|
+
start_time = time.time()
|
|
185
|
+
|
|
186
|
+
with open(local_file_path, "rb") as f:
|
|
187
|
+
sandbox.files.write(sandbox_path, f,"root",3600)
|
|
188
|
+
|
|
189
|
+
upload_time = time.time() - start_time
|
|
190
|
+
logger.info(f"流式上传完成,总耗时: {upload_time:.2f}秒")
|
|
191
|
+
logger.info(f"平均速度: {file_size / (1024*1024) / upload_time:.2f} MB/s")
|
|
192
|
+
|
|
193
|
+
# 验证文件是否存在
|
|
194
|
+
if sandbox.files.exists(sandbox_path):
|
|
195
|
+
info = sandbox.files.get_info(sandbox_path)
|
|
196
|
+
logger.info(f"✅ 文件已存在于沙箱: 路径={info.path}, 大小={info.size} 字节")
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
logger.error("❌ 文件在沙箱中不存在")
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"流式上传过程中发生错误: {e}")
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
finally:
|
|
207
|
+
try:
|
|
208
|
+
sandbox.kill()
|
|
209
|
+
logger.info("沙箱已清理")
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.warning(f"清理沙箱时发生错误: {e}")
|
|
212
|
+
|
|
213
|
+
def upload_file_chunked_32m(local_file_path: str, sandbox_path: str = "/tmp/uploaded_100mb_chunked_32m.bin"):
|
|
214
|
+
"""
|
|
215
|
+
使用客户端 32MB 分片方式上传:
|
|
216
|
+
- 本地读取 32MB 分片,逐片上传为临时文件 {sandbox_path}.partXXXXXX
|
|
217
|
+
- 在沙箱内通过 `cat` 合并为最终文件
|
|
218
|
+
- 合并成功后删除临时分片
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
local_file_path: 本地文件路径
|
|
222
|
+
sandbox_path: 沙箱中的目标路径(合并后的最终文件路径)
|
|
223
|
+
"""
|
|
224
|
+
logger.info("开始 32MB 分片上传...")
|
|
225
|
+
|
|
226
|
+
sandbox = Sandbox.create(
|
|
227
|
+
template="code-interpreter",
|
|
228
|
+
timeout=3600,
|
|
229
|
+
# debug=True,
|
|
230
|
+
metadata={"test": "code_interpreter_validation"},
|
|
231
|
+
envs={"CI_TEST": "sync_test"},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
if not sandbox.is_running():
|
|
236
|
+
logger.error("沙箱未运行,无法上传文件")
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
file_size = os.path.getsize(local_file_path)
|
|
240
|
+
logger.info(f"文件大小: {file_size / (1024*1024):.2f}MB")
|
|
241
|
+
|
|
242
|
+
chunk_size = 8 * 1024 * 1024 # 32MB 客户端缓存/分片大小
|
|
243
|
+
start_time = time.time()
|
|
244
|
+
|
|
245
|
+
part_index = 0
|
|
246
|
+
uploaded_parts = []
|
|
247
|
+
|
|
248
|
+
with open(local_file_path, "rb") as f:
|
|
249
|
+
while True:
|
|
250
|
+
chunk = f.read(chunk_size)
|
|
251
|
+
if not chunk:
|
|
252
|
+
break
|
|
253
|
+
|
|
254
|
+
part_name = f"{sandbox_path}.part{part_index:06d}"
|
|
255
|
+
t0 = time.time()
|
|
256
|
+
sandbox.files.write(part_name, chunk)
|
|
257
|
+
dt = time.time() - t0
|
|
258
|
+
uploaded_parts.append(part_name)
|
|
259
|
+
logger.info(
|
|
260
|
+
f"已上传分片 #{part_index} -> {part_name} 大小={(len(chunk)/(1024*1024)):.2f}MB, 用时={dt:.2f}s"
|
|
261
|
+
)
|
|
262
|
+
part_index += 1
|
|
263
|
+
|
|
264
|
+
if not uploaded_parts:
|
|
265
|
+
logger.error("未读取到任何分片,上传失败")
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
# 在沙箱中合并分片,确保按编号顺序连接
|
|
269
|
+
logger.info("开始在沙箱内合并分片...")
|
|
270
|
+
concat_cmd = (
|
|
271
|
+
f"bash -lc 'cat {' '.join(uploaded_parts)} > {sandbox_path}'"
|
|
272
|
+
)
|
|
273
|
+
proc = sandbox.commands.run(concat_cmd)
|
|
274
|
+
if proc.exit_code != 0:
|
|
275
|
+
logger.error(f"合并失败: exit={proc.exit_code}, stderr=\n{proc.stderr}")
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
# 校验合并后的文件大小
|
|
279
|
+
info = sandbox.files.get_info(sandbox_path)
|
|
280
|
+
if info.size != file_size:
|
|
281
|
+
logger.error(
|
|
282
|
+
f"合并后大小不一致: 期望={file_size}, 实际={info.size}"
|
|
283
|
+
)
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
# 删除分片
|
|
287
|
+
logger.info("删除沙箱内临时分片...")
|
|
288
|
+
rm_cmd = f"bash -lc 'rm -f {sandbox_path}.part*'"
|
|
289
|
+
rm_proc = sandbox.commands.run(rm_cmd)
|
|
290
|
+
if rm_proc.exit_code != 0:
|
|
291
|
+
logger.warning(f"删除分片警告: exit={rm_proc.exit_code}, stderr=\n{rm_proc.stderr}")
|
|
292
|
+
|
|
293
|
+
total_time = time.time() - start_time
|
|
294
|
+
logger.info(
|
|
295
|
+
f"✅ 32MB 分片上传完成,总耗时={total_time:.2f}s,平均速度={(file_size/(1024*1024))/total_time:.2f} MB/s"
|
|
296
|
+
)
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"32MB 分片上传过程中发生错误: {e}")
|
|
301
|
+
return False
|
|
302
|
+
finally:
|
|
303
|
+
try:
|
|
304
|
+
sandbox.kill()
|
|
305
|
+
logger.info("沙箱已清理")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.warning(f"清理沙箱时发生错误: {e}")
|
|
308
|
+
|
|
309
|
+
def main():
|
|
310
|
+
"""主函数"""
|
|
311
|
+
logger.info("=== 100MB 文件上传示例 ===")
|
|
312
|
+
|
|
313
|
+
# 1. 创建 100MB 测试文件
|
|
314
|
+
local_file = create_100mb_test_file()
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
# 2. 方式一:直接上传整个文件
|
|
318
|
+
logger.info("\n=== 方式一:直接上传 ===")
|
|
319
|
+
success1 = upload_file_to_sandbox(local_file, "/tmp/uploaded_100mb_direct.bin")
|
|
320
|
+
|
|
321
|
+
if success1:
|
|
322
|
+
logger.info("✅ 直接上传成功")
|
|
323
|
+
else:
|
|
324
|
+
logger.error("❌ 直接上传失败")
|
|
325
|
+
|
|
326
|
+
# 3. 方式二:流式分块上传
|
|
327
|
+
logger.info("\n=== 方式二:流式上传 ===")
|
|
328
|
+
success2 = upload_file_streaming(local_file, "/home/uploaded_100mb_streaming.bin")
|
|
329
|
+
|
|
330
|
+
if success2:
|
|
331
|
+
logger.info("✅ 流式上传成功")
|
|
332
|
+
else:
|
|
333
|
+
logger.error("❌ 流式上传失败")
|
|
334
|
+
|
|
335
|
+
# 4. 方式三:32MB 分片上传(客户端分片 + 服务器端合并)
|
|
336
|
+
logger.info("\n=== 方式三:32MB 分片上传 ===")
|
|
337
|
+
success3 = upload_file_chunked_32m(local_file, "/tmp/uploaded_100mb_chunked_32m.bin")
|
|
338
|
+
|
|
339
|
+
if success3:
|
|
340
|
+
logger.info("✅ 32MB 分片上传成功")
|
|
341
|
+
else:
|
|
342
|
+
logger.error("❌ 32MB 分片上传失败")
|
|
343
|
+
|
|
344
|
+
finally:
|
|
345
|
+
# 4. 清理本地测试文件
|
|
346
|
+
try:
|
|
347
|
+
os.remove(local_file)
|
|
348
|
+
logger.info(f"本地测试文件已删除: {local_file}")
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.warning(f"删除本地文件时发生错误: {e}")
|
|
351
|
+
|
|
352
|
+
logger.info("=== 示例完成 ===")
|
|
353
|
+
|
|
354
|
+
if __name__ == "__main__":
|
|
355
|
+
main()
|