Skip to content
Open
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
13 changes: 13 additions & 0 deletions porter_sandbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
SandboxNetworkingSpec,
SandboxResourcesSpec,
SandboxSpec,
Snapshot,
SnapshotListResponse,
SnapshotSpec,
StatusResponse,
VolumeFileEntry,
VolumeFileListResponse,
Expand All @@ -54,6 +57,8 @@
SandboxDomainSpecVisibility,
SandboxesPhase,
SandboxMetric,
SnapshotMode,
SnapshotStatus,
StatusResponsePhase,
VolumeFileEntryType,
VolumeObjectSpecAccess,
Expand All @@ -66,6 +71,7 @@
from .readyz import AsyncReadyz, Readyz
from .sandbox import AsyncSandbox, Sandbox
from .sandboxes import AsyncSandboxes, Sandboxes
from .snapshots import AsyncSnapshots, Snapshots
from .volume import AsyncObjectVolume, AsyncVolume, ObjectVolume, Volume, VolumeFile
from .volumes import AsyncVolumes, Volumes

Expand All @@ -77,6 +83,7 @@
"AsyncReadyz",
"AsyncSandbox",
"AsyncSandboxes",
"AsyncSnapshots",
"AsyncVolume",
"AsyncVolumes",
"AuthenticationError",
Expand Down Expand Up @@ -124,6 +131,12 @@
"Sandboxes",
"SandboxesPhase",
"ServerError",
"Snapshot",
"SnapshotListResponse",
"SnapshotMode",
"SnapshotSpec",
"SnapshotStatus",
"Snapshots",
"StatusResponse",
"StatusResponsePhase",
"Volume",
Expand Down
3 changes: 3 additions & 0 deletions porter_sandbox/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .resources.healthz import AsyncHealthz, Healthz
from .resources.readyz import AsyncReadyz, Readyz
from .resources.sandboxes import AsyncSandboxes, Sandboxes
from .resources.snapshots import AsyncSnapshots, Snapshots
from .resources.volumes import AsyncVolumes, Volumes


Expand All @@ -26,6 +27,7 @@ def __init__(
config=Config.resolve(api_key=api_key, base_url=base_url, timeout=timeout),
)
self.sandboxes: Sandboxes = Sandboxes(self._base)
self.snapshots: Snapshots = Snapshots(self._base)
self.volumes: Volumes = Volumes(self._base)
self.healthz: Healthz = Healthz(self._base)
self.readyz: Readyz = Readyz(self._base)
Expand Down Expand Up @@ -55,6 +57,7 @@ def __init__(
config=Config.resolve(api_key=api_key, base_url=base_url, timeout=timeout),
)
self.sandboxes: AsyncSandboxes = AsyncSandboxes(self._base)
self.snapshots: AsyncSnapshots = AsyncSnapshots(self._base)
self.volumes: AsyncVolumes = AsyncVolumes(self._base)
self.healthz: AsyncHealthz = AsyncHealthz(self._base)
self.readyz: AsyncReadyz = AsyncReadyz(self._base)
Expand Down
26 changes: 24 additions & 2 deletions porter_sandbox/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
FilterValuesResponsePhases,
LogLineLevel,
SandboxDomainSpecVisibility,
SnapshotMode,
SnapshotStatus,
StatusResponsePhase,
VolumeFileEntryType,
VolumeObjectSpecAccess,
Expand Down Expand Up @@ -159,7 +161,8 @@ class SandboxResourcesSpec(BaseModel):


class SandboxSpec(BaseModel):
image: str = Field(description="Container image to run")
image: str = Field(description="Container image to run. Empty when snapshot_id is set, since the\nsnapshot is the image.\n")
snapshot_id: str | None = Field(default=None, description="Start the sandbox from this snapshot's filesystem instead of an image,\nso image must be left unset. The snapshot carries no command or volumes:\nan omitted command runs the base image's, and the sandbox mounts only\nthe volumes this request asks for.\n")
name: str | None = Field(default=None, description="Sandbox name, unique within the cluster. Must be a valid DNS label\n(lowercase alphanumeric and dashes). Defaults to the sandbox's id\nwhen omitted.\n")
tags: dict[str, str] | None = Field(default=None, description="Arbitrary key/value labels for identifying and filtering sandboxes")
command: list[str] | None = Field(default=None, description="Override image entrypoint")
Expand All @@ -173,6 +176,25 @@ class SandboxSpec(BaseModel):
ttl_seconds: int | None = Field(default=None, description="Maximum lifetime in seconds, counted from when the sandbox starts\nrunning (from creation while it waits to start). The sandbox is\nterminated once it elapses. Omit for no limit.\n")


