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
4 changes: 2 additions & 2 deletions examples/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def main() -> None:
try:
sandbox.filesystem.write_file(path="/workspace/checkpoint.txt", content="before snapshot\n")

snapshot: Snapshot = client.snapshots.create(sandbox, name="example-checkpoint")
snapshot: Snapshot = sandbox.create_snapshot(name="example-checkpoint")
print("snapshot:", snapshot.id)

restored: Sandbox = client.snapshots.resume(snapshot_name=snapshot.name)
restored: Sandbox = client.snapshots.restore(snapshot_name=snapshot.name)
try:
content = restored.filesystem.read_file(path="/workspace/checkpoint.txt")
print("restored file:", content.strip())
Expand Down
9 changes: 5 additions & 4 deletions leap0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@
SandboxStatus,
sandbox_id_of,
)
from .models.snapshot import CreateSnapshotParams, ResumeSnapshotParams, Snapshot, SnapshotListResponse, snapshot_id_of
from .models.sandbox import CreateSnapshotParams
from .models.snapshot import RestoreSnapshotParams, Snapshot, SnapshotListResponse, snapshot_id_of
from .models.ssh import SshAccess, SshValidation
from .models.template import (
AwsRegistryCredentials,
Expand Down Expand Up @@ -205,7 +206,7 @@
"RegistryCredentials",
"RegistryCredentialsDict",
"RegistryCredentialsInput",
"ResumeSnapshotParams",
"RestoreSnapshotParams",
"Sandbox",
"SandboxListItem",
"SandboxListResponse",
Expand Down Expand Up @@ -280,8 +281,8 @@
"SandboxStatus": (".models.sandbox", "SandboxStatus"),
"SandboxState": (".models.sandbox", "SandboxState"),
"sandbox_id_of": (".models.sandbox", "sandbox_id_of"),
"CreateSnapshotParams": (".models.snapshot", "CreateSnapshotParams"),
"ResumeSnapshotParams": (".models.snapshot", "ResumeSnapshotParams"),
"CreateSnapshotParams": (".models.sandbox", "CreateSnapshotParams"),
"RestoreSnapshotParams": (".models.snapshot", "RestoreSnapshotParams"),
"Snapshot": (".models.snapshot", "Snapshot"),
"SnapshotListResponse": (".models.snapshot", "SnapshotListResponse"),
"snapshot_id_of": (".models.snapshot", "snapshot_id_of"),
Expand Down
47 changes: 47 additions & 0 deletions leap0/_async/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
DEFAULT_VCPU,
)
from ..models.sandbox import (
CreateSnapshotParams,
CreatePresignedURLParams,
CreateSandboxParams,
ObjectStorageMount,
Expand All @@ -25,6 +26,8 @@
_validate_object_storage_mount_update,
sandbox_id_of,
)
from ..models.snapshot import Snapshot
from .._schemas.snapshot import SnapshotCreateResponseDict
from .._schemas.sandbox import ListSandboxesResponseDict, NetworkPolicyDict, ObjectStorageMountDict, ObjectStorageMountRequestDict, ObjectStorageMountUpdateDict, PresignedURLResponseDict, SandboxCreateResponseDict, SandboxStatusResponseDict
from .._utils.errors import intercept_errors
from .._utils.url import ensure_leading_slash, sandbox_base_url, websocket_url_from_http
Expand Down Expand Up @@ -126,6 +129,30 @@ async def pause(self, http_timeout: float | None = None) -> AsyncSandbox:
self._data = latest._data
return self

async def create_snapshot(
self,
*,
name: str | None = None,
kill_sandbox_after: bool = False,
http_timeout: float | None = None,
) -> Snapshot:
"""Create a snapshot from this sandbox.

Args:
name: Optional snapshot name. Auto-generated if omitted.
kill_sandbox_after: Terminate the source sandbox after the snapshot is stored.
http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Returns:
Snapshot: Created snapshot metadata.
"""
return await self._client.sandboxes.create_snapshot(
self,
name=name,
kill_sandbox_after=kill_sandbox_after,
http_timeout=http_timeout,
)

