Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion leap0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,20 @@
from .models.snapshot import CreateSnapshotParams, ResumeSnapshotParams, Snapshot, snapshot_id_of
from .models.ssh import SshAccess, SshValidation
from .models.template import (
AwsRegistryCredentials,
AwsRegistryCredentialsDict,
AzureRegistryCredentials,
AzureRegistryCredentialsDict,
BasicRegistryCredentials,
BasicRegistryCredentialsDict,
CreateTemplateParams,
GcpRegistryCredentials,
GcpRegistryCredentialsDict,
ImageConfig,
RegistryCredentialType,
RegistryCredentials,
RegistryCredentialsDict,
RegistryCredentialsInput,
RenameTemplateParams,
Template,
)
Expand Down Expand Up @@ -121,8 +127,11 @@
"AsyncSnapshotsClient",
"AsyncSshClient",
"AsyncTemplatesClient",
"AwsRegistryCredentials",
"AwsRegistryCredentialsDict",
"AzureRegistryCredentials",
"AzureRegistryCredentialsDict",
"BasicRegistryCredentials",
"BasicRegistryCredentialsDict",
"CodeContext",
"CodeExecutionError",
Expand Down Expand Up @@ -151,6 +160,7 @@
"FileEdit",
"FileInfo",
"FilesystemClient",
"GcpRegistryCredentials",
"GitClient",
"GitCommitResult",
"GitResult",
Expand Down Expand Up @@ -181,7 +191,9 @@
"PtySession",
"RenameTemplateParams",
"RegistryCredentialType",
"RegistryCredentials",
"RegistryCredentialsDict",
"RegistryCredentialsInput",
"ResumeSnapshotParams",
"Sandbox",
"SandboxState",
Expand Down Expand Up @@ -273,13 +285,19 @@
"SshAccess": (".models.ssh", "SshAccess"),
"SshValidation": (".models.ssh", "SshValidation"),
"CreateTemplateParams": (".models.template", "CreateTemplateParams"),
"BasicRegistryCredentials": (".models.template", "BasicRegistryCredentials"),
"BasicRegistryCredentialsDict": (".models.template", "BasicRegistryCredentialsDict"),
"AwsRegistryCredentials": (".models.template", "AwsRegistryCredentials"),
"AwsRegistryCredentialsDict": (".models.template", "AwsRegistryCredentialsDict"),
"GcpRegistryCredentials": (".models.template", "GcpRegistryCredentials"),
"GcpRegistryCredentialsDict": (".models.template", "GcpRegistryCredentialsDict"),
"AzureRegistryCredentials": (".models.template", "AzureRegistryCredentials"),
"AzureRegistryCredentialsDict": (".models.template", "AzureRegistryCredentialsDict"),
"ImageConfig": (".models.template", "ImageConfig"),
"RegistryCredentialType": (".models.template", "RegistryCredentialType"),
"RegistryCredentials": (".models.template", "RegistryCredentials"),
"RegistryCredentialsDict": (".models.template", "RegistryCredentialsDict"),
"RegistryCredentialsInput": (".models.template", "RegistryCredentialsInput"),
"Template": (".models.template", "Template"),
"RenameTemplateParams": (".models.template", "RenameTemplateParams"),
"CodeContext": (".models.code_interpreter", "CodeContext"),
Expand Down Expand Up @@ -316,4 +334,3 @@ def __getattr__(name: str) -> object:

