e2b-code-interpreter 0.0.8a0__tar.gz → 0.0.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e2b-code-interpreter
3
- Version: 0.0.8a0
3
+ Version: 0.0.9
4
4
  Summary: E2B Code Interpreter - Stateful code execution
5
5
  Home-page: https://e2b.dev/
6
6
  License: Apache-2.0
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  import logging
5
4
  import threading
6
5
  import uuid
@@ -63,9 +62,6 @@ class JupyterExtension:
63
62
  self._connected_kernels: Dict[str, Future[JupyterKernelWebSocket]] = {}
64
63
  self._default_kernel_id: Optional[str] = None
65
64
 
66
- def get_default_url(self) -> str:
67
- return f"https://{self._sandbox.get_hostname(8888)}/doc/tree/RTC:default.ipynb"
68
-
69
65
  def exec_cell(
70
66
  self,
71
67
  code: str,
@@ -109,47 +105,6 @@ class JupyterExtension:
109
105
  f"Received result from kernel {kernel_id}, message_id: {message_id}, result: {result}"
110
106
  )
111
107
 
112
- nb = self._sandbox.filesystem.read(f"/home/user/default.ipynb", timeout=timeout)
113
- nb_parsed = json.loads(nb)
114
- cell = {
115
- "cell_type": "code",
116
- "metadata": {},
117
- "source": code,
118
- }
119
-
120
- outputs = []
121
- if result.logs.stdout:
122
- outputs.append(
123
- {"output_type": "stream", "name": "stdout", "text": result.logs.stdout}
124
- )
125
- if result.logs.stderr:
126
- outputs.append(
127
- {"output_type": "stream", "name": "stderr", "text": result.logs.stderr}
128
- )
129
-
130
- if result.results:
131
- outputs = [
132
- {
133
- "output_type": (
134
- "execute_result" if r.is_main_result else "display_data"
135
- ),
136
- "data": r.raw,
137
- "metadata": {},
138
- }
139
- for r in result.results
140
- ]
141
-
142
- cell["execution_count"] = result.execution_count
143
- cell["outputs"] = outputs
144
-
145
- if nb_parsed["cells"] and not nb_parsed["cells"][-1]["source"]:
146
- nb_parsed["cells"][-1] = cell
147
- else:
148
- nb_parsed["cells"].append(cell)
149
-
150
- self._sandbox.filesystem.write(
151
- f"/home/user/default.ipynb", json.dumps(nb_parsed), timeout=timeout
152
- )
153
108
  return result
154
109
 
155
110
  @property
@@ -164,77 +119,59 @@ class JupyterExtension:
164
119
  self._default_kernel_id = self._kernel_id_set.result()
165
120
 
166
121
  return self._default_kernel_id
