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
|
@@ -1,485 +1,485 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
import logging
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from typing import (
|
|
7
|
-
Any,
|
|
8
|
-
Awaitable,
|
|
9
|
-
Callable,
|
|
10
|
-
Dict,
|
|
11
|
-
Iterable,
|
|
12
|
-
List,
|
|
13
|
-
Optional,
|
|
14
|
-
TypeVar,
|
|
15
|
-
Union,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
from google.protobuf.timestamp_pb2 import Timestamp
|
|
19
|
-
from httpx import Response
|
|
20
|
-
|
|
21
|
-
from ..exceptions import NotFoundException, SandboxException, TimeoutException
|
|
22
|
-
from ..generated import api_pb2
|
|
23
|
-
|
|
24
|
-
T = TypeVar("T")
|
|
25
|
-
OutputHandler = Union[
|
|
26
|
-
Callable[[T], Any],
|
|
27
|
-
Callable[[T], Awaitable[Any]],
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger(__name__)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class OutputMessage:
|
|
35
|
-
"""
|
|
36
|
-
Represents an output message from the sandbox code execution.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
content: str
|
|
40
|
-
"""
|
|
41
|
-
The output content.
|
|
42
|
-
"""
|
|
43
|
-
timestamp: int
|
|
44
|
-
"""
|
|
45
|
-
Unix epoch in nanoseconds
|
|
46
|
-
"""
|
|
47
|
-
error: bool = False
|
|
48
|
-
"""
|
|
49
|
-
Whether the output is an error.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __str__(self):
|
|
53
|
-
return self.content
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass
|
|
57
|
-
class ExecutionError:
|
|
58
|
-
"""
|
|
59
|
-
Represents an error that occurred during the execution of a cell.
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
name: str
|
|
63
|
-
"""
|
|
64
|
-
Name of the error.
|
|
65
|
-
"""
|
|
66
|
-
value: str
|
|
67
|
-
"""
|
|
68
|
-
Value of the error.
|
|
69
|
-
"""
|
|
70
|
-
traceback: str
|
|
71
|
-
"""
|
|
72
|
-
The raw traceback of the error.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
def __init__(self, name: str, value: str, traceback: str, **kwargs):
|
|
76
|
-
self.name = name
|
|
77
|
-
self.value = value
|
|
78
|
-
self.traceback = traceback
|
|
79
|
-
|
|
80
|
-
def to_json(self) -> str:
|
|
81
|
-
"""
|
|
82
|
-
Returns the JSON representation of the Error object.
|
|
83
|
-
"""
|
|
84
|
-
data = {"name": self.name, "value": self.value, "traceback": self.traceback}
|
|
85
|
-
return json.dumps(data)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class MIMEType(str):
|
|
89
|
-
"""
|
|
90
|
-
Represents a MIME type.
|
|
91
|
-
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@dataclass
|
|
95
|
-
class Result:
|
|
96
|
-
"""
|
|
97
|
-
Represents the result of code execution.
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
exit_code: int = 0
|
|
101
|
-
"""Process exit code."""
|
|
102
|
-
started_at: Optional[datetime] = None
|
|
103
|
-
"""Start time."""
|
|
104
|
-
finished_at: Optional[datetime] = None
|
|
105
|
-
"""End time."""
|
|
106
|
-
text: Optional[str] = None
|
|
107
|
-
"""Text representation."""
|
|
108
|
-
html: Optional[str] = None
|
|
109
|
-
"""HTML representation."""
|
|
110
|
-
markdown: Optional[str] = None
|
|
111
|
-
"""Markdown representation."""
|
|
112
|
-
svg: Optional[str] = None
|
|
113
|
-
"""SVG representation."""
|
|
114
|
-
png: Optional[str] = None
|
|
115
|
-
"""PNG representation."""
|
|
116
|
-
jpeg: Optional[str] = None
|
|
117
|
-
"""JPEG representation."""
|
|
118
|
-
pdf: Optional[str] = None
|
|
119
|
-
"""PDF representation."""
|
|
120
|
-
latex: Optional[str] = None
|
|
121
|
-
"""LaTeX representation."""
|
|
122
|
-
json_data: Optional[dict] = None
|
|
123
|
-
"""JSON data."""
|
|
124
|
-
javascript: Optional[str] = None
|
|
125
|
-
"""JavaScript representation."""
|
|
126
|
-
data: Optional[dict] = None
|
|
127
|
-
"""Raw data."""
|
|
128
|
-
chart: Optional[api_pb2.Chart] = None
|
|
129
|
-
"""Chart data."""
|
|
130
|
-
execution_count: Optional[int] = None
|
|
131
|
-
"""Execution count."""
|
|
132
|
-
is_main_result: bool = False
|
|
133
|
-
"""Whether this is the main result."""
|
|
134
|
-
extra: Optional[dict] = None
|
|
135
|
-
"""Extra data."""
|
|
136
|
-
|
|
137
|
-
def __init__(
|
|
138
|
-
self,
|
|
139
|
-
exit_code: int = 0,
|
|
140
|
-
started_at: Optional[Timestamp] = None,
|
|
141
|
-
finished_at: Optional[Timestamp] = None,
|
|
142
|
-
text: Optional[str] = None,
|
|
143
|
-
html: Optional[str] = None,
|
|
144
|
-
markdown: Optional[str] = None,
|
|
145
|
-
svg: Optional[str] = None,
|
|
146
|
-
png: Optional[str] = None,
|
|
147
|
-
jpeg: Optional[str] = None,
|
|
148
|
-
pdf: Optional[str] = None,
|
|
149
|
-
latex: Optional[str] = None,
|
|
150
|
-
json: Optional[str] = None,
|
|
151
|
-
javascript: Optional[str] = None,
|
|
152
|
-
data: Optional[str] = None,
|
|
153
|
-
chart: Optional[api_pb2.Chart] = None,
|
|
154
|
-
execution_count: Optional[int] = None,
|
|
155
|
-
is_main_result: bool = False,
|
|
156
|
-
extra: Optional[Dict[str, str]] = None,
|
|
157
|
-
**kwargs,
|
|
158
|
-
):
|
|
159
|
-
self.exit_code = exit_code
|
|
160
|
-
self.started_at = self._convert_timestamp(started_at) if started_at else None
|
|
161
|
-
self.finished_at = self._convert_timestamp(finished_at) if finished_at else None
|
|
162
|
-
self.text = text
|
|
163
|
-
self.html = html
|
|
164
|
-
self.markdown = markdown
|
|
165
|
-
self.svg = svg
|
|
166
|
-
self.png = png
|
|
167
|
-
self.jpeg = jpeg
|
|
168
|
-
self.pdf = pdf
|
|
169
|
-
self.latex = latex
|
|
170
|
-
self.json_data = json.loads(json) if json else None
|
|
171
|
-
self.javascript = javascript
|
|
172
|
-
self.data = json.loads(data) if data else None
|
|
173
|
-
self.chart = chart
|
|
174
|
-
self.execution_count = execution_count
|
|
175
|
-
self.is_main_result = is_main_result
|
|
176
|
-
self.extra = dict(extra) if extra else {}
|
|
177
|
-
|
|
178
|
-
def _convert_timestamp(self, timestamp: Timestamp) -> datetime:
|
|
179
|
-
"""Convert protobuf timestamp to datetime."""
|
|
180
|
-
return datetime.fromtimestamp(timestamp.seconds + timestamp.nanos / 1e9)
|
|
181
|
-
|
|
182
|
-
def formats(self) -> Iterable[str]:
|
|
183
|
-
"""
|
|
184
|
-
Returns all available formats of the result.
|
|
185
|
-
|
|
186
|
-
:return: All available formats of the result.
|
|
187
|
-
"""
|
|
188
|
-
formats = []
|
|
189
|
-
if self.text:
|
|
190
|
-
formats.append("text")
|
|
191
|
-
if self.html:
|
|
192
|
-
formats.append("html")
|
|
193
|
-
if self.markdown:
|
|
194
|
-
formats.append("markdown")
|
|
195
|
-
if self.svg:
|
|
196
|
-
formats.append("svg")
|
|
197
|
-
if self.png:
|
|
198
|
-
formats.append("png")
|
|
199
|
-
if self.jpeg:
|
|
200
|
-
formats.append("jpeg")
|
|
201
|
-
if self.pdf:
|
|
202
|
-
formats.append("pdf")
|
|
203
|
-
if self.latex:
|
|
204
|
-
formats.append("latex")
|
|
205
|
-
if self.json_data:
|
|
206
|
-
formats.append("json")
|
|
207
|
-
if self.javascript:
|
|
208
|
-
formats.append("javascript")
|
|
209
|
-
if self.data:
|
|
210
|
-
formats.append("data")
|
|
211
|
-
if self.chart:
|
|
212
|
-
formats.append("chart")
|
|
213
|
-
|
|
214
|
-
if self.extra:
|
|
215
|
-
for key in self.extra:
|
|
216
|
-
formats.append(key)
|
|
217
|
-
|
|
218
|
-
return formats
|
|
219
|
-
|
|
220
|
-
def __str__(self) -> str:
|
|
221
|
-
"""
|
|
222
|
-
Returns the text representation of the data.
|
|
223
|
-
|
|
224
|
-
:return: The text representation of the data.
|
|
225
|
-
"""
|
|
226
|
-
if self.text:
|
|
227
|
-
return f"Result(exit_code={self.exit_code}, text={self.text})"
|
|
228
|
-
else:
|
|
229
|
-
return f"Result(exit_code={self.exit_code}, formats={list(self.formats())})"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@dataclass
|
|
233
|
-
class Logs:
|
|
234
|
-
"""
|
|
235
|
-
Data printed to stdout and stderr during execution.
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
stdout: List[str]
|
|
239
|
-
"""List of strings printed to stdout."""
|
|
240
|
-
stderr: List[str]
|
|
241
|
-
"""List of strings printed to stderr."""
|
|
242
|
-
|
|
243
|
-
def __init__(self, stdout: List[str] = None, stderr: List[str] = None, **kwargs):
|
|
244
|
-
self.stdout = stdout or []
|
|
245
|
-
self.stderr = stderr or []
|
|
246
|
-
|
|
247
|
-
def __repr__(self):
|
|
248
|
-
return f"Logs(stdout={self.stdout}, stderr={self.stderr})"
|
|
249
|
-
|
|
250
|
-
def to_json(self) -> str:
|
|
251
|
-
"""
|
|
252
|
-
Returns the JSON representation of the Logs object.
|
|
253
|
-
"""
|
|
254
|
-
data = {"stdout": self.stdout, "stderr": self.stderr}
|
|
255
|
-
return json.dumps(data)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
@dataclass
|
|
259
|
-
class Execution:
|
|
260
|
-
"""
|
|
261
|
-
Represents the result of a code execution.
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
results: List[Result]
|
|
265
|
-
"""List of execution results."""
|
|
266
|
-
logs: Logs
|
|
267
|
-
"""Logs printed during execution."""
|
|
268
|
-
error: Optional[ExecutionError]
|
|
269
|
-
"""Error object if an error occurred."""
|
|
270
|
-
execution_count: Optional[int]
|
|
271
|
-
"""Execution count."""
|
|
272
|
-
|
|
273
|
-
def __init__(
|
|
274
|
-
self,
|
|
275
|
-
results: List[Result] = None,
|
|
276
|
-
logs: Logs = None,
|
|
277
|
-
error: Optional[ExecutionError] = None,
|
|
278
|
-
execution_count: Optional[int] = None,
|
|
279
|
-
**kwargs,
|
|
280
|
-
):
|
|
281
|
-
self.results = results or []
|
|
282
|
-
self.logs = logs or Logs()
|
|
283
|
-
self.error = error
|
|
284
|
-
self.execution_count = execution_count
|
|
285
|
-
|
|
286
|
-
def __repr__(self):
|
|
287
|
-
return f"Execution(results={self.results}, logs={self.logs}, error={self.error}, execution_count={self.execution_count})"
|
|
288
|
-
|
|
289
|
-
@property
|
|
290
|
-
def text(self) -> Optional[str]:
|
|
291
|
-
"""
|
|
292
|
-
Returns the text representation of the main result.
|
|
293
|
-
|
|
294
|
-
:return: The text representation of the main result.
|
|
295
|
-
"""
|
|
296
|
-
for result in self.results:
|
|
297
|
-
if result.is_main_result:
|
|
298
|
-
return result.text
|
|
299
|
-
return None
|
|
300
|
-
|
|
301
|
-
def to_json(self) -> str:
|
|
302
|
-
"""
|
|
303
|
-
Returns the JSON representation of the Execution object.
|
|
304
|
-
"""
|
|
305
|
-
data = {
|
|
306
|
-
"results": [self._serialize_result(result) for result in self.results],
|
|
307
|
-
"logs": self.logs.to_json(),
|
|
308
|
-
"error": self.error.to_json() if self.error else None,
|
|
309
|
-
"execution_count": self.execution_count,
|
|
310
|
-
}
|
|
311
|
-
return json.dumps(data)
|
|
312
|
-
|
|
313
|
-
def _serialize_result(self, result: Result) -> Dict[str, Any]:
|
|
314
|
-
"""Serialize a single result to JSON-serializable format."""
|
|
315
|
-
serialized = {
|
|
316
|
-
"exit_code": result.exit_code,
|
|
317
|
-
"started_at": result.started_at.isoformat() if result.started_at else None,
|
|
318
|
-
"finished_at": (
|
|
319
|
-
result.finished_at.isoformat() if result.finished_at else None
|
|
320
|
-
),
|
|
321
|
-
"text": result.text,
|
|
322
|
-
"html": result.html,
|
|
323
|
-
"markdown": result.markdown,
|
|
324
|
-
"svg": result.svg,
|
|
325
|
-
"png": result.png,
|
|
326
|
-
"jpeg": result.jpeg,
|
|
327
|
-
"pdf": result.pdf,
|
|
328
|
-
"latex": result.latex,
|
|
329
|
-
"json": result.json_data,
|
|
330
|
-
"javascript": result.javascript,
|
|
331
|
-
"data": result.data,
|
|
332
|
-
"execution_count": result.execution_count,
|
|
333
|
-
"is_main_result": result.is_main_result,
|
|
334
|
-
"extra": result.extra,
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
# Remove None values
|
|
338
|
-
return {k: v for k, v in serialized.items() if v is not None}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
@dataclass
|
|
342
|
-
class Context:
|
|
343
|
-
"""
|
|
344
|
-
Represents a context for code execution.
|
|
345
|
-
"""
|
|
346
|
-
|
|
347
|
-
id: str
|
|
348
|
-
"""The ID of the context."""
|
|
349
|
-
language: str
|
|
350
|
-
"""The language of the context."""
|
|
351
|
-
cwd: str
|
|
352
|
-
"""The working directory of the context."""
|
|
353
|
-
|
|
354
|
-
def __init__(self, context_id: str, language: str, cwd: str, **kwargs):
|
|
355
|
-
self.id = context_id
|
|
356
|
-
self.language = language
|
|
357
|
-
self.cwd = cwd
|
|
358
|
-
|
|
359
|
-
@classmethod
|
|
360
|
-
def from_json(cls, data: Dict[str, str]):
|
|
361
|
-
return cls(
|
|
362
|
-
context_id=data.get("id"),
|
|
363
|
-
language=data.get("language"),
|
|
364
|
-
cwd=data.get("cwd"),
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def parse_output(
|
|
369
|
-
execution: Execution,
|
|
370
|
-
output: api_pb2.ExecuteResponse,
|
|
371
|
-
on_stdout: Optional[OutputHandler[OutputMessage]] = None,
|
|
372
|
-
on_stderr: Optional[OutputHandler[OutputMessage]] = None,
|
|
373
|
-
on_result: Optional[OutputHandler[Result]] = None,
|
|
374
|
-
on_error: Optional[OutputHandler[ExecutionError]] = None,
|
|
375
|
-
):
|
|
376
|
-
"""
|
|
377
|
-
Parse the output from the execution service and update the execution object.
|
|
378
|
-
"""
|
|
379
|
-
if output.HasField("stdout"):
|
|
380
|
-
content = output.stdout.content
|
|
381
|
-
execution.logs.stdout.append(content)
|
|
382
|
-
if on_stdout:
|
|
383
|
-
message = OutputMessage(
|
|
384
|
-
content=content,
|
|
385
|
-
timestamp=int(datetime.now().timestamp() * 1e9),
|
|
386
|
-
error=False,
|
|
387
|
-
)
|
|
388
|
-
if asyncio.iscoroutinefunction(on_stdout):
|
|
389
|
-
asyncio.create_task(on_stdout(message))
|
|
390
|
-
else:
|
|
391
|
-
on_stdout(message)
|
|
392
|
-
|
|
393
|
-
elif output.HasField("stderr"):
|
|
394
|
-
content = output.stderr.content
|
|
395
|
-
execution.logs.stderr.append(content)
|
|
396
|
-
if on_stderr:
|
|
397
|
-
message = OutputMessage(
|
|
398
|
-
content=content,
|
|
399
|
-
timestamp=int(datetime.now().timestamp() * 1e9),
|
|
400
|
-
error=True,
|
|
401
|
-
)
|
|
402
|
-
if asyncio.iscoroutinefunction(on_stderr):
|
|
403
|
-
asyncio.create_task(on_stderr(message))
|
|
404
|
-
else:
|
|
405
|
-
on_stderr(message)
|
|
406
|
-
|
|
407
|
-
elif output.HasField("result"):
|
|
408
|
-
result_msg = output.result
|
|
409
|
-
result = Result(
|
|
410
|
-
exit_code=result_msg.exit_code,
|
|
411
|
-
started_at=result_msg.started_at,
|
|
412
|
-
finished_at=result_msg.finished_at,
|
|
413
|
-
text=result_msg.text,
|
|
414
|
-
html=result_msg.html,
|
|
415
|
-
markdown=result_msg.markdown,
|
|
416
|
-
svg=result_msg.svg,
|
|
417
|
-
png=result_msg.png,
|
|
418
|
-
jpeg=result_msg.jpeg,
|
|
419
|
-
pdf=result_msg.pdf,
|
|
420
|
-
latex=result_msg.latex,
|
|
421
|
-
json=result_msg.json,
|
|
422
|
-
javascript=result_msg.javascript,
|
|
423
|
-
data=result_msg.data,
|
|
424
|
-
chart=result_msg.chart,
|
|
425
|
-
execution_count=result_msg.execution_count,
|
|
426
|
-
is_main_result=result_msg.is_main_result,
|
|
427
|
-
extra=dict(result_msg.extra),
|
|
428
|
-
)
|
|
429
|
-
execution.results.append(result)
|
|
430
|
-
if on_result:
|
|
431
|
-
if asyncio.iscoroutinefunction(on_result):
|
|
432
|
-
asyncio.create_task(on_result(result))
|
|
433
|
-
else:
|
|
434
|
-
on_result(result)
|
|
435
|
-
|
|
436
|
-
# Update execution count if this is the main result
|
|
437
|
-
if result.is_main_result and result.execution_count is not None:
|
|
438
|
-
execution.execution_count = result.execution_count
|
|
439
|
-
|
|
440
|
-
elif output.HasField("error"):
|
|
441
|
-
error_msg = output.error
|
|
442
|
-
error = ExecutionError(
|
|
443
|
-
name=error_msg.name,
|
|
444
|
-
value=error_msg.value,
|
|
445
|
-
traceback=error_msg.traceback,
|
|
446
|
-
)
|
|
447
|
-
execution.error = error
|
|
448
|
-
if on_error:
|
|
449
|
-
if asyncio.iscoroutinefunction(on_error):
|
|
450
|
-
asyncio.create_task(on_error(error))
|
|
451
|
-
else:
|
|
452
|
-
on_error(error)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
async def aextract_exception(res: Response):
|
|
456
|
-
"""Asynchronously extract exception from response."""
|
|
457
|
-
if res.is_success:
|
|
458
|
-
return None
|
|
459
|
-
|
|
460
|
-
await res.aread()
|
|
461
|
-
return extract_exception(res)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def extract_exception(res: Response):
|
|
465
|
-
"""Extract exception from response."""
|
|
466
|
-
if res.is_success:
|
|
467
|
-
return None
|
|
468
|
-
|
|
469
|
-
res.read()
|
|
470
|
-
return format_exception(res)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
def format_exception(res: Response):
|
|
474
|
-
"""Format exception from response."""
|
|
475
|
-
if res.is_success:
|
|
476
|
-
return None
|
|
477
|
-
|
|
478
|
-
if res.status_code == 404:
|
|
479
|
-
return NotFoundException(res.text)
|
|
480
|
-
elif res.status_code == 502:
|
|
481
|
-
return TimeoutException(
|
|
482
|
-
f"{res.text}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
|
|
483
|
-
)
|
|
484
|
-
else:
|
|
485
|
-
return SandboxException(f"{res.status_code}: {res.text}")
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Awaitable,
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
Iterable,
|
|
12
|
+
List,
|
|
13
|
+
Optional,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
19
|
+
from httpx import Response
|
|
20
|
+
|
|
21
|
+
from ..exceptions import NotFoundException, SandboxException, TimeoutException
|
|
22
|
+
from ..generated import api_pb2
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
OutputHandler = Union[
|
|
26
|
+
Callable[[T], Any],
|
|
27
|
+
Callable[[T], Awaitable[Any]],
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class OutputMessage:
|
|
35
|
+
"""
|
|
36
|
+
Represents an output message from the sandbox code execution.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
content: str
|
|
40
|
+
"""
|
|
41
|
+
The output content.
|
|
42
|
+
"""
|
|
43
|
+
timestamp: int
|
|
44
|
+
"""
|
|
45
|
+
Unix epoch in nanoseconds
|
|
46
|
+
"""
|
|
47
|
+
error: bool = False
|
|
48
|
+
"""
|
|
49
|
+
Whether the output is an error.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
return self.content
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ExecutionError:
|
|
58
|
+
"""
|
|
59
|
+
Represents an error that occurred during the execution of a cell.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name: str
|
|
63
|
+
"""
|
|
64
|
+
Name of the error.
|
|
65
|
+
"""
|
|
66
|
+
value: str
|
|
67
|
+
"""
|
|
68
|
+
Value of the error.
|
|
69
|
+
"""
|
|
70
|
+
traceback: str
|
|
71
|
+
"""
|
|
72
|
+
The raw traceback of the error.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, name: str, value: str, traceback: str, **kwargs):
|
|
76
|
+
self.name = name
|
|
77
|
+
self.value = value
|
|
78
|
+
self.traceback = traceback
|
|
79
|
+
|
|
80
|
+
def to_json(self) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Returns the JSON representation of the Error object.
|
|
83
|
+
"""
|
|
84
|
+
data = {"name": self.name, "value": self.value, "traceback": self.traceback}
|
|
85
|
+
return json.dumps(data)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MIMEType(str):
|
|
89
|
+
"""
|
|
90
|
+
Represents a MIME type.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class Result:
|
|
96
|
+
"""
|
|
97
|
+
Represents the result of code execution.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
exit_code: int = 0
|
|
101
|
+
"""Process exit code."""
|
|
102
|
+
started_at: Optional[datetime] = None
|
|
103
|
+
"""Start time."""
|
|
104
|
+
finished_at: Optional[datetime] = None
|
|
105
|
+
"""End time."""
|
|
106
|
+
text: Optional[str] = None
|
|
107
|
+
"""Text representation."""
|
|
108
|
+
html: Optional[str] = None
|
|
109
|
+
"""HTML representation."""
|
|
110
|
+
markdown: Optional[str] = None
|
|
111
|
+
"""Markdown representation."""
|
|
112
|
+
svg: Optional[str] = None
|
|
113
|
+
"""SVG representation."""
|
|
114
|
+
png: Optional[str] = None
|
|
115
|
+
"""PNG representation."""
|
|
116
|
+
jpeg: Optional[str] = None
|
|
117
|
+
"""JPEG representation."""
|
|
118
|
+
pdf: Optional[str] = None
|
|
119
|
+
"""PDF representation."""
|
|
120
|
+
latex: Optional[str] = None
|
|
121
|
+
"""LaTeX representation."""
|
|
122
|
+
json_data: Optional[dict] = None
|
|
123
|
+
"""JSON data."""
|
|
124
|
+
javascript: Optional[str] = None
|
|
125
|
+
"""JavaScript representation."""
|
|
126
|
+
data: Optional[dict] = None
|
|
127
|
+
"""Raw data."""
|
|
128
|
+
chart: Optional[api_pb2.Chart] = None
|
|
129
|
+
"""Chart data."""
|
|
130
|
+
execution_count: Optional[int] = None
|
|
131
|
+
"""Execution count."""
|
|
132
|
+
is_main_result: bool = False
|
|
133
|
+
"""Whether this is the main result."""
|
|
134
|
+
extra: Optional[dict] = None
|
|
135
|
+
"""Extra data."""
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
exit_code: int = 0,
|
|
140
|
+
started_at: Optional[Timestamp] = None,
|
|
141
|
+
finished_at: Optional[Timestamp] = None,
|
|
142
|
+
text: Optional[str] = None,
|
|
143
|
+
html: Optional[str] = None,
|
|
144
|
+
markdown: Optional[str] = None,
|
|
145
|
+
svg: Optional[str] = None,
|
|
146
|
+
png: Optional[str] = None,
|
|
147
|
+
jpeg: Optional[str] = None,
|
|
148
|
+
pdf: Optional[str] = None,
|
|
149
|
+
latex: Optional[str] = None,
|
|
150
|
+
json: Optional[str] = None,
|
|
151
|
+
javascript: Optional[str] = None,
|
|
152
|
+
data: Optional[str] = None,
|
|
153
|
+
chart: Optional[api_pb2.Chart] = None,
|
|
154
|
+
execution_count: Optional[int] = None,
|
|
155
|
+
is_main_result: bool = False,
|
|
156
|
+
extra: Optional[Dict[str, str]] = None,
|
|
157
|
+
**kwargs,
|
|
158
|
+
):
|
|
159
|
+
self.exit_code = exit_code
|
|
160
|
+
self.started_at = self._convert_timestamp(started_at) if started_at else None
|
|
161
|
+
self.finished_at = self._convert_timestamp(finished_at) if finished_at else None
|
|
162
|
+
self.text = text
|
|
163
|
+
self.html = html
|
|
164
|
+
self.markdown = markdown
|
|
165
|
+
self.svg = svg
|
|
166
|
+
self.png = png
|
|
167
|
+
self.jpeg = jpeg
|
|
168
|
+
self.pdf = pdf
|
|
169
|
+
self.latex = latex
|
|
170
|
+
self.json_data = json.loads(json) if json else None
|
|
171
|
+
self.javascript = javascript
|
|
172
|
+
self.data = json.loads(data) if data else None
|
|
173
|
+
self.chart = chart
|
|
174
|
+
self.execution_count = execution_count
|
|
175
|
+
self.is_main_result = is_main_result
|
|
176
|
+
self.extra = dict(extra) if extra else {}
|
|
177
|
+
|
|
178
|
+
def _convert_timestamp(self, timestamp: Timestamp) -> datetime:
|
|
179
|
+
"""Convert protobuf timestamp to datetime."""
|
|
180
|
+
return datetime.fromtimestamp(timestamp.seconds + timestamp.nanos / 1e9)
|
|
181
|
+
|
|
182
|
+
def formats(self) -> Iterable[str]:
|
|
183
|
+
"""
|
|
184
|
+
Returns all available formats of the result.
|
|
185
|
+
|
|
186
|
+
:return: All available formats of the result.
|
|
187
|
+
"""
|
|
188
|
+
formats = []
|
|
189
|
+
if self.text:
|
|
190
|
+
formats.append("text")
|
|
191
|
+
if self.html:
|
|
192
|
+
formats.append("html")
|
|
193
|
+
if self.markdown:
|
|
194
|
+
formats.append("markdown")
|
|
195
|
+
if self.svg:
|
|
196
|
+
formats.append("svg")
|
|
197
|
+
if self.png:
|
|
198
|
+
formats.append("png")
|
|
199
|
+
if self.jpeg:
|
|
200
|
+
formats.append("jpeg")
|
|
201
|
+
if self.pdf:
|
|
202
|
+
formats.append("pdf")
|
|
203
|
+
if self.latex:
|
|
204
|
+
formats.append("latex")
|
|
205
|
+
if self.json_data:
|
|
206
|
+
formats.append("json")
|
|
207
|
+
if self.javascript:
|
|
208
|
+
formats.append("javascript")
|
|
209
|
+
if self.data:
|
|
210
|
+
formats.append("data")
|
|
211
|
+
if self.chart:
|
|
212
|
+
formats.append("chart")
|
|
213
|
+
|
|
214
|
+
if self.extra:
|
|
215
|
+
for key in self.extra:
|
|
216
|
+
formats.append(key)
|
|
217
|
+
|
|
218
|
+
return formats
|
|
219
|
+
|
|
220
|
+
def __str__(self) -> str:
|
|
221
|
+
"""
|
|
222
|
+
Returns the text representation of the data.
|
|
223
|
+
|
|
224
|
+
:return: The text representation of the data.
|
|
225
|
+
"""
|
|
226
|
+
if self.text:
|
|
227
|
+
return f"Result(exit_code={self.exit_code}, text={self.text})"
|
|
228
|
+
else:
|
|
229
|
+
return f"Result(exit_code={self.exit_code}, formats={list(self.formats())})"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class Logs:
|
|
234
|
+
"""
|
|
235
|
+
Data printed to stdout and stderr during execution.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
stdout: List[str]
|
|
239
|
+
"""List of strings printed to stdout."""
|
|
240
|
+
stderr: List[str]
|
|
241
|
+
"""List of strings printed to stderr."""
|
|
242
|
+
|
|
243
|
+
def __init__(self, stdout: List[str] = None, stderr: List[str] = None, **kwargs):
|
|
244
|
+
self.stdout = stdout or []
|
|
245
|
+
self.stderr = stderr or []
|
|
246
|
+
|
|
247
|
+
def __repr__(self):
|
|
248
|
+
return f"Logs(stdout={self.stdout}, stderr={self.stderr})"
|
|
249
|
+
|
|
250
|
+
def to_json(self) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Returns the JSON representation of the Logs object.
|
|
253
|
+
"""
|
|
254
|
+
data = {"stdout": self.stdout, "stderr": self.stderr}
|
|
255
|
+
return json.dumps(data)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dataclass
|
|
259
|
+
class Execution:
|
|
260
|
+
"""
|
|
261
|
+
Represents the result of a code execution.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
results: List[Result]
|
|
265
|
+
"""List of execution results."""
|
|
266
|
+
logs: Logs
|
|
267
|
+
"""Logs printed during execution."""
|
|
268
|
+
error: Optional[ExecutionError]
|
|
269
|
+
"""Error object if an error occurred."""
|
|
270
|
+
execution_count: Optional[int]
|
|
271
|
+
"""Execution count."""
|
|
272
|
+
|
|
273
|
+
def __init__(
|
|
274
|
+
self,
|
|
275
|
+
results: List[Result] = None,
|
|
276
|
+
logs: Logs = None,
|
|
277
|
+
error: Optional[ExecutionError] = None,
|
|
278
|
+
execution_count: Optional[int] = None,
|
|
279
|
+
**kwargs,
|
|
280
|
+
):
|
|
281
|
+
self.results = results or []
|
|
282
|
+
self.logs = logs or Logs()
|
|
283
|
+
self.error = error
|
|
284
|
+
self.execution_count = execution_count
|
|
285
|
+
|
|
286
|
+
def __repr__(self):
|
|
287
|
+
return f"Execution(results={self.results}, logs={self.logs}, error={self.error}, execution_count={self.execution_count})"
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def text(self) -> Optional[str]:
|
|
291
|
+
"""
|
|
292
|
+
Returns the text representation of the main result.
|
|
293
|
+
|
|
294
|
+
:return: The text representation of the main result.
|
|
295
|
+
"""
|
|
296
|
+
for result in self.results:
|
|
297
|
+
if result.is_main_result:
|
|
298
|
+
return result.text
|
|
299
|
+
return None
|
|
300
|
+
|
|
301
|
+
def to_json(self) -> str:
|
|
302
|
+
"""
|
|
303
|
+
Returns the JSON representation of the Execution object.
|
|
304
|
+
"""
|
|
305
|
+
data = {
|
|
306
|
+
"results": [self._serialize_result(result) for result in self.results],
|
|
307
|
+
"logs": self.logs.to_json(),
|
|
308
|
+
"error": self.error.to_json() if self.error else None,
|
|
309
|
+
"execution_count": self.execution_count,
|
|
310
|
+
}
|
|
311
|
+
return json.dumps(data)
|
|
312
|
+
|
|
313
|
+
def _serialize_result(self, result: Result) -> Dict[str, Any]:
|
|
314
|
+
"""Serialize a single result to JSON-serializable format."""
|
|
315
|
+
serialized = {
|
|
316
|
+
"exit_code": result.exit_code,
|
|
317
|
+
"started_at": result.started_at.isoformat() if result.started_at else None,
|
|
318
|
+
"finished_at": (
|
|
319
|
+
result.finished_at.isoformat() if result.finished_at else None
|
|
320
|
+
),
|
|
321
|
+
"text": result.text,
|
|
322
|
+
"html": result.html,
|
|
323
|
+
"markdown": result.markdown,
|
|
324
|
+
"svg": result.svg,
|
|
325
|
+
"png": result.png,
|
|
326
|
+
"jpeg": result.jpeg,
|
|
327
|
+
"pdf": result.pdf,
|
|
328
|
+
"latex": result.latex,
|
|
329
|
+
"json": result.json_data,
|
|
330
|
+
"javascript": result.javascript,
|
|
331
|
+
"data": result.data,
|
|
332
|
+
"execution_count": result.execution_count,
|
|
333
|
+
"is_main_result": result.is_main_result,
|
|
334
|
+
"extra": result.extra,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Remove None values
|
|
338
|
+
return {k: v for k, v in serialized.items() if v is not None}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass
|
|
342
|
+
class Context:
|
|
343
|
+
"""
|
|
344
|
+
Represents a context for code execution.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
id: str
|
|
348
|
+
"""The ID of the context."""
|
|
349
|
+
language: str
|
|
350
|
+
"""The language of the context."""
|
|
351
|
+
cwd: str
|
|
352
|
+
"""The working directory of the context."""
|
|
353
|
+
|
|
354
|
+
def __init__(self, context_id: str, language: str, cwd: str, **kwargs):
|
|
355
|
+
self.id = context_id
|
|
356
|
+
self.language = language
|
|
357
|
+
self.cwd = cwd
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def from_json(cls, data: Dict[str, str]):
|
|
361
|
+
return cls(
|
|
362
|
+
context_id=data.get("id"),
|
|
363
|
+
language=data.get("language"),
|
|
364
|
+
cwd=data.get("cwd"),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def parse_output(
|
|
369
|
+
execution: Execution,
|
|
370
|
+
output: api_pb2.ExecuteResponse,
|
|
371
|
+
on_stdout: Optional[OutputHandler[OutputMessage]] = None,
|
|
372
|
+
on_stderr: Optional[OutputHandler[OutputMessage]] = None,
|
|
373
|
+
on_result: Optional[OutputHandler[Result]] = None,
|
|
374
|
+
on_error: Optional[OutputHandler[ExecutionError]] = None,
|
|
375
|
+
):
|
|
376
|
+
"""
|
|
377
|
+
Parse the output from the execution service and update the execution object.
|
|
378
|
+
"""
|
|
379
|
+
if output.HasField("stdout"):
|
|
380
|
+
content = output.stdout.content
|
|
381
|
+
execution.logs.stdout.append(content)
|
|
382
|
+
if on_stdout:
|
|
383
|
+
message = OutputMessage(
|
|
384
|
+
content=content,
|
|
385
|
+
timestamp=int(datetime.now().timestamp() * 1e9),
|
|
386
|
+
error=False,
|
|
387
|
+
)
|
|
388
|
+
if asyncio.iscoroutinefunction(on_stdout):
|
|
389
|
+
asyncio.create_task(on_stdout(message))
|
|
390
|
+
else:
|
|
391
|
+
on_stdout(message)
|
|
392
|
+
|
|
393
|
+
elif output.HasField("stderr"):
|
|
394
|
+
content = output.stderr.content
|
|
395
|
+
execution.logs.stderr.append(content)
|
|
396
|
+
if on_stderr:
|
|
397
|
+
message = OutputMessage(
|
|
398
|
+
content=content,
|
|
399
|
+
timestamp=int(datetime.now().timestamp() * 1e9),
|
|
400
|
+
error=True,
|
|
401
|
+
)
|
|
402
|
+
if asyncio.iscoroutinefunction(on_stderr):
|
|
403
|
+
asyncio.create_task(on_stderr(message))
|
|
404
|
+
else:
|
|
405
|
+
on_stderr(message)
|
|
406
|
+
|
|
407
|
+
elif output.HasField("result"):
|
|
408
|
+
result_msg = output.result
|
|
409
|
+
result = Result(
|
|
410
|
+
exit_code=result_msg.exit_code,
|
|
411
|
+
started_at=result_msg.started_at,
|
|
412
|
+
finished_at=result_msg.finished_at,
|
|
413
|
+
text=result_msg.text,
|
|
414
|
+
html=result_msg.html,
|
|
415
|
+
markdown=result_msg.markdown,
|
|
416
|
+
svg=result_msg.svg,
|
|
417
|
+
png=result_msg.png,
|
|
418
|
+
jpeg=result_msg.jpeg,
|
|
419
|
+
pdf=result_msg.pdf,
|
|
420
|
+
latex=result_msg.latex,
|
|
421
|
+
json=result_msg.json,
|
|
422
|
+
javascript=result_msg.javascript,
|
|
423
|
+
data=result_msg.data,
|
|
424
|
+
chart=result_msg.chart,
|
|
425
|
+
execution_count=result_msg.execution_count,
|
|
426
|
+
is_main_result=result_msg.is_main_result,
|
|
427
|
+
extra=dict(result_msg.extra),
|
|
428
|
+
)
|
|
429
|
+
execution.results.append(result)
|
|
430
|
+
if on_result:
|
|
431
|
+
if asyncio.iscoroutinefunction(on_result):
|
|
432
|
+
asyncio.create_task(on_result(result))
|
|
433
|
+
else:
|
|
434
|
+
on_result(result)
|
|
435
|
+
|
|
436
|
+
# Update execution count if this is the main result
|
|
437
|
+
if result.is_main_result and result.execution_count is not None:
|
|
438
|
+
execution.execution_count = result.execution_count
|
|
439
|
+
|
|
440
|
+
elif output.HasField("error"):
|
|
441
|
+
error_msg = output.error
|
|
442
|
+
error = ExecutionError(
|
|
443
|
+
name=error_msg.name,
|
|
444
|
+
value=error_msg.value,
|
|
445
|
+
traceback=error_msg.traceback,
|
|
446
|
+
)
|
|
447
|
+
execution.error = error
|
|
448
|
+
if on_error:
|
|
449
|
+
if asyncio.iscoroutinefunction(on_error):
|
|
450
|
+
asyncio.create_task(on_error(error))
|
|
451
|
+
else:
|
|
452
|
+
on_error(error)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
async def aextract_exception(res: Response):
|
|
456
|
+
"""Asynchronously extract exception from response."""
|
|
457
|
+
if res.is_success:
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
await res.aread()
|
|
461
|
+
return extract_exception(res)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def extract_exception(res: Response):
|
|
465
|
+
"""Extract exception from response."""
|
|
466
|
+
if res.is_success:
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
res.read()
|
|
470
|
+
return format_exception(res)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def format_exception(res: Response):
|
|
474
|
+
"""Format exception from response."""
|
|
475
|
+
if res.is_success:
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
if res.status_code == 404:
|
|
479
|
+
return NotFoundException(res.text)
|
|
480
|
+
elif res.status_code == 502:
|
|
481
|
+
return TimeoutException(
|
|
482
|
+
f"{res.text}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
|
|
483
|
+
)
|
|
484
|
+
else:
|
|
485
|
+
return SandboxException(f"{res.status_code}: {res.text}")
|