From c9610e9949a0683d647f21f5fb0fc9a98762e020 Mon Sep 17 00:00:00 2001 From: Yeuoly Date: Wed, 31 Dec 2025 17:51:38 +0800 Subject: [PATCH] feat: implement transport abstractions for virtual environments and add E2B environment provider --- .../__base/virtual_environment.py | 14 +- .../channel/pipe_transport.py | 32 ++- .../channel/queue_transport.py | 66 +++++ .../channel/socket_transport.py | 32 ++- .../virtual_environment/channel/transport.py | 79 ++++-- .../providers/docker_daemon_sandbox.py | 14 +- .../providers/e2b_sandbox.py | 230 ++++++++++++++++++ .../providers/local_without_isolation.py | 14 +- api/pyproject.toml | 3 +- api/uv.lock | 75 +++++- 10 files changed, 518 insertions(+), 41 deletions(-) create mode 100644 api/core/virtual_environment/channel/queue_transport.py create mode 100644 api/core/virtual_environment/providers/e2b_sandbox.py diff --git a/api/core/virtual_environment/__base/virtual_environment.py b/api/core/virtual_environment/__base/virtual_environment.py index bd59acb892..439b0aa085 100644 --- a/api/core/virtual_environment/__base/virtual_environment.py +++ b/api/core/virtual_environment/__base/virtual_environment.py @@ -4,7 +4,7 @@ from io import BytesIO from typing import Any from core.virtual_environment.__base.entities import CommandStatus, ConnectionHandle, FileState, Metadata -from core.virtual_environment.channel.transport import Transport +from core.virtual_environment.channel.transport import TransportReadCloser, TransportWriteCloser class VirtualEnvironment(ABC): @@ -12,7 +12,7 @@ class VirtualEnvironment(ABC): Base class for virtual environment implementations. """ - def __init__(self, options: Mapping[str, Any], environments: Mapping[str, Any] | None = None) -> None: + def __init__(self, options: Mapping[str, Any], environments: Mapping[str, str] | None = None) -> None: """ Initialize the virtual environment with metadata. """ @@ -21,7 +21,7 @@ class VirtualEnvironment(ABC): self.metadata = self.construct_environment(options, environments or {}) @abstractmethod - def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, Any]) -> Metadata: + def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, str]) -> Metadata: """ Construct the unique identifier for the virtual environment. @@ -118,8 +118,8 @@ class VirtualEnvironment(ABC): @abstractmethod def execute_command( - self, connection_handle: ConnectionHandle, command: list[str] - ) -> tuple[str, Transport, Transport, Transport]: + self, connection_handle: ConnectionHandle, command: list[str], environments: Mapping[str, str] | None = None + ) -> tuple[str, TransportWriteCloser, TransportReadCloser, TransportReadCloser]: """ Execute a command in the virtual environment. @@ -128,8 +128,8 @@ class VirtualEnvironment(ABC): command (list[str]): The command to execute as a list of strings. Returns: - tuple[int, Transport, Transport, Transport]: A tuple containing pid and 3 handle - to os.pipe(): (stdin, stdout, stderr). + tuple[int, TransportWriteCloser, TransportReadCloser, TransportReadCloser] + a tuple containing pid and 3 handle to os.pipe(): (stdin, stdout, stderr). After exuection, the 3 handles will be closed by caller. """ diff --git a/api/core/virtual_environment/channel/pipe_transport.py b/api/core/virtual_environment/channel/pipe_transport.py index 12a3c8dadd..27bc4a3bf5 100644 --- a/api/core/virtual_environment/channel/pipe_transport.py +++ b/api/core/virtual_environment/channel/pipe_transport.py @@ -1,6 +1,6 @@ import os -from core.virtual_environment.channel.transport import Transport +from core.virtual_environment.channel.transport import Transport, TransportReadCloser, TransportWriteCloser class PipeTransport(Transport): @@ -26,3 +26,33 @@ class PipeTransport(Transport): def close(self) -> None: os.close(self.r_fd) os.close(self.w_fd) + + +class PipeReadCloser(TransportReadCloser): + """ + A Transport implementation using OS pipe for reading. + """ + + def __init__(self, r_fd: int): + self.r_fd = r_fd + + def read(self, n: int) -> bytes: + return os.read(self.r_fd, n) + + def close(self) -> None: + os.close(self.r_fd) + + +class PipeWriteCloser(TransportWriteCloser): + """ + A Transport implementation using OS pipe for writing. + """ + + def __init__(self, w_fd: int): + self.w_fd = w_fd + + def write(self, data: bytes) -> None: + os.write(self.w_fd, data) + + def close(self) -> None: + os.close(self.w_fd) diff --git a/api/core/virtual_environment/channel/queue_transport.py b/api/core/virtual_environment/channel/queue_transport.py new file mode 100644 index 0000000000..d87badaf4a --- /dev/null +++ b/api/core/virtual_environment/channel/queue_transport.py @@ -0,0 +1,66 @@ +from queue import Queue + +from core.virtual_environment.channel.transport import TransportReadCloser + + +class QueueTransportReadCloser(TransportReadCloser): + """ + Transport implementation using queues for inter-thread communication. + + Usage: + q_transport = QueueTransportReadCloser() + write_handler = q_transport.get_write_handler() + + # In writer thread + write_handler.write(b"data") + + # In reader thread + data = q_transport.read(1024) + + # Close transport when done + q_transport.close() + """ + + class WriteHandler: + """ + A write handler that writes data to a queue. + """ + + def __init__(self, queue: Queue[bytes | None]) -> None: + self.queue = queue + + def write(self, data: bytes) -> None: + self.queue.put(data) + + def __init__( + self, + ) -> None: + """ + Initialize the QueueTransportReadCloser with write function. + """ + self.q = Queue[bytes | None]() + + def get_write_handler(self) -> WriteHandler: + """ + Get a write handler that writes to the internal queue. + """ + return QueueTransportReadCloser.WriteHandler(self.q) + + def close(self) -> None: + """ + Close the transport by putting a sentinel value in the queue. + """ + self.q.put(None) + + def read(self, n: int) -> bytes: + """ + Read up to n bytes from the queue. + """ + data = bytearray() + while len(data) < n: + chunk = self.q.get() + if chunk is None: + break + data.extend(chunk) + + return bytes(data) diff --git a/api/core/virtual_environment/channel/socket_transport.py b/api/core/virtual_environment/channel/socket_transport.py index 959168e280..87d5cebf6a 100644 --- a/api/core/virtual_environment/channel/socket_transport.py +++ b/api/core/virtual_environment/channel/socket_transport.py @@ -1,6 +1,6 @@ import socket -from core.virtual_environment.channel.transport import Transport +from core.virtual_environment.channel.transport import Transport, TransportReadCloser, TransportWriteCloser class SocketTransport(Transport): @@ -19,3 +19,33 @@ class SocketTransport(Transport): def close(self) -> None: self.sock.close() + + +class SocketReadCloser(TransportReadCloser): + """ + A Transport implementation using a socket for reading. + """ + + def __init__(self, sock: socket.SocketIO): + self.sock = sock + + def read(self, n: int) -> bytes: + return self.sock.read(n) + + def close(self) -> None: + self.sock.close() + + +class SocketWriteCloser(TransportWriteCloser): + """ + A Transport implementation using a socket for writing. + """ + + def __init__(self, sock: socket.SocketIO): + self.sock = sock + + def write(self, data: bytes) -> None: + self.sock.write(data) + + def close(self) -> None: + self.sock.close() diff --git a/api/core/virtual_environment/channel/transport.py b/api/core/virtual_environment/channel/transport.py index 78f0a3c06e..67d24d8797 100644 --- a/api/core/virtual_environment/channel/transport.py +++ b/api/core/virtual_environment/channel/transport.py @@ -2,24 +2,75 @@ from abc import abstractmethod from typing import Protocol -class Transport(Protocol): - @abstractmethod - def write(self, data: bytes) -> None: - """ - Write data to the transport. - """ - pass - - @abstractmethod - def read(self, n: int) -> bytes: - """ - Read up to n bytes from the transport. - """ - pass +class TransportCloser(Protocol): + """ + Transport that can be closed. + """ @abstractmethod def close(self) -> None: """ Close the transport. """ + + +class TransportWriter(Protocol): + """ + Transport that can be written to. + """ + + @abstractmethod + def write(self, data: bytes) -> None: + """ + Write data to the transport. + """ + + +class TransportReader(Protocol): + """ + Transport that can be read from. + """ + + @abstractmethod + def read(self, n: int) -> bytes: + """ + Read up to n bytes from the transport. + """ + + +class TransportReadCloser(TransportReader, TransportCloser): + """ + Transport that can be read from and closed. + """ + + +class TransportWriteCloser(TransportWriter, TransportCloser): + """ + Transport that can be written to and closed. + """ + + +class Transport(TransportReader, TransportWriter, TransportCloser): + """ + Transport that can be read from, written to, and closed. + """ + + +class NopTransportWriteCloser(TransportWriteCloser): + """ + A no-operation TransportWriteCloser implementation. + + This transport does nothing on write and close operations. + """ + + def write(self, data: bytes) -> None: + """ + No-operation write method. + """ + pass + + def close(self) -> None: + """ + No-operation close method. + """ pass diff --git a/api/core/virtual_environment/providers/docker_daemon_sandbox.py b/api/core/virtual_environment/providers/docker_daemon_sandbox.py index ba964bd4f6..9d57aa6148 100644 --- a/api/core/virtual_environment/providers/docker_daemon_sandbox.py +++ b/api/core/virtual_environment/providers/docker_daemon_sandbox.py @@ -15,8 +15,8 @@ import docker from core.virtual_environment.__base.entities import Arch, CommandStatus, ConnectionHandle, FileState, Metadata from core.virtual_environment.__base.exec import VirtualEnvironmentLaunchFailedError from core.virtual_environment.__base.virtual_environment import VirtualEnvironment -from core.virtual_environment.channel.socket_transport import SocketTransport -from core.virtual_environment.channel.transport import Transport +from core.virtual_environment.channel.socket_transport import SocketReadCloser, SocketWriteCloser +from core.virtual_environment.channel.transport import TransportReadCloser, TransportWriteCloser """ EXAMPLE: @@ -69,7 +69,7 @@ class DockerDaemonEnvironment(VirtualEnvironment): DOCKER_IMAGE = "docker_image" DOCKER_COMMAND = "docker_command" - def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, Any]) -> Metadata: + def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, str]) -> Metadata: """ Construct the Docker daemon virtual environment. """ @@ -253,7 +253,7 @@ class DockerDaemonEnvironment(VirtualEnvironment): def execute_command( self, connection_handle: ConnectionHandle, command: list[str], environments: Mapping[str, str] | None = None - ) -> tuple[str, Transport, Transport, Transport]: + ) -> tuple[str, TransportWriteCloser, TransportReadCloser, TransportReadCloser]: container = self._get_container() container_id = container.id if not isinstance(container_id, str) or not container_id: @@ -279,8 +279,10 @@ class DockerDaemonEnvironment(VirtualEnvironment): exec_id: str = str(exec_info.get("Id")) raw_sock: socket.SocketIO = cast(socket.SocketIO, api_client.exec_start(exec_id, socket=True, tty=False)) # pyright: ignore[reportUnknownMemberType] # - transport = SocketTransport(raw_sock) - return exec_id, transport, transport, transport + stdin_transport = SocketWriteCloser(raw_sock) + stdout_transport = SocketReadCloser(raw_sock) + + return exec_id, stdin_transport, stdout_transport, stdout_transport def get_command_status(self, connection_handle: ConnectionHandle, pid: str) -> CommandStatus: api_client = self.get_docker_api_client(self.get_docker_sock()) diff --git a/api/core/virtual_environment/providers/e2b_sandbox.py b/api/core/virtual_environment/providers/e2b_sandbox.py new file mode 100644 index 0000000000..2304667cd0 --- /dev/null +++ b/api/core/virtual_environment/providers/e2b_sandbox.py @@ -0,0 +1,230 @@ +import os +import threading +from collections.abc import Mapping, Sequence +from enum import StrEnum +from functools import cached_property +from io import BytesIO +from typing import Any +from uuid import uuid4 + +from e2b_code_interpreter import Sandbox + +from core.virtual_environment.__base.entities import Arch, CommandStatus, ConnectionHandle, FileState, Metadata +from core.virtual_environment.__base.exec import ArchNotSupportedError +from core.virtual_environment.__base.virtual_environment import VirtualEnvironment +from core.virtual_environment.channel.queue_transport import QueueTransportReadCloser +from core.virtual_environment.channel.transport import ( + NopTransportWriteCloser, + TransportReadCloser, + TransportWriteCloser, +) + +""" +import logging +from collections.abc import Mapping +from typing import Any + +from core.virtual_environment.providers.e2b_sandbox import E2BEnvironment + +options: Mapping[str, Any] = {E2BEnvironment.OptionsKey.API_KEY: "?????????"} + + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + +# environment = DockerDaemonEnvironment(options=options) +# environment = LocalVirtualEnvironment(options=options) +environment = E2BEnvironment(options=options) + +connection_handle = environment.establish_connection() + +pid, transport_stdin, transport_stdout, transport_stderr = environment.execute_command( + connection_handle, ["uname", "-a"] +) + +logger.info("Executed command with PID: %s", pid) + +# consume stdout +output = transport_stdout.read(1024) +logger.info("Command output: %s", output.decode().strip()) + +environment.release_connection(connection_handle) +environment.release_environment() + +""" + + +class E2BEnvironment(VirtualEnvironment): + """ + E2B virtual environment provider. + """ + + _WORKDIR = "/home/user" + + class OptionsKey(StrEnum): + API_KEY = "api_key" + E2B_LIST_FILE_DEPTH = "e2b_list_file_depth" + E2B_DEFAULT_TEMPLATE = "code-interpreter-v1" + + class StoreKey(StrEnum): + SANDBOX = "sandbox" + + def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, str]) -> Metadata: + """ + Construct a new E2B virtual environment. + """ + # TODO: add Dify as the user agent + sandbox = Sandbox.create( + template=options.get(self.OptionsKey.E2B_DEFAULT_TEMPLATE, "code-interpreter-v1"), + api_key=options.get(self.OptionsKey.API_KEY, ""), + ) + info = sandbox.get_info(api_key=options.get(self.OptionsKey.API_KEY, "")) + output = sandbox.commands.run("uname -m").stdout.strip() + + return Metadata( + id=info.sandbox_id, + arch=self._convert_architecture(output), + store={ + self.StoreKey.SANDBOX: sandbox, + }, + ) + + def release_environment(self) -> None: + """ + Release the E2B virtual environment. + """ + if not Sandbox.kill(api_key=self.api_key, sandbox_id=self.metadata.id): + raise Exception(f"Failed to release E2B sandbox with ID: {self.metadata.id}") + + def establish_connection(self) -> ConnectionHandle: + """ + Establish a connection to the E2B virtual environment. + """ + return ConnectionHandle(id=uuid4().hex) + + def release_connection(self, connection_handle: ConnectionHandle) -> None: + """ + Release the connection to the E2B virtual environment. + """ + pass + + def upload_file(self, path: str, content: BytesIO) -> None: + """ + Upload a file to the E2B virtual environment. + + Args: + path (str): The path to upload the file to. + content (BytesIO): The content of the file. + """ + path = os.path.join(self._WORKDIR, path.lstrip("/")) + + sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX] + sandbox.files.write(path, content) # pyright: ignore[reportUnknownMemberType] # + + def download_file(self, path: str) -> BytesIO: + """ + Download a file from the E2B virtual environment. + + Args: + path (str): The path to download the file from. + Returns: + BytesIO: The content of the file. + """ + path = os.path.join(self._WORKDIR, path.lstrip("/")) + + sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX] + content = sandbox.files.read(path) + return BytesIO(content.encode()) + + def list_files(self, directory_path: str, limit: int) -> Sequence[FileState]: + """ + List files in a directory of the E2B virtual environment. + """ + sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX] + directory_path = os.path.join(self._WORKDIR, directory_path.lstrip("/")) + files_info = sandbox.files.list(directory_path, depth=self.options.get(self.OptionsKey.E2B_LIST_FILE_DEPTH, 3)) + return [ + FileState( + path=os.path.relpath(file_info.path, self._WORKDIR), + size=file_info.size, + created_at=int(file_info.modified_time.timestamp()), + updated_at=int(file_info.modified_time.timestamp()), + ) + for file_info in files_info + ] + + def execute_command( + self, connection_handle: ConnectionHandle, command: list[str], environments: Mapping[str, str] | None = None + ) -> tuple[str, TransportWriteCloser, TransportReadCloser, TransportReadCloser]: + """ + Execute a command in the E2B virtual environment. + + STDIN is not yet supported. E2B's API is such a terrible mess... to support it may lead a bad design. + as a result we leave it for future improvement. + """ + sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX] + stdout_stream = QueueTransportReadCloser() + stderr_stream = QueueTransportReadCloser() + + threading.Thread( + target=self._cmd_thread, + args=(sandbox, command, environments, stdout_stream, stderr_stream), + ).start() + + return ( + "N/A", + NopTransportWriteCloser(), # stdin not supported yet + stdout_stream, + stderr_stream, + ) + + def get_command_status(self, connection_handle: ConnectionHandle, pid: str) -> CommandStatus: + return super().get_command_status(connection_handle, pid) + + def _cmd_thread( + self, + sandbox: Sandbox, + command: list[str], + environments: Mapping[str, str] | None, + stdout_stream: QueueTransportReadCloser, + stderr_stream: QueueTransportReadCloser, + ) -> None: + """ """ + stdout_stream_write_handler = stdout_stream.get_write_handler() + stderr_stream_write_handler = stderr_stream.get_write_handler() + sandbox.commands.run( + cmd=" ".join(command), + envs=dict(environments or {}), + # stdin=True, + on_stdout=lambda data: stdout_stream_write_handler.write(data.encode()), + on_stderr=lambda data: stderr_stream_write_handler.write(data.encode()), + ) + + # Close the write handlers to signal EOF + stdout_stream.close() + stderr_stream.close() + + @cached_property + def api_key(self) -> str: + """ + Get the API key for the E2B environment. + """ + return self.options.get(self.OptionsKey.API_KEY, "") + + def _convert_architecture(self, arch_str: str) -> Arch: + """ + Convert architecture string to standard format. + """ + arch_map = { + "x86_64": Arch.AMD64, + "aarch64": Arch.ARM64, + "armv7l": Arch.ARM64, + "arm64": Arch.ARM64, + "amd64": Arch.AMD64, + "arm64v8": Arch.ARM64, + "arm64v7": Arch.ARM64, + } + if arch_str in arch_map: + return arch_map[arch_str] + + raise ArchNotSupportedError(f"Unsupported architecture: {arch_str}") diff --git a/api/core/virtual_environment/providers/local_without_isolation.py b/api/core/virtual_environment/providers/local_without_isolation.py index 386ece0774..68ffc6978c 100644 --- a/api/core/virtual_environment/providers/local_without_isolation.py +++ b/api/core/virtual_environment/providers/local_without_isolation.py @@ -11,8 +11,8 @@ from uuid import uuid4 from core.virtual_environment.__base.entities import Arch, CommandStatus, ConnectionHandle, FileState, Metadata from core.virtual_environment.__base.exec import ArchNotSupportedError from core.virtual_environment.__base.virtual_environment import VirtualEnvironment -from core.virtual_environment.channel.pipe_transport import PipeTransport -from core.virtual_environment.channel.transport import Transport +from core.virtual_environment.channel.pipe_transport import PipeReadCloser, PipeWriteCloser +from core.virtual_environment.channel.transport import TransportReadCloser, TransportWriteCloser class LocalVirtualEnvironment(VirtualEnvironment): @@ -23,7 +23,7 @@ class LocalVirtualEnvironment(VirtualEnvironment): NEVER USE IT IN PRODUCTION ENVIRONMENTS. """ - def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, Any]) -> Metadata: + def construct_environment(self, options: Mapping[str, Any], environments: Mapping[str, str]) -> Metadata: """ Construct the local virtual environment. @@ -118,7 +118,7 @@ class LocalVirtualEnvironment(VirtualEnvironment): def execute_command( self, connection_handle: ConnectionHandle, command: list[str], environments: Mapping[str, str] | None = None - ) -> tuple[str, Transport, Transport, Transport]: + ) -> tuple[str, TransportWriteCloser, TransportReadCloser, TransportReadCloser]: """ Execute a command in the local virtual environment. @@ -162,9 +162,9 @@ class LocalVirtualEnvironment(VirtualEnvironment): os.close(stderr_write_fd) # Create PipeTransport instances for stdin, stdout, and stderr - stdin_transport = PipeTransport(r_fd=stdin_read_fd, w_fd=stdin_write_fd) - stdout_transport = PipeTransport(r_fd=stdout_read_fd, w_fd=stdout_write_fd) - stderr_transport = PipeTransport(r_fd=stderr_read_fd, w_fd=stderr_write_fd) + stdin_transport = PipeWriteCloser(w_fd=stdin_write_fd) + stdout_transport = PipeReadCloser(r_fd=stdout_read_fd) + stderr_transport = PipeReadCloser(r_fd=stderr_read_fd) # Return the process ID and file descriptors for stdin, stdout, and stderr return str(process.pid), stdin_transport, stdout_transport, stderr_transport diff --git a/api/pyproject.toml b/api/pyproject.toml index 242776001c..d30b2f1dd4 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -88,12 +88,13 @@ dependencies = [ "httpx-sse~=0.4.0", "sendgrid~=6.12.3", "flask-restx~=1.3.0", - "packaging~=23.2", + "packaging==24.1", "croniter>=6.0.0", "weaviate-client==4.17.0", "apscheduler>=3.11.0", "weave>=0.52.16", "docker>=7.1.0", + "e2b-code-interpreter>=2.4.1", ] # Before adding new dependency, consider place it in # alphabet order (a-z) and suitable group. diff --git a/api/uv.lock b/api/uv.lock index 0355248d13..d8add45324 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -685,6 +685,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/ad/d71da675eef85ac153eef5111ca0caa924548c9591da00939bcabba8de8e/bottleneck-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:81e3822499f057a917b7d3972ebc631ac63c6bbcc79ad3542a66c4c40634e3a6", size = 113493 }, ] +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508 }, +] + [[package]] name = "brotli" version = "1.2.0" @@ -1383,6 +1392,7 @@ dependencies = [ { name = "charset-normalizer" }, { name = "croniter" }, { name = "docker" }, + { name = "e2b-code-interpreter" }, { name = "flask" }, { name = "flask-compress" }, { name = "flask-cors" }, @@ -1582,6 +1592,7 @@ requires-dist = [ { name = "charset-normalizer", specifier = ">=3.4.4" }, { name = "croniter", specifier = ">=6.0.0" }, { name = "docker", specifier = ">=7.1.0" }, + { name = "e2b-code-interpreter", specifier = ">=2.4.1" }, { name = "flask", specifier = "~=3.1.2" }, { name = "flask-compress", specifier = ">=1.17,<1.18" }, { name = "flask-cors", specifier = "~=6.0.0" }, @@ -1629,7 +1640,7 @@ requires-dist = [ { name = "opentelemetry-semantic-conventions", specifier = "==0.48b0" }, { name = "opentelemetry-util-http", specifier = "==0.48b0" }, { name = "opik", specifier = "~=1.8.72" }, - { name = "packaging", specifier = "~=23.2" }, + { name = "packaging", specifier = "==24.1" }, { name = "pandas", extras = ["excel", "output-formatting", "performance"], specifier = "~=2.2.2" }, { name = "psycogreen", specifier = "~=1.0.2" }, { name = "psycopg2-binary", specifier = "~=2.9.6" }, @@ -1799,6 +1810,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, ] +[[package]] +name = "dockerfile-parse" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/df/929ee0b5d2c8bd8d713c45e71b94ab57c7e11e322130724d54f469b2cd48/dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc", size = 24556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/6c/79cd5bc1b880d8c1a9a5550aa8dacd57353fa3bb2457227e1fb47383eb49/dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6", size = 14845 }, +] + [[package]] name = "docstring-parser" version = "0.17.0" @@ -1833,6 +1853,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922 }, ] +[[package]] +name = "e2b" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "dockerfile-parse" }, + { name = "httpcore" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "rich" }, + { name = "typing-extensions" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/d6/bf09e2a2f81a0296dc20b59a53b3cb8e019aaf6a734a3708b711cfd0ba48/e2b-2.9.0.tar.gz", hash = "sha256:b6b0d7dc816e9e0f6ca82ddd4add8e86d72068fb79d9430a07167ab773a822fa", size = 111618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/a3/cd58df6423356e967b1b5b26d2b3adb2d001b47d04c4e5499778ef88c23f/e2b-2.9.0-py3-none-any.whl", hash = "sha256:216752c7be17630721ad60a023f488ad3a2a1c995eaf4c5c7d0d1ec6f9129742", size = 204021 }, +] + +[[package]] +name = "e2b-code-interpreter" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "e2b" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/eb/db6e51edd9f3402fd68d026572579b9b1bd833b10d990376a1e4c05d5b8d/e2b_code_interpreter-2.4.1.tar.gz", hash = "sha256:4b15014ee0d0dfcdc3072e1f409cbb87ca48f48d53d75629b7257e5513b9e7dd", size = 10700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/e7/09b9106ead227f7be14bd97c3181391ee498bb38933b1a9c566b72c8567a/e2b_code_interpreter-2.4.1-py3-none-any.whl", hash = "sha256:15d35f025b4a15033e119f2e12e7ac65657ad2b5a013fa9149e74581fbee778a", size = 13719 }, +] + [[package]] name = "elastic-transport" version = "8.17.1" @@ -4319,11 +4374,11 @@ wheels = [ [[package]] name = "packaging" -version = "23.2" +version = "24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714 } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011 }, + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] [[package]] @@ -7018,6 +7073,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546 }, ] +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854 }, +] + [[package]] name = "wcwidth" version = "0.2.14"