167
- #
168
- # def create_kernel(
169
- # self,
170
- # name: str,
171
- # path: str = "/home/user",
172
- # kernel_name: str = "python3",
173
- # timeout: Optional[float] = TIMEOUT,
174
- # ) -> str:
175
- # """
176
- # Creates a new kernel, this can be useful if you want to have multiple independent code execution environments.
177
- #
178
- # The kernel can be optionally configured to start in a specific working directory and/or
179
- # with a specific kernel name. If no kernel name is provided, the default kernel will be used.
180
- # Once the kernel is created, this method establishes a WebSocket connection to the new kernel for
181
- # real-time communication.
182
- #
183
- # :param name: Name of the kernel
184
- # :param path: Sets the current working directory for the kernel. Defaults to "/home/user".
185
- # :param kernel_name: Specifies which kernel should be used, useful if you have multiple kernel types.
186
- # :param timeout: Timeout for the kernel creation request.
187
- # :return: Kernel id of the created kernel
188
- # """
189
- #
190
- # x = {
191
- # "metadata": {
192
- # "signature": "hex-digest",
193
- # "kernel_info": {"name": kernel_name},
194
- # "language_info": {
195
- # "name": kernel_name,
196
- # "version": "3.10.14",
197
- # }, # TODO: get version
198
- # },
199
- # "nbformat": 4,
200
- # "nbformat_minor": 0,
201
- # "cells": [],
202
- # }
203
- #
204
- # self._sandbox.filesystem.write(
205
- # f"/home/user/default.ipynb", json.dumps(x), timeout=timeout
206
- # )
207
- # self._sandbox.process.start("chmod 777 /home/user/default.ipynb")
208
- #
209
- # data = {
210
- # "name": name,
211
- # "kernel": {"name": kernel_name},
212
- # "notebook": {"name": "default.ipynb"},
213
- # "path": path,
214
- # "type": "notebook",
215
- # }
216
- #
217
- # logger.debug(f"Creating kernel with data: {data}")
218
- #
219
- # response = requests.post(
220
- # f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions",
221
- # json=data,
222
- # timeout=timeout,
223
- # )
224
- # if not response.ok:
225
- # raise KernelException(f"Failed to create kernel: {response.text}")
226
- #
227
- # response_data = response.json()
228
- # kernel_id = response_data["kernel"]["id"]
229
- # session_id = response_data["id"]
230
- #
231
- # logger.debug(f"Created kernel {kernel_id}, session {session_id}")
232
- #
233
- # threading.Thread(
234
- # target=self._connect_to_kernel_ws, args=(kernel_id, session_id, timeout)
235
- # ).start()
236
- #
237
- # return kernel_id
122
+
123
+ def create_kernel(
124
+ self,
125
+ cwd: str = "/home/user",
126
+ kernel_name: Optional[str] = None,
127
+ timeout: Optional[float] = TIMEOUT,
128
+ ) -> str:
129
+ """
130
+ Creates a new kernel, this can be useful if you want to have multiple independent code execution environments.
131
+
132
+ The kernel can be optionally configured to start in a specific working directory and/or
133
+ with a specific kernel name. If no kernel name is provided, the default kernel will be used.
134
+ Once the kernel is created, this method establishes a WebSocket connection to the new kernel for
135
+ real-time communication.
136
+
137
+ :param cwd: Sets the current working directory for the kernel. Defaults to "/home/user".
138
+ :param kernel_name:
139
+ Specifies which kernel should be used, useful if you have multiple kernel types.
140
+ If not provided, the default kernel will be used.
141
+ :param timeout: Timeout for the kernel creation request.
142
+ :return: Kernel id of the created kernel
143
+ """
144
+ kernel_name = kernel_name or "python3"
145
+
146
+ data = {"path": str(uuid.uuid4()), "kernel": {"name": kernel_name}, "type": "notebook", "name": str(uuid.uuid4())}
147
+ logger.debug(f"Creating kernel with data: {data}")
148
+
149
+ response = requests.post(
150
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions",
151
+ json=data,
152
+ timeout=timeout,
153
+ )
154
+ if not response.ok:
155
+ raise KernelException(f"Failed to create kernel: {response.text}")
156
+
157
+ session_data = response.json()
158
+ session_id = session_data["id"]
159
+ kernel_id = session_data["kernel"]["id"]
160
+
161
+ response = requests.patch(
162
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions/{session_id}",
163
+ json={"path": cwd},
164
+ timeout=timeout,
165
+ )
166
+ if not response.ok:
167
+ raise KernelException(f"Failed to create kernel: {response.text}")
168
+
169
+ logger.debug(f"Created kernel {kernel_id}")
170
+
171
+ threading.Thread(
172
+ target=self._connect_to_kernel_ws, args=(kernel_id, session_id, timeout)
173
+ ).start()
174
+ return kernel_id
238
175
 