class Snapshot(BaseModel):
id: str = Field(description="Snapshot id")
sandbox_id: str = Field(description="Sandbox the snapshot was captured from")
mode: SnapshotMode
status: SnapshotStatus = Field(description="Capture state. A sandbox can be started from a snapshot once it is ready. A failed capture keeps its record so the reason stays visible.\n")
t_created_unix_ms: int | None = Field(default=None, description="When the capture started, in unix milliseconds")
t_ready_unix_ms: int | None = Field(default=None, description="When the capture completed, in unix milliseconds")
failure_reason: str | None = Field(default=None, description="Why the capture failed, when it did")
size_bytes: int | None = Field(default=None, description="Total size of what was captured")


class SnapshotListResponse(BaseModel):
snapshots: list[Snapshot]


class SnapshotSpec(BaseModel):
mode: SnapshotMode | None = Field(default=None, description="What to capture. Defaults to capturing the filesystem.")


class StatusResponse(BaseModel):
id: str = Field(description="Sandbox ID")
name: str = Field(description="Sandbox name (the id when no name was given)")
Expand Down Expand Up @@ -237,4 +259,4 @@ class VolumeSpec(BaseModel):
object: VolumeObjectSpec | None = Field(default=None)


__all__ = ["CountPoint", "CountResponse", "CreateResponse", "Error", "ExecRequest", "ExecResponse", "ExecTarget", "FilterValuesResponse", "HealthResponse", "ListResponse", "LogLine", "LogsResponse", "LookupResult", "MetricSummaryResponse", "Pagination", "ReadinessResponse", "SandboxDomainSpec", "SandboxEgressSpec", "SandboxMetricsPoint", "SandboxMetricsResponse", "SandboxMetricsResult", "SandboxMetricsSeries", "SandboxNetworkingSpec", "SandboxResourcesSpec", "SandboxSpec", "StatusResponse", "Volume", "VolumeFileEntry", "VolumeFileListResponse", "VolumeFileMoveRequest", "VolumeListResponse", "VolumeObjectSpec", "VolumeSpec"]
__all__ = ["CountPoint", "CountResponse", "CreateResponse", "Error", "ExecRequest", "ExecResponse", "ExecTarget", "FilterValuesResponse", "HealthResponse", "ListResponse", "LogLine", "LogsResponse", "LookupResult", "MetricSummaryResponse", "Pagination", "ReadinessResponse", "SandboxDomainSpec", "SandboxEgressSpec", "SandboxMetricsPoint", "SandboxMetricsResponse", "SandboxMetricsResult", "SandboxMetricsSeries", "SandboxNetworkingSpec", "SandboxResourcesSpec", "SandboxSpec", "Snapshot", "SnapshotListResponse", "SnapshotSpec", "StatusResponse", "Volume", "VolumeFileEntry", "VolumeFileListResponse", "VolumeFileMoveRequest", "VolumeListResponse", "VolumeObjectSpec", "VolumeSpec"]
13 changes: 12 additions & 1 deletion porter_sandbox/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ class SandboxMetric(str, Enum):
NETWORK_TX = "network_tx"


class SnapshotMode(str, Enum):
FILESYSTEM = "filesystem"
FULL = "full"


class SnapshotStatus(str, Enum):
PENDING = "pending"
READY = "ready"
FAILED = "failed"


class StatusResponsePhase(str, Enum):
QUEUED = "queued"
CREATING = "creating"
Expand Down Expand Up @@ -80,4 +91,4 @@ class VolumeType(str, Enum):
OBJECT = "object"


