scalebox-sdk 0.1.24__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.
Files changed (87) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +130 -128
  3. scalebox/api/client/__init__.py +8 -8
  4. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
  5. scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
  6. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  7. scalebox/api/client/client.py +288 -288
  8. scalebox/api/client/models/connect_sandbox.py +59 -0
  9. scalebox/api/client/models/error.py +2 -2
  10. scalebox/api/client/models/listed_sandbox.py +19 -1
  11. scalebox/api/client/models/new_sandbox.py +10 -0
  12. scalebox/api/client/models/sandbox.py +138 -125
  13. scalebox/api/client/models/sandbox_detail.py +24 -0
  14. scalebox/api/client/types.py +46 -46
  15. scalebox/cli.py +125 -125
  16. scalebox/client/aclient.py +57 -57
  17. scalebox/client/client.py +102 -102
  18. scalebox/code_interpreter/__init__.py +12 -12
  19. scalebox/code_interpreter/charts.py +230 -230
  20. scalebox/code_interpreter/constants.py +3 -3
  21. scalebox/code_interpreter/exceptions.py +13 -13
  22. scalebox/code_interpreter/models.py +485 -485
  23. scalebox/connection_config.py +34 -1
  24. scalebox/csx_connect/__init__.py +1 -1
  25. scalebox/csx_connect/client.py +485 -485
  26. scalebox/csx_desktop/main.py +651 -651
  27. scalebox/exceptions.py +83 -83
  28. scalebox/generated/api.py +61 -61
  29. scalebox/generated/api_pb2.py +203 -203
  30. scalebox/generated/api_pb2.pyi +956 -956
  31. scalebox/generated/api_pb2_connect.py +1407 -1407
  32. scalebox/generated/rpc.py +50 -50
  33. scalebox/sandbox/main.py +146 -139
  34. scalebox/sandbox/sandbox_api.py +105 -91
  35. scalebox/sandbox/signature.py +40 -40
  36. scalebox/sandbox/utils.py +34 -34
  37. scalebox/sandbox_async/commands/command.py +307 -307
  38. scalebox/sandbox_async/commands/command_handle.py +187 -187
  39. scalebox/sandbox_async/commands/pty.py +187 -187
  40. scalebox/sandbox_async/filesystem/filesystem.py +557 -557
  41. scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
  42. scalebox/sandbox_async/main.py +228 -46
  43. scalebox/sandbox_async/sandbox_api.py +124 -3
  44. scalebox/sandbox_async/utils.py +7 -7
  45. scalebox/sandbox_sync/__init__.py +2 -2
  46. scalebox/sandbox_sync/commands/command.py +300 -300
  47. scalebox/sandbox_sync/commands/command_handle.py +150 -150
  48. scalebox/sandbox_sync/commands/pty.py +181 -181
  49. scalebox/sandbox_sync/filesystem/filesystem.py +3 -3
  50. scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
  51. scalebox/sandbox_sync/main.py +208 -133
  52. scalebox/sandbox_sync/sandbox_api.py +119 -3
  53. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  54. scalebox/test/README.md +329 -329
  55. scalebox/test/bedrock_openai_adapter.py +67 -0
  56. scalebox/test/code_interpreter_test.py +34 -34
  57. scalebox/test/code_interpreter_test_sync.py +34 -34
  58. scalebox/test/run_stress_code_interpreter_sync.py +166 -0
  59. scalebox/test/simple_upload_example.py +123 -0
  60. scalebox/test/stabitiy_test.py +310 -0
  61. scalebox/test/test_browser_use.py +25 -0
  62. scalebox/test/test_browser_use_scalebox.py +61 -0
  63. scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
  64. scalebox/test/test_connect_pause_async.py +277 -0
  65. scalebox/test/test_connect_pause_sync.py +267 -0
  66. scalebox/test/test_desktop_sandbox_sf.py +117 -0
  67. scalebox/test/test_download_url.py +49 -0
  68. scalebox/test/test_sandbox_async_comprehensive.py +1 -1
  69. scalebox/test/test_sandbox_object_storage_example.py +146 -0
  70. scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
  71. scalebox/test/test_sf.py +137 -0
  72. scalebox/test/test_watch_dir_async.py +56 -0
  73. scalebox/test/testacreate.py +1 -1
  74. scalebox/test/testagetinfo.py +1 -1
  75. scalebox/test/testcomputeuse.py +243 -243
  76. scalebox/test/testsandbox_api.py +13 -0
  77. scalebox/test/testsandbox_sync.py +1 -1
  78. scalebox/test/upload_100mb_example.py +355 -0
  79. scalebox/utils/httpcoreclient.py +297 -297
  80. scalebox/utils/httpxclient.py +403 -403
  81. scalebox/version.py +2 -2
  82. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
  83. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +87 -69
  84. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
  85. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
  86. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
  87. {scalebox_sdk-0.1.24.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()