239
176
  def restart_kernel(
240
177
  self, kernel_id: Optional[str] = None, timeout: Optional[float] = TIMEOUT
@@ -264,51 +201,51 @@ class JupyterExtension:
264
201
  threading.Thread(
265
202
  target=self._connect_to_kernel_ws, args=(kernel_id, None, timeout)
266
203
  ).start()
267
- #
268
- # def shutdown_kernel(
269
- # self, kernel_id: Optional[str] = None, timeout: Optional[float] = TIMEOUT
270
- # ) -> None:
271
- # """
272
- # Shuts down an existing Jupyter kernel. This method is used to gracefully terminate a kernel's process.
273
- #
274
- # :param kernel_id: The unique identifier of the kernel to shutdown. If not provided, the default kernel is shutdown.
275
- # :param timeout: The timeout for the kernel shutdown request.
276
- # """
277
- # kernel_id = kernel_id or self.default_kernel_id
278
- # logger.debug(f"Shutting down kernel {kernel_id}")
279
- #
280
- # self._connected_kernels[kernel_id].result().close()
281
- # del self._connected_kernels[kernel_id]
282
- # logger.debug(f"Closed websocket connection to kernel {kernel_id}")
283
- #
284
- # response = requests.delete(
285
- # f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}",
286
- # timeout=timeout,
287
- # )
288
- # if not response.ok:
289
- # raise KernelException(f"Failed to shutdown kernel {kernel_id}")
290
- #
291
- # logger.debug(f"Shutdown kernel {kernel_id}")
292
- #
293
- # def list_kernels(self, timeout: Optional[float] = TIMEOUT) -> List[str]:
294
- # """
295
- # Lists all available Jupyter kernels.
296
- #
297
- # This method fetches a list of all currently available Jupyter kernels from the server. It can be used
298
- # to retrieve the IDs of all kernels that are currently running or available for connection.
299
- #
300
- # :param timeout: The timeout for the kernel list request.
301
- # :return: List of kernel ids
302
- # """
303
- # response = requests.get(
304
- # f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
305
- # timeout=timeout,
306
- # )
307
- #
308
- # if not response.ok:
309
- # raise KernelException(f"Failed to list kernels: {response.text}")
310
- #
311
- # return [kernel["id"] for kernel in response.json()]
204
+
205
+ def shutdown_kernel(
206
+ self, kernel_id: Optional[str] = None, timeout: Optional[float] = TIMEOUT
207
+ ) -> None:
208
+ """
209
+ Shuts down an existing Jupyter kernel. This method is used to gracefully terminate a kernel's process.
210
+
211
+ :param kernel_id: The unique identifier of the kernel to shutdown. If not provided, the default kernel is shutdown.
212
+ :param timeout: The timeout for the kernel shutdown request.
213
+ """
214
+ kernel_id = kernel_id or self.default_kernel_id
215
+ logger.debug(f"Shutting down kernel {kernel_id}")
216
+
217
+ self._connected_kernels[kernel_id].result().close()
218
+ del self._connected_kernels[kernel_id]
219
+ logger.debug(f"Closed websocket connection to kernel {kernel_id}")
220
+
221
+ response = requests.delete(
222
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}",
223
+ timeout=timeout,
224
+ )
225
+ if not response.ok:
226
+ raise KernelException(f"Failed to shutdown kernel {kernel_id}")
227
+
228
+ logger.debug(f"Shutdown kernel {kernel_id}")
229
+
230
+ def list_kernels(self, timeout: Optional[float] = TIMEOUT) -> List[str]:
231
+ """
232
+ Lists all available Jupyter kernels.
233
+
234
+ This method fetches a list of all currently available Jupyter kernels from the server. It can be used
235
+ to retrieve the IDs of all kernels that are currently running or available for connection.
236
+
237
+ :param timeout: The timeout for the kernel list request.
238
+ :return: List of kernel ids
239
+ """
240
+ response = requests.get(
241
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
242
+ timeout=timeout,
243
+ )
244
+
245
+ if not response.ok:
246
+ raise KernelException(f"Failed to list kernels: {response.text}")
247
+
248
+ return [kernel["id"] for kernel in response.json()]
312
249
 
