watasu-code-interpreter 0.1.47__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.
- watasu_code_interpreter-0.1.47/.gitignore +10 -0
- watasu_code_interpreter-0.1.47/PKG-INFO +45 -0
- watasu_code_interpreter-0.1.47/README.md +35 -0
- watasu_code_interpreter-0.1.47/pyproject.toml +20 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/__init__.py +36 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/charts.py +223 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/code_interpreter_async.py +3 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/code_interpreter_sync.py +3 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/constants.py +5 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/exceptions.py +18 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/main.py +289 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/models.py +303 -0
- watasu_code_interpreter-0.1.47/src/watasu_code_interpreter/py.typed +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: watasu-code-interpreter
|
|
3
|
+
Version: 0.1.47
|
|
4
|
+
Summary: Code Interpreter SDK for Watasu
|
|
5
|
+
Project-URL: Repository, https://github.com/watasuio/sdk
|
|
6
|
+
License-Expression: MIT OR Apache-2.0
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: watasu<0.2,>=0.1.47
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Watasu Code Interpreter Python SDK
|
|
12
|
+
|
|
13
|
+
Python package for Watasu code interpreter sandboxes.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install watasu-code-interpreter
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Set `WATASU_API_KEY` before using the SDK.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from watasu_code_interpreter import Sandbox
|
|
27
|
+
|
|
28
|
+
with Sandbox.create() as sbx:
|
|
29
|
+
context = sbx.create_code_context()
|
|
30
|
+
execution = sbx.run_code(
|
|
31
|
+
"print('hello')\n2 + 3",
|
|
32
|
+
context=context,
|
|
33
|
+
on_stdout=lambda message: print(message.line),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
print(execution.text)
|
|
37
|
+
sbx.remove_code_context(context)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`Sandbox.create()` starts the `code-interpreter` template by default. Code runs
|
|
41
|
+
in persistent contexts and returns structured `results`, `logs`, and `error`
|
|
42
|
+
fields for each execution.
|
|
43
|
+
|
|
44
|
+
The package installs `watasu` and exposes the `watasu_code_interpreter` import
|
|
45
|
+
path.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Watasu Code Interpreter Python SDK
|
|
2
|
+
|
|
3
|
+
Python package for Watasu code interpreter sandboxes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install watasu-code-interpreter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Set `WATASU_API_KEY` before using the SDK.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from watasu_code_interpreter import Sandbox
|
|
17
|
+
|
|
18
|
+
with Sandbox.create() as sbx:
|
|
19
|
+
context = sbx.create_code_context()
|
|
20
|
+
execution = sbx.run_code(
|
|
21
|
+
"print('hello')\n2 + 3",
|
|
22
|
+
context=context,
|
|
23
|
+
on_stdout=lambda message: print(message.line),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
print(execution.text)
|
|
27
|
+
sbx.remove_code_context(context)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`Sandbox.create()` starts the `code-interpreter` template by default. Code runs
|
|
31
|
+
in persistent contexts and returns structured `results`, `logs`, and `error`
|
|
32
|
+
fields for each execution.
|
|
33
|
+
|
|
34
|
+
The package installs `watasu` and exposes the `watasu_code_interpreter` import
|
|
35
|
+
path.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "watasu-code-interpreter"
|
|
7
|
+
version = "0.1.47"
|
|
8
|
+
description = "Code Interpreter SDK for Watasu"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT OR Apache-2.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"watasu>=0.1.47,<0.2",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Repository = "https://github.com/watasuio/sdk"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/watasu_code_interpreter"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Code execution helpers for the Watasu Python SDK."""
|
|
2
|
+
|
|
3
|
+
from watasu import * # noqa: F403
|
|
4
|
+
from watasu import __all__ as _watasu_all
|
|
5
|
+
|
|
6
|
+
from . import code_interpreter_async, code_interpreter_sync, constants, exceptions
|
|
7
|
+
from .main import AsyncSandbox, Sandbox
|
|
8
|
+
from .models import (
|
|
9
|
+
Context,
|
|
10
|
+
Execution,
|
|
11
|
+
ExecutionError,
|
|
12
|
+
Logs,
|
|
13
|
+
MIMEType,
|
|
14
|
+
OutputHandler,
|
|
15
|
+
OutputMessage,
|
|
16
|
+
Result,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_code_interpreter_all = [
|
|
20
|
+
"AsyncSandbox",
|
|
21
|
+
"Context",
|
|
22
|
+
"code_interpreter_async",
|
|
23
|
+
"code_interpreter_sync",
|
|
24
|
+
"constants",
|
|
25
|
+
"Execution",
|
|
26
|
+
"ExecutionError",
|
|
27
|
+
"exceptions",
|
|
28
|
+
"Logs",
|
|
29
|
+
"MIMEType",
|
|
30
|
+
"OutputHandler",
|
|
31
|
+
"OutputMessage",
|
|
32
|
+
"Result",
|
|
33
|
+
"Sandbox",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
__all__ = sorted(set(_watasu_all) | set(_code_interpreter_all))
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChartType(str, enum.Enum):
|
|
8
|
+
"""Supported chart kinds returned by code execution."""
|
|
9
|
+
|
|
10
|
+
LINE = "line"
|
|
11
|
+
SCATTER = "scatter"
|
|
12
|
+
BAR = "bar"
|
|
13
|
+
PIE = "pie"
|
|
14
|
+
BOX_AND_WHISKER = "box_and_whisker"
|
|
15
|
+
SUPERCHART = "superchart"
|
|
16
|
+
UNKNOWN = "unknown"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ScaleType(str, enum.Enum):
|
|
20
|
+
"""Supported chart axis scale kinds."""
|
|
21
|
+
|
|
22
|
+
LINEAR = "linear"
|
|
23
|
+
DATETIME = "datetime"
|
|
24
|
+
CATEGORICAL = "categorical"
|
|
25
|
+
LOG = "log"
|
|
26
|
+
SYMLOG = "symlog"
|
|
27
|
+
LOGIT = "logit"
|
|
28
|
+
FUNCTION = "function"
|
|
29
|
+
FUNCTIONLOG = "functionlog"
|
|
30
|
+
ASINH = "asinh"
|
|
31
|
+
UNKNOWN = "unknown"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Chart:
|
|
35
|
+
"""Extracted chart data for custom visualizations."""
|
|
36
|
+
|
|
37
|
+
type: ChartType
|
|
38
|
+
title: str
|
|
39
|
+
elements: List[Any]
|
|
40
|
+
|
|
41
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
42
|
+
self._raw_data = dict(kwargs)
|
|
43
|
+
self.type = _chart_type(kwargs.get("type"))
|
|
44
|
+
self.title = str(kwargs.get("title") or "")
|
|
45
|
+
self.elements = list(kwargs.get("elements") or [])
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> dict:
|
|
48
|
+
return self._raw_data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Chart2D(Chart):
|
|
52
|
+
x_label: Optional[str]
|
|
53
|
+
y_label: Optional[str]
|
|
54
|
+
x_unit: Optional[str]
|
|
55
|
+
y_unit: Optional[str]
|
|
56
|
+
|
|
57
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
58
|
+
super().__init__(**kwargs)
|
|
59
|
+
self.x_label = kwargs.get("x_label")
|
|
60
|
+
self.y_label = kwargs.get("y_label")
|
|
61
|
+
self.x_unit = kwargs.get("x_unit")
|
|
62
|
+
self.y_unit = kwargs.get("y_unit")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PointData:
|
|
66
|
+
label: str
|
|
67
|
+
points: List[Tuple[Union[str, float], Union[str, float]]]
|
|
68
|
+
|
|
69
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
70
|
+
self.label = str(kwargs.get("label") or "")
|
|
71
|
+
self.points = [(x, y) for x, y in kwargs.get("points") or []]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PointChart(Chart2D):
|
|
75
|
+
x_ticks: List[Union[str, float]]
|
|
76
|
+
x_tick_labels: List[str]
|
|
77
|
+
x_scale: ScaleType
|
|
78
|
+
y_ticks: List[Union[str, float]]
|
|
79
|
+
y_tick_labels: List[str]
|
|
80
|
+
y_scale: ScaleType
|
|
81
|
+
elements: List[PointData]
|
|
82
|
+
|
|
83
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
84
|
+
super().__init__(**kwargs)
|
|
85
|
+
self.x_ticks = list(kwargs.get("x_ticks") or [])
|
|
86
|
+
self.x_tick_labels = list(kwargs.get("x_tick_labels") or [])
|
|
87
|
+
self.x_scale = _scale_type(kwargs.get("x_scale"))
|
|
88
|
+
self.y_ticks = list(kwargs.get("y_ticks") or [])
|
|
89
|
+
self.y_tick_labels = list(kwargs.get("y_tick_labels") or [])
|
|
90
|
+
self.y_scale = _scale_type(kwargs.get("y_scale"))
|
|
91
|
+
self.elements = [PointData(**item) for item in kwargs.get("elements") or []]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class LineChart(PointChart):
|
|
95
|
+
type = ChartType.LINE
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ScatterChart(PointChart):
|
|
99
|
+
type = ChartType.SCATTER
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class BarData:
|
|
103
|
+
label: str
|
|
104
|
+
group: str
|
|
105
|
+
value: str
|
|
106
|
+
|
|
107
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
108
|
+
self.label = str(kwargs.get("label") or "")
|
|
109
|
+
self.value = str(kwargs.get("value") or "")
|
|
110
|
+
self.group = str(kwargs.get("group") or "")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BarChart(Chart2D):
|
|
114
|
+
type = ChartType.BAR
|
|
115
|
+
elements: List[BarData]
|
|
116
|
+
|
|
117
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
118
|
+
super().__init__(**kwargs)
|
|
119
|
+
self.elements = [BarData(**item) for item in kwargs.get("elements") or []]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class PieData:
|
|
123
|
+
label: str
|
|
124
|
+
angle: float
|
|
125
|
+
radius: float
|
|
126
|
+
|
|
127
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
128
|
+
self.label = str(kwargs.get("label") or "")
|
|
129
|
+
self.angle = float(kwargs.get("angle") or 0)
|
|
130
|
+
self.radius = float(kwargs.get("radius") or 0)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class PieChart(Chart):
|
|
134
|
+
type = ChartType.PIE
|
|
135
|
+
elements: List[PieData]
|
|
136
|
+
|
|
137
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
138
|
+
super().__init__(**kwargs)
|
|
139
|
+
self.elements = [PieData(**item) for item in kwargs.get("elements") or []]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BoxAndWhiskerData:
|
|
143
|
+
label: str
|
|
144
|
+
min: float
|
|
145
|
+
first_quartile: float
|
|
146
|
+
median: float
|
|
147
|
+
third_quartile: float
|
|
148
|
+
max: float
|
|
149
|
+
outliers: List[float]
|
|
150
|
+
|
|
151
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
152
|
+
self.label = str(kwargs.get("label") or "")
|
|
153
|
+
self.min = float(kwargs.get("min") or 0)
|
|
154
|
+
self.first_quartile = float(kwargs.get("first_quartile") or 0)
|
|
155
|
+
self.median = float(kwargs.get("median") or 0)
|
|
156
|
+
self.third_quartile = float(kwargs.get("third_quartile") or 0)
|
|
157
|
+
self.max = float(kwargs.get("max") or 0)
|
|
158
|
+
self.outliers = [float(item) for item in kwargs.get("outliers") or []]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class BoxAndWhiskerChart(Chart2D):
|
|
162
|
+
type = ChartType.BOX_AND_WHISKER
|
|
163
|
+
elements: List[BoxAndWhiskerData]
|
|
164
|
+
|
|
165
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
166
|
+
super().__init__(**kwargs)
|
|
167
|
+
self.elements = [
|
|
168
|
+
BoxAndWhiskerData(**item) for item in kwargs.get("elements") or []
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class SuperChart(Chart):
|
|
173
|
+
type = ChartType.SUPERCHART
|
|
174
|
+
elements: List[
|
|
175
|
+
Union[LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart, Chart]
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
179
|
+
super().__init__(**kwargs)
|
|
180
|
+
self.elements = [
|
|
181
|
+
_deserialize_chart(item) or Chart(**item)
|
|
182
|
+
for item in kwargs.get("elements") or []
|
|
183
|
+
if isinstance(item, dict)
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
ChartTypes = Union[
|
|
188
|
+
LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart, SuperChart
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _deserialize_chart(data: Optional[dict]) -> Optional[ChartTypes]:
|
|
193
|
+
if not data:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
chart_type = _chart_type(data.get("type"))
|
|
197
|
+
if chart_type == ChartType.LINE:
|
|
198
|
+
return LineChart(**data)
|
|
199
|
+
if chart_type == ChartType.SCATTER:
|
|
200
|
+
return ScatterChart(**data)
|
|
201
|
+
if chart_type == ChartType.BAR:
|
|
202
|
+
return BarChart(**data)
|
|
203
|
+
if chart_type == ChartType.PIE:
|
|
204
|
+
return PieChart(**data)
|
|
205
|
+
if chart_type == ChartType.BOX_AND_WHISKER:
|
|
206
|
+
return BoxAndWhiskerChart(**data)
|
|
207
|
+
if chart_type == ChartType.SUPERCHART:
|
|
208
|
+
return SuperChart(**data)
|
|
209
|
+
return Chart(**data)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _chart_type(value: Any) -> ChartType:
|
|
213
|
+
try:
|
|
214
|
+
return ChartType(value)
|
|
215
|
+
except ValueError:
|
|
216
|
+
return ChartType.UNKNOWN
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _scale_type(value: Any) -> ScaleType:
|
|
220
|
+
try:
|
|
221
|
+
return ScaleType(value)
|
|
222
|
+
except ValueError:
|
|
223
|
+
return ScaleType.UNKNOWN
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from watasu import TimeoutException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def format_request_timeout_error() -> Exception:
|
|
5
|
+
"""Build the standard request-timeout exception."""
|
|
6
|
+
return TimeoutException(
|
|
7
|
+
"Request timed out - the 'request_timeout' option can be used to increase this timeout"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def format_execution_timeout_error() -> Exception:
|
|
12
|
+
"""Build the standard code-execution-timeout exception."""
|
|
13
|
+
return TimeoutException(
|
|
14
|
+
"Execution timed out - the 'timeout' option can be used to increase this timeout"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["format_execution_timeout_error", "format_request_timeout_error"]
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
from watasu.connection_config import ApiParams
|
|
8
|
+
from watasu.exceptions import InvalidArgumentException
|
|
9
|
+
from watasu.sandbox_async.main import AsyncSandbox as BaseAsyncSandbox
|
|
10
|
+
from watasu.sandbox_async.main import _AsyncDualMethod, _thread_callback
|
|
11
|
+
from watasu.sandbox_sync.main import Sandbox as BaseSandbox
|
|
12
|
+
|
|
13
|
+
from .constants import DEFAULT_TEMPLATE
|
|
14
|
+
from .models import Context, Execution, ExecutionError, OutputMessage, Result
|
|
15
|
+
from .models import context_from_api
|
|
16
|
+
from .models import execution_from_api
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Sandbox(BaseSandbox):
|
|
20
|
+
"""Sandbox specialized for running Python code."""
|
|
21
|
+
|
|
22
|
+
default_template = DEFAULT_TEMPLATE
|
|
23
|
+
|
|
24
|
+
def run_code(
|
|
25
|
+
self,
|
|
26
|
+
code: str,
|
|
27
|
+
language: Optional[str] = None,
|
|
28
|
+
context: Optional[Union[Context, str]] = None,
|
|
29
|
+
on_stdout: Optional[Callable[[OutputMessage], None]] = None,
|
|
30
|
+
on_stderr: Optional[Callable[[OutputMessage], None]] = None,
|
|
31
|
+
on_result: Optional[Callable[[Result], None]] = None,
|
|
32
|
+
on_error: Optional[Callable[[ExecutionError], None]] = None,
|
|
33
|
+
envs: Optional[Dict[str, str]] = None,
|
|
34
|
+
timeout: Optional[int] = None,
|
|
35
|
+
request_timeout: Optional[float] = None,
|
|
36
|
+
) -> Execution:
|
|
37
|
+
"""Run Python code in the sandbox and return structured execution output."""
|
|
38
|
+
|
|
39
|
+
if not isinstance(code, str):
|
|
40
|
+
raise InvalidArgumentException("code must be a string")
|
|
41
|
+
if language is not None and context is not None:
|
|
42
|
+
raise InvalidArgumentException("language and context cannot both be set")
|
|
43
|
+
|
|
44
|
+
payload = _compact(
|
|
45
|
+
{
|
|
46
|
+
"code": code,
|
|
47
|
+
"language": language,
|
|
48
|
+
"context_id": _context_id(context),
|
|
49
|
+
"env_vars": envs,
|
|
50
|
+
"timeout_seconds": timeout,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
response = self._require_data_plane().post_json(
|
|
54
|
+
"/runtime/v1/code/run",
|
|
55
|
+
json=payload,
|
|
56
|
+
request_timeout=request_timeout,
|
|
57
|
+
)
|
|
58
|
+
execution = execution_from_api(response)
|
|
59
|
+
_emit_callbacks(execution, on_stdout, on_stderr, on_result, on_error)
|
|
60
|
+
return execution
|
|
61
|
+
|
|
62
|
+
def create_code_context(
|
|
63
|
+
self,
|
|
64
|
+
cwd: Optional[str] = None,
|
|
65
|
+
language: Optional[str] = None,
|
|
66
|
+
request_timeout: Optional[float] = None,
|
|
67
|
+
) -> Context:
|
|
68
|
+
"""Create a persistent code context."""
|
|
69
|
+
|
|
70
|
+
payload = _compact({"cwd": cwd, "language": language})
|
|
71
|
+
response = self._require_data_plane().post_json(
|
|
72
|
+
"/runtime/v1/code/contexts",
|
|
73
|
+
json=payload,
|
|
74
|
+
request_timeout=request_timeout,
|
|
75
|
+
)
|
|
76
|
+
return context_from_api(response)
|
|
77
|
+
|
|
78
|
+
def remove_code_context(
|
|
79
|
+
self,
|
|
80
|
+
context: Union[Context, str],
|
|
81
|
+
request_timeout: Optional[float] = None,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Remove a persistent code context."""
|
|
84
|
+
|
|
85
|
+
self._require_data_plane().delete_json(
|
|
86
|
+
f"/runtime/v1/code/contexts/{_context_path_id(context)}",
|
|
87
|
+
request_timeout=request_timeout,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def list_code_contexts(self, request_timeout: Optional[float] = None) -> List[Context]:
|
|
91
|
+
"""List persistent code contexts."""
|
|
92
|
+
|
|
93
|
+
response = self._require_data_plane().get_json(
|
|
94
|
+
"/runtime/v1/code/contexts",
|
|
95
|
+
request_timeout=request_timeout,
|
|
96
|
+
)
|
|
97
|
+
contexts = response if isinstance(response, list) else response.get("contexts", [])
|
|
98
|
+
return [context_from_api(item) for item in contexts]
|
|
99
|
+
|
|
100
|
+
def restart_code_context(
|
|
101
|
+
self,
|
|
102
|
+
context: Union[Context, str],
|
|
103
|
+
request_timeout: Optional[float] = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Restart a persistent code context."""
|
|
106
|
+
|
|
107
|
+
self._require_data_plane().post_json(
|
|
108
|
+
f"/runtime/v1/code/contexts/{_context_path_id(context)}/restart",
|
|
109
|
+
json={},
|
|
110
|
+
request_timeout=request_timeout,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AsyncSandbox(BaseAsyncSandbox):
|
|
115
|
+
"""Async sandbox specialized for running Python code."""
|
|
116
|
+
|
|
117
|
+
default_template = Sandbox.default_template
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
async def create(cls, *args: Any, **kwargs: Any) -> "AsyncSandbox":
|
|
121
|
+
"""Create a code-interpreter sandbox and return async helpers."""
|
|
122
|
+
|
|
123
|
+
return cls(sync_sandbox=await asyncio.to_thread(Sandbox.create, *args, **kwargs))
|
|
124
|
+
|
|
125
|
+
beta_create = create
|
|
126
|
+
|
|
127
|
+
async def _connect_instance(
|
|
128
|
+
self, timeout: Optional[int] = None, **opts: ApiParams
|
|
129
|
+
) -> "AsyncSandbox":
|
|
130
|
+
"""Reconnect this sandbox and refresh its data-plane session."""
|
|
131
|
+
|
|
132
|
+
await asyncio.to_thread(self._sync.connect, timeout=timeout, **opts)
|
|
133
|
+
self._set_sync(self._sync)
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
async def _connect_class(
|
|
138
|
+
cls, sandbox_id: str, timeout: Optional[int] = None, **opts: ApiParams
|
|
139
|
+
) -> "AsyncSandbox":
|
|
140
|
+
"""Connect to an existing code-interpreter sandbox by id."""
|
|
141
|
+
|
|
142
|
+
return cls(
|
|
143
|
+
sync_sandbox=await asyncio.to_thread(
|
|
144
|
+
Sandbox.connect, sandbox_id, timeout=timeout, **opts
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
connect = _AsyncDualMethod(_connect_instance, _connect_class)
|
|
149
|
+
|
|
150
|
+
async def run_code(
|
|
151
|
+
self,
|
|
152
|
+
code: str,
|
|
153
|
+
language: Optional[str] = None,
|
|
154
|
+
context: Optional[Union[Context, str]] = None,
|
|
155
|
+
on_stdout: Optional[Callable[[OutputMessage], None]] = None,
|
|
156
|
+
on_stderr: Optional[Callable[[OutputMessage], None]] = None,
|
|
157
|
+
on_result: Optional[Callable[[Result], None]] = None,
|
|
158
|
+
on_error: Optional[Callable[[ExecutionError], None]] = None,
|
|
159
|
+
envs: Optional[Dict[str, str]] = None,
|
|
160
|
+
timeout: Optional[int] = None,
|
|
161
|
+
request_timeout: Optional[float] = None,
|
|
162
|
+
) -> Execution:
|
|
163
|
+
"""Run Python code in the sandbox and return structured execution output."""
|
|
164
|
+
|
|
165
|
+
loop = asyncio.get_running_loop()
|
|
166
|
+
return await asyncio.to_thread(
|
|
167
|
+
self._sync.run_code,
|
|
168
|
+
code,
|
|
169
|
+
language=language,
|
|
170
|
+
context=context,
|
|
171
|
+
on_stdout=_thread_callback(loop, on_stdout),
|
|
172
|
+
on_stderr=_thread_callback(loop, on_stderr),
|
|
173
|
+
on_result=_thread_callback(loop, on_result),
|
|
174
|
+
on_error=_thread_callback(loop, on_error),
|
|
175
|
+
envs=envs,
|
|
176
|
+
timeout=timeout,
|
|
177
|
+
request_timeout=request_timeout,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
async def create_code_context(
|
|
181
|
+
self,
|
|
182
|
+
cwd: Optional[str] = None,
|
|
183
|
+
language: Optional[str] = None,
|
|
184
|
+
request_timeout: Optional[float] = None,
|
|
185
|
+
) -> Context:
|
|
186
|
+
"""Create a persistent code context."""
|
|
187
|
+
|
|
188
|
+
return await asyncio.to_thread(
|
|
189
|
+
self._sync.create_code_context,
|
|
190
|
+
cwd=cwd,
|
|
191
|
+
language=language,
|
|
192
|
+
request_timeout=request_timeout,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def remove_code_context(
|
|
196
|
+
self,
|
|
197
|
+
context: Union[Context, str],
|
|
198
|
+
request_timeout: Optional[float] = None,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Remove a persistent code context."""
|
|
201
|
+
|
|
202
|
+
await asyncio.to_thread(
|
|
203
|
+
self._sync.remove_code_context,
|
|
204
|
+
context,
|
|
205
|
+
request_timeout=request_timeout,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
async def list_code_contexts(
|
|
209
|
+
self, request_timeout: Optional[float] = None
|
|
210
|
+
) -> List[Context]:
|
|
211
|
+
"""List persistent code contexts."""
|
|
212
|
+
|
|
213
|
+
return await asyncio.to_thread(
|
|
214
|
+
self._sync.list_code_contexts,
|
|
215
|
+
request_timeout=request_timeout,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
async def restart_code_context(
|
|
219
|
+
self,
|
|
220
|
+
context: Union[Context, str],
|
|
221
|
+
request_timeout: Optional[float] = None,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Restart a persistent code context."""
|
|
224
|
+
|
|
225
|
+
await asyncio.to_thread(
|
|
226
|
+
self._sync.restart_code_context,
|
|
227
|
+
context,
|
|
228
|
+
request_timeout=request_timeout,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _emit_callbacks(
|
|
233
|
+
execution: Execution,
|
|
234
|
+
on_stdout: Optional[Callable[[OutputMessage], None]],
|
|
235
|
+
on_stderr: Optional[Callable[[OutputMessage], None]],
|
|
236
|
+
on_result: Optional[Callable[[Result], None]],
|
|
237
|
+
on_error: Optional[Callable[[ExecutionError], None]],
|
|
238
|
+
) -> None:
|
|
239
|
+
for message in execution.logs.stdout:
|
|
240
|
+
if on_stdout is not None:
|
|
241
|
+
on_stdout(message)
|
|
242
|
+
for message in execution.logs.stderr:
|
|
243
|
+
if on_stderr is not None:
|
|
244
|
+
on_stderr(message)
|
|
245
|
+
for result in execution.results:
|
|
246
|
+
if on_result is not None:
|
|
247
|
+
on_result(result)
|
|
248
|
+
if execution.error is not None and on_error is not None:
|
|
249
|
+
on_error(execution.error)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _context_id(context: Optional[Union[Context, str]]) -> Optional[str]:
|
|
253
|
+
if context is None:
|
|
254
|
+
return None
|
|
255
|
+
return _context_id_required(context)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _context_id_required(context: Union[Context, str]) -> str:
|
|
259
|
+
if isinstance(context, str):
|
|
260
|
+
context_id = context
|
|
261
|
+
elif isinstance(context, dict):
|
|
262
|
+
value = context.get("id")
|
|
263
|
+
context_id = str(value) if value is not None else ""
|
|
264
|
+
else:
|
|
265
|
+
value = getattr(context, "id", None)
|
|
266
|
+
context_id = str(value) if value is not None else ""
|
|
267
|
+
|
|
268
|
+
if not context_id:
|
|
269
|
+
raise InvalidArgumentException("context id is required")
|
|
270
|
+
return context_id
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _context_path_id(context: Union[Context, str]) -> str:
|
|
274
|
+
return quote(_context_id_required(context), safe="")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _compact(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
278
|
+
return {key: value for key, value in payload.items() if value is not None}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
__all__ = [
|
|
282
|
+
"AsyncSandbox",
|
|
283
|
+
"Context",
|
|
284
|
+
"Execution",
|
|
285
|
+
"ExecutionError",
|
|
286
|
+
"OutputMessage",
|
|
287
|
+
"Result",
|
|
288
|
+
"Sandbox",
|
|
289
|
+
]
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
from .charts import ChartTypes, _deserialize_chart
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
OutputHandler = Callable[[T], Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MIMEType(str):
|
|
14
|
+
"""MIME type marker used by code execution results."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class OutputMessage:
|
|
19
|
+
"""One stdout or stderr line emitted by code execution."""
|
|
20
|
+
|
|
21
|
+
line: str
|
|
22
|
+
timestamp: float = field(default_factory=time.time)
|
|
23
|
+
error: bool = False
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return self.line
|
|
27
|
+
|
|
28
|
+
def to_json(self) -> Dict[str, Any]:
|
|
29
|
+
return {
|
|
30
|
+
"line": self.line,
|
|
31
|
+
"timestamp": self.timestamp,
|
|
32
|
+
"error": self.error,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Logs:
|
|
38
|
+
"""Captured stdout and stderr output for an execution."""
|
|
39
|
+
|
|
40
|
+
stdout: List[OutputMessage] = field(default_factory=list)
|
|
41
|
+
stderr: List[OutputMessage] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
def to_json(self) -> Dict[str, Any]:
|
|
44
|
+
return {
|
|
45
|
+
"stdout": [message.to_json() for message in self.stdout],
|
|
46
|
+
"stderr": [message.to_json() for message in self.stderr],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ExecutionError:
|
|
52
|
+
"""Structured exception raised by user code inside the sandbox."""
|
|
53
|
+
|
|
54
|
+
name: str
|
|
55
|
+
value: str
|
|
56
|
+
traceback: str
|
|
57
|
+
|
|
58
|
+
def to_json(self) -> Dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"name": self.name,
|
|
61
|
+
"value": self.value,
|
|
62
|
+
"traceback": self.traceback,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Result:
|
|
68
|
+
"""Rich result produced by the last expression of a code execution."""
|
|
69
|
+
|
|
70
|
+
text: Optional[str] = None
|
|
71
|
+
html: Optional[str] = None
|
|
72
|
+
markdown: Optional[str] = None
|
|
73
|
+
svg: Optional[str] = None
|
|
74
|
+
png: Optional[str] = None
|
|
75
|
+
jpeg: Optional[str] = None
|
|
76
|
+
pdf: Optional[str] = None
|
|
77
|
+
latex: Optional[str] = None
|
|
78
|
+
json: Any = None
|
|
79
|
+
javascript: Optional[str] = None
|
|
80
|
+
data: Any = None
|
|
81
|
+
chart: Optional[ChartTypes] = None
|
|
82
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
83
|
+
is_main_result: bool = False
|
|
84
|
+
|
|
85
|
+
def __post_init__(self) -> None:
|
|
86
|
+
if isinstance(self.chart, dict):
|
|
87
|
+
self.chart = _deserialize_chart(self.chart)
|
|
88
|
+
|
|
89
|
+
def __getitem__(self, item):
|
|
90
|
+
return getattr(self, item)
|
|
91
|
+
|
|
92
|
+
def formats(self) -> List[str]:
|
|
93
|
+
"""Return available display formats for this result."""
|
|
94
|
+
|
|
95
|
+
names = [
|
|
96
|
+
"text",
|
|
97
|
+
"html",
|
|
98
|
+
"markdown",
|
|
99
|
+
"svg",
|
|
100
|
+
"png",
|
|
101
|
+
"jpeg",
|
|
102
|
+
"pdf",
|
|
103
|
+
"latex",
|
|
104
|
+
"json",
|
|
105
|
+
"javascript",
|
|
106
|
+
"data",
|
|
107
|
+
"chart",
|
|
108
|
+
]
|
|
109
|
+
formats = [name for name in names if getattr(self, name) is not None]
|
|
110
|
+
if self.extra:
|
|
111
|
+
formats.extend(self.extra)
|
|
112
|
+
return formats
|
|
113
|
+
|
|
114
|
+
def to_json(self) -> Dict[str, Any]:
|
|
115
|
+
payload = {
|
|
116
|
+
"text": self.text,
|
|
117
|
+
"html": self.html,
|
|
118
|
+
"markdown": self.markdown,
|
|
119
|
+
"svg": self.svg,
|
|
120
|
+
"png": self.png,
|
|
121
|
+
"jpeg": self.jpeg,
|
|
122
|
+
"pdf": self.pdf,
|
|
123
|
+
"latex": self.latex,
|
|
124
|
+
"json": self.json,
|
|
125
|
+
"javascript": self.javascript,
|
|
126
|
+
"data": self.data,
|
|
127
|
+
"chart": self.chart,
|
|
128
|
+
"extra": self.extra,
|
|
129
|
+
"is_main_result": self.is_main_result,
|
|
130
|
+
}
|
|
131
|
+
return {key: value for key, value in payload.items() if value is not None}
|
|
132
|
+
|
|
133
|
+
def __repr__(self) -> str:
|
|
134
|
+
if self.text:
|
|
135
|
+
return f"Result({self.text})"
|
|
136
|
+
return "Result(Formats: " + ", ".join(self.formats()) + ")"
|
|
137
|
+
|
|
138
|
+
def __str__(self) -> str:
|
|
139
|
+
return self.__repr__()
|
|
140
|
+
|
|
141
|
+
def _repr_html_(self) -> Optional[str]:
|
|
142
|
+
return self.html
|
|
143
|
+
|
|
144
|
+
def _repr_markdown_(self) -> Optional[str]:
|
|
145
|
+
return self.markdown
|
|
146
|
+
|
|
147
|
+
def _repr_svg_(self) -> Optional[str]:
|
|
148
|
+
return self.svg
|
|
149
|
+
|
|
150
|
+
def _repr_png_(self) -> Optional[str]:
|
|
151
|
+
return self.png
|
|
152
|
+
|
|
153
|
+
def _repr_jpeg_(self) -> Optional[str]:
|
|
154
|
+
return self.jpeg
|
|
155
|
+
|
|
156
|
+
def _repr_pdf_(self) -> Optional[str]:
|
|
157
|
+
return self.pdf
|
|
158
|
+
|
|
159
|
+
def _repr_latex_(self) -> Optional[str]:
|
|
160
|
+
return self.latex
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class Execution:
|
|
165
|
+
"""Complete result of a sandbox code execution."""
|
|
166
|
+
|
|
167
|
+
results: List[Result] = field(default_factory=list)
|
|
168
|
+
logs: Logs = field(default_factory=Logs)
|
|
169
|
+
error: Optional[ExecutionError] = None
|
|
170
|
+
execution_count: Optional[int] = None
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def text(self) -> Optional[str]:
|
|
174
|
+
"""Text for the main result, when code produced one."""
|
|
175
|
+
|
|
176
|
+
for result in self.results:
|
|
177
|
+
if result.is_main_result and result.text is not None:
|
|
178
|
+
return result.text
|
|
179
|
+
for result in self.results:
|
|
180
|
+
if result.text is not None:
|
|
181
|
+
return result.text
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
def to_json(self) -> Dict[str, Any]:
|
|
185
|
+
return {
|
|
186
|
+
"results": [result.to_json() for result in self.results],
|
|
187
|
+
"logs": self.logs.to_json(),
|
|
188
|
+
"error": self.error.to_json() if self.error else None,
|
|
189
|
+
"execution_count": self.execution_count,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass(init=False)
|
|
194
|
+
class Context:
|
|
195
|
+
"""Code execution context metadata."""
|
|
196
|
+
|
|
197
|
+
id: str
|
|
198
|
+
language: Optional[str] = None
|
|
199
|
+
cwd: Optional[str] = None
|
|
200
|
+
|
|
201
|
+
def __init__(
|
|
202
|
+
self,
|
|
203
|
+
id: Optional[str] = None,
|
|
204
|
+
language: Optional[str] = None,
|
|
205
|
+
cwd: Optional[str] = None,
|
|
206
|
+
context_id: Optional[str] = None,
|
|
207
|
+
**_: Any,
|
|
208
|
+
) -> None:
|
|
209
|
+
self.id = str(context_id if context_id is not None else id)
|
|
210
|
+
self.language = language
|
|
211
|
+
self.cwd = cwd
|
|
212
|
+
|
|
213
|
+
def to_json(self) -> Dict[str, Any]:
|
|
214
|
+
return {
|
|
215
|
+
"id": self.id,
|
|
216
|
+
"language": self.language,
|
|
217
|
+
"cwd": self.cwd,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def execution_from_api(payload: Dict[str, Any]) -> Execution:
|
|
222
|
+
execution = payload.get("execution") or payload
|
|
223
|
+
logs = execution.get("logs") or {}
|
|
224
|
+
return Execution(
|
|
225
|
+
results=[result_from_api(item) for item in execution.get("results") or []],
|
|
226
|
+
logs=Logs(
|
|
227
|
+
stdout=[
|
|
228
|
+
output_message_from_api(item, error=False)
|
|
229
|
+
for item in logs.get("stdout") or []
|
|
230
|
+
],
|
|
231
|
+
stderr=[
|
|
232
|
+
output_message_from_api(item, error=True)
|
|
233
|
+
for item in logs.get("stderr") or []
|
|
234
|
+
],
|
|
235
|
+
),
|
|
236
|
+
error=error_from_api(execution.get("error")),
|
|
237
|
+
execution_count=execution.get("execution_count"),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def context_from_api(payload: Dict[str, Any]) -> Context:
|
|
242
|
+
return Context(
|
|
243
|
+
id=payload.get("id"),
|
|
244
|
+
language=payload.get("language"),
|
|
245
|
+
cwd=payload.get("cwd"),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def result_from_api(payload: Dict[str, Any]) -> Result:
|
|
250
|
+
known = {
|
|
251
|
+
"text",
|
|
252
|
+
"html",
|
|
253
|
+
"markdown",
|
|
254
|
+
"svg",
|
|
255
|
+
"png",
|
|
256
|
+
"jpeg",
|
|
257
|
+
"pdf",
|
|
258
|
+
"latex",
|
|
259
|
+
"json",
|
|
260
|
+
"javascript",
|
|
261
|
+
"data",
|
|
262
|
+
"chart",
|
|
263
|
+
"extra",
|
|
264
|
+
"is_main_result",
|
|
265
|
+
}
|
|
266
|
+
return Result(
|
|
267
|
+
text=payload.get("text"),
|
|
268
|
+
html=payload.get("html"),
|
|
269
|
+
markdown=payload.get("markdown"),
|
|
270
|
+
svg=payload.get("svg"),
|
|
271
|
+
png=payload.get("png"),
|
|
272
|
+
jpeg=payload.get("jpeg"),
|
|
273
|
+
pdf=payload.get("pdf"),
|
|
274
|
+
latex=payload.get("latex"),
|
|
275
|
+
json=payload.get("json"),
|
|
276
|
+
javascript=payload.get("javascript"),
|
|
277
|
+
data=payload.get("data"),
|
|
278
|
+
chart=_deserialize_chart(payload.get("chart")),
|
|
279
|
+
extra=payload.get("extra") or {
|
|
280
|
+
key: value for key, value in payload.items() if key not in known
|
|
281
|
+
},
|
|
282
|
+
is_main_result=bool(payload.get("is_main_result")),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def output_message_from_api(payload: Any, error: bool) -> OutputMessage:
|
|
287
|
+
if isinstance(payload, dict):
|
|
288
|
+
return OutputMessage(
|
|
289
|
+
line=str(payload.get("line", "")),
|
|
290
|
+
timestamp=float(payload.get("timestamp") or time.time()),
|
|
291
|
+
error=bool(payload.get("error", error)),
|
|
292
|
+
)
|
|
293
|
+
return OutputMessage(line=str(payload), error=error)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def error_from_api(payload: Any) -> Optional[ExecutionError]:
|
|
297
|
+
if not isinstance(payload, dict):
|
|
298
|
+
return None
|
|
299
|
+
return ExecutionError(
|
|
300
|
+
name=str(payload.get("name") or ""),
|
|
301
|
+
value=str(payload.get("value") or ""),
|
|
302
|
+
traceback=str(payload.get("traceback") or ""),
|
|
303
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|