__all__ = ["FilterValuesResponsePhases", "LogLineLevel", "SandboxDomainSpecVisibility", "SandboxesPhase", "SandboxMetric", "StatusResponsePhase", "VolumeFileEntryType", "VolumeObjectSpecAccess", "VolumePhase", "VolumeSpecType", "VolumeType"]
__all__ = ["FilterValuesResponsePhases", "LogLineLevel", "SandboxDomainSpecVisibility", "SandboxesPhase", "SandboxMetric", "SnapshotMode", "SnapshotStatus", "StatusResponsePhase", "VolumeFileEntryType", "VolumeObjectSpecAccess", "VolumePhase", "VolumeSpecType", "VolumeType"]
3 changes: 3 additions & 0 deletions porter_sandbox/porter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from porter_sandbox.healthz import AsyncHealthz, Healthz
from porter_sandbox.readyz import AsyncReadyz, Readyz
from porter_sandbox.sandboxes import AsyncSandboxes, Sandboxes
from porter_sandbox.snapshots import AsyncSnapshots, Snapshots
from porter_sandbox.volumes import AsyncVolumes, Volumes


Expand All @@ -28,6 +29,7 @@ def __init__(
self.healthz: Healthz = Healthz(self._client.healthz)
self.readyz: Readyz = Readyz(self._client.readyz)
self.sandboxes: Sandboxes = Sandboxes(self._client.sandboxes)
self.snapshots: Snapshots = Snapshots(self._client.snapshots)
self.volumes: Volumes = Volumes(self._client.volumes)

@property
Expand Down Expand Up @@ -64,6 +66,7 @@ def __init__(
self.healthz: AsyncHealthz = AsyncHealthz(self._client.healthz)
self.readyz: AsyncReadyz = AsyncReadyz(self._client.readyz)
self.sandboxes: AsyncSandboxes = AsyncSandboxes(self._client.sandboxes)
self.snapshots: AsyncSnapshots = AsyncSnapshots(self._client.snapshots)
self.volumes: AsyncVolumes = AsyncVolumes(self._client.volumes)

@property
Expand Down
3 changes: 2 additions & 1 deletion porter_sandbox/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .healthz import AsyncHealthz, Healthz
from .readyz import AsyncReadyz, Readyz
from .sandboxes import AsyncSandboxes, Sandboxes
from .snapshots import AsyncSnapshots, Snapshots
from .volumes import AsyncVolumes, Volumes

__all__ = ["Healthz", "AsyncHealthz", "Readyz", "AsyncReadyz", "Sandboxes", "AsyncSandboxes", "Volumes", "AsyncVolumes"]
__all__ = ["Healthz", "AsyncHealthz", "Readyz", "AsyncReadyz", "Sandboxes", "AsyncSandboxes", "Snapshots", "AsyncSnapshots", "Volumes", "AsyncVolumes"]
101 changes: 101 additions & 0 deletions porter_sandbox/resources/snapshots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Generated by oagen. DO NOT EDIT.


from __future__ import annotations

from typing import Any, TypeVar

from pydantic import BaseModel

from .._async_base_client import _AsyncBaseClient
from .._base_client import _BaseClient
from .._models import Snapshot, SnapshotListResponse, SnapshotSpec

_M = TypeVar("_M", bound=BaseModel)


def _coerce(model_cls: type[_M], data: Any) -> _M:
"""Validate API response data into the generated model type."""
return model_cls.model_validate(data)


class Snapshots:
"""Snapshots resource."""

def __init__(self, client: _BaseClient) -> None:
self._client = client

def create_snapshot(self, id: str, body: SnapshotSpec) -> Snapshot:
"""
Snapshot a sandbox

Capture a running sandbox so a new sandbox can be started from it later.
The sandbox keeps running.
"""
path = f"/v1/sandbox/{id}/snapshot"
response = self._client._request(method="POST", path=path, json=body.model_dump(by_alias=True, exclude_none=True) if hasattr(body, "model_dump") else body)
return _coerce(Snapshot, response)

def list_snapshots(self, sandbox_id: str | None = None) -> SnapshotListResponse:
"""
List snapshots

List captured snapshots, most recent first.
"""
path = "/v1/snapshot"
params: dict[str, Any] = {}
if sandbox_id is not None:
params["sandbox_id"] = sandbox_id
response = self._client._request(method="GET", path=path, params=params)
return _coerce(SnapshotListResponse, response)

def delete_snapshot(self, id: str) -> None:
"""
Delete snapshot

Delete a snapshot and everything it captured.
"""
path = f"/v1/snapshot/{id}"
self._client._request(method="DELETE", path=path)
return None


class AsyncSnapshots:
"""Snapshots resource."""

def __init__(self, client: _AsyncBaseClient) -> None:
self._client = client

async def create_snapshot(self, id: str, body: SnapshotSpec) -> Snapshot:
"""
Snapshot a sandbox

Capture a running sandbox so a new sandbox can be started from it later.
The sandbox keeps running.
"""
path = f"/v1/sandbox/{id}/snapshot"
response = await self._client._request(method="POST", path=path, json=body.model_dump(by_alias=True, exclude_none=True) if hasattr(body, "model_dump") else body)
return _coerce(Snapshot, response)

async def list_snapshots(self, sandbox_id: str | None = None) -> SnapshotListResponse:
"""
List snapshots

List captured snapshots, most recent first.
"""
path = "/v1/snapshot"
params: dict[str, Any] = {}
if sandbox_id is not None:
params["sandbox_id"] = sandbox_id
response = await self._client._request(method="GET", path=path, params=params)
return _coerce(SnapshotListResponse, response)

async def delete_snapshot(self, id: str) -> None:
"""
Delete snapshot

Delete a snapshot and everything it captured.
"""
path = f"/v1/snapshot/{id}"
await self._client._request(method="DELETE", path=path)
return None
4 changes: 4 additions & 0 deletions porter_sandbox/sandboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def create(
self,
image: str,
*,
snapshot_id: str | None = None,
name: str | None = None,
tags: dict[str, str] | None = None,
command: list[str] | None = None,
Expand All @@ -46,6 +47,7 @@ def create(
) -> Sandbox:
spec = SandboxSpec(
image=image,
snapshot_id=snapshot_id,
name=name,
tags=tags,
command=command,
Expand Down Expand Up @@ -98,6 +100,7 @@ async def create(
self,
image: str,
*,
snapshot_id: str | None = None,
name: str | None = None,
tags: dict[str, str] | None = None,
command: list[str] | None = None,
Expand All @@ -112,6 +115,7 @@ async def create(
) -> AsyncSandbox:
spec = SandboxSpec(
image=image,
snapshot_id=snapshot_id,
name=name,
tags=tags,
command=command,
Expand Down
52 changes: 52 additions & 0 deletions porter_sandbox/snapshots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by oagen. DO NOT EDIT.


from __future__ import annotations

from porter_sandbox._models import Snapshot, SnapshotListResponse, SnapshotSpec
from porter_sandbox.resources.snapshots import AsyncSnapshots as AsyncSnapshotsResource
from porter_sandbox.resources.snapshots import Snapshots as SnapshotsResource


class Snapshots:
"""User-facing snapshots namespace."""

def __init__(self, resource: SnapshotsResource) -> None:
self._resource = resource

@property
def raw(self) -> SnapshotsResource:
"""Direct access to the generated low-level snapshots resource."""
return self._resource

def create(self, id: str, body: SnapshotSpec) -> Snapshot:
return self._resource.create_snapshot(id=id, body=body)

def list(self, sandbox_id: str | None = None) -> SnapshotListResponse:
return self._resource.list_snapshots(sandbox_id=sandbox_id)

def delete(self, id: str) -> None:
self._resource.delete_snapshot(id=id)
return None


class AsyncSnapshots:
"""User-facing snapshots namespace."""

def __init__(self, resource: AsyncSnapshotsResource) -> None:
self._resource = resource

@property
def raw(self) -> AsyncSnapshotsResource:
"""Direct access to the generated low-level snapshots resource."""
return self._resource

async def create(self, id: str, body: SnapshotSpec) -> Snapshot:
return await self._resource.create_snapshot(id=id, body=body)

async def list(self, sandbox_id: str | None = None) -> SnapshotListResponse:
return await self._resource.list_snapshots(sandbox_id=sandbox_id)

async def delete(self, id: str) -> None:
await self._resource.delete_snapshot(id=id)
return None
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "porter-sandbox"
version = "0.1.55"
version = "0.1.57"
description = "Python SDK for the Porter Sandbox API"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
Loading
Loading