python-sandbox-sdk 1.0.2__tar.gz → 1.0.3__tar.gz
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.
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/PKG-INFO +1 -1
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/pyproject.toml +1 -1
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk/async_client.py +15 -4
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk/client.py +92 -79
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/PKG-INFO +1 -1
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/setup.py +1 -1
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/README.md +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk/__init__.py +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk/dto.py +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/SOURCES.txt +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/dependency_links.txt +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/requires.txt +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/top_level.txt +0 -0
- {python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-sandbox-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers
|
|
5
5
|
Home-page: https://github.com/zjwan461/python-sandbox
|
|
6
6
|
Author-email: Jerry <826935261@qq.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-sandbox-sdk"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -85,6 +85,19 @@ class AsyncSandboxClient:
|
|
|
85
85
|
data = await resp.json()
|
|
86
86
|
return data["sessionId"]
|
|
87
87
|
|
|
88
|
+
async def create_default_session(self) -> str:
|
|
89
|
+
"""
|
|
90
|
+
创建新的沙箱会话
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
会话 ID
|
|
94
|
+
"""
|
|
95
|
+
session = await self._get_session()
|
|
96
|
+
async with session.post(f"{self.base_url}/api/sandbox/session-default") as resp:
|
|
97
|
+
await self._raise_if_error(resp)
|
|
98
|
+
data = await resp.json()
|
|
99
|
+
return data["sessionId"]
|
|
100
|
+
|
|
88
101
|
async def delete_session(self, session_id: str) -> None:
|
|
89
102
|
"""
|
|
90
103
|
删除会话并清理容器
|
|
@@ -167,9 +180,7 @@ class AsyncSandboxClient:
|
|
|
167
180
|
data["exitCode"], data.get("stdout", ""), data.get("stderr", "")
|
|
168
181
|
)
|
|
169
182
|
|
|
170
|
-
async def pip_uninstall(
|
|
171
|
-
self, session_id: str, package_name: str
|
|
172
|
-
) -> CommandResult:
|
|
183
|
+
async def pip_uninstall(self, session_id: str, package_name: str) -> CommandResult:
|
|
173
184
|
"""
|
|
174
185
|
卸载 Python 包
|
|
175
186
|
|
|
@@ -268,7 +279,7 @@ class AsyncSandboxClient:
|
|
|
268
279
|
import os
|
|
269
280
|
|
|
270
281
|
url = f"{self.base_url}/api/sandbox/file/upload"
|
|
271
|
-
|
|
282
|
+
|
|
272
283
|
# 使用 BytesIO 避免临时文件和阻塞 I/O
|
|
273
284
|
file_obj = io.BytesIO(data)
|
|
274
285
|
form = aiohttp.FormData()
|
|
@@ -20,16 +20,19 @@ from .dto import CommandResult
|
|
|
20
20
|
|
|
21
21
|
class SandboxError(Exception):
|
|
22
22
|
"""SDK 异常基类"""
|
|
23
|
+
|
|
23
24
|
pass
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class ApiKeyMissingError(SandboxError):
|
|
27
28
|
"""API Key 缺失错误"""
|
|
29
|
+
|
|
28
30
|
pass
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class ApiRequestError(SandboxError):
|
|
32
34
|
"""API 请求错误"""
|
|
35
|
+
|
|
33
36
|
def __init__(self, status_code: int, message: str):
|
|
34
37
|
self.status_code = status_code
|
|
35
38
|
super().__init__(f"HTTP {status_code}: {message}")
|
|
@@ -38,16 +41,16 @@ class ApiRequestError(SandboxError):
|
|
|
38
41
|
class SandboxClient:
|
|
39
42
|
"""
|
|
40
43
|
Python Sandbox 客户端
|
|
41
|
-
|
|
44
|
+
|
|
42
45
|
Attributes:
|
|
43
46
|
base_url: Sandbox API 基础 URL (如 http://localhost:8080)
|
|
44
47
|
api_key: API 认证密钥
|
|
45
48
|
"""
|
|
46
|
-
|
|
49
|
+
|
|
47
50
|
def __init__(self, base_url: str, api_key: str):
|
|
48
51
|
"""
|
|
49
52
|
创建客户端实例
|
|
50
|
-
|
|
53
|
+
|
|
51
54
|
Args:
|
|
52
55
|
base_url: Sandbox API 基础 URL
|
|
53
56
|
api_key: API 认证密钥
|
|
@@ -55,180 +58,188 @@ class SandboxClient:
|
|
|
55
58
|
self.base_url = base_url.rstrip("/")
|
|
56
59
|
self.api_key = api_key
|
|
57
60
|
self._session = requests.Session()
|
|
58
|
-
self._session.headers.update(
|
|
59
|
-
"X-Api-Key": api_key,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
self._session.headers.update(
|
|
62
|
+
{"X-Api-Key": api_key, "Content-Type": "application/json"}
|
|
63
|
+
)
|
|
64
|
+
|
|
63
65
|
# ==================== 会话管理 ====================
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
def create_session(self) -> str:
|
|
66
68
|
"""
|
|
67
69
|
创建新的沙箱会话
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
Returns:
|
|
70
72
|
会话 ID
|
|
71
73
|
"""
|
|
72
74
|
resp = self._session.post(f"{self.base_url}/api/sandbox/session")
|
|
73
75
|
self._raise_if_error(resp)
|
|
74
76
|
return resp.json()["sessionId"]
|
|
75
|
-
|
|
77
|
+
|
|
78
|
+
def create_default_session(self) -> str:
|
|
79
|
+
"""
|
|
80
|
+
创建默认沙箱会话
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
会话 ID
|
|
84
|
+
"""
|
|
85
|
+
resp = self._session.post(f"{self.base_url}/api/sandbox/session-default")
|
|
86
|
+
self._raise_if_error(resp)
|
|
87
|
+
return resp.json()["sessionId"]
|
|
88
|
+
|
|
76
89
|
def delete_session(self, session_id: str) -> None:
|
|
77
90
|
"""
|
|
78
91
|
删除会话并清理容器
|
|
79
|
-
|
|
92
|
+
|
|
80
93
|
Args:
|
|
81
94
|
session_id: 会话 ID
|
|
82
95
|
"""
|
|
83
96
|
url = f"{self.base_url}/api/sandbox/session/{session_id}"
|
|
84
97
|
resp = self._session.delete(url)
|
|
85
98
|
self._raise_if_error(resp)
|
|
86
|
-
|
|
99
|
+
|
|
87
100
|
# ==================== 代码执行 ====================
|
|
88
|
-
|
|
101
|
+
|
|
89
102
|
def exec_python(self, session_id: str, code: str) -> CommandResult:
|
|
90
103
|
"""
|
|
91
104
|
在沙箱中执行 Python 代码
|
|
92
|
-
|
|
105
|
+
|
|
93
106
|
Args:
|
|
94
107
|
session_id: 会话 ID
|
|
95
108
|
code: Python 源代码
|
|
96
|
-
|
|
109
|
+
|
|
97
110
|
Returns:
|
|
98
111
|
命令执行结果
|
|
99
112
|
"""
|
|
100
113
|
payload = {"sessionId": session_id, "code": code}
|
|
101
114
|
resp = self._session.post(
|
|
102
|
-
f"{self.base_url}/api/sandbox/exec/python",
|
|
103
|
-
json=payload
|
|
115
|
+
f"{self.base_url}/api/sandbox/exec/python", json=payload
|
|
104
116
|
)
|
|
105
117
|
self._raise_if_error(resp)
|
|
106
118
|
data = resp.json()
|
|
107
|
-
return CommandResult(
|
|
108
|
-
|
|
119
|
+
return CommandResult(
|
|
120
|
+
data["exitCode"], data.get("stdout", ""), data.get("stderr", "")
|
|
121
|
+
)
|
|
122
|
+
|
|
109
123
|
def exec_shell(self, session_id: str, command: str) -> CommandResult:
|
|
110
124
|
"""
|
|
111
125
|
在沙箱中执行 Shell 命令
|
|
112
|
-
|
|
126
|
+
|
|
113
127
|
Args:
|
|
114
128
|
session_id: 会话 ID
|
|
115
129
|
command: Shell 命令
|
|
116
|
-
|
|
130
|
+
|
|
117
131
|
Returns:
|
|
118
132
|
命令执行结果
|
|
119
133
|
"""
|
|
120
134
|
payload = {"sessionId": session_id, "command": command}
|
|
121
135
|
resp = self._session.post(
|
|
122
|
-
f"{self.base_url}/api/sandbox/exec/shell",
|
|
123
|
-
json=payload
|
|
136
|
+
f"{self.base_url}/api/sandbox/exec/shell", json=payload
|
|
124
137
|
)
|
|
125
138
|
self._raise_if_error(resp)
|
|
126
139
|
data = resp.json()
|
|
127
|
-
return CommandResult(
|
|
128
|
-
|
|
140
|
+
return CommandResult(
|
|
141
|
+
data["exitCode"], data.get("stdout", ""), data.get("stderr", "")
|
|
142
|
+
)
|
|
143
|
+
|
|
129
144
|
# ==================== pip 包管理 ====================
|
|
130
|
-
|
|
145
|
+
|
|
131
146
|
def pip_install(self, session_id: str, package_name: str) -> CommandResult:
|
|
132
147
|
"""
|
|
133
148
|
安装 Python 包
|
|
134
|
-
|
|
149
|
+
|
|
135
150
|
Args:
|
|
136
151
|
session_id: 会话 ID
|
|
137
152
|
package_name: 包名(支持版本约束)
|
|
138
|
-
|
|
153
|
+
|
|
139
154
|
Returns:
|
|
140
155
|
命令执行结果
|
|
141
156
|
"""
|
|
142
157
|
payload = {"sessionId": session_id, "pkg": package_name}
|
|
143
158
|
resp = self._session.post(
|
|
144
|
-
f"{self.base_url}/api/sandbox/pip/install",
|
|
145
|
-
json=payload
|
|
159
|
+
f"{self.base_url}/api/sandbox/pip/install", json=payload
|
|
146
160
|
)
|
|
147
161
|
self._raise_if_error(resp)
|
|
148
162
|
data = resp.json()
|
|
149
|
-
return CommandResult(
|
|
150
|
-
|
|
163
|
+
return CommandResult(
|
|
164
|
+
data["exitCode"], data.get("stdout", ""), data.get("stderr", "")
|
|
165
|
+
)
|
|
166
|
+
|
|
151
167
|
def pip_uninstall(self, session_id: str, package_name: str) -> CommandResult:
|
|
152
168
|
"""
|
|
153
169
|
卸载 Python 包
|
|
154
|
-
|
|
170
|
+
|
|
155
171
|
Args:
|
|
156
172
|
session_id: 会话 ID
|
|
157
173
|
package_name: 包名
|
|
158
|
-
|
|
174
|
+
|
|
159
175
|
Returns:
|
|
160
176
|
命令执行结果
|
|
161
177
|
"""
|
|
162
178
|
payload = {"sessionId": session_id, "pkg": package_name}
|
|
163
179
|
resp = self._session.post(
|
|
164
|
-
f"{self.base_url}/api/sandbox/pip/uninstall",
|
|
165
|
-
json=payload
|
|
180
|
+
f"{self.base_url}/api/sandbox/pip/uninstall", json=payload
|
|
166
181
|
)
|
|
167
182
|
self._raise_if_error(resp)
|
|
168
183
|
data = resp.json()
|
|
169
|
-
return CommandResult(
|
|
170
|
-
|
|
184
|
+
return CommandResult(
|
|
185
|
+
data["exitCode"], data.get("stdout", ""), data.get("stderr", "")
|
|
186
|
+
)
|
|
187
|
+
|
|
171
188
|
def pip_list(self, session_id: str) -> str:
|
|
172
189
|
"""
|
|
173
190
|
列出已安装的 Python 包
|
|
174
|
-
|
|
191
|
+
|
|
175
192
|
Args:
|
|
176
193
|
session_id: 会话 ID
|
|
177
|
-
|
|
194
|
+
|
|
178
195
|
Returns:
|
|
179
196
|
安装包列表文本
|
|
180
197
|
"""
|
|
181
198
|
resp = self._session.get(
|
|
182
|
-
f"{self.base_url}/api/sandbox/pip/list",
|
|
183
|
-
params={"sessionId": session_id}
|
|
199
|
+
f"{self.base_url}/api/sandbox/pip/list", params={"sessionId": session_id}
|
|
184
200
|
)
|
|
185
201
|
self._raise_if_error(resp)
|
|
186
202
|
return resp.json()["packages"]
|
|
187
|
-
|
|
203
|
+
|
|
188
204
|
# ==================== 文件操作 ====================
|
|
189
|
-
|
|
205
|
+
|
|
190
206
|
def write_file(self, session_id: str, container_path: str, content: str) -> None:
|
|
191
207
|
"""
|
|
192
208
|
向沙箱写入文件内容
|
|
193
|
-
|
|
209
|
+
|
|
194
210
|
Args:
|
|
195
211
|
session_id: 会话 ID
|
|
196
212
|
container_path: 容器内目标路径
|
|
197
213
|
content: 文件内容
|
|
198
214
|
"""
|
|
199
|
-
payload = {
|
|
200
|
-
"sessionId": session_id,
|
|
201
|
-
"path": container_path,
|
|
202
|
-
"content": content
|
|
203
|
-
}
|
|
215
|
+
payload = {"sessionId": session_id, "path": container_path, "content": content}
|
|
204
216
|
resp = self._session.post(
|
|
205
|
-
f"{self.base_url}/api/sandbox/file/write",
|
|
206
|
-
json=payload
|
|
217
|
+
f"{self.base_url}/api/sandbox/file/write", json=payload
|
|
207
218
|
)
|
|
208
219
|
self._raise_if_error(resp)
|
|
209
|
-
|
|
220
|
+
|
|
210
221
|
def read_file(self, session_id: str, container_path: str) -> str:
|
|
211
222
|
"""
|
|
212
223
|
读取沙箱中的文件内容
|
|
213
|
-
|
|
224
|
+
|
|
214
225
|
Args:
|
|
215
226
|
session_id: 会话 ID
|
|
216
227
|
container_path: 容器内文件路径
|
|
217
|
-
|
|
228
|
+
|
|
218
229
|
Returns:
|
|
219
230
|
文件内容字符串
|
|
220
231
|
"""
|
|
221
232
|
resp = self._session.get(
|
|
222
233
|
f"{self.base_url}/api/sandbox/file/read",
|
|
223
|
-
params={"sessionId": session_id, "path": container_path}
|
|
234
|
+
params={"sessionId": session_id, "path": container_path},
|
|
224
235
|
)
|
|
225
236
|
self._raise_if_error(resp)
|
|
226
237
|
return resp.json()["content"]
|
|
227
|
-
|
|
238
|
+
|
|
228
239
|
def upload_file(self, session_id: str, container_path: str, data: bytes) -> None:
|
|
229
240
|
"""
|
|
230
241
|
上传二进制文件到沙箱
|
|
231
|
-
|
|
242
|
+
|
|
232
243
|
Args:
|
|
233
244
|
session_id: 会话 ID
|
|
234
245
|
container_path: 容器内目标路径
|
|
@@ -236,56 +247,58 @@ class SandboxClient:
|
|
|
236
247
|
"""
|
|
237
248
|
import tempfile
|
|
238
249
|
import os
|
|
239
|
-
|
|
250
|
+
|
|
240
251
|
# 写入临时文件
|
|
241
252
|
fd, tmp_path = tempfile.mkstemp(prefix="sandbox_upload_")
|
|
242
253
|
try:
|
|
243
254
|
# os.fdopen 接管了 fd 的所有权,with 块结束时会自动关闭 fd
|
|
244
|
-
with os.fdopen(fd,
|
|
255
|
+
with os.fdopen(fd, "wb") as f:
|
|
245
256
|
f.write(data)
|
|
246
|
-
|
|
247
|
-
with open(tmp_path,
|
|
248
|
-
files = {
|
|
249
|
-
data_params = {
|
|
250
|
-
|
|
257
|
+
|
|
258
|
+
with open(tmp_path, "rb") as f:
|
|
259
|
+
files = {"file": (os.path.basename(container_path), f)}
|
|
260
|
+
data_params = {"sessionId": session_id, "path": container_path}
|
|
261
|
+
|
|
251
262
|
# 发送 multipart 请求(requests 会自动设置正确的 Content-Type)
|
|
252
263
|
url = f"{self.base_url}/api/sandbox/file/upload"
|
|
253
264
|
# 使用新的 session 避免继承主 session 的 Content-Type: application/json
|
|
254
265
|
with requests.Session() as upload_session:
|
|
255
266
|
upload_session.headers.update({"X-Api-Key": self.api_key})
|
|
256
|
-
resp = upload_session.post(
|
|
267
|
+
resp = upload_session.post(
|
|
268
|
+
url, files=files, data=data_params, timeout=120
|
|
269
|
+
)
|
|
257
270
|
self._raise_if_error(resp)
|
|
258
271
|
finally:
|
|
259
272
|
if os.path.exists(tmp_path):
|
|
260
273
|
os.unlink(tmp_path)
|
|
261
|
-
|
|
274
|
+
|
|
262
275
|
def download_file(self, session_id: str, container_path: str) -> bytes:
|
|
263
276
|
"""
|
|
264
277
|
下载沙箱中的文件
|
|
265
|
-
|
|
278
|
+
|
|
266
279
|
Args:
|
|
267
280
|
session_id: 会话 ID
|
|
268
281
|
container_path: 容器内文件路径
|
|
269
|
-
|
|
282
|
+
|
|
270
283
|
Returns:
|
|
271
284
|
文件字节数据
|
|
272
285
|
"""
|
|
273
286
|
url = f"{self.base_url}/api/sandbox/file/download"
|
|
274
287
|
params = {"sessionId": session_id, "path": container_path}
|
|
275
|
-
|
|
288
|
+
|
|
276
289
|
# 使用新的 session 避免继承主 session 的 Content-Type: application/json
|
|
277
290
|
with requests.Session() as s:
|
|
278
291
|
s.headers.update({"X-Api-Key": self.api_key})
|
|
279
292
|
resp = s.get(url, params=params, timeout=60)
|
|
280
293
|
self._raise_if_error(resp)
|
|
281
294
|
return resp.content
|
|
282
|
-
|
|
295
|
+
|
|
283
296
|
# ==================== 健康检查 ====================
|
|
284
|
-
|
|
297
|
+
|
|
285
298
|
def is_health(self) -> bool:
|
|
286
299
|
"""
|
|
287
300
|
检查沙箱服务是否可用
|
|
288
|
-
|
|
301
|
+
|
|
289
302
|
Returns:
|
|
290
303
|
True 如果服务正常运行
|
|
291
304
|
"""
|
|
@@ -294,9 +307,9 @@ class SandboxClient:
|
|
|
294
307
|
return resp.status_code == 200
|
|
295
308
|
except Exception:
|
|
296
309
|
return False
|
|
297
|
-
|
|
310
|
+
|
|
298
311
|
# ==================== 内部方法 ====================
|
|
299
|
-
|
|
312
|
+
|
|
300
313
|
@staticmethod
|
|
301
314
|
def _raise_if_error(resp: requests.Response) -> None:
|
|
302
315
|
"""如果响应状态码表示错误则抛出异常"""
|
|
@@ -307,13 +320,13 @@ class SandboxClient:
|
|
|
307
320
|
except Exception:
|
|
308
321
|
message = resp.text
|
|
309
322
|
raise ApiRequestError(resp.status_code, message)
|
|
310
|
-
|
|
323
|
+
|
|
311
324
|
def close(self) -> None:
|
|
312
325
|
"""关闭 HTTP 会话"""
|
|
313
326
|
self._session.close()
|
|
314
|
-
|
|
327
|
+
|
|
315
328
|
def __enter__(self):
|
|
316
329
|
return self
|
|
317
|
-
|
|
330
|
+
|
|
318
331
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
319
332
|
self.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-sandbox-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers
|
|
5
5
|
Home-page: https://github.com/zjwan461/python-sandbox
|
|
6
6
|
Author-email: Jerry <826935261@qq.com>
|
|
@@ -26,7 +26,7 @@ def _read_readme() -> str:
|
|
|
26
26
|
|
|
27
27
|
setup(
|
|
28
28
|
name="python-sandbox-sdk",
|
|
29
|
-
version="1.0.
|
|
29
|
+
version="1.0.3",
|
|
30
30
|
description="Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers",
|
|
31
31
|
long_description=_read_readme(),
|
|
32
32
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{python_sandbox_sdk-1.0.2 → python_sandbox_sdk-1.0.3}/python_sandbox_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|