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.
- scalebox/__init__.py +2 -2
- scalebox/api/__init__.py +3 -1
- scalebox/api/client/api/sandboxes/get_sandboxes.py +1 -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 +24 -3
- 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/code_interpreter_async.py +3 -1
- scalebox/code_interpreter/code_interpreter_sync.py +3 -1
- 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 +36 -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 +73 -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 +178 -0
- scalebox/test/simple_upload_example.py +131 -0
- scalebox/test/stabitiy_test.py +323 -0
- scalebox/test/test_browser_use.py +27 -0
- scalebox/test/test_browser_use_scalebox.py +62 -0
- scalebox/test/test_code_interpreter_execcode.py +289 -211
- scalebox/test/test_code_interpreter_sync_comprehensive.py +116 -69
- scalebox/test/test_connect_pause_async.py +300 -0
- scalebox/test/test_connect_pause_sync.py +300 -0
- scalebox/test/test_csx_desktop_examples.py +3 -3
- scalebox/test/test_desktop_sandbox_sf.py +112 -0
- scalebox/test/test_download_url.py +41 -0
- scalebox/test/test_existing_sandbox.py +1037 -0
- scalebox/test/test_sandbox_async_comprehensive.py +5 -3
- scalebox/test/test_sandbox_object_storage_example.py +151 -0
- scalebox/test/test_sandbox_object_storage_example_async.py +159 -0
- scalebox/test/test_sandbox_sync_comprehensive.py +1 -1
- scalebox/test/test_sf.py +141 -0
- scalebox/test/test_watch_dir_async.py +58 -0
- scalebox/test/testacreate.py +1 -1
- scalebox/test/testagetinfo.py +1 -3
- scalebox/test/testcomputeuse.py +243 -243
- scalebox/test/testsandbox_api.py +5 -5
- scalebox/test/testsandbox_async.py +17 -47
- scalebox/test/testsandbox_sync.py +19 -15
- scalebox/test/upload_100mb_example.py +377 -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.2.dist-info}/METADATA +1 -1
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/RECORD +78 -60
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/WHEEL +1 -1
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
OpenAI-compatible adapter for Amazon Bedrock Claude 3.7 Sonnet
|
|
4
|
+
POST /v1/chat/completions -> Bedrock invoke-model
|
|
5
|
+
"""
|
|
6
|
+
import json, boto3, os
|
|
7
|
+
from fastapi import FastAPI, Response, status
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from typing import List, Dict # <- 修正语法
|
|
10
|
+
|
|
11
|
+
app = FastAPI()
|
|
12
|
+
BEDROCK = boto3.client("bedrock-runtime", region_name="us-east-1")
|
|
13
|
+
MODEL_ID = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Message(BaseModel):
|
|
17
|
+
role: str
|
|
18
|
+
content: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ChatRequest(BaseModel):
|
|
22
|
+
model: str
|
|
23
|
+
messages: List[Message]
|
|
24
|
+
max_tokens: int = 4096
|
|
25
|
+
temperature: float = 0.1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.post("/v1/chat/completions")
|
|
29
|
+
def chat_completions(req: ChatRequest):
|
|
30
|
+
# 1. OpenAI -> Claude 格式
|
|
31
|
+
claude_body = {
|
|
32
|
+
"anthropic_version": "bedrock-2023-05-31",
|
|
33
|
+
"max_tokens": req.max_tokens,
|
|
34
|
+
"temperature": req.temperature,
|
|
35
|
+
"messages": [{"role": m.role, "content": m.content} for m in req.messages],
|
|
36
|
+
}
|
|
37
|
+
# 2. 调 Bedrock
|
|
38
|
+
response = BEDROCK.invoke_model(
|
|
39
|
+
modelId=MODEL_ID,
|
|
40
|
+
body=json.dumps(claude_body),
|
|
41
|
+
contentType="application/json",
|
|
42
|
+
)
|
|
43
|
+
# 3. 解析 Claude 响应
|
|
44
|
+
result = json.loads(response["body"].read())
|
|
45
|
+
text = result["content"][0]["text"]
|
|
46
|
+
|
|
47
|
+
# 4. 伪装成 OpenAI 格式返回
|
|
48
|
+
openai_reply = {
|
|
49
|
+
"id": "chatcmpl-bedrock",
|
|
50
|
+
"object": "chat.completion",
|
|
51
|
+
"created": 1234567890,
|
|
52
|
+
"model": "gpt-4",
|
|
53
|
+
"choices": [
|
|
54
|
+
{
|
|
55
|
+
"index": 0,
|
|
56
|
+
"message": {"role": "assistant", "content": text},
|
|
57
|
+
"finish_reason": "stop",
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
return Response(content=json.dumps(openai_reply), media_type="application/json")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 健康检查
|
|
65
|
+
@app.get("/v1/models")
|
|
66
|
+
def models():
|
|
67
|
+
return {"data": [{"id": "gpt-4", "object": "model"}]}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
import uvicorn
|
|
72
|
+
|
|
73
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from scalebox.code_interpreter import AsyncSandbox
|
|
5
|
-
|
|
6
|
-
# from scalebox.sandbox_async.main import AsyncSandbox
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
async def pty_output_handler(output):
|
|
10
|
-
"""处理 PTY 输出的回调函数"""
|
|
11
|
-
print(f"输出: {output}")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
async def main():
|
|
15
|
-
sandbox = AsyncSandbox.create()
|
|
16
|
-
proc = await sandbox.run_code(
|
|
17
|
-
"""
|
|
18
|
-
import time
|
|
19
|
-
for i in range(3):
|
|
20
|
-
print("Hello Scalebox", i)
|
|
21
|
-
time.sleep(50)
|
|
22
|
-
""",
|
|
23
|
-
language="python3",
|
|
24
|
-
request_timeout=3600,
|
|
25
|
-
on_stdout=pty_output_handler,
|
|
26
|
-
on_stderr=pty_output_handler,
|
|
27
|
-
on_result=pty_output_handler,
|
|
28
|
-
)
|
|
29
|
-
print(proc)
|
|
30
|
-
time.sleep(10)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if __name__ == "__main__":
|
|
34
|
-
asyncio.run(main())
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from scalebox.code_interpreter import AsyncSandbox
|
|
5
|
+
|
|
6
|
+
# from scalebox.sandbox_async.main import AsyncSandbox
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def pty_output_handler(output):
|
|
10
|
+
"""处理 PTY 输出的回调函数"""
|
|
11
|
+
print(f"输出: {output}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def main():
|
|
15
|
+
sandbox = AsyncSandbox.create()
|
|
16
|
+
proc = await sandbox.run_code(
|
|
17
|
+
"""
|
|
18
|
+
import time
|
|
19
|
+
for i in range(3):
|
|
20
|
+
print("Hello Scalebox", i)
|
|
21
|
+
time.sleep(50)
|
|
22
|
+
""",
|
|
23
|
+
language="python3",
|
|
24
|
+
request_timeout=3600,
|
|
25
|
+
on_stdout=pty_output_handler,
|
|
26
|
+
on_stderr=pty_output_handler,
|
|
27
|
+
on_result=pty_output_handler,
|
|
28
|
+
)
|
|
29
|
+
print(proc)
|
|
30
|
+
time.sleep(10)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
asyncio.run(main())
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from code_interpreter import Sandbox
|
|
5
|
-
|
|
6
|
-
# from scalebox.sandbox_async.main import AsyncSandbox
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def pty_output_handler(output):
|
|
10
|
-
"""处理 PTY 输出的回调函数"""
|
|
11
|
-
print(f"输出: {output}")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def main():
|
|
15
|
-
sandbox = Sandbox()
|
|
16
|
-
proc = sandbox.run_code(
|
|
17
|
-
"""
|
|
18
|
-
import time
|
|
19
|
-
for i in range(3):
|
|
20
|
-
print("Hello Scalebox", i)
|
|
21
|
-
time.sleep(50)
|
|
22
|
-
""",
|
|
23
|
-
language="python3",
|
|
24
|
-
request_timeout=3600,
|
|
25
|
-
on_stdout=pty_output_handler,
|
|
26
|
-
on_stderr=pty_output_handler,
|
|
27
|
-
on_result=pty_output_handler,
|
|
28
|
-
)
|
|
29
|
-
print(proc)
|
|
30
|
-
time.sleep(10)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if __name__ == "__main__":
|
|
34
|
-
main()
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from code_interpreter import Sandbox
|
|
5
|
+
|
|
6
|
+
# from scalebox.sandbox_async.main import AsyncSandbox
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pty_output_handler(output):
|
|
10
|
+
"""处理 PTY 输出的回调函数"""
|
|
11
|
+
print(f"输出: {output}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
sandbox = Sandbox()
|
|
16
|
+
proc = sandbox.run_code(
|
|
17
|
+
"""
|
|
18
|
+
import time
|
|
19
|
+
for i in range(3):
|
|
20
|
+
print("Hello Scalebox", i)
|
|
21
|
+
time.sleep(50)
|
|
22
|
+
""",
|
|
23
|
+
language="python3",
|
|
24
|
+
request_timeout=3600,
|
|
25
|
+
on_stdout=pty_output_handler,
|
|
26
|
+
on_stderr=pty_output_handler,
|
|
27
|
+
on_result=pty_output_handler,
|
|
28
|
+
)
|
|
29
|
+
print(proc)
|
|
30
|
+
time.sleep(10)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = "/root/scalebox"
|
|
11
|
+
TEST_FILE = os.path.join(
|
|
12
|
+
REPO_ROOT, "test", "test_code_interpreter_sync_comprehensive.py"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _extract_final_report(stdout_text: str) -> str:
|
|
17
|
+
lines = stdout_text.splitlines()
|
|
18
|
+
if not lines:
|
|
19
|
+
return ""
|
|
20
|
+
# Prefer capturing from the last report title to include full details
|
|
21
|
+
title_indices = [
|
|
22
|
+
i
|
|
23
|
+
for i, line in enumerate(lines)
|
|
24
|
+
if "CodeInterpreter" in line and "测试报告" in line
|
|
25
|
+
]
|
|
26
|
+
if title_indices:
|
|
27
|
+
start_idx = title_indices[-1]
|
|
28
|
+
return "\n".join(lines[start_idx:]).strip()
|
|
29
|
+
# Fallback: last long '====' separator block
|
|
30
|
+
sep_indices = [
|
|
31
|
+
i
|
|
32
|
+
for i, line in enumerate(lines)
|
|
33
|
+
if line.strip().startswith("=")
|
|
34
|
+
and set(line.strip()) == {"="}
|
|
35
|
+
and len(line.strip()) >= 20
|
|
36
|
+
]
|
|
37
|
+
if sep_indices:
|
|
38
|
+
start_idx = sep_indices[-1]
|
|
39
|
+
return "\n".join(lines[start_idx:]).strip()
|
|
40
|
+
return stdout_text.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _extract_sandbox_id(text: str) -> str:
|
|
44
|
+
# Common explicit patterns
|
|
45
|
+
patterns = [
|
|
46
|
+
r"sandbox[_\s-]*id\s*[:=]\s*([A-Za-z0-9_\-]+)",
|
|
47
|
+
r"Sandbox[_\s-]*ID\s*[:=]\s*([A-Za-z0-9_\-]+)",
|
|
48
|
+
r"sandbox\s*:\s*([A-Za-z0-9_\-]+)",
|
|
49
|
+
r"sandboxId\s*[:=]\s*([A-Za-z0-9_\-]+)",
|
|
50
|
+
]
|
|
51
|
+
for pat in patterns:
|
|
52
|
+
m = re.search(pat, text, flags=re.IGNORECASE)
|
|
53
|
+
if m:
|
|
54
|
+
return m.group(1)
|
|
55
|
+
# UUID-like fallback
|
|
56
|
+
uuid_pat = r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b"
|
|
57
|
+
m2 = re.search(uuid_pat, text)
|
|
58
|
+
if m2:
|
|
59
|
+
return m2.group(0)
|
|
60
|
+
# sbx_* style fallback
|
|
61
|
+
m3 = re.search(r"\bsbx_[A-Za-z0-9]+\b", text)
|
|
62
|
+
if m3:
|
|
63
|
+
return m3.group(0)
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def run_once(run_id: int) -> Tuple[int, float, str, str]:
|
|
68
|
+
start = time.perf_counter()
|
|
69
|
+
process = await asyncio.create_subprocess_exec(
|
|
70
|
+
sys.executable,
|
|
71
|
+
TEST_FILE,
|
|
72
|
+
cwd=REPO_ROOT,
|
|
73
|
+
stdout=asyncio.subprocess.PIPE,
|
|
74
|
+
stderr=asyncio.subprocess.PIPE,
|
|
75
|
+
)
|
|
76
|
+
stdout, stderr = await process.communicate()
|
|
77
|
+
duration = time.perf_counter() - start
|
|
78
|
+
exit_code = process.returncode
|
|
79
|
+
result = "PASS" if exit_code == 0 else "FAIL"
|
|
80
|
+
stdout_text = stdout.decode(errors="ignore") if stdout else ""
|
|
81
|
+
stderr_text = stderr.decode(errors="ignore") if stderr else ""
|
|
82
|
+
report = _extract_final_report(stdout_text) or _extract_final_report(stderr_text)
|
|
83
|
+
if not report:
|
|
84
|
+
# Fallback to a brief tail if no recognizable report
|
|
85
|
+
tail = (stdout_text or stderr_text).strip().splitlines()[-10:]
|
|
86
|
+
report = "\n".join(tail).strip()
|
|
87
|
+
# Determine if success rate is effectively 0%
|
|
88
|
+
combined_text = "\n".join([report, stdout_text, stderr_text])
|
|
89
|
+
zero_rate = False
|
|
90
|
+
# Match ASCII/full-width colon and percent, and allow 0, 0.0, 0.00 etc.
|
|
91
|
+
if re.search(r"成功率\s*[::]\s*0(?:\.0+)?\s*[%%]", combined_text):
|
|
92
|
+
zero_rate = True
|
|
93
|
+
else:
|
|
94
|
+
# Fallback by parsing totals if present (失败数 == 总测试数)
|
|
95
|
+
total_m = re.search(r"总测试数\s*[::]\s*(\d+)", combined_text)
|
|
96
|
+
fail_m = re.search(r"失败数\s*[::]\s*(\d+)", combined_text)
|
|
97
|
+
if total_m and fail_m:
|
|
98
|
+
try:
|
|
99
|
+
total_n = int(total_m.group(1))
|
|
100
|
+
fail_n = int(fail_m.group(1))
|
|
101
|
+
if total_n > 0 and fail_n == total_n:
|
|
102
|
+
zero_rate = True
|
|
103
|
+
except ValueError:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# If success rate is 0%, mark as FAIL and attach detailed errors
|
|
107
|
+
if zero_rate:
|
|
108
|
+
result = "FAIL"
|
|
109
|
+
stderr_lines = stderr_text.strip().splitlines()
|
|
110
|
+
stdout_lines = stdout_text.strip().splitlines()
|
|
111
|
+
# Extract sandbox id from combined text
|
|
112
|
+
sandbox_id = _extract_sandbox_id(combined_text)
|
|
113
|
+
detail = []
|
|
114
|
+
if sandbox_id:
|
|
115
|
+
detail.append(f"Sandbox ID: {sandbox_id}")
|
|
116
|
+
if stderr_lines:
|
|
117
|
+
detail.append("[stderr]")
|
|
118
|
+
detail.extend(stderr_lines)
|
|
119
|
+
if stdout_lines:
|
|
120
|
+
detail.append("[stdout]")
|
|
121
|
+
detail.extend(stdout_lines)
|
|
122
|
+
if detail:
|
|
123
|
+
report = f"{report}\n\n" + "\n".join(detail)
|
|
124
|
+
return run_id, duration, result, report
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def run_concurrent(concurrency: int) -> None:
|
|
128
|
+
if not os.path.isfile(TEST_FILE):
|
|
129
|
+
print(f"Test file not found: {TEST_FILE}")
|
|
130
|
+
sys.exit(2)
|
|
131
|
+
|
|
132
|
+
tasks = [asyncio.create_task(run_once(i + 1)) for i in range(concurrency)]
|
|
133
|
+
|
|
134
|
+
started_at = time.perf_counter()
|
|
135
|
+
completed = 0
|
|
136
|
+
passed = 0
|
|
137
|
+
failed = 0
|
|
138
|
+
|
|
139
|
+
for coro in asyncio.as_completed(tasks):
|
|
140
|
+
run_id, duration, result, report = await coro
|
|
141
|
+
completed += 1
|
|
142
|
+
if result == "PASS":
|
|
143
|
+
passed += 1
|
|
144
|
+
else:
|
|
145
|
+
failed += 1
|
|
146
|
+
print(f"Run {run_id:03d}: {result} in {duration:.2f}s", flush=True)
|
|
147
|
+
if report:
|
|
148
|
+
print(report, flush=True)
|
|
149
|
+
print("-" * 64, flush=True)
|
|
150
|
+
|
|
151
|
+
total_time = time.perf_counter() - started_at
|
|
152
|
+
print("-" * 64)
|
|
153
|
+
print(
|
|
154
|
+
f"Completed: {completed}, Passed: {passed}, Failed: {failed}, Total time: {total_time:.2f}s"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main() -> None:
|
|
159
|
+
# Default concurrency to 80, but allow override via CLI
|
|
160
|
+
try:
|
|
161
|
+
concurrency = int(sys.argv[1]) if len(sys.argv) > 1 else 80
|
|
162
|
+
except ValueError:
|
|
163
|
+
print("First argument must be an integer concurrency level.")
|
|
164
|
+
sys.exit(2)
|
|
165
|
+
|
|
166
|
+
if concurrency <= 0:
|
|
167
|
+
print("Concurrency must be a positive integer.")
|
|
168
|
+
sys.exit(2)
|
|
169
|
+
|
|
170
|
+
# Use Proactor on Windows; default loop elsewhere
|
|
171
|
+
if sys.platform.startswith("win"):
|
|
172
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) # type: ignore[attr-defined]
|
|
173
|
+
|
|
174
|
+
asyncio.run(run_concurrent(concurrency))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
main()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
简单的 100MB 文件上传示例
|
|
4
|
+
使用 sandbox_sync.filesystem.filesystem.py 上传文件到沙箱
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
import logging
|
|
10
|
+
from scalebox.sandbox_sync.main import Sandbox
|
|
11
|
+
from scalebox.connection_config import ConnectionConfig
|
|
12
|
+
|
|
13
|
+
# 配置日志
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
16
|
+
)
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_test_file(file_path: str, size_mb: int = 100):
|
|
21
|
+
"""创建指定大小的测试文件"""
|
|
22
|
+
logger.info(f"创建 {size_mb}MB 测试文件: {file_path}")
|
|
23
|
+
|
|
24
|
+
file_size = size_mb * 1024 * 1024 # 转换为字节
|
|
25
|
+
chunk_size = 1024 * 1024 # 1MB 块
|
|
26
|
+
|
|
27
|
+
with open(file_path, "wb") as f:
|
|
28
|
+
written = 0
|
|
29
|
+
while written < file_size:
|
|
30
|
+
# 生成测试数据
|
|
31
|
+
chunk_data = bytes(
|
|
32
|
+
[i % 256 for i in range(min(chunk_size, file_size - written))]
|
|
33
|
+
)
|
|
34
|
+
f.write(chunk_data)
|
|
35
|
+
written += len(chunk_data)
|
|
36
|
+
|
|
37
|
+
if written % (10 * 1024 * 1024) == 0: # 每10MB显示进度
|
|
38
|
+
logger.info(f"进度: {written // (1024*1024)}MB")
|
|
39
|
+
|
|
40
|
+
logger.info(f"文件创建完成: {os.path.getsize(file_path) / (1024*1024):.2f}MB")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
"""主函数 - 演示文件上传"""
|
|
45
|
+
|
|
46
|
+
# 1. 创建 100MB 测试文件
|
|
47
|
+
test_file = "/tmp/test_100mb.bin"
|
|
48
|
+
create_test_file(test_file, 100)
|
|
49
|
+
|
|
50
|
+
# 2. 创建沙箱连接配置
|
|
51
|
+
connection_config = ConnectionConfig(
|
|
52
|
+
debug=True, request_timeout=300.0 # 调试模式 # 5分钟超时
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# 3. 创建沙箱实例
|
|
56
|
+
logger.info("连接沙箱...")
|
|
57
|
+
sandbox = Sandbox(
|
|
58
|
+
sandbox_id="upload_test_sandbox",
|
|
59
|
+
sandbox_domain=None,
|
|
60
|
+
envd_version="v1.0",
|
|
61
|
+
envd_access_token=None,
|
|
62
|
+
connection_config=connection_config,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# 4. 检查沙箱状态
|
|
67
|
+
if not sandbox.is_running():
|
|
68
|
+
logger.error("沙箱未运行")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
logger.info(f"沙箱连接成功: {sandbox.sandbox_id}")
|
|
72
|
+
|
|
73
|
+
# 5. 读取本地文件
|
|
74
|
+
logger.info("读取本地文件...")
|
|
75
|
+
start_time = time.time()
|
|
76
|
+
|
|
77
|
+
with open(test_file, "rb") as f:
|
|
78
|
+
file_data = f.read()
|
|
79
|
+
|
|
80
|
+
read_time = time.time() - start_time
|
|
81
|
+
logger.info(
|
|
82
|
+
f"文件读取完成: {len(file_data) / (1024*1024):.2f}MB, 耗时: {read_time:.2f}秒"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 6. 上传文件到沙箱
|
|
86
|
+
logger.info("开始上传文件到沙箱...")
|
|
87
|
+
upload_start = time.time()
|
|
88
|
+
|
|
89
|
+
# 使用 filesystem.write 方法上传
|
|
90
|
+
result = sandbox.files.write("/tmp/uploaded_100mb.bin", file_data)
|
|
91
|
+
|
|
92
|
+
upload_time = time.time() - upload_start
|
|
93
|
+
logger.info(f"上传完成: {result.path}")
|
|
94
|
+
logger.info(f"上传耗时: {upload_time:.2f}秒")
|
|
95
|
+
logger.info(f"上传速度: {len(file_data) / (1024*1024) / upload_time:.2f} MB/s")
|
|
96
|
+
|
|
97
|
+
# 7. 验证上传结果
|
|
98
|
+
logger.info("验证上传结果...")
|
|
99
|
+
|
|
100
|
+
# 检查文件是否存在
|
|
101
|
+
if sandbox.files.exists("/tmp/uploaded_100mb.bin"):
|
|
102
|
+
logger.info("✅ 文件上传成功")
|
|
103
|
+
|
|
104
|
+
# 获取文件信息
|
|
105
|
+
file_info = sandbox.files.get_info("/tmp/uploaded_100mb.bin")
|
|
106
|
+
logger.info(f"文件信息: 路径={file_info.path}, 大小={file_info.size}字节")
|
|
107
|
+
|
|
108
|
+
else:
|
|
109
|
+
logger.error("❌ 文件上传失败")
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"上传过程中发生错误: {e}")
|
|
113
|
+
|
|
114
|
+
finally:
|
|
115
|
+
# 8. 清理资源
|
|
116
|
+
try:
|
|
117
|
+
sandbox.kill()
|
|
118
|
+
logger.info("沙箱已清理")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.warning(f"清理沙箱时发生错误: {e}")
|
|
121
|
+
|
|
122
|
+
# 删除本地测试文件
|
|
123
|
+
try:
|
|
124
|
+
os.remove(test_file)
|
|
125
|
+
logger.info("本地测试文件已删除")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.warning(f"删除本地文件时发生错误: {e}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
main()
|