313
250
  def close(self):
314
251
  """
@@ -319,23 +256,16 @@ class JupyterExtension:
319
256
  ws.result().close()
320
257
 
321
258
  def _connect_to_kernel_ws(
322
- self,
323
- kernel_id: str,
324
- session_id: Optional[str],
325
- timeout: Optional[float] = TIMEOUT,
259
+ self, kernel_id: str, session_id: Optional[str], timeout: Optional[float] = TIMEOUT
326
260
  ) -> JupyterKernelWebSocket:
327
261
  """
328
262
  Establishes a WebSocket connection to a specified Jupyter kernel.
329
263
 
330
264
  :param kernel_id: Kernel id
331
- :param session_id: Session id
332
265
  :param timeout: The timeout for the kernel connection request.
333
266
 
334
267
  :return: Websocket connection
335
268
  """
336
- if not session_id:
337
- session_id = uuid.uuid4()
338
-
339
269
  logger.debug(f"Connecting to kernel's ({kernel_id}) websocket")
340
270
  future = Future()
341
271
  self._connected_kernels[kernel_id] = future
@@ -343,7 +273,7 @@ class JupyterExtension:
343
273
  session_id = session_id or str(uuid.uuid4())
344
274
  ws = JupyterKernelWebSocket(
345
275
  url=f"{self._sandbox.get_protocol('ws')}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}/channels",
346
- session_id=session_id,
276
+ session_id=session_id
347
277
  )
348
278
 
349
279
  ws.connect(timeout=timeout)
@@ -362,18 +292,17 @@ class JupyterExtension:
362
292
  logger.debug("Starting to connect to the default kernel")
363
293
 
364
294
  def setup_default_kernel():
365
- session_info = self._sandbox.filesystem.read(
366
- "/root/.jupyter/.session_info", timeout=timeout
295
+ kernel_id = self._sandbox.filesystem.read(
296
+ "/root/.jupyter/kernel_id", timeout=timeout
367
297
  )
368
298
 
369
- if session_info is None and not self._sandbox.is_open:
299
+ if kernel_id is None and not self._sandbox.is_open:
370
300
  return
371
301
 
372
- data = json.loads(session_info)
373
- kernel_id = data["kernel"]["id"]
374
- session_id = data["id"]
302
+ kernel_id = kernel_id.strip()
303
+
375
304
  logger.debug(f"Default kernel id: {kernel_id}")
376
- self._connect_to_kernel_ws(kernel_id, session_id, timeout=timeout)
305
+ self._connect_to_kernel_ws(kernel_id, None, timeout=timeout)
377
306
  self._kernel_id_set.set_result(kernel_id)
378
307
 
379
308
  threading.Thread(target=setup_default_kernel).start()
@@ -161,7 +161,11 @@ class JupyterKernelWebSocket:
161
161
 
162
162
  :param data: The message data
163
163
  """
164
- parent_msg_ig = data["parent_header"]["msg_id"]
164
+ parent_msg_ig = data["parent_header"].get("msg_id", None)
165
+ if parent_msg_ig is None:
166
+ logger.warning("Parent message ID not found. %s", data)
167
+ return
168
+
165
169
  logger.debug(f"Received message {data['msg_type']} for {parent_msg_ig}")
166
170
 
167
171
  cell = self._cells.get(parent_msg_ig)
@@ -124,6 +124,9 @@ class Result:
124
124
  """
125
125
  return self.text
126
126
 
127
+ def __repr__(self) -> str:
128
+ return f"Result({self.text})"
129
+
127
130
  def _repr_html_(self) -> Optional[str]:
128
131
  """
129
132
  Returns the HTML representation of the data.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "e2b-code-interpreter"
3
- version = "0.0.8a0"
3
+ version = "0.0.9"
4
4
  description = "E2B Code Interpreter - Stateful code execution"
5
5
  authors = ["e2b <hello@e2b.dev>"]
6
6
  license = "Apache-2.0"