async def delete(self, http_timeout: float | None = None) -> None:
"""Terminate and delete a sandbox.

Expand Down Expand Up @@ -377,6 +404,26 @@ async def pause(
)
return self._wrap_sandbox(SandboxData.from_dict(data))

@intercept_errors("Failed to create snapshot: ")
async def create_snapshot(
self,
sandbox: SandboxRef,
*,
name: str | None = None,
kill_sandbox_after: bool = False,
http_timeout: float | None = None,
) -> Snapshot:
"""Create a snapshot from a running sandbox."""
payload = CreateSnapshotParams(name=name, kill_sandbox_after=kill_sandbox_after).to_payload()
data: SnapshotCreateResponseDict = await self._transport.request_json(
"POST",
f"/v1/sandbox/{sandbox_id_of(sandbox)}/snapshot/create",
json=payload,
expected_status=201,
timeout=http_timeout,
)
return Snapshot.from_dict(data)

@intercept_errors("Failed to get sandbox: ")
async def get(self, sandbox: SandboxRef, http_timeout: float | None = None) -> AsyncSandboxT | SandboxData | SandboxStatus:
"""Get the latest sandbox metadata.
Expand Down
89 changes: 9 additions & 80 deletions leap0/_async/snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from typing import Generic, TypeVar, cast

from .._internal.types import SandboxFactory
from ..models.sandbox import Sandbox, SandboxRef, sandbox_id_of
from ..models.snapshot import CreateSnapshotParams, ResumeSnapshotParams, Snapshot, SnapshotListResponse, SnapshotRef, snapshot_id_of
from .._schemas.snapshot import ListSnapshotsResponseDict, SnapshotCreateResponseDict
from ..models.sandbox import Sandbox
from ..models.snapshot import RestoreSnapshotParams, Snapshot, SnapshotListResponse, SnapshotRef, snapshot_id_of
from .._schemas.snapshot import ListSnapshotsResponseDict
from .._schemas.sandbox import NetworkPolicyDict, SandboxCreateResponseDict
from .._utils.errors import intercept_errors
from ._transport import AsyncTransport
Expand All @@ -14,7 +14,7 @@


class AsyncSnapshotsClient(Generic[AsyncSnapshotSandboxT]):
"""Create, resume, and delete sandbox snapshots.
"""List, restore, and delete sandbox snapshots.

A snapshot captures the full state of a running sandbox so it can be
restored later.
Expand Down Expand Up @@ -72,75 +72,8 @@ async def list(
))
return SnapshotListResponse.from_dict(data)

@intercept_errors("Failed to create snapshot: ")
async def create(
self,
sandbox: SandboxRef,
*,
name: str | None = None,
http_timeout: float | None = None,
) -> Snapshot:
"""Create a snapshot of a running sandbox without stopping it.

Args:
sandbox: Sandbox ID or object to snapshot.
name: Optional snapshot name. Auto-generated if omitted.

http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Args:
sandbox: Sandbox ID or object to pause.
name: Optional snapshot name. Auto-generated if omitted.

http_timeout: Optional HTTP request timeout in seconds for this SDK call.
Returns:
Snapshot: Created snapshot metadata.

Returns:
Snapshot: Snapshot metadata including ID and optional name.
"""
payload = CreateSnapshotParams(name=name).to_payload()
data = cast(SnapshotCreateResponseDict, await self._transport.request_json(
"POST",
f"/v1/sandbox/{sandbox_id_of(sandbox)}/snapshot/create",
json=payload,
expected_status=201,
timeout=http_timeout,
))
return Snapshot.from_dict(data)

@intercept_errors("Failed to pause sandbox: ")
async def pause(
self,
sandbox: SandboxRef,
*,
name: str | None = None,
http_timeout: float | None = None,
) -> Snapshot:
"""Pause a running sandbox and create a snapshot in one step.

The sandbox is stopped after the snapshot is taken.

Args:
sandbox: Sandbox ID or object.
name: Name used by this operation.
http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Returns:
object: Result returned by this operation.
"""
payload = CreateSnapshotParams(name=name).to_payload()
data = cast(SnapshotCreateResponseDict, await self._transport.request_json(
"POST",
f"/v1/sandbox/{sandbox_id_of(sandbox)}/snapshot/pause",
json=payload,
expected_status=201,
timeout=http_timeout,
))
return Snapshot.from_dict(data)

