Skip to content

Commit d200af2

Browse files
Add volumes.delete for DELETE /volume/:key (#115)
* Add volumes.delete for DELETE /volume/:key Co-authored-by: shri <shri@metlo.com> * Document volume delete 409s for ambiguous names Co-authored-by: shri <shri@metlo.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 6566700 commit d200af2

7 files changed

Lines changed: 69 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ sandbox = client.sandboxes.create(
223223
)
224224

225225
sandbox.stop()
226+
client.volumes.delete(same_volume.id)
226227
client.close()
227228
```
228229

hyperbrowser/client/managers/async_manager/volume.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from hyperbrowser.models.volume import (
55
CreateVolumeParams,
66
Volume,
7+
VolumeDeleteResult,
78
VolumeListParams,
89
VolumeListResponse,
910
)
@@ -43,3 +44,10 @@ async def get(self, volume_id: str) -> Volume:
4344
self._client._build_url(f"/volume/{volume_id}")
4445
)
4546
return Volume(**response.data)
47+
48+
async def delete(self, volume_id: str) -> VolumeDeleteResult:
49+
"""Delete a volume by id or name. Ambiguous names and active mounts return 409."""
50+
response = await self._client.transport.delete(
51+
self._client._build_url(f"/volume/{volume_id}")
52+
)
53+
return VolumeDeleteResult(**response.data)

hyperbrowser/client/managers/sync_manager/volume.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from hyperbrowser.models.volume import (
55
CreateVolumeParams,
66
Volume,
7+
VolumeDeleteResult,
78
VolumeListParams,
89
VolumeListResponse,
910
)
@@ -43,3 +44,10 @@ def get(self, volume_id: str) -> Volume:
4344
self._client._build_url(f"/volume/{volume_id}")
4445
)
4546
return Volume(**response.data)
47+
48+
def delete(self, volume_id: str) -> VolumeDeleteResult:
49+
"""Delete a volume by id or name. Ambiguous names and active mounts return 409."""
50+
response = self._client.transport.delete(
51+
self._client._build_url(f"/volume/{volume_id}")
52+
)
53+
return VolumeDeleteResult(**response.data)

hyperbrowser/models/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,13 @@
211211
ProfileListResponse,
212212
ProfileResponse,
213213
)
214-
from .volume import CreateVolumeParams, Volume, VolumeListParams, VolumeListResponse
214+
from .volume import (
215+
CreateVolumeParams,
216+
Volume,
217+
VolumeDeleteResult,
218+
VolumeListParams,
219+
VolumeListResponse,
220+
)
215221
from .scrape import (
216222
BatchScrapeJobResponse,
217223
BatchScrapeJobStatusResponse,
@@ -539,6 +545,7 @@
539545
"Volume",
540546
"VolumeListParams",
541547
"VolumeListResponse",
548+
"VolumeDeleteResult",
542549
# scrape
543550
"BatchScrapeJobResponse",
544551
"BatchScrapeJobStatusResponse",

hyperbrowser/models/volume.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,9 @@ class VolumeListResponse(VolumeBaseModel):
3636
total_count: Optional[int] = Field(default=None, alias="totalCount")
3737
page: Optional[int] = None
3838
per_page: Optional[int] = Field(default=None, alias="perPage")
39+
40+
41+
class VolumeDeleteResult(VolumeBaseModel):
42+
deleted: bool
43+
id: Optional[str] = None
44+
name: Optional[str] = None

tests/test_volume_wire_contract.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
VolumeManager as AsyncVolumeManager,
55
)
66
from hyperbrowser.client.managers.sync_manager.volume import VolumeManager
7-
from hyperbrowser.models import CreateVolumeParams, Volume, VolumeListParams
7+
from hyperbrowser.models import (
8+
CreateVolumeParams,
9+
Volume,
10+
VolumeDeleteResult,
11+
VolumeListParams,
12+
)
813

914

1015
VOLUME_PAYLOAD = {
@@ -26,6 +31,12 @@
2631
"perPage": 20,
2732
}
2833

34+
VOLUME_DELETE_PAYLOAD = {
35+
"deleted": True,
36+
"id": "2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d",
37+
"name": "project-cache",
38+
}
39+
2940

3041
class StubResponse:
3142
def __init__(self, data):
@@ -55,6 +66,10 @@ def get(self, url, params=None, follow_redirects=False):
5566
return StubResponse(VOLUME_DETAIL_PAYLOAD)
5667
return StubResponse({})
5768

69+
def delete(self, url):
70+
self.calls.append({"method": "DELETE", "url": url})
71+
return StubResponse(VOLUME_DELETE_PAYLOAD)
72+
5873

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

97+
async def delete(self, url):
98+
self.calls.append({"method": "DELETE", "url": url})
99+
return StubResponse(VOLUME_DELETE_PAYLOAD)
100+
82101

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

135155
create_call = client.transport.calls[0]
136156
list_call = client.transport.calls[1]
137157
get_call = client.transport.calls[2]
158+
delete_call = client.transport.calls[3]
138159

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

177+
assert delete_call["method"] == "DELETE"
178+
assert delete_call["url"].endswith("/volume/2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
179+
assert deleted.deleted is True
180+
assert deleted.id == "2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d"
181+
assert deleted.name == "project-cache"
182+
assert isinstance(deleted, VolumeDeleteResult)
183+
156184

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

170199
create_call = client.transport.calls[0]
171200
list_call = client.transport.calls[1]
172201
get_call = client.transport.calls[2]
202+
delete_call = client.transport.calls[3]
173203

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

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

190225
def test_sync_volume_list_without_params_remains_supported():
191226
client = FakeSyncClient()

tests/typecheck/valid_requests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def valid_sync_requests(client: Hyperbrowser) -> None:
143143
)
144144
client.volumes.list({"page": 0, "limit": -1})
145145
client.volumes.list(LegacyVolumeListParams(page=0, limit=-1))
146+
client.volumes.delete("2d6f01cf-c5d7-4c61-ae9e-0264f1c8063d")
146147

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

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

0 commit comments

Comments
 (0)