def __dir__() -> list[str]:
return sorted(__all__)
"GcpRegistryCredentialsDict",
35 changes: 18 additions & 17 deletions leap0/_async/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,13 @@ def _reset_meter_provider_if_current(provider: MeterProvider) -> None:
class AsyncLeap0Client:
"""Top-level asynchronous client for the Leap0 API.

Use this client to create sandboxes and access all async service clients.
Use this client to create sandboxes and access top-level control-plane
services. Sandbox-scoped services are exposed through bound sandbox objects.

Attributes:
sandboxes: Client for sandbox lifecycle operations.
snapshots: Client for snapshot lifecycle operations.
templates: Client for template management.
filesystem: Client for sandbox filesystem operations.
git: Client for Git operations inside a sandbox.
process: Client for one-shot process execution.
pty: Client for interactive PTY sessions.
lsp: Client for Language Server Protocol operations.
ssh: Client for SSH credential management.
code_interpreter: Client for code execution APIs.
desktop: Client for desktop automation APIs.
"""
DEFAULT_BASE_URL = DEFAULT_BASE_URL
DEFAULT_SANDBOX_DOMAIN = DEFAULT_SANDBOX_DOMAIN
Expand Down Expand Up @@ -155,14 +148,14 @@ def __init__(
sandbox_factory=lambda data: AsyncSandbox(self, data),
)
self.templates = AsyncTemplatesClient(self._transport)
self.filesystem = AsyncFilesystemClient(self._transport)
self.git = AsyncGitClient(self._transport)
self.process = AsyncProcessClient(self._transport)
self.pty = AsyncPtyClient(self._transport)
self.lsp = AsyncLspClient(self._transport)
self.ssh = AsyncSshClient(self._transport)
self.code_interpreter = AsyncCodeInterpreterClient(self._transport, sandbox_domain=config.sandbox_domain)
self.desktop = AsyncDesktopClient(self._transport, sandbox_domain=config.sandbox_domain)
self._filesystem = AsyncFilesystemClient(self._transport)
self._git = AsyncGitClient(self._transport)
self._process = AsyncProcessClient(self._transport)
self._pty = AsyncPtyClient(self._transport)
self._lsp = AsyncLspClient(self._transport)
self._ssh = AsyncSshClient(self._transport)
self._code_interpreter = AsyncCodeInterpreterClient(self._transport, sandbox_domain=config.sandbox_domain)
self._desktop = AsyncDesktopClient(self._transport, sandbox_domain=config.sandbox_domain)

if config.sdk_otel_enabled:
self._init_otel()
Expand Down Expand Up @@ -209,6 +202,14 @@ def _init_otel(self) -> None:
self._meter_provider = _shared_meter_provider
self._uses_shared_meter_provider = True

def __getattr__(self, name: str) -> object:
if name in {"filesystem", "git", "process", "pty", "lsp", "ssh", "code_interpreter", "desktop"}:
raise AttributeError(
f"{type(self).__name__!s} has no attribute {name!r}; use a bound sandbox handle instead, "
f"for example sandbox.{name}"
)
raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}")

@with_instrumentation("async_client.get_sandbox")
async def get_sandbox(self, sandbox_id: str) -> AsyncSandbox:
"""Get a sandbox by ID.
Expand Down
9 changes: 3 additions & 6 deletions leap0/_async/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ async def write_bytes(self, sandbox: SandboxRef, *, path: str, content: bytes, p
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
await client.filesystem.write_bytes(
sandbox,
await sandbox.filesystem.write_bytes(
path="/workspace/logo.png",
content=image_bytes,
)
Expand Down Expand Up @@ -128,8 +127,7 @@ async def write_file(self, sandbox: SandboxRef, *, path: str, content: str, enco
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
await client.filesystem.write_file(
sandbox,
await sandbox.filesystem.write_file(
path="/workspace/app.py",
content="print('hello')\n",
)
Expand All @@ -154,8 +152,7 @@ async def write_files_bytes(self, sandbox: SandboxRef, *, files: dict[str, bytes
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
await client.filesystem.write_files_bytes(
sandbox,
await sandbox.filesystem.write_files_bytes(
files={"/workspace/a.bin": b"a", "/workspace/b.bin": b"b"},
)
```
Expand Down
3 changes: 1 addition & 2 deletions leap0/_async/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ async def execute(self, sandbox: SandboxRef, *, command: str, cwd: str | None =

Example:
```python
result = await client.process.execute(
sandbox,
result = await sandbox.process.execute(
command="ls -la /workspace",
)
print(result.stdout)
Expand Down
20 changes: 10 additions & 10 deletions leap0/_async/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, cast

from ..constants import OTEL_EXPORTER_OTLP_ENDPOINT_ENV, OTEL_EXPORTER_OTLP_HEADERS_ENV
from .._internal.types import SandboxFactory
from .._internal.types import SandboxFactory, SandboxHandle
from ..models.config import (
DEFAULT_MEMORY_MIB,
DEFAULT_TEMPLATE_NAME,
Expand Down Expand Up @@ -60,7 +60,7 @@ class _AsyncBoundSandboxCallable(Protocol):
async def __call__(self, sandbox: object, *args: object, **kwargs: object) -> object: ...


class AsyncSandbox:
class AsyncSandbox(SandboxHandle):
"""Sandbox object with bound asynchronous service clients.

