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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ sandbox = client.sandboxes.create(
)

sandbox.stop()
client.volumes.delete(same_volume.id)
client.close()
```

Expand Down
8 changes: 8 additions & 0 deletions hyperbrowser/client/managers/async_manager/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from hyperbrowser.models.volume import (
CreateVolumeParams,
Volume,
VolumeDeleteResult,
VolumeListParams,
VolumeListResponse,
)
Expand Down Expand Up @@ -43,3 +44,10 @@ async def get(self, volume_id: str) -> Volume:
self._client._build_url(f"/volume/{volume_id}")
)
return Volume(**response.data)

async def delete(self, volume_id: str) -> VolumeDeleteResult:
"""Delete a volume by id or name. Ambiguous names and active mounts return 409."""
response = await self._client.transport.delete(
self._client._build_url(f"/volume/{volume_id}")
)
return VolumeDeleteResult(**response.data)
8 changes: 8 additions & 0 deletions hyperbrowser/client/managers/sync_manager/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from hyperbrowser.models.volume import (
CreateVolumeParams,
Volume,
VolumeDeleteResult,
VolumeListParams,
VolumeListResponse,
)
Expand Down Expand Up @@ -43,3 +44,10 @@ def get(self, volume_id: str) -> Volume:
self._client._build_url(f"/volume/{volume_id}")
)
return Volume(**response.data)

def delete(self, volume_id: str) -> VolumeDeleteResult:
"""Delete a volume by id or name. Ambiguous names and active mounts return 409."""
response = self._client.transport.delete(
self._client._build_url(f"/volume/{volume_id}")
)
return VolumeDeleteResult(**response.data)
9 changes: 8 additions & 1 deletion hyperbrowser/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,13 @@
ProfileListResponse,
ProfileResponse,
)
from .volume import CreateVolumeParams, Volume, VolumeListParams, VolumeListResponse
from .volume import (
CreateVolumeParams,
Volume,
VolumeDeleteResult,
VolumeListParams,
VolumeListResponse,
)
from .scrape import (
BatchScrapeJobResponse,
BatchScrapeJobStatusResponse,
Expand Down Expand Up @@ -537,6 +543,7 @@
"Volume",
"VolumeListParams",
"VolumeListResponse",
"VolumeDeleteResult",
# scrape
"BatchScrapeJobResponse",
"BatchScrapeJobStatusResponse",
Expand Down
6 changes: 6 additions & 0 deletions hyperbrowser/models/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ class VolumeListResponse(VolumeBaseModel):
total_count: Optional[int] = Field(default=None, alias="totalCount")
page: Optional[int] = None
per_page: Optional[int] = Field(default=None, alias="perPage")


class VolumeDeleteResult(VolumeBaseModel):
deleted: bool
id: Optional[str] = None
name: Optional[str] = None
37 changes: 36 additions & 1 deletion tests/test_volume_wire_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
VolumeManager as AsyncVolumeManager,
)
from hyperbrowser.client.managers.sync_manager.volume import VolumeManager
from hyperbrowser.models import CreateVolumeParams, Volume, VolumeListParams
from hyperbrowser.models import (
CreateVolumeParams,
Volume,
VolumeDeleteResult,
VolumeListParams,
)


VOLUME_PAYLOAD = {
Expand All @@ -26,6 +31,12 @@
"perPage": 20,
}

VOLUME_DELETE_PAYLOAD = {
"deleted": True,
"id": "2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d",
"name": "project-cache",
}


class StubResponse:
def __init__(self, data):
Expand Down Expand Up @@ -55,6 +66,10 @@ def get(self, url, params=None, follow_redirects=False):
return StubResponse(VOLUME_DETAIL_PAYLOAD)
return StubResponse({})

def delete(self, url):
self.calls.append({"method": "DELETE", "url": url})
return StubResponse(VOLUME_DELETE_PAYLOAD)


class RecordingAsyncTransport:
def __init__(self):
Expand All @@ -79,6 +94,10 @@ async def get(self, url, params=None, follow_redirects=False):
return StubResponse(VOLUME_DETAIL_PAYLOAD)
return StubResponse({})

async def delete(self, url):
self.calls.append({"method": "DELETE", "url": url})
return StubResponse(VOLUME_DELETE_PAYLOAD)


class FakeSyncClient:
def __init__(self):
Expand Down Expand Up @@ -131,10 +150,12 @@ def test_sync_volume_manager_uses_expected_wire_keys(use_legacy_model):
created = manager.create(CreateVolumeParams(name="project-cache"))
listed = manager.list(list_params)
fetched = manager.get("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
deleted = manager.delete("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")

create_call = client.transport.calls[0]
list_call = client.transport.calls[1]
get_call = client.transport.calls[2]
delete_call = client.transport.calls[3]

assert create_call["method"] == "POST"
assert create_call["url"].endswith("/volume")
Expand All @@ -153,6 +174,13 @@ def test_sync_volume_manager_uses_expected_wire_keys(use_legacy_model):
assert fetched.name == "project-cache"
assert fetched.transfer_amount is None

assert delete_call["method"] == "DELETE"
assert delete_call["url"].endswith("/volume/2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
assert deleted.deleted is True
assert deleted.id == "2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d"
assert deleted.name == "project-cache"
assert isinstance(deleted, VolumeDeleteResult)


@pytest.mark.anyio
@pytest.mark.parametrize("use_legacy_model", [False, True])
Expand All @@ -166,10 +194,12 @@ async def test_async_volume_manager_uses_expected_wire_keys(use_legacy_model):
created = await manager.create({"name": "project-cache"})
listed = await manager.list(list_params)
fetched = await manager.get("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
deleted = await manager.delete("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")

create_call = client.transport.calls[0]
list_call = client.transport.calls[1]
get_call = client.transport.calls[2]
delete_call = client.transport.calls[3]

assert create_call["method"] == "POST"
assert create_call["url"].endswith("/volume")
Expand All @@ -186,6 +216,11 @@ async def test_async_volume_manager_uses_expected_wire_keys(use_legacy_model):
assert created.transfer_amount == 0
assert fetched.name == "project-cache"

assert delete_call["method"] == "DELETE"
assert delete_call["url"].endswith("/volume/2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
assert deleted.deleted is True
assert deleted.name == "project-cache"


def test_sync_volume_list_without_params_remains_supported():
client = FakeSyncClient()
Expand Down
2 changes: 2 additions & 0 deletions tests/typecheck/valid_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def valid_sync_requests(client: Hyperbrowser) -> None:
)
client.volumes.list({"page": 0, "limit": -1})
client.volumes.list(LegacyVolumeListParams(page=0, limit=-1))
client.volumes.delete("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")

client.sessions.create(LegacyCreateSessionParams(use_stealth=True, region="us"))
client.web.fetch(LegacyFetchParams(url="https://example.com"))
Expand Down Expand Up @@ -198,6 +199,7 @@ async def valid_async_requests(client: AsyncHyperbrowser) -> None:
)
await client.sandboxes.list_image_builds({"status": "verifying", "limit": -1})
await client.volumes.list({"page": 0, "limit": -1})
await client.volumes.delete("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")

await client.sessions.create(LegacyCreateSessionParams(use_proxy=True, region="us"))
await client.web.fetch(LegacyFetchParams(url="https://example.com"))