mirror of
https://github.com/langgenius/dify.git
synced 2026-02-12 05:54:24 +08:00
feat: implement transport abstractions for virtual environments and add E2B environment provider
This commit is contained in:
parent
29dc083d8d
commit
c9610e9949
@ -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.
|
||||
"""
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
66
api/core/virtual_environment/channel/queue_transport.py
Normal file
66
api/core/virtual_environment/channel/queue_transport.py
Normal file
@ -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)
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
230
api/core/virtual_environment/providers/e2b_sandbox.py
Normal file
230
api/core/virtual_environment/providers/e2b_sandbox.py
Normal file
@ -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}")
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
75
api/uv.lock
generated
75
api/uv.lock
generated
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user