e2b-code-interpreter 0.0.1a0__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.
- e2b_code_interpreter-0.0.1a0/PKG-INFO +151 -0
- e2b_code_interpreter-0.0.1a0/README.md +128 -0
- e2b_code_interpreter-0.0.1a0/e2b_code_interpreter/__init__.py +2 -0
- e2b_code_interpreter-0.0.1a0/e2b_code_interpreter/main.py +289 -0
- e2b_code_interpreter-0.0.1a0/e2b_code_interpreter/messaging.py +253 -0
- e2b_code_interpreter-0.0.1a0/e2b_code_interpreter/models.py +215 -0
- e2b_code_interpreter-0.0.1a0/pyproject.toml +31 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: e2b-code-interpreter
|
|
3
|
+
Version: 0.0.1a0
|
|
4
|
+
Summary: E2B Code Interpreter - Stateful code execution
|
|
5
|
+
Home-page: https://e2b.dev/
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: e2b
|
|
8
|
+
Author-email: hello@e2b.dev
|
|
9
|
+
Requires-Python: >=3.8,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Requires-Dist: e2b (>=0.14.11)
|
|
17
|
+
Requires-Dist: pydantic (>1,<3)
|
|
18
|
+
Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
|
|
19
|
+
Project-URL: Bug Tracker, https://github.com/e2b-dev/code-interpreter/issues
|
|
20
|
+
Project-URL: Repository, https://github.com/e2b-dev/e2b-code-interpreter/tree/python
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Code interpreter extension for Python
|
|
24
|
+
|
|
25
|
+
The repository contains a template and modules for the code interpreter sandbox. It is based on the Jupyter server and implements the Jupyter Kernel messaging protocol. This allows for sharing context between code executions and improves support for plotting charts and other display-able data.
|
|
26
|
+
|
|
27
|
+
## Key Features
|
|
28
|
+
|
|
29
|
+
- **Stateful Execution**: Unlike traditional sandboxes that treat each code execution independently, this package maintains context across executions.
|
|
30
|
+
- **Displaying Graph & Data**: Implements parts of the [Jupyter Kernel messaging protocol](https://jupyter-client.readthedocs.io/en/latest/messaging.html), which support for interactive features like plotting charts, rendering DataFrames, etc.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pip install e2b-code-interpreter
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### Minimal example with the sharing context
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
44
|
+
|
|
45
|
+
with CodeInterpreter() as sandbox:
|
|
46
|
+
sandbox.exec_cell("x = 1")
|
|
47
|
+
|
|
48
|
+
result = sandbox.exec_cell("x += 1; x")
|
|
49
|
+
print(result.text) # outputs 2
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Get charts and any display-able data
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import base64
|
|
57
|
+
import io
|
|
58
|
+
|
|
59
|
+
from matplotlib import image as mpimg, pyplot as plt
|
|
60
|
+
|
|
61
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
62
|
+
|
|
63
|
+
code = """
|
|
64
|
+
import matplotlib.pyplot as plt
|
|
65
|
+
import numpy as np
|
|
66
|
+
|
|
67
|
+
x = np.linspace(0, 20, 100)
|
|
68
|
+
y = np.sin(x)
|
|
69
|
+
|
|
70
|
+
plt.plot(x, y)
|
|
71
|
+
plt.show()
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
with CodeInterpreter() as sandbox:
|
|
75
|
+
# you can install dependencies in "jupyter notebook style"
|
|
76
|
+
sandbox.exec_cell("!pip install matplotlib")
|
|
77
|
+
|
|
78
|
+
# plot random graph
|
|
79
|
+
result = sandbox.exec_cell(code)
|
|
80
|
+
|
|
81
|
+
# there's your image
|
|
82
|
+
image = result.display_data[0]["image/png"]
|
|
83
|
+
|
|
84
|
+
# example how to show the image / prove it works
|
|
85
|
+
i = base64.b64decode(image)
|
|
86
|
+
i = io.BytesIO(i)
|
|
87
|
+
i = mpimg.imread(i, format='PNG')
|
|
88
|
+
|
|
89
|
+
plt.imshow(i, interpolation='nearest')
|
|
90
|
+
plt.show()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Streaming code output
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
97
|
+
|
|
98
|
+
code = """
|
|
99
|
+
import time
|
|
100
|
+
|
|
101
|
+
print("hello")
|
|
102
|
+
time.sleep(5)
|
|
103
|
+
print("world")
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
with CodeInterpreter() as sandbox:
|
|
107
|
+
sandbox.exec_cell(code, on_stdout=print, on_stderr=print)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pre-installed Python packages inside the sandbox
|
|
111
|
+
|
|
112
|
+
The full and always up-to-date list can be found in the [`requirements.txt`](https://github.com/e2b-dev/E2B/blob/stateful-code-interpreter/sandboxes/code-interpreter-stateful/requirements.txt) file.
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
# Jupyter server requirements
|
|
116
|
+
jupyter-server==2.13.0
|
|
117
|
+
ipykernel==6.29.3
|
|
118
|
+
ipython==8.22.2
|
|
119
|
+
|
|
120
|
+
# Other packages
|
|
121
|
+
aiohttp==3.9.3
|
|
122
|
+
beautifulsoup4==4.12.3
|
|
123
|
+
bokeh==3.3.4
|
|
124
|
+
gensim==4.3.2
|
|
125
|
+
imageio==2.34.0
|
|
126
|
+
joblib==1.3.2
|
|
127
|
+
librosa==0.10.1
|
|
128
|
+
matplotlib==3.8.3
|
|
129
|
+
nltk==3.8.1
|
|
130
|
+
numpy==1.26.4
|
|
131
|
+
opencv-python==4.9.0.80
|
|
132
|
+
openpyxl==3.1.2
|
|
133
|
+
pandas==1.5.3
|
|
134
|
+
plotly==5.19.0
|
|
135
|
+
pytest==8.1.0
|
|
136
|
+
python-docx==1.1.0
|
|
137
|
+
pytz==2024.1
|
|
138
|
+
requests==2.26.0
|
|
139
|
+
scikit-image==0.22.0
|
|
140
|
+
scikit-learn==1.4.1.post1
|
|
141
|
+
scipy==1.12.0
|
|
142
|
+
seaborn==0.13.2
|
|
143
|
+
soundfile==0.12.1
|
|
144
|
+
spacy==3.7.4
|
|
145
|
+
textblob==0.18.0
|
|
146
|
+
tornado==6.4
|
|
147
|
+
urllib3==1.26.7
|
|
148
|
+
xarray==2024.2.0
|
|
149
|
+
xlrd==2.0.1
|
|
150
|
+
```
|
|
151
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Code interpreter extension for Python
|
|
2
|
+
|
|
3
|
+
The repository contains a template and modules for the code interpreter sandbox. It is based on the Jupyter server and implements the Jupyter Kernel messaging protocol. This allows for sharing context between code executions and improves support for plotting charts and other display-able data.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Stateful Execution**: Unlike traditional sandboxes that treat each code execution independently, this package maintains context across executions.
|
|
8
|
+
- **Displaying Graph & Data**: Implements parts of the [Jupyter Kernel messaging protocol](https://jupyter-client.readthedocs.io/en/latest/messaging.html), which support for interactive features like plotting charts, rendering DataFrames, etc.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install e2b-code-interpreter
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
### Minimal example with the sharing context
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
22
|
+
|
|
23
|
+
with CodeInterpreter() as sandbox:
|
|
24
|
+
sandbox.exec_cell("x = 1")
|
|
25
|
+
|
|
26
|
+
result = sandbox.exec_cell("x += 1; x")
|
|
27
|
+
print(result.text) # outputs 2
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Get charts and any display-able data
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import base64
|
|
35
|
+
import io
|
|
36
|
+
|
|
37
|
+
from matplotlib import image as mpimg, pyplot as plt
|
|
38
|
+
|
|
39
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
40
|
+
|
|
41
|
+
code = """
|
|
42
|
+
import matplotlib.pyplot as plt
|
|
43
|
+
import numpy as np
|
|
44
|
+
|
|
45
|
+
x = np.linspace(0, 20, 100)
|
|
46
|
+
y = np.sin(x)
|
|
47
|
+
|
|
48
|
+
plt.plot(x, y)
|
|
49
|
+
plt.show()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
with CodeInterpreter() as sandbox:
|
|
53
|
+
# you can install dependencies in "jupyter notebook style"
|
|
54
|
+
sandbox.exec_cell("!pip install matplotlib")
|
|
55
|
+
|
|
56
|
+
# plot random graph
|
|
57
|
+
result = sandbox.exec_cell(code)
|
|
58
|
+
|
|
59
|
+
# there's your image
|
|
60
|
+
image = result.display_data[0]["image/png"]
|
|
61
|
+
|
|
62
|
+
# example how to show the image / prove it works
|
|
63
|
+
i = base64.b64decode(image)
|
|
64
|
+
i = io.BytesIO(i)
|
|
65
|
+
i = mpimg.imread(i, format='PNG')
|
|
66
|
+
|
|
67
|
+
plt.imshow(i, interpolation='nearest')
|
|
68
|
+
plt.show()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Streaming code output
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from e2b_code_interpreter import CodeInterpreter
|
|
75
|
+
|
|
76
|
+
code = """
|
|
77
|
+
import time
|
|
78
|
+
|
|
79
|
+
print("hello")
|
|
80
|
+
time.sleep(5)
|
|
81
|
+
print("world")
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
with CodeInterpreter() as sandbox:
|
|
85
|
+
sandbox.exec_cell(code, on_stdout=print, on_stderr=print)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pre-installed Python packages inside the sandbox
|
|
89
|
+
|
|
90
|
+
The full and always up-to-date list can be found in the [`requirements.txt`](https://github.com/e2b-dev/E2B/blob/stateful-code-interpreter/sandboxes/code-interpreter-stateful/requirements.txt) file.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
# Jupyter server requirements
|
|
94
|
+
jupyter-server==2.13.0
|
|
95
|
+
ipykernel==6.29.3
|
|
96
|
+
ipython==8.22.2
|
|
97
|
+
|
|
98
|
+
# Other packages
|
|
99
|
+
aiohttp==3.9.3
|
|
100
|
+
beautifulsoup4==4.12.3
|
|
101
|
+
bokeh==3.3.4
|
|
102
|
+
gensim==4.3.2
|
|
103
|
+
imageio==2.34.0
|
|
104
|
+
joblib==1.3.2
|
|
105
|
+
librosa==0.10.1
|
|
106
|
+
matplotlib==3.8.3
|
|
107
|
+
nltk==3.8.1
|
|
108
|
+
numpy==1.26.4
|
|
109
|
+
opencv-python==4.9.0.80
|
|
110
|
+
openpyxl==3.1.2
|
|
111
|
+
pandas==1.5.3
|
|
112
|
+
plotly==5.19.0
|
|
113
|
+
pytest==8.1.0
|
|
114
|
+
python-docx==1.1.0
|
|
115
|
+
pytz==2024.1
|
|
116
|
+
requests==2.26.0
|
|
117
|
+
scikit-image==0.22.0
|
|
118
|
+
scikit-learn==1.4.1.post1
|
|
119
|
+
scipy==1.12.0
|
|
120
|
+
seaborn==0.13.2
|
|
121
|
+
soundfile==0.12.1
|
|
122
|
+
spacy==3.7.4
|
|
123
|
+
textblob==0.18.0
|
|
124
|
+
tornado==6.4
|
|
125
|
+
urllib3==1.26.7
|
|
126
|
+
xarray==2024.2.0
|
|
127
|
+
xlrd==2.0.1
|
|
128
|
+
```
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
from concurrent.futures import Future
|
|
6
|
+
from typing import Any, Callable, List, Optional, Dict
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from e2b import EnvVars, ProcessMessage, Sandbox
|
|
10
|
+
from e2b.constants import TIMEOUT
|
|
11
|
+
|
|
12
|
+
from e2b_code_interpreter.messaging import JupyterKernelWebSocket
|
|
13
|
+
from e2b_code_interpreter.models import KernelException, Result
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CodeInterpreter(Sandbox):
|
|
20
|
+
"""
|
|
21
|
+
E2B code interpreter sandbox extension.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
template = "code-interpreter-stateful"
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
template: Optional[str] = None,
|
|
29
|
+
api_key: Optional[str] = None,
|
|
30
|
+
cwd: Optional[str] = None,
|
|
31
|
+
env_vars: Optional[EnvVars] = None,
|
|
32
|
+
timeout: Optional[float] = TIMEOUT,
|
|
33
|
+
on_stdout: Optional[Callable[[ProcessMessage], Any]] = None,
|
|
34
|
+
on_stderr: Optional[Callable[[ProcessMessage], Any]] = None,
|
|
35
|
+
on_exit: Optional[Callable[[int], Any]] = None,
|
|
36
|
+
**kwargs,
|
|
37
|
+
):
|
|
38
|
+
super().__init__(
|
|
39
|
+
template=template or self.template,
|
|
40
|
+
api_key=api_key,
|
|
41
|
+
cwd=cwd,
|
|
42
|
+
env_vars=env_vars,
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
on_stdout=on_stdout,
|
|
45
|
+
on_stderr=on_stderr,
|
|
46
|
+
on_exit=on_exit,
|
|
47
|
+
**kwargs,
|
|
48
|
+
)
|
|
49
|
+
self.notebook = JupyterExtension(self, timeout=timeout)
|
|
50
|
+
# Close all the websocket connections when the interpreter is closed
|
|
51
|
+
self._process_cleanup.append(self.notebook.close)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class JupyterExtension:
|
|
55
|
+
_default_kernel_id: Optional[str] = None
|
|
56
|
+
_connected_kernels: Dict[str, Future[JupyterKernelWebSocket]] = {}
|
|
57
|
+
|
|
58
|
+
def __init__(self, sandbox: CodeInterpreter, timeout: Optional[float] = TIMEOUT):
|
|
59
|
+
self._sandbox = sandbox
|
|
60
|
+
self._kernel_id_set = Future()
|
|
61
|
+
self._start_connecting_to_default_kernel(timeout=timeout)
|
|
62
|
+
|
|
63
|
+
def exec_cell(
|
|
64
|
+
self,
|
|
65
|
+
code: str,
|
|
66
|
+
kernel_id: Optional[str] = None,
|
|
67
|
+
on_stdout: Optional[Callable[[ProcessMessage], Any]] = None,
|
|
68
|
+
on_stderr: Optional[Callable[[ProcessMessage], Any]] = None,
|
|
69
|
+
on_display_data: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
|
70
|
+
timeout: Optional[float] = TIMEOUT,
|
|
71
|
+
) -> Result:
|
|
72
|
+
"""
|
|
73
|
+
Execute code in a notebook cell.
|
|
74
|
+
|
|
75
|
+
:param code: Code to execute
|
|
76
|
+
:param kernel_id: The ID of the kernel to execute the code on. If not provided, the default kernel is used.
|
|
77
|
+
:param on_stdout: A callback function to handle standard output messages from the code execution.
|
|
78
|
+
:param on_stderr: A callback function to handle standard error messages from the code execution.
|
|
79
|
+
:param on_display_data: A callback function to handle display data messages from the code execution.
|
|
80
|
+
:param timeout: Timeout for the call
|
|
81
|
+
|
|
82
|
+
:return: Result of the execution
|
|
83
|
+
"""
|
|
84
|
+
kernel_id = kernel_id or self.default_kernel_id
|
|
85
|
+
ws_future = self._connected_kernels.get(kernel_id)
|
|
86
|
+
|
|
87
|
+
logger.debug(f"Executing code in kernel {kernel_id}")
|
|
88
|
+
|
|
89
|
+
if ws_future:
|
|
90
|
+
logger.debug(f"Using existing websocket connection to kernel {kernel_id}")
|
|
91
|
+
ws = ws_future.result(timeout=timeout)
|
|
92
|
+
else:
|
|
93
|
+
logger.debug(f"Creating new websocket connection to kernel {kernel_id}")
|
|
94
|
+
ws = self._connect_to_kernel_ws(kernel_id, timeout=timeout)
|
|
95
|
+
|
|
96
|
+
session_id = ws.send_execution_message(code, on_stdout, on_stderr, on_display_data)
|
|
97
|
+
logger.debug(
|
|
98
|
+
f"Sent execution message to kernel {kernel_id}, session_id: {session_id}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
result = ws.get_result(session_id, timeout=timeout)
|
|
102
|
+
logger.debug(
|
|
103
|
+
f"Received result from kernel {kernel_id}, session_id: {session_id}, result: {result}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def default_kernel_id(self) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Get the default kernel id
|
|
112
|
+
|
|
113
|
+
:return: Default kernel id
|
|
114
|
+
"""
|
|
115
|
+
if not self._default_kernel_id:
|
|
116
|
+
logger.debug("Waiting for default kernel id")
|
|
117
|
+
self._default_kernel_id = self._kernel_id_set.result()
|
|
118
|
+
|
|
119
|
+
return self._default_kernel_id
|
|
120
|
+
|
|
121
|
+
def create_kernel(
|
|
122
|
+
self,
|
|
123
|
+
cwd: str = "/home/user",
|
|
124
|
+
kernel_name: Optional[str] = None,
|
|
125
|
+
timeout: Optional[float] = TIMEOUT,
|
|
126
|
+
) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Creates a new kernel, this can be useful if you want to have multiple independent code execution environments.
|
|
129
|
+
|
|
130
|
+
The kernel can be optionally configured to start in a specific working directory and/or
|
|
131
|
+
with a specific kernel name. If no kernel name is provided, the default kernel will be used.
|
|
132
|
+
Once the kernel is created, this method establishes a WebSocket connection to the new kernel for
|
|
133
|
+
real-time communication.
|
|
134
|
+
|
|
135
|
+
:param cwd: Sets the current working directory for the kernel. Defaults to "/home/user".
|
|
136
|
+
:param kernel_name:
|
|
137
|
+
Specifies which kernel should be used, useful if you have multiple kernel types.
|
|
138
|
+
If not provided, the default kernel will be used.
|
|
139
|
+
:param timeout: Timeout for the kernel creation request.
|
|
140
|
+
:return: Kernel id of the created kernel
|
|
141
|
+
"""
|
|
142
|
+
data = {"cwd": cwd}
|
|
143
|
+
if kernel_name:
|
|
144
|
+
data["kernel_name"] = kernel_name
|
|
145
|
+
logger.debug(f"Creating kernel with data: {data}")
|
|
146
|
+
|
|
147
|
+
response = requests.post(
|
|
148
|
+
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
|
|
149
|
+
json=data,
|
|
150
|
+
timeout=timeout,
|
|
151
|
+
)
|
|
152
|
+
if not response.ok:
|
|
153
|
+
raise KernelException(f"Failed to create kernel: {response.text}")
|
|
154
|
+
|
|
155
|
+
kernel_id = response.json()["id"]
|
|
156
|
+
logger.debug(f"Created kernel {kernel_id}")
|
|
157
|
+
|
|
158
|
+
threading.Thread(
|
|
159
|
+
target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
|
|
160
|
+
).start()
|
|
161
|
+
return kernel_id
|
|
162
|
+
|
|
163
|
+
def restart_kernel(
|
|
164
|
+
self, kernel_id: Optional[str] = None, timeout: Optional[float] = TIMEOUT
|
|
165
|
+
) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Restarts an existing Jupyter kernel. This can be useful to reset the kernel's state or to recover from errors.
|
|
168
|
+
|
|
169
|
+
:param kernel_id: The unique identifier of the kernel to restart. If not provided, the default kernel is restarted.
|
|
170
|
+
:param timeout: The timeout in milliseconds for the kernel restart request.
|
|
171
|
+
"""
|
|
172
|
+
kernel_id = kernel_id or self.default_kernel_id
|
|
173
|
+
logger.debug(f"Restarting kernel {kernel_id}")
|
|
174
|
+
|
|
175
|
+
self._connected_kernels[kernel_id].result().close()
|
|
176
|
+
del self._connected_kernels[kernel_id]
|
|
177
|
+
logger.debug(f"Closed websocket connection to kernel {kernel_id}")
|
|
178
|
+
|
|
179
|
+
response = requests.post(
|
|
180
|
+
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}/restart",
|
|
181
|
+
timeout=timeout,
|
|
182
|
+
)
|
|
183
|
+
if not response.ok:
|
|
184
|
+
raise KernelException(f"Failed to restart kernel {kernel_id}")
|
|
185
|
+
|
|
186
|
+
logger.debug(f"Restarted kernel {kernel_id}")
|
|
187
|
+
|
|
188
|
+
threading.Thread(
|
|
189
|
+
target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
|
|
190
|
+
).start()
|
|
191
|
+
|
|
192
|
+
def shutdown_kernel(
|
|
193
|
+
self, kernel_id: Optional[str] = None, timeout: Optional[float] = TIMEOUT
|
|
194
|
+
) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Shuts down an existing Jupyter kernel. This method is used to gracefully terminate a kernel's process.
|
|
197
|
+
|
|
198
|
+
:param kernel_id: The unique identifier of the kernel to shutdown. If not provided, the default kernel is shutdown.
|
|
199
|
+
:param timeout: The timeout for the kernel shutdown request.
|
|
200
|
+
"""
|
|
201
|
+
kernel_id = kernel_id or self.default_kernel_id
|
|
202
|
+
logger.debug(f"Shutting down kernel {kernel_id}")
|
|
203
|
+
|
|
204
|
+
self._connected_kernels[kernel_id].result().close()
|
|
205
|
+
del self._connected_kernels[kernel_id]
|
|
206
|
+
logger.debug(f"Closed websocket connection to kernel {kernel_id}")
|
|
207
|
+
|
|
208
|
+
response = requests.delete(
|
|
209
|
+
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}",
|
|
210
|
+
timeout=timeout,
|
|
211
|
+
)
|
|
212
|
+
if not response.ok:
|
|
213
|
+
raise KernelException(f"Failed to shutdown kernel {kernel_id}")
|
|
214
|
+
|
|
215
|
+
logger.debug(f"Shutdown kernel {kernel_id}")
|
|
216
|
+
|
|
217
|
+
def list_kernels(self, timeout: Optional[float] = TIMEOUT) -> List[str]:
|
|
218
|
+
"""
|
|
219
|
+
Lists all available Jupyter kernels.
|
|
220
|
+
|
|
221
|
+
This method fetches a list of all currently available Jupyter kernels from the server. It can be used
|
|
222
|
+
to retrieve the IDs of all kernels that are currently running or available for connection.
|
|
223
|
+
|
|
224
|
+
:param timeout: The timeout for the kernel list request.
|
|
225
|
+
:return: List of kernel ids
|
|
226
|
+
"""
|
|
227
|
+
response = requests.get(
|
|
228
|
+
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
|
|
229
|
+
timeout=timeout,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if not response.ok:
|
|
233
|
+
raise KernelException(f"Failed to list kernels: {response.text}")
|
|
234
|
+
|
|
235
|
+
return [kernel["id"] for kernel in response.json()]
|
|
236
|
+
|
|
237
|
+
def close(self):
|
|
238
|
+
"""
|
|
239
|
+
Close all the websocket connections to the kernels. It doesn't shutdown the kernels.
|
|
240
|
+
"""
|
|
241
|
+
logger.debug("Closing all websocket connections")
|
|
242
|
+
for ws in self._connected_kernels.values():
|
|
243
|
+
ws.result().close()
|
|
244
|
+
|
|
245
|
+
def _connect_to_kernel_ws(
|
|
246
|
+
self, kernel_id: str, timeout: Optional[float] = TIMEOUT
|
|
247
|
+
) -> JupyterKernelWebSocket:
|
|
248
|
+
"""
|
|
249
|
+
Establishes a WebSocket connection to a specified Jupyter kernel.
|
|
250
|
+
|
|
251
|
+
:param kernel_id: Kernel id
|
|
252
|
+
:param timeout: The timeout for the kernel connection request.
|
|
253
|
+
|
|
254
|
+
:return: Websocket connection
|
|
255
|
+
"""
|
|
256
|
+
logger.debug(f"Connecting to kernel's ({kernel_id}) websocket")
|
|
257
|
+
future = Future()
|
|
258
|
+
self._connected_kernels[kernel_id] = future
|
|
259
|
+
|
|
260
|
+
ws = JupyterKernelWebSocket(
|
|
261
|
+
url=f"{self._sandbox.get_protocol('ws')}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}/channels",
|
|
262
|
+
)
|
|
263
|
+
ws.connect(timeout=timeout)
|
|
264
|
+
logger.debug(f"Connected to kernel's ({kernel_id}) websocket.")
|
|
265
|
+
|
|
266
|
+
future.set_result(ws)
|
|
267
|
+
return ws
|
|
268
|
+
|
|
269
|
+
def _start_connecting_to_default_kernel(
|
|
270
|
+
self, timeout: Optional[float] = TIMEOUT
|
|
271
|
+
) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Start connecting to the default kernel in a separate thread to avoid blocking the main thread.
|
|
274
|
+
:param timeout: Timeout for the call
|
|
275
|
+
"""
|
|
276
|
+
logger.debug("Starting to connect to the default kernel")
|
|
277
|
+
|
|
278
|
+
def setup_default_kernel():
|
|
279
|
+
kernel_id = self._sandbox.filesystem.read(
|
|
280
|
+
"/root/.jupyter/kernel_id", timeout=timeout
|
|
281
|
+
)
|
|
282
|
+
if kernel_id is None and not self._sandbox.is_open:
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
logger.debug(f"Default kernel id: {kernel_id}")
|
|
286
|
+
self._connect_to_kernel_ws(kernel_id, timeout=timeout)
|
|
287
|
+
self._kernel_id_set.set_result(kernel_id)
|
|
288
|
+
|
|
289
|
+
threading.Thread(target=setup_default_kernel).start()
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from concurrent.futures import Future
|
|
7
|
+
from queue import Queue
|
|
8
|
+
from typing import Callable, Dict, List, Any, Optional
|
|
9
|
+
|
|
10
|
+
from e2b import ProcessMessage
|
|
11
|
+
from e2b.constants import TIMEOUT
|
|
12
|
+
from e2b.sandbox import TimeoutException
|
|
13
|
+
from e2b.sandbox.websocket_client import WebSocket
|
|
14
|
+
from e2b.utils.future import DeferredFuture
|
|
15
|
+
from pydantic import ConfigDict, PrivateAttr, BaseModel
|
|
16
|
+
|
|
17
|
+
from e2b_code_interpreter.models import Result, Data, Error, MIMEType
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CellExecution:
|
|
23
|
+
"""
|
|
24
|
+
Represents the execution of a cell in the Jupyter kernel.
|
|
25
|
+
It's an internal class used by JupyterKernelWebSocket.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
input_accepted: bool = False
|
|
29
|
+
on_stdout: Optional[Callable[[ProcessMessage], None]] = None
|
|
30
|
+
on_stderr: Optional[Callable[[ProcessMessage], None]] = None
|
|
31
|
+
on_display_data: Optional[Callable[[Dict[MIMEType, str]], None]] = None
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
on_stdout: Optional[Callable[[ProcessMessage], None]] = None,
|
|
36
|
+
on_stderr: Optional[Callable[[ProcessMessage], None]] = None,
|
|
37
|
+
on_display_data: Optional[Callable[[Dict[MIMEType, str]], None]] = None,
|
|
38
|
+
):
|
|
39
|
+
self.partial_result = Result()
|
|
40
|
+
self.result = Future()
|
|
41
|
+
self.on_stdout = on_stdout
|
|
42
|
+
self.on_stderr = on_stderr
|
|
43
|
+
self.on_display_data = on_display_data
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class JupyterKernelWebSocket(BaseModel):
|
|
47
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
48
|
+
|
|
49
|
+
url: str
|
|
50
|
+
|
|
51
|
+
_cells: Dict[str, CellExecution] = {}
|
|
52
|
+
_waiting_for_replies: Dict[str, DeferredFuture] = PrivateAttr(default_factory=dict)
|
|
53
|
+
_queue_in: Queue = PrivateAttr(default_factory=Queue)
|
|
54
|
+
_queue_out: Queue = PrivateAttr(default_factory=Queue)
|
|
55
|
+
_process_cleanup: List[Callable[[], Any]] = PrivateAttr(default_factory=list)
|
|
56
|
+
_closed: bool = PrivateAttr(default=False)
|
|
57
|
+
|
|
58
|
+
def process_messages(self):
|
|
59
|
+
while True:
|
|
60
|
+
data = self._queue_out.get()
|
|
61
|
+
|
|
62
|
+
logger.debug(f"WebSocket received message: {data}".strip())
|
|
63
|
+
self._receive_message(json.loads(data))
|
|
64
|
+
self._queue_out.task_done()
|
|
65
|
+
|
|
66
|
+
def connect(self, timeout: float = TIMEOUT):
|
|
67
|
+
started = threading.Event()
|
|
68
|
+
stopped = threading.Event()
|
|
69
|
+
self._process_cleanup.append(stopped.set)
|
|
70
|
+
|
|
71
|
+
threading.Thread(
|
|
72
|
+
target=self.process_messages, daemon=True, name="e2b-process-messages"
|
|
73
|
+
).start()
|
|
74
|
+
|
|
75
|
+
threading.Thread(
|
|
76
|
+
target=WebSocket(
|
|
77
|
+
url=self.url,
|
|
78
|
+
queue_in=self._queue_in,
|
|
79
|
+
queue_out=self._queue_out,
|
|
80
|
+
started=started,
|
|
81
|
+
stopped=stopped,
|
|
82
|
+
).run,
|
|
83
|
+
daemon=True,
|
|
84
|
+
name="e2b-code-interpreter-websocket",
|
|
85
|
+
).start()
|
|
86
|
+
|
|
87
|
+
logger.debug("WebSocket waiting to start")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
start_time = time.time()
|
|
91
|
+
while (
|
|
92
|
+
not started.is_set()
|
|
93
|
+
and time.time() - start_time < timeout
|
|
94
|
+
and not self._closed
|
|
95
|
+
):
|
|
96
|
+
time.sleep(0.1)
|
|
97
|
+
|
|
98
|
+
if not started.is_set():
|
|
99
|
+
raise TimeoutException("WebSocket failed to start")
|
|
100
|
+
except BaseException as e:
|
|
101
|
+
self.close()
|
|
102
|
+
raise Exception(f"WebSocket failed to start: {e}") from e
|
|
103
|
+
|
|
104
|
+
logger.debug("WebSocket started")
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _get_execute_request(msg_id: str, code: str) -> str:
|
|
108
|
+
return json.dumps(
|
|
109
|
+
{
|
|
110
|
+
"header": {
|
|
111
|
+
"msg_id": msg_id,
|
|
112
|
+
"username": "e2b",
|
|
113
|
+
"session": str(uuid.uuid4()),
|
|
114
|
+
"msg_type": "execute_request",
|
|
115
|
+
"version": "5.3",
|
|
116
|
+
},
|
|
117
|
+
"parent_header": {},
|
|
118
|
+
"metadata": {},
|
|
119
|
+
"content": {
|
|
120
|
+
"code": code,
|
|
121
|
+
"silent": False,
|
|
122
|
+
"store_history": False,
|
|
123
|
+
"user_expressions": {},
|
|
124
|
+
"allow_stdin": False,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def send_execution_message(
|
|
130
|
+
self,
|
|
131
|
+
code: str,
|
|
132
|
+
on_stdout: Optional[Callable[[ProcessMessage], None]] = None,
|
|
133
|
+
on_stderr: Optional[Callable[[ProcessMessage], None]] = None,
|
|
134
|
+
on_display_data: Optional[Callable[[Dict[MIMEType, str]], None]] = None,
|
|
135
|
+
) -> str:
|
|
136
|
+
message_id = str(uuid.uuid4())
|
|
137
|
+
logger.debug(f"Sending execution message: {message_id}")
|
|
138
|
+
|
|
139
|
+
self._cells[message_id] = CellExecution(
|
|
140
|
+
on_stdout=on_stdout,
|
|
141
|
+
on_stderr=on_stderr,
|
|
142
|
+
on_display_data=on_display_data,
|
|
143
|
+
)
|
|
144
|
+
request = self._get_execute_request(message_id, code)
|
|
145
|
+
self._queue_in.put(request)
|
|
146
|
+
return message_id
|
|
147
|
+
|
|
148
|
+
def get_result(self, message_id: str, timeout: Optional[float] = TIMEOUT) -> Result:
|
|
149
|
+
result = self._cells[message_id].result.result(timeout=timeout)
|
|
150
|
+
logger.debug(f"Got result for message: {message_id}")
|
|
151
|
+
del self._cells[message_id]
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
def _receive_message(self, data: dict):
|
|
155
|
+
"""
|
|
156
|
+
Process messages from the WebSocket
|
|
157
|
+
|
|
158
|
+
Message types:
|
|
159
|
+
https://jupyter-client.readthedocs.io/en/stable/messaging.html
|
|
160
|
+
|
|
161
|
+
:param data: The message data
|
|
162
|
+
"""
|
|
163
|
+
parent_msg_ig = data["parent_header"]["msg_id"]
|
|
164
|
+
logger.debug(f"Received message {data['msg_type']} for {parent_msg_ig}")
|
|
165
|
+
|
|
166
|
+
cell = self._cells.get(parent_msg_ig)
|
|
167
|
+
if not cell:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
result = cell.partial_result
|
|
171
|
+
|
|
172
|
+
if data["msg_type"] == "error":
|
|
173
|
+
logger.debug(f"Cell {parent_msg_ig} finished execution with error")
|
|
174
|
+
result.error = Error(
|
|
175
|
+
name=data["content"]["ename"],
|
|
176
|
+
value=data["content"]["evalue"],
|
|
177
|
+
traceback_raw=data["content"]["traceback"],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
elif data["msg_type"] == "stream":
|
|
181
|
+
if data["content"]["name"] == "stdout":
|
|
182
|
+
result.logs.stdout.append(data["content"]["text"])
|
|
183
|
+
if cell.on_stdout:
|
|
184
|
+
cell.on_stdout(
|
|
185
|
+
ProcessMessage(
|
|
186
|
+
line=data["content"]["text"],
|
|
187
|
+
timestamp=time.time_ns(),
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
elif data["content"]["name"] == "stderr":
|
|
192
|
+
result.logs.stderr.append(data["content"]["text"])
|
|
193
|
+
if cell.on_stderr:
|
|
194
|
+
cell.on_stderr(
|
|
195
|
+
ProcessMessage(
|
|
196
|
+
line=data["content"]["text"],
|
|
197
|
+
error=True,
|
|
198
|
+
timestamp=time.time_ns(),
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
elif data["msg_type"] in "display_data":
|
|
203
|
+
result.data.append(Data(is_main_result=False, data=data["content"]["data"]))
|
|
204
|
+
if cell.on_display_data:
|
|
205
|
+
cell.on_display_data(data["content"]["data"])
|
|
206
|
+
elif data["msg_type"] == "execute_result":
|
|
207
|
+
result.data.append(Data(is_main_result=True, data=data["content"]["data"]))
|
|
208
|
+
elif data["msg_type"] == "status":
|
|
209
|
+
if data["content"]["execution_state"] == "idle":
|
|
210
|
+
if cell.input_accepted:
|
|
211
|
+
logger.debug(f"Cell {parent_msg_ig} finished execution")
|
|
212
|
+
cell.result.set_result(result)
|
|
213
|
+
|
|
214
|
+
elif data["content"]["execution_state"] == "error":
|
|
215
|
+
logger.debug(f"Cell {parent_msg_ig} finished execution with error")
|
|
216
|
+
result.error = Error(
|
|
217
|
+
name=data["content"]["ename"],
|
|
218
|
+
value=data["content"]["evalue"],
|
|
219
|
+
traceback_raw=data["content"]["traceback"],
|
|
220
|
+
)
|
|
221
|
+
cell.result.set_result(result)
|
|
222
|
+
|
|
223
|
+
elif data["msg_type"] == "execute_reply":
|
|
224
|
+
if data["content"]["status"] == "error":
|
|
225
|
+
logger.debug(f"Cell {parent_msg_ig} finished execution with error")
|
|
226
|
+
result.error = Error(
|
|
227
|
+
name=data["content"]["ename"],
|
|
228
|
+
value=data["content"]["evalue"],
|
|
229
|
+
traceback_raw=data["content"]["traceback"],
|
|
230
|
+
)
|
|
231
|
+
elif data["content"]["status"] == "ok":
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
elif data["msg_type"] == "execute_input":
|
|
235
|
+
logger.debug(f"Input accepted for {parent_msg_ig}")
|
|
236
|
+
cell.input_accepted = True
|
|
237
|
+
else:
|
|
238
|
+
logger.error(f"[UNHANDLED MESSAGE TYPE]: {data['msg_type']}")
|
|
239
|
+
print("[UNHANDLED MESSAGE TYPE]:", data["msg_type"])
|
|
240
|
+
|
|
241
|
+
def close(self):
|
|
242
|
+
logger.debug("Closing WebSocket")
|
|
243
|
+
self._closed = True
|
|
244
|
+
|
|
245
|
+
for cancel in self._process_cleanup:
|
|
246
|
+
cancel()
|
|
247
|
+
|
|
248
|
+
self._process_cleanup.clear()
|
|
249
|
+
|
|
250
|
+
for handler in self._waiting_for_replies.values():
|
|
251
|
+
logger.debug(f"Cancelling waiting for execution result for {handler}")
|
|
252
|
+
handler.cancel()
|
|
253
|
+
del handler
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import List, Optional, Iterable, Dict
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Error(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Represents an error that occurred during the execution of a cell.
|
|
9
|
+
The error contains the name of the error, the value of the error, and the traceback.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
"Name of the exception."
|
|
14
|
+
value: str
|
|
15
|
+
"Value of the exception."
|
|
16
|
+
traceback_raw: List[str]
|
|
17
|
+
"List of strings representing the traceback."
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def traceback(self) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Returns the traceback as a single string.
|
|
23
|
+
|
|
24
|
+
:return: The traceback as a single string.
|
|
25
|
+
"""
|
|
26
|
+
return "\n".join(self.traceback_raw)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MIMEType(str):
|
|
30
|
+
"""
|
|
31
|
+
Represents a MIME type.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Data:
|
|
36
|
+
"""
|
|
37
|
+
Represents the data to be displayed as a result of executing a cell in a Jupyter notebook.
|
|
38
|
+
This is result returned by ipython kernel: https://ipython.readthedocs.io/en/stable/development/execution.html#execution-semantics
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
The result can contain multiple types of data, such as text, images, plots, etc. Each type of data is represented
|
|
42
|
+
as a string, and the result can contain multiple types of data. The text representation is always present, and
|
|
43
|
+
the other representations are optional.
|
|
44
|
+
|
|
45
|
+
The class also provides methods to display the data in a Jupyter notebook.
|
|
46
|
+
"""
|
|
47
|
+
text: str
|
|
48
|
+
"Text representation of the data. Always present."
|
|
49
|
+
html: Optional[str] = None
|
|
50
|
+
markdown: Optional[str] = None
|
|
51
|
+
svg: Optional[str] = None
|
|
52
|
+
png: Optional[str] = None
|
|
53
|
+
jpeg: Optional[str] = None
|
|
54
|
+
pdf: Optional[str] = None
|
|
55
|
+
latex: Optional[str] = None
|
|
56
|
+
json: Optional[dict] = None
|
|
57
|
+
javascript: Optional[str] = None
|
|
58
|
+
extra: Optional[dict] = None
|
|
59
|
+
"Extra data that can be included. Not part of the standard types."
|
|
60
|
+
|
|
61
|
+
is_main_result: bool
|
|
62
|
+
"Whether this data is the result of the cell. Data can be produced by display calls of which can be multiple in a cell."
|
|
63
|
+
|
|
64
|
+
raw: Dict[MIMEType, str]
|
|
65
|
+
"Dictionary that maps MIME types to their corresponding string representations of the data."
|
|
66
|
+
|
|
67
|
+
def __init__(self, is_main_result: bool, data: [MIMEType, str]):
|
|
68
|
+
self.is_main_result = is_main_result
|
|
69
|
+
self.raw = copy.deepcopy(data)
|
|
70
|
+
|
|
71
|
+
self.text = data.pop("text/plain")
|
|
72
|
+
self.html = data.pop("text/html", None)
|
|
73
|
+
self.markdown = data.pop("text/markdown", None)
|
|
74
|
+
self.svg = data.pop("image/svg+xml", None)
|
|
75
|
+
self.png = data.pop("image/png", None)
|
|
76
|
+
self.jpeg = data.pop("image/jpeg", None)
|
|
77
|
+
self.pdf = data.pop("application/pdf", None)
|
|
78
|
+
self.latex = data.pop("text/latex", None)
|
|
79
|
+
self.json = data.pop("application/json", None)
|
|
80
|
+
self.javascript = data.pop("application/javascript", None)
|
|
81
|
+
self.extra = data
|
|
82
|
+
|
|
83
|
+
def keys(self) -> Iterable[str]:
|
|
84
|
+
"""
|
|
85
|
+
Returns the MIME types of the data.
|
|
86
|
+
|
|
87
|
+
:return: The MIME types of the data.
|
|
88
|
+
"""
|
|
89
|
+
return self.raw.keys()
|
|
90
|
+
|
|
91
|
+
def __str__(self) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Returns the text representation of the data.
|
|
94
|
+
|
|
95
|
+
:return: The text representation of the data.
|
|
96
|
+
"""
|
|
97
|
+
return self.text
|
|
98
|
+
|
|
99
|
+
def _repr_html_(self) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Returns the HTML representation of the data.
|
|
102
|
+
|
|
103
|
+
:return: The HTML representation of the data.
|
|
104
|
+
"""
|
|
105
|
+
return self.html
|
|
106
|
+
|
|
107
|
+
def _repr_markdown_(self) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Returns the Markdown representation of the data.
|
|
110
|
+
|
|
111
|
+
:return: The Markdown representation of the data.
|
|
112
|
+
"""
|
|
113
|
+
return self.markdown
|
|
114
|
+
|
|
115
|
+
def _repr_svg_(self) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Returns the SVG representation of the data.
|
|
118
|
+
|
|
119
|
+
:return: The SVG representation of the data.
|
|
120
|
+
"""
|
|
121
|
+
return self.svg
|
|
122
|
+
|
|
123
|
+
def _repr_png_(self) -> str:
|
|
124
|
+
"""
|
|
125
|
+
Returns the base64 representation of the PNG data.
|
|
126
|
+
|
|
127
|
+
:return: The base64 representation of the PNG data.
|
|
128
|
+
"""
|
|
129
|
+
return self.png
|
|
130
|
+
|
|
131
|
+
def _repr_jpeg_(self) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Returns the base64 representation of the JPEG data.
|
|
134
|
+
|
|
135
|
+
:return: The base64 representation of the JPEG data.
|
|
136
|
+
"""
|
|
137
|
+
return self.jpeg
|
|
138
|
+
|
|
139
|
+
def _repr_pdf_(self) -> str:
|
|
140
|
+
"""
|
|
141
|
+
Returns the PDF representation of the data.
|
|
142
|
+
|
|
143
|
+
:return: The PDF representation of the data.
|
|
144
|
+
"""
|
|
145
|
+
return self.pdf
|
|
146
|
+
|
|
147
|
+
def _repr_latex_(self) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Returns the LaTeX representation of the data.
|
|
150
|
+
|
|
151
|
+
:return: The LaTeX representation of the data.
|
|
152
|
+
"""
|
|
153
|
+
return self.latex
|
|
154
|
+
|
|
155
|
+
def _repr_json_(self) -> dict:
|
|
156
|
+
"""
|
|
157
|
+
Returns the JSON representation of the data.
|
|
158
|
+
|
|
159
|
+
:return: The JSON representation of the data.
|
|
160
|
+
"""
|
|
161
|
+
return self.json
|
|
162
|
+
|
|
163
|
+
def _repr_javascript_(self) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Returns the JavaScript representation of the data.
|
|
166
|
+
|
|
167
|
+
:return: The JavaScript representation of the data.
|
|
168
|
+
"""
|
|
169
|
+
return self.javascript
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Logs(BaseModel):
|
|
173
|
+
"""
|
|
174
|
+
Data printed to stdout and stderr during execution, usually by print statements, logs, warnings, subprocesses, etc.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
stdout: List[str] = []
|
|
178
|
+
"List of strings printed to stdout by prints, subprocesses, etc."
|
|
179
|
+
stderr: List[str] = []
|
|
180
|
+
"List of strings printed to stderr by prints, subprocesses, etc."
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Result(BaseModel):
|
|
184
|
+
"""
|
|
185
|
+
Represents the result of a cell execution.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
class Config:
|
|
189
|
+
arbitrary_types_allowed = True
|
|
190
|
+
|
|
191
|
+
data: List[Data] = []
|
|
192
|
+
"List of result of the cell (interactively interpreted last line), display calls, e.g. matplotlib plots."
|
|
193
|
+
logs: Logs = Logs()
|
|
194
|
+
"Logs printed to stdout and stderr during execution."
|
|
195
|
+
error: Optional[Error] = None
|
|
196
|
+
"Error object if an error occurred, None otherwise."
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def text(self) -> Optional[str]:
|
|
200
|
+
"""
|
|
201
|
+
Returns the text representation of the result.
|
|
202
|
+
|
|
203
|
+
:return: The text representation of the result.
|
|
204
|
+
"""
|
|
205
|
+
for d in self.data:
|
|
206
|
+
if d.is_main_result:
|
|
207
|
+
return d.text
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class KernelException(Exception):
|
|
211
|
+
"""
|
|
212
|
+
Exception raised when a kernel operation fails.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "e2b-code-interpreter"
|
|
3
|
+
version = "0.0.1a0"
|
|
4
|
+
description = "E2B Code Interpreter - Stateful code execution"
|
|
5
|
+
authors = ["e2b <hello@e2b.dev>"]
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://e2b.dev/"
|
|
9
|
+
repository = "https://github.com/e2b-dev/e2b-code-interpreter/tree/python"
|
|
10
|
+
packages = [{ include = "e2b_code_interpreter" }]
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.8"
|
|
14
|
+
|
|
15
|
+
e2b = ">=0.14.11"
|
|
16
|
+
pydantic = ">1, <3"
|
|
17
|
+
websocket-client = "^1.7.0"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.group.dev.dependencies]
|
|
20
|
+
black = "^24.3.0"
|
|
21
|
+
pytest = "^7.4.0"
|
|
22
|
+
python-dotenv = "^1.0.0"
|
|
23
|
+
pytest-dotenv = "^0.5.2"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["poetry-core"]
|
|
28
|
+
build-backend = "poetry.core.masonry.api"
|
|
29
|
+
|
|
30
|
+
[tool.poetry.urls]
|
|
31
|
+
"Bug Tracker" = "https://github.com/e2b-dev/code-interpreter/issues"
|