forked from wrenn/python-sdk
fix: renamed sandbox to capsule
This commit is contained in:
@ -1,22 +1,7 @@
|
||||
from wrenn.client import AsyncWrennClient, WrennClient
|
||||
from wrenn.exceptions import (
|
||||
WrennAgentError,
|
||||
WrennAuthenticationError,
|
||||
WrennConflictError,
|
||||
WrennError,
|
||||
WrennForbiddenError,
|
||||
WrennHostHasSandboxesError,
|
||||
WrennHostUnavailableError,
|
||||
WrennInternalError,
|
||||
WrennNotFoundError,
|
||||
WrennValidationError,
|
||||
)
|
||||
from wrenn.models import FileEntry
|
||||
from wrenn.pty import AsyncPtySession, PtyEvent, PtyEventType, PtySession
|
||||
from wrenn.sandbox import (
|
||||
from wrenn.capsule import (
|
||||
Capsule,
|
||||
CodeResult,
|
||||
ExecResult,
|
||||
Sandbox,
|
||||
StreamErrorEvent,
|
||||
StreamEvent,
|
||||
StreamExitEvent,
|
||||
@ -24,6 +9,21 @@ from wrenn.sandbox import (
|
||||
StreamStderrEvent,
|
||||
StreamStdoutEvent,
|
||||
)
|
||||
from wrenn.client import AsyncWrennClient, WrennClient
|
||||
from wrenn.exceptions import (
|
||||
WrennAgentError,
|
||||
WrennAuthenticationError,
|
||||
WrennConflictError,
|
||||
WrennError,
|
||||
WrennForbiddenError,
|
||||
WrennHostHasCapsulesError,
|
||||
WrennHostUnavailableError,
|
||||
WrennInternalError,
|
||||
WrennNotFoundError,
|
||||
WrennValidationError,
|
||||
)
|
||||
from wrenn.models import FileEntry
|
||||
from wrenn.pty import AsyncPtySession, PtyEvent, PtyEventType, PtySession
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
@ -31,6 +31,7 @@ __all__ = [
|
||||
"__version__",
|
||||
"AsyncPtySession",
|
||||
"AsyncWrennClient",
|
||||
"Capsule",
|
||||
"CodeResult",
|
||||
"ExecResult",
|
||||
"FileEntry",
|
||||
@ -50,9 +51,32 @@ __all__ = [
|
||||
"WrennConflictError",
|
||||
"WrennError",
|
||||
"WrennForbiddenError",
|
||||
"WrennHostHasCapsulesError",
|
||||
"WrennHostHasSandboxesError",
|
||||
"WrennHostUnavailableError",
|
||||
"WrennInternalError",
|
||||
"WrennNotFoundError",
|
||||
"WrennValidationError",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> type:
|
||||
if name == "Sandbox":
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'Sandbox' is deprecated, use 'Capsule' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return Capsule
|
||||
if name == "WrennHostHasSandboxesError":
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"'WrennHostHasSandboxesError' is deprecated, use 'WrennHostHasCapsulesError' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return WrennHostHasCapsulesError
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
1171
src/wrenn/capsule.py
Normal file
1171
src/wrenn/capsule.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import warnings
|
||||
from typing import cast
|
||||
|
||||
import httpx
|
||||
|
||||
from wrenn.capsule import Capsule
|
||||
from wrenn.exceptions import handle_response
|
||||
from wrenn.models import (
|
||||
APIKeyResponse,
|
||||
@ -14,9 +16,8 @@ from wrenn.models import (
|
||||
Template,
|
||||
)
|
||||
from wrenn.models import (
|
||||
Sandbox as SandboxModel,
|
||||
Capsule as CapsuleModel,
|
||||
)
|
||||
from wrenn.sandbox import Sandbox
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.wrenn.dev"
|
||||
|
||||
@ -112,8 +113,8 @@ class AsyncAPIKeysResource:
|
||||
handle_response(resp)
|
||||
|
||||
|
||||
class SandboxesResource:
|
||||
"""Sync sandbox control-plane operations."""
|
||||
class CapsulesResource:
|
||||
"""Sync capsule control-plane operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -133,7 +134,7 @@ class SandboxesResource:
|
||||
vcpus: int | None = None,
|
||||
memory_mb: int | None = None,
|
||||
timeout_sec: int | None = None,
|
||||
) -> Sandbox:
|
||||
) -> Capsule:
|
||||
payload: dict = {}
|
||||
if template is not None:
|
||||
payload["template"] = template
|
||||
@ -143,27 +144,27 @@ class SandboxesResource:
|
||||
payload["memory_mb"] = memory_mb
|
||||
if timeout_sec is not None:
|
||||
payload["timeout_sec"] = timeout_sec
|
||||
resp = self._http.post("/v1/sandboxes", json=payload)
|
||||
model = SandboxModel.model_validate(handle_response(resp))
|
||||
sb = Sandbox.model_validate(model.model_dump())
|
||||
sb._bind(self._http, self._base_url, self._api_key, self._token)
|
||||
return sb
|
||||
resp = self._http.post("/v1/capsules", json=payload)
|
||||
model = CapsuleModel.model_validate(handle_response(resp))
|
||||
cap = Capsule.model_validate(model.model_dump())
|
||||
cap._bind(self._http, self._base_url, self._api_key, self._token)
|
||||
return cap
|
||||
|
||||
def list(self) -> list[SandboxModel]:
|
||||
resp = self._http.get("/v1/sandboxes")
|
||||
return [SandboxModel.model_validate(item) for item in handle_response(resp)]
|
||||
def list(self) -> list[CapsuleModel]:
|
||||
resp = self._http.get("/v1/capsules")
|
||||
return [CapsuleModel.model_validate(item) for item in handle_response(resp)]
|
||||
|
||||
def get(self, id: str) -> SandboxModel:
|
||||
resp = self._http.get(f"/v1/sandboxes/{id}")
|
||||
return SandboxModel.model_validate(handle_response(resp))
|
||||
def get(self, id: str) -> CapsuleModel:
|
||||
resp = self._http.get(f"/v1/capsules/{id}")
|
||||
return CapsuleModel.model_validate(handle_response(resp))
|
||||
|
||||
def destroy(self, id: str) -> None:
|
||||
resp = self._http.delete(f"/v1/sandboxes/{id}")
|
||||
resp = self._http.delete(f"/v1/capsules/{id}")
|
||||
handle_response(resp)
|
||||
|
||||
|
||||
class AsyncSandboxesResource:
|
||||
"""Async sandbox control-plane operations."""
|
||||
class AsyncCapsulesResource:
|
||||
"""Async capsule control-plane operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -183,7 +184,7 @@ class AsyncSandboxesResource:
|
||||
vcpus: int | None = None,
|
||||
memory_mb: int | None = None,
|
||||
timeout_sec: int | None = None,
|
||||
) -> Sandbox:
|
||||
) -> Capsule:
|
||||
payload: dict = {}
|
||||
if template is not None:
|
||||
payload["template"] = template
|
||||
@ -193,22 +194,22 @@ class AsyncSandboxesResource:
|
||||
payload["memory_mb"] = memory_mb
|
||||
if timeout_sec is not None:
|
||||
payload["timeout_sec"] = timeout_sec
|
||||
resp = await self._http.post("/v1/sandboxes", json=payload)
|
||||
model = SandboxModel.model_validate(handle_response(resp))
|
||||
sb = Sandbox.model_validate(model.model_dump())
|
||||
sb._bind(self._http, self._base_url, self._api_key, self._token)
|
||||
return sb
|
||||
resp = await self._http.post("/v1/capsules", json=payload)
|
||||
model = CapsuleModel.model_validate(handle_response(resp))
|
||||
cap = Capsule.model_validate(model.model_dump())
|
||||
cap._bind(self._http, self._base_url, self._api_key, self._token)
|
||||
return cap
|
||||
|
||||
async def list(self) -> list[SandboxModel]:
|
||||
resp = await self._http.get("/v1/sandboxes")
|
||||
return [SandboxModel.model_validate(item) for item in handle_response(resp)]
|
||||
async def list(self) -> list[CapsuleModel]:
|
||||
resp = await self._http.get("/v1/capsules")
|
||||
return [CapsuleModel.model_validate(item) for item in handle_response(resp)]
|
||||
|
||||
async def get(self, id: str) -> SandboxModel:
|
||||
resp = await self._http.get(f"/v1/sandboxes/{id}")
|
||||
return SandboxModel.model_validate(handle_response(resp))
|
||||
async def get(self, id: str) -> CapsuleModel:
|
||||
resp = await self._http.get(f"/v1/capsules/{id}")
|
||||
return CapsuleModel.model_validate(handle_response(resp))
|
||||
|
||||
async def destroy(self, id: str) -> None:
|
||||
resp = await self._http.delete(f"/v1/sandboxes/{id}")
|
||||
resp = await self._http.delete(f"/v1/capsules/{id}")
|
||||
handle_response(resp)
|
||||
|
||||
|
||||
@ -220,11 +221,11 @@ class SnapshotsResource:
|
||||
|
||||
def create(
|
||||
self,
|
||||
sandbox_id: str,
|
||||
capsule_id: str,
|
||||
name: str | None = None,
|
||||
overwrite: bool = False,
|
||||
) -> Template:
|
||||
payload: dict = {"sandbox_id": sandbox_id}
|
||||
payload: dict = {"sandbox_id": capsule_id}
|
||||
if name is not None:
|
||||
payload["name"] = name
|
||||
params: dict = {}
|
||||
@ -253,11 +254,11 @@ class AsyncSnapshotsResource:
|
||||
|
||||
async def create(
|
||||
self,
|
||||
sandbox_id: str,
|
||||
capsule_id: str,
|
||||
name: str | None = None,
|
||||
overwrite: bool = False,
|
||||
) -> Template:
|
||||
payload: dict = {"sandbox_id": sandbox_id}
|
||||
payload: dict = {"sandbox_id": capsule_id}
|
||||
if name is not None:
|
||||
payload["name"] = name
|
||||
params: dict = {}
|
||||
@ -410,10 +411,19 @@ class WrennClient:
|
||||
|
||||
self.auth = AuthResource(self._http)
|
||||
self.api_keys = APIKeysResource(self._http)
|
||||
self.sandboxes = SandboxesResource(self._http, base_url, api_key, token)
|
||||
self.capsules = CapsulesResource(self._http, base_url, api_key, token)
|
||||
self.snapshots = SnapshotsResource(self._http)
|
||||
self.hosts = HostsResource(self._http)
|
||||
|
||||
@property
|
||||
def sandboxes(self) -> CapsulesResource:
|
||||
warnings.warn(
|
||||
"'client.sandboxes' is deprecated, use 'client.capsules' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.capsules
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the underlying HTTP connection pool."""
|
||||
self._http.close()
|
||||
@ -458,10 +468,19 @@ class AsyncWrennClient:
|
||||
|
||||
self.auth = AsyncAuthResource(self._http)
|
||||
self.api_keys = AsyncAPIKeysResource(self._http)
|
||||
self.sandboxes = AsyncSandboxesResource(self._http, base_url, api_key, token)
|
||||
self.capsules = AsyncCapsulesResource(self._http, base_url, api_key, token)
|
||||
self.snapshots = AsyncSnapshotsResource(self._http)
|
||||
self.hosts = AsyncHostsResource(self._http)
|
||||
|
||||
@property
|
||||
def sandboxes(self) -> AsyncCapsulesResource:
|
||||
warnings.warn(
|
||||
"'client.sandboxes' is deprecated, use 'client.capsules' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.capsules
|
||||
|
||||
async def aclose(self) -> None:
|
||||
"""Close the underlying async HTTP connection pool."""
|
||||
await self._http.aclose()
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
@ -33,15 +35,24 @@ class WrennConflictError(WrennError):
|
||||
"""409 — State conflict (e.g. invalid_state)."""
|
||||
|
||||
|
||||
class WrennHostHasSandboxesError(WrennConflictError):
|
||||
"""409 — Host still has running sandboxes."""
|
||||
class WrennHostHasCapsulesError(WrennConflictError):
|
||||
"""409 — Host still has running capsules."""
|
||||
|
||||
def __init__(
|
||||
self, code: str, message: str, status_code: int, sandbox_ids: list[str]
|
||||
self, code: str, message: str, status_code: int, capsule_ids: list[str]
|
||||
) -> None:
|
||||
self.sandbox_ids = sandbox_ids
|
||||
self.capsule_ids = capsule_ids
|
||||
super().__init__(code, message, status_code)
|
||||
|
||||
@property
|
||||
def sandbox_ids(self) -> list[str]:
|
||||
warnings.warn(
|
||||
"'sandbox_ids' is deprecated, use 'capsule_ids' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.capsule_ids
|
||||
|
||||
|
||||
class WrennHostUnavailableError(WrennError):
|
||||
"""503 — No suitable host available."""
|
||||
@ -62,7 +73,8 @@ _ERROR_MAP: dict[str, type[WrennError]] = {
|
||||
"not_found": WrennNotFoundError,
|
||||
"invalid_state": WrennConflictError,
|
||||
"conflict": WrennConflictError,
|
||||
"host_has_sandboxes": WrennHostHasSandboxesError,
|
||||
"host_has_sandboxes": WrennHostHasCapsulesError,
|
||||
"host_has_capsules": WrennHostHasCapsulesError,
|
||||
"host_unavailable": WrennHostUnavailableError,
|
||||
"agent_error": WrennAgentError,
|
||||
"internal_error": WrennInternalError,
|
||||
@ -83,12 +95,12 @@ def handle_response(resp: httpx.Response) -> dict | list:
|
||||
|
||||
exc_cls = _ERROR_MAP.get(code, WrennError)
|
||||
|
||||
if exc_cls is WrennHostHasSandboxesError:
|
||||
raise WrennHostHasSandboxesError(
|
||||
if exc_cls is WrennHostHasCapsulesError:
|
||||
raise WrennHostHasCapsulesError(
|
||||
code=code,
|
||||
message=message,
|
||||
status_code=resp.status_code,
|
||||
sandbox_ids=body.get("sandbox_ids", []),
|
||||
capsule_ids=body.get("sandbox_ids", []),
|
||||
)
|
||||
|
||||
raise exc_cls(
|
||||
@ -101,3 +113,14 @@ def handle_response(resp: httpx.Response) -> dict | list:
|
||||
return {}
|
||||
|
||||
return resp.json()
|
||||
|
||||
|
||||
def __getattr__(name: str) -> type:
|
||||
if name == "WrennHostHasSandboxesError":
|
||||
warnings.warn(
|
||||
"'WrennHostHasSandboxesError' is deprecated, use 'WrennHostHasCapsulesError' instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return WrennHostHasCapsulesError
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from wrenn.models._generated import (
|
||||
APIKeyResponse,
|
||||
AuthResponse,
|
||||
Capsule,
|
||||
CreateAPIKeyRequest,
|
||||
CreateCapsuleRequest,
|
||||
CreateHostRequest,
|
||||
CreateHostResponse,
|
||||
CreateSandboxRequest,
|
||||
CreateSnapshotRequest,
|
||||
Encoding,
|
||||
Error,
|
||||
@ -22,7 +23,6 @@ from wrenn.models._generated import (
|
||||
RegisterHostRequest,
|
||||
RegisterHostResponse,
|
||||
RemoveRequest,
|
||||
Sandbox,
|
||||
SignupRequest,
|
||||
Status,
|
||||
Status1,
|
||||
@ -38,7 +38,7 @@ __all__ = [
|
||||
"CreateAPIKeyRequest",
|
||||
"CreateHostRequest",
|
||||
"CreateHostResponse",
|
||||
"CreateSandboxRequest",
|
||||
"CreateCapsuleRequest",
|
||||
"CreateSnapshotRequest",
|
||||
"Encoding",
|
||||
"Error",
|
||||
@ -56,7 +56,7 @@ __all__ = [
|
||||
"RegisterHostRequest",
|
||||
"RegisterHostResponse",
|
||||
"RemoveRequest",
|
||||
"Sandbox",
|
||||
"Capsule",
|
||||
"SignupRequest",
|
||||
"Status",
|
||||
"Status1",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: openapi.yaml
|
||||
# timestamp: 2026-04-11T15:00:55+00:00
|
||||
# timestamp: 2026-04-12T20:56:29+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -22,7 +22,7 @@ class LoginRequest(BaseModel):
|
||||
|
||||
|
||||
class AuthResponse(BaseModel):
|
||||
token: Annotated[str | None, Field(description="JWT token (valid for 6 hours)")] = (
|
||||
token: Annotated[str | None, Field(description='JWT token (valid for 6 hours)')] = (
|
||||
None
|
||||
)
|
||||
user_id: str | None = None
|
||||
@ -32,7 +32,7 @@ class AuthResponse(BaseModel):
|
||||
|
||||
|
||||
class CreateAPIKeyRequest(BaseModel):
|
||||
name: str | None = "Unnamed API Key"
|
||||
name: str | None = 'Unnamed API Key'
|
||||
|
||||
|
||||
class APIKeyResponse(BaseModel):
|
||||
@ -47,29 +47,29 @@ class APIKeyResponse(BaseModel):
|
||||
key: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
description="Full plaintext key. Only returned on creation, never again."
|
||||
description='Full plaintext key. Only returned on creation, never again.'
|
||||
),
|
||||
] = None
|
||||
|
||||
|
||||
class CreateSandboxRequest(BaseModel):
|
||||
template: str | None = "minimal"
|
||||
class CreateCapsuleRequest(BaseModel):
|
||||
template: str | None = 'minimal'
|
||||
vcpus: int | None = 1
|
||||
memory_mb: int | None = 512
|
||||
timeout_sec: Annotated[
|
||||
int | None,
|
||||
Field(
|
||||
description="Auto-pause TTL in seconds. The sandbox is automatically paused after this duration of inactivity (no exec or ping). 0 means no auto-pause.\n"
|
||||
description='Auto-pause TTL in seconds. The capsule is automatically paused after this duration of inactivity (no exec or ping). 0 means no auto-pause.\n'
|
||||
),
|
||||
] = 0
|
||||
|
||||
|
||||
class Range(StrEnum):
|
||||
field_5m = "5m"
|
||||
field_1h = "1h"
|
||||
field_6h = "6h"
|
||||
field_24h = "24h"
|
||||
field_30d = "30d"
|
||||
field_5m = '5m'
|
||||
field_1h = '1h'
|
||||
field_6h = '6h'
|
||||
field_24h = '24h'
|
||||
field_30d = '30d'
|
||||
|
||||
|
||||
class Current(BaseModel):
|
||||
@ -100,29 +100,29 @@ class Series(BaseModel):
|
||||
memory_mb: list[int] | None = None
|
||||
|
||||
|
||||
class SandboxStats(BaseModel):
|
||||
class CapsuleStats(BaseModel):
|
||||
range: Range | None = None
|
||||
current: Current | None = None
|
||||
peaks: Annotated[
|
||||
Peaks | None, Field(description="Maximum values over the last 30 days.")
|
||||
Peaks | None, Field(description='Maximum values over the last 30 days.')
|
||||
] = None
|
||||
series: Annotated[
|
||||
Series | None, Field(description="Parallel arrays for chart rendering.")
|
||||
Series | None, Field(description='Parallel arrays for chart rendering.')
|
||||
] = None
|
||||
|
||||
|
||||
class Status(StrEnum):
|
||||
pending = "pending"
|
||||
starting = "starting"
|
||||
running = "running"
|
||||
paused = "paused"
|
||||
hibernated = "hibernated"
|
||||
stopped = "stopped"
|
||||
missing = "missing"
|
||||
error = "error"
|
||||
pending = 'pending'
|
||||
starting = 'starting'
|
||||
running = 'running'
|
||||
paused = 'paused'
|
||||
hibernated = 'hibernated'
|
||||
stopped = 'stopped'
|
||||
missing = 'missing'
|
||||
error = 'error'
|
||||
|
||||
|
||||
class Sandbox(BaseModel):
|
||||
class Capsule(BaseModel):
|
||||
id: str | None = None
|
||||
status: Status | None = None
|
||||
template: str | None = None
|
||||
@ -139,17 +139,17 @@ class Sandbox(BaseModel):
|
||||
|
||||
class CreateSnapshotRequest(BaseModel):
|
||||
sandbox_id: Annotated[
|
||||
str, Field(description="ID of the running sandbox to snapshot.")
|
||||
str, Field(description='ID of the running capsule to snapshot.')
|
||||
]
|
||||
name: Annotated[
|
||||
str | None,
|
||||
Field(description="Name for the snapshot template. Auto-generated if omitted."),
|
||||
Field(description='Name for the snapshot template. Auto-generated if omitted.'),
|
||||
] = None
|
||||
|
||||
|
||||
class Type(StrEnum):
|
||||
base = "base"
|
||||
snapshot = "snapshot"
|
||||
base = 'base'
|
||||
snapshot = 'snapshot'
|
||||
|
||||
|
||||
class Template(BaseModel):
|
||||
@ -172,8 +172,8 @@ class Encoding(StrEnum):
|
||||
Output encoding. "base64" when stdout/stderr contain binary data.
|
||||
"""
|
||||
|
||||
utf_8 = "utf-8"
|
||||
base64 = "base64"
|
||||
utf_8 = 'utf-8'
|
||||
base64 = 'base64'
|
||||
|
||||
|
||||
class ExecResponse(BaseModel):
|
||||
@ -192,23 +192,23 @@ class ExecResponse(BaseModel):
|
||||
|
||||
|
||||
class ReadFileRequest(BaseModel):
|
||||
path: Annotated[str, Field(description="Absolute file path inside the sandbox")]
|
||||
path: Annotated[str, Field(description='Absolute file path inside the capsule')]
|
||||
|
||||
|
||||
class ListDirRequest(BaseModel):
|
||||
path: Annotated[str, Field(description="Directory path inside the sandbox")]
|
||||
path: Annotated[str, Field(description='Directory path inside the capsule')]
|
||||
depth: Annotated[
|
||||
int | None,
|
||||
Field(
|
||||
description="Recursion depth (0 = non-recursive, 1 = immediate children)"
|
||||
description='Recursion depth (0 = non-recursive, 1 = immediate children)'
|
||||
),
|
||||
] = 1
|
||||
|
||||
|
||||
class Type1(StrEnum):
|
||||
file = "file"
|
||||
directory = "directory"
|
||||
symlink = "symlink"
|
||||
file = 'file'
|
||||
directory = 'directory'
|
||||
symlink = 'symlink'
|
||||
|
||||
|
||||
class FileEntry(BaseModel):
|
||||
@ -223,14 +223,14 @@ class FileEntry(BaseModel):
|
||||
owner: str | None = None
|
||||
group: str | None = None
|
||||
modified_at: Annotated[
|
||||
int | None, Field(description="Unix timestamp (seconds)")
|
||||
int | None, Field(description='Unix timestamp (seconds)')
|
||||
] = None
|
||||
symlink_target: str | None = None
|
||||
|
||||
|
||||
class MakeDirRequest(BaseModel):
|
||||
path: Annotated[
|
||||
str, Field(description="Directory path to create inside the sandbox")
|
||||
str, Field(description='Directory path to create inside the capsule')
|
||||
]
|
||||
|
||||
|
||||
@ -239,7 +239,7 @@ class MakeDirResponse(BaseModel):
|
||||
|
||||
|
||||
class RemoveRequest(BaseModel):
|
||||
path: Annotated[str, Field(description="Path to remove inside the sandbox")]
|
||||
path: Annotated[str, Field(description='Path to remove inside the capsule')]
|
||||
|
||||
|
||||
class Type2(StrEnum):
|
||||
@ -247,51 +247,51 @@ class Type2(StrEnum):
|
||||
Host type. Regular hosts are shared; BYOC hosts belong to a team.
|
||||
"""
|
||||
|
||||
regular = "regular"
|
||||
byoc = "byoc"
|
||||
regular = 'regular'
|
||||
byoc = 'byoc'
|
||||
|
||||
|
||||
class CreateHostRequest(BaseModel):
|
||||
type: Annotated[
|
||||
Type2,
|
||||
Field(
|
||||
description="Host type. Regular hosts are shared; BYOC hosts belong to a team."
|
||||
description='Host type. Regular hosts are shared; BYOC hosts belong to a team.'
|
||||
),
|
||||
]
|
||||
team_id: Annotated[str | None, Field(description="Required for BYOC hosts.")] = None
|
||||
team_id: Annotated[str | None, Field(description='Required for BYOC hosts.')] = None
|
||||
provider: Annotated[
|
||||
str | None,
|
||||
Field(description="Cloud provider (e.g. aws, gcp, hetzner, bare-metal)."),
|
||||
Field(description='Cloud provider (e.g. aws, gcp, hetzner, bare-metal).'),
|
||||
] = None
|
||||
availability_zone: Annotated[
|
||||
str | None, Field(description="Availability zone (e.g. us-east, eu-west).")
|
||||
str | None, Field(description='Availability zone (e.g. us-east, eu-west).')
|
||||
] = None
|
||||
|
||||
|
||||
class RegisterHostRequest(BaseModel):
|
||||
token: Annotated[
|
||||
str, Field(description="One-time registration token from POST /v1/hosts.")
|
||||
str, Field(description='One-time registration token from POST /v1/hosts.')
|
||||
]
|
||||
arch: Annotated[
|
||||
str | None, Field(description="CPU architecture (e.g. x86_64, aarch64).")
|
||||
str | None, Field(description='CPU architecture (e.g. x86_64, aarch64).')
|
||||
] = None
|
||||
cpu_cores: int | None = None
|
||||
memory_mb: int | None = None
|
||||
disk_gb: int | None = None
|
||||
address: Annotated[str, Field(description="Host agent address (ip:port).")]
|
||||
address: Annotated[str, Field(description='Host agent address (ip:port).')]
|
||||
|
||||
|
||||
class Type3(StrEnum):
|
||||
regular = "regular"
|
||||
byoc = "byoc"
|
||||
regular = 'regular'
|
||||
byoc = 'byoc'
|
||||
|
||||
|
||||
class Status1(StrEnum):
|
||||
pending = "pending"
|
||||
online = "online"
|
||||
offline = "offline"
|
||||
draining = "draining"
|
||||
unreachable = "unreachable"
|
||||
pending = 'pending'
|
||||
online = 'online'
|
||||
offline = 'offline'
|
||||
draining = 'draining'
|
||||
unreachable = 'unreachable'
|
||||
|
||||
|
||||
class Host(BaseModel):
|
||||
@ -316,7 +316,7 @@ class RefreshHostTokenRequest(BaseModel):
|
||||
refresh_token: Annotated[
|
||||
str,
|
||||
Field(
|
||||
description="Refresh token obtained from registration or a previous refresh."
|
||||
description='Refresh token obtained from registration or a previous refresh.'
|
||||
),
|
||||
]
|
||||
|
||||
@ -324,12 +324,12 @@ class RefreshHostTokenRequest(BaseModel):
|
||||
class RefreshHostTokenResponse(BaseModel):
|
||||
host: Host | None = None
|
||||
token: Annotated[
|
||||
str | None, Field(description="New host JWT. Valid for 7 days.")
|
||||
str | None, Field(description='New host JWT. Valid for 7 days.')
|
||||
] = None
|
||||
refresh_token: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
description="New refresh token. Valid for 60 days; old token is revoked."
|
||||
description='New refresh token. Valid for 60 days; old token is revoked.'
|
||||
),
|
||||
] = None
|
||||
|
||||
@ -338,20 +338,20 @@ class HostDeletePreview(BaseModel):
|
||||
host: Host | None = None
|
||||
sandbox_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(description="IDs of sandboxes that would be destroyed on force-delete."),
|
||||
Field(description='IDs of capsulees that would be destroyed on force-delete.'),
|
||||
] = None
|
||||
|
||||
|
||||
class Error(BaseModel):
|
||||
code: Annotated[str | None, Field(examples=["host_has_sandboxes"])] = None
|
||||
code: Annotated[str | None, Field(examples=['host_has_sandboxes'])] = None
|
||||
message: str | None = None
|
||||
sandbox_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(description="IDs of active sandboxes blocking deletion."),
|
||||
Field(description='IDs of active capsulees blocking deletion.'),
|
||||
] = None
|
||||
|
||||
|
||||
class HostHasSandboxesError(BaseModel):
|
||||
class HostHasCapsulesError(BaseModel):
|
||||
error: Error | None = None
|
||||
|
||||
|
||||
@ -368,15 +368,15 @@ class Team(BaseModel):
|
||||
id: str | None = None
|
||||
name: str | None = None
|
||||
slug: Annotated[
|
||||
str | None, Field(description="Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)")
|
||||
str | None, Field(description='Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)')
|
||||
] = None
|
||||
created_at: AwareDatetime | None = None
|
||||
|
||||
|
||||
class Role(StrEnum):
|
||||
owner = "owner"
|
||||
admin = "admin"
|
||||
member = "member"
|
||||
owner = 'owner'
|
||||
admin = 'admin'
|
||||
member = 'member'
|
||||
|
||||
|
||||
class TeamWithRole(Team):
|
||||
@ -396,13 +396,13 @@ class TeamDetail(BaseModel):
|
||||
|
||||
|
||||
class Range1(StrEnum):
|
||||
field_5m = "5m"
|
||||
field_10m = "10m"
|
||||
field_1h = "1h"
|
||||
field_2h = "2h"
|
||||
field_6h = "6h"
|
||||
field_12h = "12h"
|
||||
field_24h = "24h"
|
||||
field_5m = '5m'
|
||||
field_10m = '10m'
|
||||
field_1h = '1h'
|
||||
field_2h = '2h'
|
||||
field_6h = '6h'
|
||||
field_12h = '12h'
|
||||
field_24h = '24h'
|
||||
|
||||
|
||||
class MetricPoint(BaseModel):
|
||||
@ -410,41 +410,41 @@ class MetricPoint(BaseModel):
|
||||
cpu_pct: Annotated[
|
||||
float | None,
|
||||
Field(
|
||||
description="CPU utilization percentage (0-100), normalized to vCPU count"
|
||||
description='CPU utilization percentage (0-100), normalized to vCPU count'
|
||||
),
|
||||
] = None
|
||||
mem_bytes: Annotated[
|
||||
int | None,
|
||||
Field(description="Resident memory in bytes (VmRSS of Firecracker process)"),
|
||||
Field(description='Resident memory in bytes (VmRSS of Firecracker process)'),
|
||||
] = None
|
||||
disk_bytes: Annotated[
|
||||
int | None, Field(description="Allocated disk bytes for the CoW sparse file")
|
||||
int | None, Field(description='Allocated disk bytes for the CoW sparse file')
|
||||
] = None
|
||||
|
||||
|
||||
class Provider(StrEnum):
|
||||
discord = "discord"
|
||||
slack = "slack"
|
||||
teams = "teams"
|
||||
googlechat = "googlechat"
|
||||
telegram = "telegram"
|
||||
matrix = "matrix"
|
||||
webhook = "webhook"
|
||||
discord = 'discord'
|
||||
slack = 'slack'
|
||||
teams = 'teams'
|
||||
googlechat = 'googlechat'
|
||||
telegram = 'telegram'
|
||||
matrix = 'matrix'
|
||||
webhook = 'webhook'
|
||||
|
||||
|
||||
class Event(StrEnum):
|
||||
capsule_created = "capsule.created"
|
||||
capsule_running = "capsule.running"
|
||||
capsule_paused = "capsule.paused"
|
||||
capsule_destroyed = "capsule.destroyed"
|
||||
template_snapshot_created = "template.snapshot.created"
|
||||
template_snapshot_deleted = "template.snapshot.deleted"
|
||||
host_up = "host.up"
|
||||
host_down = "host.down"
|
||||
capsule_created = 'capsule.created'
|
||||
capsule_running = 'capsule.running'
|
||||
capsule_paused = 'capsule.paused'
|
||||
capsule_destroyed = 'capsule.destroyed'
|
||||
template_snapshot_created = 'template.snapshot.created'
|
||||
template_snapshot_deleted = 'template.snapshot.deleted'
|
||||
host_up = 'host.up'
|
||||
host_down = 'host.down'
|
||||
|
||||
|
||||
class CreateChannelRequest(BaseModel):
|
||||
name: Annotated[str, Field(description="Unique channel name within the team.")]
|
||||
name: Annotated[str, Field(description='Unique channel name within the team.')]
|
||||
provider: Provider
|
||||
config: Annotated[
|
||||
dict[str, str],
|
||||
@ -460,7 +460,7 @@ class TestChannelRequest(BaseModel):
|
||||
config: Annotated[
|
||||
dict[str, str],
|
||||
Field(
|
||||
description="Provider-specific configuration fields (same as CreateChannelRequest.config)."
|
||||
description='Provider-specific configuration fields (same as CreateChannelRequest.config).'
|
||||
),
|
||||
]
|
||||
|
||||
@ -489,7 +489,7 @@ class ChannelResponse(BaseModel):
|
||||
updated_at: AwareDatetime | None = None
|
||||
secret: Annotated[
|
||||
str | None,
|
||||
Field(description="Webhook secret. Only returned on creation, never again."),
|
||||
Field(description='Webhook secret. Only returned on creation, never again.'),
|
||||
] = None
|
||||
|
||||
|
||||
@ -511,7 +511,7 @@ class CreateHostResponse(BaseModel):
|
||||
registration_token: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
description="One-time registration token for the host agent. Expires in 1 hour."
|
||||
description='One-time registration token for the host agent. Expires in 1 hour.'
|
||||
),
|
||||
] = None
|
||||
|
||||
@ -520,17 +520,17 @@ class RegisterHostResponse(BaseModel):
|
||||
host: Host | None = None
|
||||
token: Annotated[
|
||||
str | None,
|
||||
Field(description="Host JWT for X-Host-Token header. Valid for 7 days."),
|
||||
Field(description='Host JWT for X-Host-Token header. Valid for 7 days.'),
|
||||
] = None
|
||||
refresh_token: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
description="Refresh token for obtaining new JWTs. Valid for 60 days; rotated on each use."
|
||||
description='Refresh token for obtaining new JWTs. Valid for 60 days; rotated on each use.'
|
||||
),
|
||||
] = None
|
||||
|
||||
|
||||
class SandboxMetrics(BaseModel):
|
||||
class CapsuleMetrics(BaseModel):
|
||||
sandbox_id: str | None = None
|
||||
range: Range1 | None = None
|
||||
points: list[MetricPoint] | None = None
|
||||
|
||||
@ -66,9 +66,9 @@ class PtySession:
|
||||
break
|
||||
"""
|
||||
|
||||
def __init__(self, ws: httpx_ws.WebSocketSession, sandbox_id: str) -> None:
|
||||
def __init__(self, ws: httpx_ws.WebSocketSession, capsule_id: str) -> None:
|
||||
self._ws = ws
|
||||
self._sandbox_id = sandbox_id
|
||||
self._capsule_id = capsule_id
|
||||
self._tag: str | None = None
|
||||
self._pid: int | None = None
|
||||
self._done = False
|
||||
@ -192,9 +192,9 @@ class AsyncPtySession:
|
||||
break
|
||||
"""
|
||||
|
||||
def __init__(self, ws: httpx_ws.AsyncWebSocketSession, sandbox_id: str) -> None:
|
||||
def __init__(self, ws: httpx_ws.AsyncWebSocketSession, capsule_id: str) -> None:
|
||||
self._ws = ws
|
||||
self._sandbox_id = sandbox_id
|
||||
self._capsule_id = capsule_id
|
||||
self._tag: str | None = None
|
||||
self._pid: int | None = None
|
||||
self._done = False
|
||||
|
||||
1197
src/wrenn/sandbox.py
1197
src/wrenn/sandbox.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user