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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,35 @@ client.close()

`cpu`, `memory_mib`, and `disk_mib` are only supported for image launches.

### Manage volumes and mount them in a sandbox

```python
from hyperbrowser import Hyperbrowser
from hyperbrowser.models import CreateSandboxParams, CreateVolumeParams, SandboxVolumeMount

client = Hyperbrowser(api_key="test-key")

volume = client.volumes.create(CreateVolumeParams(name="project-cache"))
all_volumes = client.volumes.list()
same_volume = client.volumes.get(volume.id)

sandbox = client.sandboxes.create(
CreateSandboxParams(
image_name="node",
mounts={
"/workspace/cache": SandboxVolumeMount(
id=same_volume.id,
type="rw",
shared=True,
)
},
)
)

sandbox.stop()
client.close()
```

### List sandboxes with filters

```python
Expand Down
2 changes: 2 additions & 0 deletions hyperbrowser/client/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .managers.async_manager.session import SessionManager
from .managers.async_manager.team import TeamManager
from .managers.async_manager.computer_action import ComputerActionManager
from .managers.async_manager.volume import VolumeManager


class AsyncHyperbrowser(HyperbrowserBase):
Expand Down Expand Up @@ -47,6 +48,7 @@ def __init__(
self.team = TeamManager(self)
self.computer_action = ComputerActionManager(self)
self.sandboxes = SandboxManager(self)
self.volumes = VolumeManager(self)

async def close(self) -> None:
await self.transport.close()
Expand Down
26 changes: 26 additions & 0 deletions hyperbrowser/client/managers/async_manager/volume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from hyperbrowser.models.volume import CreateVolumeParams, Volume, VolumeListResponse


class VolumeManager:
def __init__(self, client):
self._client = client

async def create(self, params: CreateVolumeParams) -> Volume:
if not isinstance(params, CreateVolumeParams):
raise TypeError("params must be a CreateVolumeParams instance")

response = await self._client.transport.post(
self._client._build_url("/volume"),
data=params.model_dump(exclude_none=True, by_alias=True),
)
return Volume(**response.data)

async def list(self) -> VolumeListResponse:
response = await self._client.transport.get(self._client._build_url("/volume"))
return VolumeListResponse(**response.data)

async def get(self, volume_id: str) -> Volume:
response = await self._client.transport.get(
self._client._build_url(f"/volume/{volume_id}")
)
return Volume(**response.data)
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ def list(
"/sandbox/files",
params=self._with_run_as_params(
{
"path": path,
"depth": depth,
"path": path,
"depth": depth,
}
),
)
Expand Down
26 changes: 26 additions & 0 deletions hyperbrowser/client/managers/sync_manager/volume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from hyperbrowser.models.volume import CreateVolumeParams, Volume, VolumeListResponse


class VolumeManager:
def __init__(self, client):
self._client = client

def create(self, params: CreateVolumeParams) -> Volume:
if not isinstance(params, CreateVolumeParams):
raise TypeError("params must be a CreateVolumeParams instance")

response = self._client.transport.post(
self._client._build_url("/volume"),
data=params.model_dump(exclude_none=True, by_alias=True),
)
return Volume(**response.data)

def list(self) -> VolumeListResponse:
response = self._client.transport.get(self._client._build_url("/volume"))
return VolumeListResponse(**response.data)

def get(self, volume_id: str) -> Volume:
response = self._client.transport.get(
self._client._build_url(f"/volume/{volume_id}")
)
return Volume(**response.data)
2 changes: 2 additions & 0 deletions hyperbrowser/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .managers.sync_manager.session import SessionManager
from .managers.sync_manager.team import TeamManager
from .managers.sync_manager.computer_action import ComputerActionManager
from .managers.sync_manager.volume import VolumeManager


class Hyperbrowser(HyperbrowserBase):
Expand Down Expand Up @@ -47,6 +48,7 @@ def __init__(
self.team = TeamManager(self)
self.computer_action = ComputerActionManager(self)
self.sandboxes = SandboxManager(self)
self.volumes = VolumeManager(self)

def close(self) -> None:
self.transport.close()
9 changes: 9 additions & 0 deletions hyperbrowser/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
ProfileListResponse,
ProfileResponse,
)
from .volume import CreateVolumeParams, Volume, VolumeListResponse
from .scrape import (
BatchScrapeJobResponse,
BatchScrapeJobStatusResponse,
Expand Down Expand Up @@ -245,6 +246,8 @@
Sandbox,
SandboxDetail,
SandboxRuntimeSession,
SandboxVolumeMountType,
SandboxVolumeMount,
CreateSandboxParams,
StartSandboxFromSnapshotParams,
SandboxListParams,
Expand Down Expand Up @@ -450,6 +453,10 @@
"ProfileListParams",
"ProfileListResponse",
"ProfileResponse",
# volume
"CreateVolumeParams",
"Volume",
"VolumeListResponse",
# scrape
"BatchScrapeJobResponse",
"BatchScrapeJobStatusResponse",
Expand Down Expand Up @@ -499,6 +506,8 @@
"Sandbox",
"SandboxDetail",
"SandboxRuntimeSession",
"SandboxVolumeMountType",
"SandboxVolumeMount",
"CreateSandboxParams",
"StartSandboxFromSnapshotParams",
"SandboxListParams",
Expand Down
11 changes: 11 additions & 0 deletions hyperbrowser/models/_parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Any


def _parse_optional_int(value: Any):
if value is None or isinstance(value, int):
return value
if isinstance(value, str) and value.strip() == "":
return None
if isinstance(value, str):
return int(value)
return value
22 changes: 12 additions & 10 deletions hyperbrowser/models/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from ._parsers import _parse_optional_int
from .session import SessionLaunchState, SessionStatus

SandboxStatus = SessionStatus
Expand All @@ -29,6 +30,7 @@
SandboxFileReadFormat = Literal["text", "bytes", "blob", "stream"]
SandboxFileWatchRoute = Literal["ws", "stream"]
SandboxFileSystemEventType = Literal["chmod", "create", "remove", "rename", "write"]
SandboxVolumeMountType = Literal["rw", "ro"]


def _parse_optional_datetime(value):
Expand All @@ -37,16 +39,6 @@ def _parse_optional_datetime(value):
return value


def _parse_optional_int(value):
if value is None or isinstance(value, int):
return value
if isinstance(value, str) and value.strip() == "":
return None
if isinstance(value, str):
return int(value)
return value


def _parse_optional_datetime_from_millis(value):
if value in (None, ""):
return None
Expand Down Expand Up @@ -95,6 +87,12 @@ class SandboxUnexposeResult(SandboxBaseModel):
exposed: bool


class SandboxVolumeMount(SandboxBaseModel):
id: str
type: Optional[SandboxVolumeMountType] = None
shared: Optional[bool] = None


class Sandbox(SandboxBaseModel):
id: str
team_id: str = Field(alias="teamId")
Expand Down Expand Up @@ -180,6 +178,10 @@ class CreateSandboxParams(SandboxBaseModel):
default=None,
serialization_alias="exposedPorts",
)
mounts: Optional[Dict[str, SandboxVolumeMount]] = Field(
default=None,
serialization_alias="mounts",
)
timeout_minutes: Optional[int] = Field(
default=None, serialization_alias="timeoutMinutes"
)
Expand Down
29 changes: 29 additions & 0 deletions hyperbrowser/models/volume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List, Optional

from pydantic import BaseModel, ConfigDict, Field, field_validator

from ._parsers import _parse_optional_int


class VolumeBaseModel(BaseModel):
model_config = ConfigDict(populate_by_name=True)


class CreateVolumeParams(VolumeBaseModel):
name: str


class Volume(VolumeBaseModel):
id: str
name: str
size: Optional[int] = None
transfer_amount: Optional[int] = Field(default=None, alias="transferAmount")

@field_validator("size", "transfer_amount", mode="before")
@classmethod
def parse_optional_int_fields(cls, value):
return _parse_optional_int(value)


class VolumeListResponse(VolumeBaseModel):
volumes: List[Volume]
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hyperbrowser"
version = "0.90.1"
version = "0.90.2"
description = "Python SDK for hyperbrowser"
authors = ["Nikhil Shahi <nshahi1998@gmail.com>"]
license = "MIT"
Expand Down
Loading