@intercept_errors("Failed to resume snapshot: ")
async def resume(
@intercept_errors("Failed to restore snapshot: ")
async def restore(
self,
*,
snapshot_name: str,
Expand All @@ -156,24 +89,20 @@ async def resume(
auto_pause: Automatically pause the restored sandbox on timeout.
timeout: Sandbox timeout in seconds.
network_policy: Override the network policy from the snapshot.

http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Args:
http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Returns:
Sandbox: Newly resumed sandbox.
Sandbox: Newly restored sandbox.
"""
payload = ResumeSnapshotParams(
payload = RestoreSnapshotParams(
snapshot_name=snapshot_name,
auto_pause=auto_pause,
timeout=timeout,
network_policy=network_policy,
).to_payload()
data = cast(SandboxCreateResponseDict, await self._transport.request_json(
"POST",
"/v1/snapshot/resume",
"/v1/snapshot/restore",
json=payload,
expected_status=201,
timeout=http_timeout,
Expand Down
47 changes: 47 additions & 0 deletions leap0/_sync/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
DEFAULT_VCPU,
)
from ..models.sandbox import (
CreateSnapshotParams,
CreatePresignedURLParams,
CreateSandboxParams,
ObjectStorageMount,
Expand All @@ -24,6 +25,8 @@
_validate_object_storage_mount_update,
sandbox_id_of,
)
from ..models.snapshot import Snapshot
from .._schemas.snapshot import SnapshotCreateResponseDict
from .._schemas.sandbox import ListSandboxesResponseDict, NetworkPolicyDict, ObjectStorageMountDict, ObjectStorageMountRequestDict, ObjectStorageMountUpdateDict, PresignedURLResponseDict, SandboxCreateResponseDict, SandboxStatusResponseDict
from .._utils.errors import intercept_errors
from .._utils.url import ensure_leading_slash, sandbox_base_url, websocket_url_from_http
Expand Down Expand Up @@ -123,6 +126,30 @@ def pause(self, http_timeout: float | None = None) -> Sandbox:
self._data = latest._data
return self

def create_snapshot(
self,
*,
name: str | None = None,
kill_sandbox_after: bool = False,
http_timeout: float | None = None,
) -> Snapshot:
"""Create a snapshot from this sandbox.

Args:
name: Optional snapshot name. Auto-generated if omitted.
kill_sandbox_after: Terminate the source sandbox after the snapshot is stored.
http_timeout: Optional HTTP request timeout in seconds for this SDK call.

Returns:
Snapshot: Created snapshot metadata.
"""
return self._client.sandboxes.create_snapshot(
self,
name=name,
kill_sandbox_after=kill_sandbox_after,
http_timeout=http_timeout,
)

def delete(self, http_timeout: float | None = None) -> None:
"""Delete the sandbox.

Expand Down Expand Up @@ -376,6 +403,26 @@ def pause(self, sandbox: SandboxRef, http_timeout: float | None = None) -> Sandb
)
return self._wrap_sandbox(SandboxData.from_dict(data))

@intercept_errors("Failed to create snapshot: ")
def create_snapshot(
self,
sandbox: SandboxRef,
*,
name: str | None = None,
kill_sandbox_after: bool = False,
http_timeout: float | None = None,
) -> Snapshot:
"""Create a snapshot from a running sandbox."""
payload = CreateSnapshotParams(name=name, kill_sandbox_after=kill_sandbox_after).to_payload()
data: SnapshotCreateResponseDict = self._transport.request_json(
"POST",
f"/v1/sandbox/{sandbox_id_of(sandbox)}/snapshot/create",
json=payload,
expected_status=201,
timeout=http_timeout,
)
return Snapshot.from_dict(data)

@intercept_errors("Failed to get sandbox: ")
def get(self, sandbox: SandboxRef, http_timeout: float | None = None) -> SandboxT | SandboxData | SandboxStatus:
"""Get the latest sandbox metadata.
Expand Down
Loading