Skip to content

Commit eae3555

Browse files
Add sandboxes.delete_image for DELETE /images/:key (#112)
* Add sandboxes.delete_image for DELETE /images/:key Expose sync and async image deletion so Python clients can remove team Firecracker images by name or id against the new control-plane endpoint. Co-authored-by: shri <shri@metlo.com> * Accept deleted image revision fields on delete_image Parse optional id, imageName, and uploaded from DELETE /images/:key so callers can tell which revision was removed. Co-authored-by: shri <shri@metlo.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent a0ddd2e commit eae3555

5 files changed

Lines changed: 57 additions & 2 deletions

File tree

‎hyperbrowser/client/managers/async_manager/sandbox.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
SandboxNetworkPolicy,
2828
SandboxNetworkUpdateResult,
2929
SandboxRuntimeSession,
30+
SandboxImageDeleteResult,
3031
SandboxSnapshotDeleteResult,
3132
SandboxSnapshotListParams,
3233
SandboxSnapshotListResponse,
@@ -427,6 +428,10 @@ async def list(
427428
)
428429
return SandboxListResponse(**payload)
429430

431+
async def delete_image(self, image: str) -> SandboxImageDeleteResult:
432+
payload = await self._request("DELETE", f"/images/{image}")
433+
return SandboxImageDeleteResult(**payload)
434+
430435
async def list_images(
431436
self,
432437
params: Optional[

‎hyperbrowser/client/managers/sync_manager/sandbox.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
SandboxNetworkPolicy,
2626
SandboxNetworkUpdateResult,
2727
SandboxRuntimeSession,
28+
SandboxImageDeleteResult,
2829
SandboxSnapshotDeleteResult,
2930
SandboxSnapshotListParams,
3031
SandboxSnapshotListResponse,
@@ -419,6 +420,10 @@ def list(
419420
)
420421
return SandboxListResponse(**payload)
421422

423+
def delete_image(self, image: str) -> SandboxImageDeleteResult:
424+
payload = self._request("DELETE", f"/images/{image}")
425+
return SandboxImageDeleteResult(**payload)
426+
422427
def list_images(
423428
self,
424429
params: Optional[

‎hyperbrowser/models/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@
311311
SandboxImageSummary,
312312
SandboxSnapshotStatus,
313313
SandboxSnapshotDeleteResult,
314+
SandboxImageDeleteResult,
314315
SandboxSnapshotListResponse,
315316
SandboxSnapshotSummary,
316317
SandboxSnapshotListParams,
@@ -610,6 +611,7 @@
610611
"SandboxImageSummary",
611612
"SandboxSnapshotStatus",
612613
"SandboxSnapshotDeleteResult",
614+
"SandboxImageDeleteResult",
613615
"SandboxSnapshotListResponse",
614616
"SandboxSnapshotSummary",
615617
"SandboxSnapshotListParams",

‎hyperbrowser/models/sandbox.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,13 @@ class SandboxSnapshotDeleteResult(SandboxBaseModel):
351351
deleted: bool
352352

353353

354+
class SandboxImageDeleteResult(SandboxBaseModel):
355+
deleted: bool
356+
id: Optional[str] = None
357+
image_name: Optional[str] = Field(default=None, alias="imageName")
358+
uploaded: Optional[bool] = None
359+
360+
354361
class SandboxMemorySnapshotParams(SandboxBaseModel):
355362
snapshot_name: Optional[str] = Field(
356363
default=None, serialization_alias="snapshotName"

‎tests/test_sandbox_wire_contract.py‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,17 @@ def request(self, method, url, params=None, json=None):
443443
)
444444
elif "/images/builds/" in url:
445445
payload = IMAGE_BUILD_PAYLOAD
446+
elif "/images/" in url:
447+
payload = (
448+
{
449+
"deleted": True,
450+
"id": "img_123",
451+
"imageName": "custom_node",
452+
"uploaded": True,
453+
}
454+
if method == "DELETE"
455+
else IMAGE_LIST_PAYLOAD
456+
)
446457
elif url.endswith("/images"):
447458
payload = IMAGE_LIST_PAYLOAD
448459
elif url.endswith("/snapshots"):
@@ -645,6 +656,17 @@ async def request(self, method, url, params=None, json=None):
645656
)
646657
elif "/images/builds/" in url:
647658
payload = IMAGE_BUILD_PAYLOAD
659+
elif "/images/" in url:
660+
payload = (
661+
{
662+
"deleted": True,
663+
"id": "img_123",
664+
"imageName": "custom_node",
665+
"uploaded": True,
666+
}
667+
if method == "DELETE"
668+
else IMAGE_LIST_PAYLOAD
669+
)
648670
elif url.endswith("/images"):
649671
payload = IMAGE_LIST_PAYLOAD
650672
elif url.endswith("/snapshots"):
@@ -1004,8 +1026,9 @@ def test_sync_sandbox_snapshot_and_image_build_list_contract(use_legacy_model):
10041026
builds = manager.list_image_builds(params)
10051027
snapshot = manager.get_snapshot("snapshot-1")
10061028
deleted = manager.delete_snapshot("snapshot-1")
1029+
deleted_image = manager.delete_image("custom_node")
10071030

1008-
list_call, get_call, delete_call = client.transport.client.calls
1031+
list_call, get_call, delete_call, delete_image_call = client.transport.client.calls
10091032
assert list_call == {
10101033
"method": "GET",
10111034
"url": "https://api.example.com/images/builds",
@@ -1024,6 +1047,12 @@ def test_sync_sandbox_snapshot_and_image_build_list_contract(use_legacy_model):
10241047
assert delete_call["method"] == "DELETE"
10251048
assert delete_call["url"].endswith("/snapshots/snapshot-1")
10261049
assert deleted.deleted is True
1050+
assert delete_image_call["method"] == "DELETE"
1051+
assert delete_image_call["url"].endswith("/images/custom_node")
1052+
assert deleted_image.deleted is True
1053+
assert deleted_image.id == "img_123"
1054+
assert deleted_image.image_name == "custom_node"
1055+
assert deleted_image.uploaded is True
10271056

10281057

10291058
@pytest.mark.parametrize(
@@ -1637,8 +1666,9 @@ async def test_async_sandbox_snapshot_and_image_build_list_contract(
16371666
builds = await manager.list_image_builds(params)
16381667
snapshot = await manager.get_snapshot("snapshot-1")
16391668
deleted = await manager.delete_snapshot("snapshot-1")
1669+
deleted_image = await manager.delete_image("custom_node")
16401670

1641-
list_call, get_call, delete_call = client.transport.client.calls
1671+
list_call, get_call, delete_call, delete_image_call = client.transport.client.calls
16421672
assert list_call == {
16431673
"method": "GET",
16441674
"url": "https://api.example.com/images/builds",
@@ -1657,6 +1687,12 @@ async def test_async_sandbox_snapshot_and_image_build_list_contract(
16571687
assert delete_call["method"] == "DELETE"
16581688
assert delete_call["url"].endswith("/snapshots/snapshot-1")
16591689
assert deleted.deleted is True
1690+
assert delete_image_call["method"] == "DELETE"
1691+
assert delete_image_call["url"].endswith("/images/custom_node")
1692+
assert deleted_image.deleted is True
1693+
assert deleted_image.id == "img_123"
1694+
assert deleted_image.image_name == "custom_node"
1695+
assert deleted_image.uploaded is True
16601696

16611697

16621698
@pytest.mark.anyio

0 commit comments

Comments
 (0)