Attributes:
Expand All @@ -76,14 +76,14 @@ class AsyncSandbox:
def __init__(self, client: "AsyncLeap0Client", data: SandboxData | SandboxStatus):
self._client: "AsyncLeap0Client" = client
self._data: SandboxData | SandboxStatus = data
self.filesystem = _AsyncSandboxServiceProxy(client.filesystem, self)
self.git = _AsyncSandboxServiceProxy(client.git, self)
self.process = _AsyncSandboxServiceProxy(client.process, self)
self.pty = _AsyncSandboxServiceProxy(client.pty, self)
self.lsp = _AsyncSandboxServiceProxy(client.lsp, self)
self.ssh = _AsyncSandboxServiceProxy(client.ssh, self)
self.code_interpreter = _AsyncSandboxServiceProxy(client.code_interpreter, self)
self.desktop = _AsyncSandboxServiceProxy(client.desktop, self)
self.filesystem = _AsyncSandboxServiceProxy(client._filesystem, self)
self.git = _AsyncSandboxServiceProxy(client._git, self)
self.process = _AsyncSandboxServiceProxy(client._process, self)
self.pty = _AsyncSandboxServiceProxy(client._pty, self)
self.lsp = _AsyncSandboxServiceProxy(client._lsp, self)
self.ssh = _AsyncSandboxServiceProxy(client._ssh, self)
self.code_interpreter = _AsyncSandboxServiceProxy(client._code_interpreter, self)
self.desktop = _AsyncSandboxServiceProxy(client._desktop, self)

def __getattr__(self, name: str) -> object:
return getattr(self._data, name)
Expand Down
3 changes: 2 additions & 1 deletion leap0/_async/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from ..models.template import (
CreateTemplateParams,
RegistryCredentials,
RegistryCredentialsDict,
RenameTemplateParams,
Template,
Expand Down Expand Up @@ -36,7 +37,7 @@ def __init__(self, transport: AsyncTransport):
self._transport = transport

@intercept_errors("Failed to create template: ")
async def create(self, *, name: str, uri: str, credentials: RegistryCredentialsDict | None = None) -> Template:
async def create(self, *, name: str, uri: str, credentials: RegistryCredentials | RegistryCredentialsDict | None = None) -> Template:
"""Upload a new template from a container image URI.

Args:
Expand Down
6 changes: 6 additions & 0 deletions leap0/_internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ class SandboxFactory(Protocol[SandboxModelT, SandboxReturnT]):
def __call__(self, data: SandboxModelT) -> SandboxReturnT: ...


class SandboxHandle:
"""Nominal base type for SDK sandbox references."""

id: str


class SyncSandboxService(Protocol):
"""Protocol for sandbox-bound synchronous service callables."""
def __call__(self, sandbox: object, *args: object, **kwargs: object) -> object: ...
Expand Down
37 changes: 19 additions & 18 deletions leap0/_sync/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
class Leap0Client:
"""Top-level client for the Leap0 API.

Use this client to create sandboxes and access all service clients. It can
be used directly or as a context manager.
Use this client to create sandboxes and access top-level control-plane
services. Sandbox-scoped services are exposed through bound sandbox objects.
It can be used directly or as a context manager.

Args:
api_key: API key for authentication. Falls back to ``LEAP0_API_KEY``.
Expand All @@ -60,14 +61,6 @@ class Leap0Client:
sandboxes: Client for sandbox lifecycle operations.
snapshots: Client for snapshot lifecycle operations.
templates: Client for template management.
filesystem: Client for sandbox filesystem operations.
git: Client for Git operations inside a sandbox.
process: Client for one-shot process execution.
pty: Client for interactive PTY sessions.
lsp: Client for Language Server Protocol operations.
ssh: Client for SSH credential management.
code_interpreter: Client for code execution APIs.
desktop: Client for desktop automation APIs.
"""
DEFAULT_BASE_URL = DEFAULT_BASE_URL
DEFAULT_SANDBOX_DOMAIN = DEFAULT_SANDBOX_DOMAIN
Expand Down Expand Up @@ -129,14 +122,14 @@ def __init__(
sandbox_factory=lambda data: Sandbox(self, data),
)
self.templates = TemplatesClient(self._transport)
self.filesystem = FilesystemClient(self._transport)
self.git = GitClient(self._transport)
self.process = ProcessClient(self._transport)
self.pty = PtyClient(self._transport)
self.lsp = LspClient(self._transport)
self.ssh = SshClient(self._transport)
self.code_interpreter = CodeInterpreterClient(self._transport, sandbox_domain=config.sandbox_domain)
self.desktop = DesktopClient(self._transport, sandbox_domain=config.sandbox_domain)
self._filesystem = FilesystemClient(self._transport)
self._git = GitClient(self._transport)
self._process = ProcessClient(self._transport)
self._pty = PtyClient(self._transport)
self._lsp = LspClient(self._transport)
self._ssh = SshClient(self._transport)
self._code_interpreter = CodeInterpreterClient(self._transport, sandbox_domain=config.sandbox_domain)
self._desktop = DesktopClient(self._transport, sandbox_domain=config.sandbox_domain)

if config.sdk_otel_enabled:
self._init_otel()
Expand Down Expand Up @@ -168,6 +161,14 @@ def _init_otel(self) -> None:
else:
self._meter_provider = current_meter_provider

def __getattr__(self, name: str) -> object:
if name in {"filesystem", "git", "process", "pty", "lsp", "ssh", "code_interpreter", "desktop"}:
raise AttributeError(
f"{type(self).__name__!s} has no attribute {name!r}; use a bound sandbox handle instead, "
f"for example sandbox.{name}"
)
raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}")

@with_instrumentation("client.get_sandbox")
def get_sandbox(self, sandbox_id: str) -> Sandbox:
"""Get a sandbox object with bound service clients by ID.
Expand Down
3 changes: 1 addition & 2 deletions leap0/_sync/code_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ class CodeInterpreterClient:

Example:
```python
result = client.code_interpreter.execute(
sandbox,
result = sandbox.code_interpreter.execute(
code="sum([1, 2, 3])",
language="python",
)
Expand Down
9 changes: 3 additions & 6 deletions leap0/_sync/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def write_bytes(self, sandbox: SandboxRef, *, path: str, content: bytes, permiss
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
client.filesystem.write_bytes(
sandbox,
sandbox.filesystem.write_bytes(
path="/workspace/logo.png",
content=image_bytes,
)
Expand Down Expand Up @@ -126,8 +125,7 @@ def write_file(self, sandbox: SandboxRef, *, path: str, content: str, encoding:
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
client.filesystem.write_file(
sandbox,
sandbox.filesystem.write_file(
path="/workspace/app.py",
content="print('hello')\n",
)
Expand All @@ -152,8 +150,7 @@ def write_files_bytes(self, sandbox: SandboxRef, *, files: dict[str, bytes], htt
http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Example:
```python
client.filesystem.write_files_bytes(
sandbox,
sandbox.filesystem.write_files_bytes(
files={"/workspace/a.bin": b"a", "/workspace/b.bin": b"b"},
)
```
Expand Down
3 changes: 1 addition & 2 deletions leap0/_sync/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ def execute(self, sandbox: SandboxRef, *, command: str, cwd: str | None = None,

Example:
```python
result = client.process.execute(
sandbox,
result = sandbox.process.execute(
command="ls -la /workspace",
)
print(result.stdout)
Expand Down
Loading