From e96fc42d1e1a43ba00f38d1f5e087bfc7da70710 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:05:07 +0200 Subject: [PATCH 1/7] feat(sdk): add name filter to snapshot list Add an optional `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`, mirroring the infra snapshots list endpoint (e2b-dev/infra#3184). The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"); unknown names return an empty list. Applied equivalently across the spec, JS SDK, and Python sync + async SDKs, with tests and a changeset. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/snapshot-list-name-filter.md | 26 +++++++++++++++++ packages/js-sdk/src/api/schema.gen.ts | 2 ++ packages/js-sdk/src/sandbox/sandboxApi.ts | 9 ++++++ .../js-sdk/tests/sandbox/snapshot-api.test.ts | 28 +++++++++++++++++++ .../api/client/api/snapshots/get_snapshots.py | 15 ++++++++++ .../python-sdk/e2b/sandbox/sandbox_api.py | 2 ++ packages/python-sdk/e2b/sandbox_async/main.py | 9 ++++++ .../python-sdk/e2b/sandbox_async/paginator.py | 1 + packages/python-sdk/e2b/sandbox_sync/main.py | 9 ++++++ .../python-sdk/e2b/sandbox_sync/paginator.py | 1 + .../async/sandbox_async/test_snapshot_api.py | 25 +++++++++++++++++ .../sync/sandbox_sync/test_snapshot_api.py | 21 ++++++++++++++ spec/openapi.yml | 6 ++++ 13 files changed, 154 insertions(+) create mode 100644 .changeset/snapshot-list-name-filter.md diff --git a/.changeset/snapshot-list-name-filter.md b/.changeset/snapshot-list-name-filter.md new file mode 100644 index 0000000000..b56cf5a8cb --- /dev/null +++ b/.changeset/snapshot-list-name-filter.md @@ -0,0 +1,26 @@ +--- +"e2b": minor +"@e2b/python-sdk": minor +--- + +Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`. + +The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. +`"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`). Unknown names +return an empty list rather than an error. + +```ts +// JS/TS +const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' }) +const snapshots = await paginator.nextItems() +``` + +```python +# Python (sync) +paginator = Sandbox.list_snapshots(name="my-snapshot") +snapshots = paginator.next_items() + +# Python (async) +paginator = AsyncSandbox.list_snapshots(name="my-snapshot") +snapshots = await paginator.next_items() +``` diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index c6f357d03e..5908fc2606 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -622,6 +622,8 @@ export interface paths { query?: { /** @description Maximum number of items to return per page */ limit?: components["parameters"]["paginationLimit"]; + /** @description Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). */ + name?: string; /** @description Cursor to start the list from */ nextToken?: components["parameters"]["paginationNextToken"]; sandboxID?: string; diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 4cedebcfa5..2da2aafc5e 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -450,6 +450,12 @@ export interface SnapshotListOpts extends Omit { */ sandboxId?: string + /** + * Filter snapshots by name or ID, optionally tag-qualified + * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + */ + name?: string + /** * Number of snapshots to return per page. * @@ -1335,11 +1341,13 @@ export class SandboxPaginator extends Paginator { */ export class SnapshotPaginator extends Paginator { private readonly sandboxId?: string + private readonly name?: string constructor(opts?: SnapshotListOpts) { super(opts, opts?.limit, opts?.nextToken) this.sandboxId = opts?.sandboxId + this.name = opts?.name } async nextItems(opts?: SandboxApiOpts): Promise { @@ -1354,6 +1362,7 @@ export class SnapshotPaginator extends Paginator { params: { query: { sandboxID: this.sandboxId, + name: this.name, limit: this.limit, nextToken: this.nextToken, }, diff --git a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts index e22387a4af..3c6432af65 100644 --- a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts @@ -159,6 +159,34 @@ sandboxTest.skipIf(isDebug)( } ) +sandboxTest.skipIf(isDebug)( + 'list snapshots filtered by name', + async ({ sandbox, sandboxTestId }) => { + const snapshotName = `snap-filter-${sandboxTestId}` + + const snapshot = await sandbox.createSnapshot({ name: snapshotName }) + + try { + // Filtering by the snapshot name should return the snapshot + const paginator = Sandbox.listSnapshots({ name: snapshotName }) + const snapshots = await paginator.nextItems() + + const found = snapshots.find((s) => s.snapshotId === snapshot.snapshotId) + assert.isDefined(found) + + // Filtering by an unknown name should return an empty list + const emptyPaginator = Sandbox.listSnapshots({ + name: `${snapshotName}-does-not-exist`, + }) + const emptySnapshots = await emptyPaginator.nextItems() + assert.isArray(emptySnapshots) + assert.equal(emptySnapshots.length, 0) + } finally { + await Sandbox.deleteSnapshot(snapshot.snapshotId) + } + } +) + sandboxTest.skipIf(isDebug)('delete snapshot', async ({ sandbox }) => { const snapshot = await sandbox.createSnapshot() diff --git a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py index 1b826e3d64..58731da4a1 100644 --- a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py +++ b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py @@ -13,6 +13,7 @@ def _get_kwargs( *, sandbox_id: Union[Unset, str] = UNSET, + name: Union[Unset, str] = UNSET, limit: Union[Unset, int] = 100, next_token: Union[Unset, str] = UNSET, ) -> dict[str, Any]: @@ -20,6 +21,8 @@ def _get_kwargs( params["sandboxID"] = sandbox_id + params["name"] = name + params["limit"] = limit params["nextToken"] = next_token @@ -76,6 +79,7 @@ def sync_detailed( *, client: AuthenticatedClient, sandbox_id: Union[Unset, str] = UNSET, + name: Union[Unset, str] = UNSET, limit: Union[Unset, int] = 100, next_token: Union[Unset, str] = UNSET, ) -> Response[Union[Error, list["SnapshotInfo"]]]: @@ -83,6 +87,7 @@ def sync_detailed( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -96,6 +101,7 @@ def sync_detailed( kwargs = _get_kwargs( sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, ) @@ -111,6 +117,7 @@ def sync( *, client: AuthenticatedClient, sandbox_id: Union[Unset, str] = UNSET, + name: Union[Unset, str] = UNSET, limit: Union[Unset, int] = 100, next_token: Union[Unset, str] = UNSET, ) -> Optional[Union[Error, list["SnapshotInfo"]]]: @@ -118,6 +125,7 @@ def sync( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -132,6 +140,7 @@ def sync( return sync_detailed( client=client, sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, ).parsed @@ -141,6 +150,7 @@ async def asyncio_detailed( *, client: AuthenticatedClient, sandbox_id: Union[Unset, str] = UNSET, + name: Union[Unset, str] = UNSET, limit: Union[Unset, int] = 100, next_token: Union[Unset, str] = UNSET, ) -> Response[Union[Error, list["SnapshotInfo"]]]: @@ -148,6 +158,7 @@ async def asyncio_detailed( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -161,6 +172,7 @@ async def asyncio_detailed( kwargs = _get_kwargs( sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, ) @@ -174,6 +186,7 @@ async def asyncio( *, client: AuthenticatedClient, sandbox_id: Union[Unset, str] = UNSET, + name: Union[Unset, str] = UNSET, limit: Union[Unset, int] = 100, next_token: Union[Unset, str] = UNSET, ) -> Optional[Union[Error, list["SnapshotInfo"]]]: @@ -181,6 +194,7 @@ async def asyncio( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -196,6 +210,7 @@ async def asyncio( await asyncio_detailed( client=client, sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, ) diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index d0982864f2..89a7070e5e 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -604,12 +604,14 @@ class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]): def __init__( self, sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ): super().__init__(limit=limit, next_token=next_token, **opts) self.sandbox_id = sandbox_id + self.name = name class SandboxPaginatorBase(PaginatorBase[SandboxInfo, ApiParams]): diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 791b9b360d..f12e99a9a4 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -769,6 +769,7 @@ async def create_snapshot( @overload def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -776,6 +777,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -787,6 +789,7 @@ def list_snapshots( @staticmethod def list_snapshots( sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -795,6 +798,7 @@ def list_snapshots( List all snapshots. :param sandbox_id: Filter snapshots by source sandbox ID + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -805,6 +809,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -812,6 +817,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -819,6 +825,7 @@ def list_snapshots( """ return AsyncSnapshotPaginator( sandbox_id=self.sandbox_id, + name=name, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -827,12 +834,14 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: return AsyncSnapshotPaginator( sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_async/paginator.py b/packages/python-sdk/e2b/sandbox_async/paginator.py index 269248c5e8..bc8f5380a7 100644 --- a/packages/python-sdk/e2b/sandbox_async/paginator.py +++ b/packages/python-sdk/e2b/sandbox_async/paginator.py @@ -116,6 +116,7 @@ async def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: res = await get_snapshots.asyncio_detailed( client=api_client, sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, + name=self.name if self.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index df73f2b086..e217f30986 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -759,6 +759,7 @@ def create_snapshot( @overload def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -766,6 +767,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -777,6 +779,7 @@ def list_snapshots( @staticmethod def list_snapshots( sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -785,6 +788,7 @@ def list_snapshots( List all snapshots. :param sandbox_id: Filter snapshots by source sandbox ID + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -795,6 +799,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -802,6 +807,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -809,6 +815,7 @@ def list_snapshots( """ return SnapshotPaginator( sandbox_id=self.sandbox_id, + name=name, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -817,12 +824,14 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: return SnapshotPaginator( sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_sync/paginator.py b/packages/python-sdk/e2b/sandbox_sync/paginator.py index 9f0d4bf22f..aa896dd4ba 100644 --- a/packages/python-sdk/e2b/sandbox_sync/paginator.py +++ b/packages/python-sdk/e2b/sandbox_sync/paginator.py @@ -116,6 +116,7 @@ def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: res = get_snapshots.sync_detailed( client=api_client, sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, + name=self.name if self.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py index e9673b069f..80354a62e1 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py @@ -96,6 +96,31 @@ async def test_list_snapshots_for_sandbox(async_sandbox: AsyncSandbox): await AsyncSandbox.delete_snapshot(snapshot.snapshot_id) +@pytest.mark.skip_debug() +async def test_list_snapshots_filtered_by_name( + async_sandbox: AsyncSandbox, sandbox_test_id: str +): + snapshot_name = f"snap-filter-{sandbox_test_id}" + + snapshot = await async_sandbox.create_snapshot(name=snapshot_name) + + try: + paginator = AsyncSandbox.list_snapshots(name=snapshot_name) + snapshots = await paginator.next_items() + + found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) + assert found + + empty_paginator = AsyncSandbox.list_snapshots( + name=f"{snapshot_name}-does-not-exist" + ) + empty_snapshots = await empty_paginator.next_items() + assert isinstance(empty_snapshots, list) + assert len(empty_snapshots) == 0 + finally: + await AsyncSandbox.delete_snapshot(snapshot.snapshot_id) + + @pytest.mark.skip_debug() async def test_create_named_snapshot(async_sandbox: AsyncSandbox, sandbox_test_id: str): snapshot_name = f"snap-{sandbox_test_id}" diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py index 1eef251d9d..2fa6d29824 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py @@ -94,6 +94,27 @@ def test_list_snapshots_for_sandbox(sandbox: Sandbox): Sandbox.delete_snapshot(snapshot.snapshot_id) +@pytest.mark.skip_debug() +def test_list_snapshots_filtered_by_name(sandbox: Sandbox, sandbox_test_id: str): + snapshot_name = f"snap-filter-{sandbox_test_id}" + + snapshot = sandbox.create_snapshot(name=snapshot_name) + + try: + paginator = Sandbox.list_snapshots(name=snapshot_name) + snapshots = paginator.next_items() + + found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) + assert found + + empty_paginator = Sandbox.list_snapshots(name=f"{snapshot_name}-does-not-exist") + empty_snapshots = empty_paginator.next_items() + assert isinstance(empty_snapshots, list) + assert len(empty_snapshots) == 0 + finally: + Sandbox.delete_snapshot(snapshot.snapshot_id) + + @pytest.mark.skip_debug() def test_create_named_snapshot(sandbox: Sandbox, sandbox_test_id: str): snapshot_name = f"snap-{sandbox_test_id}" diff --git a/spec/openapi.yml b/spec/openapi.yml index 0f20f8d70c..e2d998d87c 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -2618,6 +2618,12 @@ paths: schema: type: string description: Filter snapshots by source sandbox ID + - name: name + in: query + description: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + required: false + schema: + type: string - $ref: '#/components/parameters/paginationLimit' - $ref: '#/components/parameters/paginationNextToken' responses: From b2b979ce9fd285ff7ca85afee1280765e5e39c3c Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:24:25 +0200 Subject: [PATCH 2/7] refactor(sdk): move snapshot list filters into a query object Align Sandbox.listSnapshots() / Sandbox.list_snapshots() with Sandbox.list(): snapshot filters (sandboxId + the new name filter) now live inside a `query` object (JS) / SnapshotQuery (Python) instead of flat top-level params. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/snapshot-list-name-filter.md | 20 ++++++++++--- packages/js-sdk/src/sandbox/index.ts | 8 +++-- packages/js-sdk/src/sandbox/sandboxApi.ts | 29 ++++++++++--------- .../js-sdk/tests/sandbox/snapshot-api.test.ts | 4 +-- packages/python-sdk/e2b/__init__.py | 2 ++ .../python-sdk/e2b/sandbox/sandbox_api.py | 17 ++++++++--- packages/python-sdk/e2b/sandbox_async/main.py | 27 +++++++++-------- .../python-sdk/e2b/sandbox_async/paginator.py | 6 ++-- packages/python-sdk/e2b/sandbox_sync/main.py | 27 +++++++++-------- .../python-sdk/e2b/sandbox_sync/paginator.py | 6 ++-- .../async/sandbox_async/test_snapshot_api.py | 8 ++--- .../sync/sandbox_sync/test_snapshot_api.py | 12 +++++--- 12 files changed, 101 insertions(+), 65 deletions(-) diff --git a/.changeset/snapshot-list-name-filter.md b/.changeset/snapshot-list-name-filter.md index b56cf5a8cb..f4d3155b9a 100644 --- a/.changeset/snapshot-list-name-filter.md +++ b/.changeset/snapshot-list-name-filter.md @@ -3,24 +3,36 @@ "@e2b/python-sdk": minor --- -Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`. +Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()` +and move snapshot list filters into a `query` object, matching the shape of +`Sandbox.list()`. The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`). Unknown names return an empty list rather than an error. +Note: this reshapes the recently added snapshot list filter — the `sandboxId` +filter now lives inside `query` instead of being a top-level option. + ```ts // JS/TS -const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' }) +const paginator = Sandbox.listSnapshots({ query: { name: 'my-snapshot' } }) const snapshots = await paginator.nextItems() + +// still filter by source sandbox +Sandbox.listSnapshots({ query: { sandboxId: 'sandbox-id' } }) ``` ```python # Python (sync) -paginator = Sandbox.list_snapshots(name="my-snapshot") +from e2b import Sandbox, SnapshotQuery + +paginator = Sandbox.list_snapshots(query=SnapshotQuery(name="my-snapshot")) snapshots = paginator.next_items() # Python (async) -paginator = AsyncSandbox.list_snapshots(name="my-snapshot") +from e2b import AsyncSandbox, SnapshotQuery + +paginator = AsyncSandbox.list_snapshots(query=SnapshotQuery(name="my-snapshot")) snapshots = await paginator.next_items() ``` diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 56caea6300..ed964d0596 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -609,10 +609,14 @@ export class Sandbox extends SandboxApi { * * @returns paginator for listing snapshots from this sandbox. */ - listSnapshots(opts?: Omit): SnapshotPaginator { + listSnapshots( + opts?: Omit & { + query?: Omit, 'sandboxId'> + } + ): SnapshotPaginator { return SandboxApi.listSnapshots({ ...this.resolveApiOpts(opts), - sandboxId: this.sandboxId, + query: { ...opts?.query, sandboxId: this.sandboxId }, }) } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 2da2aafc5e..885fd334d7 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -446,15 +446,20 @@ export interface SandboxMetricsOpts extends SandboxApiOpts { */ export interface SnapshotListOpts extends Omit { /** - * Filter snapshots by source sandbox ID. + * Filter the list of snapshots. If there are multiple filters they are combined with AND. */ - sandboxId?: string + query?: { + /** + * Filter snapshots by source sandbox ID. + */ + sandboxId?: string - /** - * Filter snapshots by name or ID, optionally tag-qualified - * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). - */ - name?: string + /** + * Filter snapshots by name or ID, optionally tag-qualified + * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + */ + name?: string + } /** * Number of snapshots to return per page. @@ -1340,14 +1345,12 @@ export class SandboxPaginator extends Paginator { * ``` */ export class SnapshotPaginator extends Paginator { - private readonly sandboxId?: string - private readonly name?: string + private readonly query: SnapshotListOpts['query'] constructor(opts?: SnapshotListOpts) { super(opts, opts?.limit, opts?.nextToken) - this.sandboxId = opts?.sandboxId - this.name = opts?.name + this.query = opts?.query } async nextItems(opts?: SandboxApiOpts): Promise { @@ -1361,8 +1364,8 @@ export class SnapshotPaginator extends Paginator { const res = await client.api.GET('/snapshots', { params: { query: { - sandboxID: this.sandboxId, - name: this.name, + sandboxID: this.query?.sandboxId, + name: this.query?.name, limit: this.limit, nextToken: this.nextToken, }, diff --git a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts index 3c6432af65..dba97a5913 100644 --- a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts @@ -168,7 +168,7 @@ sandboxTest.skipIf(isDebug)( try { // Filtering by the snapshot name should return the snapshot - const paginator = Sandbox.listSnapshots({ name: snapshotName }) + const paginator = Sandbox.listSnapshots({ query: { name: snapshotName } }) const snapshots = await paginator.nextItems() const found = snapshots.find((s) => s.snapshotId === snapshot.snapshotId) @@ -176,7 +176,7 @@ sandboxTest.skipIf(isDebug)( // Filtering by an unknown name should return an empty list const emptyPaginator = Sandbox.listSnapshots({ - name: `${snapshotName}-does-not-exist`, + query: { name: `${snapshotName}-does-not-exist` }, }) const emptySnapshots = await emptyPaginator.nextItems() assert.isArray(emptySnapshots) diff --git a/packages/python-sdk/e2b/__init__.py b/packages/python-sdk/e2b/__init__.py index c8b65aa74a..b6da96d640 100644 --- a/packages/python-sdk/e2b/__init__.py +++ b/packages/python-sdk/e2b/__init__.py @@ -92,6 +92,7 @@ SandboxQuery, SandboxState, SnapshotInfo, + SnapshotQuery, ) from .sandbox_async.commands.command_handle import AsyncCommandHandle from .sandbox_async.filesystem.watch_handle import AsyncWatchHandle @@ -206,6 +207,7 @@ "ALL_TRAFFIC", # Snapshot "SnapshotInfo", + "SnapshotQuery", "SnapshotPaginator", "AsyncSnapshotPaginator", # Signature diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 89a7070e5e..3af836ca51 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -600,18 +600,27 @@ class SnapshotInfo: """Full names of the snapshot template including team namespace and tag (e.g. team-slug/my-snapshot:v2).""" +@dataclass +class SnapshotQuery: + """Query parameters for listing snapshots.""" + + sandbox_id: Optional[str] = None + """Filter snapshots by source sandbox ID.""" + + name: Optional[str] = None + """Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1").""" + + class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]): def __init__( self, - sandbox_id: Optional[str] = None, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ): super().__init__(limit=limit, next_token=next_token, **opts) - self.sandbox_id = sandbox_id - self.name = name + self.query = query class SandboxPaginatorBase(PaginatorBase[SandboxInfo, ApiParams]): diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index f12e99a9a4..3959b891a4 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -27,6 +27,7 @@ SandboxNetworkOpts, SandboxNetworkUpdate, SnapshotInfo, + SnapshotQuery, ) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_async.commands.command import Commands @@ -769,7 +770,7 @@ async def create_snapshot( @overload def list_snapshots( self, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -777,7 +778,7 @@ def list_snapshots( """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -788,8 +789,7 @@ def list_snapshots( @overload @staticmethod def list_snapshots( - sandbox_id: Optional[str] = None, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -797,8 +797,7 @@ def list_snapshots( """ List all snapshots. - :param sandbox_id: Filter snapshots by source sandbox ID - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots by source sandbox ID or name, e.g. `SnapshotQuery(name="my-snapshot")` :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -809,7 +808,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -817,15 +816,17 @@ def list_snapshots( """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination :return: Paginator for listing snapshots """ return AsyncSnapshotPaginator( - sandbox_id=self.sandbox_id, - name=name, + query=SnapshotQuery( + sandbox_id=self.sandbox_id, + name=query.name if query else None, + ), limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -833,15 +834,13 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( - sandbox_id: Optional[str] = None, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: return AsyncSnapshotPaginator( - sandbox_id=sandbox_id, - name=name, + query=query, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_async/paginator.py b/packages/python-sdk/e2b/sandbox_async/paginator.py index bc8f5380a7..2af3af86ae 100644 --- a/packages/python-sdk/e2b/sandbox_async/paginator.py +++ b/packages/python-sdk/e2b/sandbox_async/paginator.py @@ -115,8 +115,10 @@ async def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: api_client = get_api_client(config) res = await get_snapshots.asyncio_detailed( client=api_client, - sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, - name=self.name if self.name else UNSET, + sandbox_id=self.query.sandbox_id + if self.query and self.query.sandbox_id + else UNSET, + name=self.query.name if self.query and self.query.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index e217f30986..20b3d35516 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -25,6 +25,7 @@ SandboxNetworkOpts, SandboxNetworkUpdate, SnapshotInfo, + SnapshotQuery, ) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_sync.commands.command import Commands @@ -759,7 +760,7 @@ def create_snapshot( @overload def list_snapshots( self, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -767,7 +768,7 @@ def list_snapshots( """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -778,8 +779,7 @@ def list_snapshots( @overload @staticmethod def list_snapshots( - sandbox_id: Optional[str] = None, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -787,8 +787,7 @@ def list_snapshots( """ List all snapshots. - :param sandbox_id: Filter snapshots by source sandbox ID - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots by source sandbox ID or name, e.g. `SnapshotQuery(name="my-snapshot")` :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -799,7 +798,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -807,15 +806,17 @@ def list_snapshots( """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") + :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination :return: Paginator for listing snapshots """ return SnapshotPaginator( - sandbox_id=self.sandbox_id, - name=name, + query=SnapshotQuery( + sandbox_id=self.sandbox_id, + name=query.name if query else None, + ), limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -823,15 +824,13 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( - sandbox_id: Optional[str] = None, - name: Optional[str] = None, + query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: return SnapshotPaginator( - sandbox_id=sandbox_id, - name=name, + query=query, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_sync/paginator.py b/packages/python-sdk/e2b/sandbox_sync/paginator.py index aa896dd4ba..0a89d9e4ed 100644 --- a/packages/python-sdk/e2b/sandbox_sync/paginator.py +++ b/packages/python-sdk/e2b/sandbox_sync/paginator.py @@ -115,8 +115,10 @@ def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: api_client = get_api_client(config) res = get_snapshots.sync_detailed( client=api_client, - sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, - name=self.name if self.name else UNSET, + sandbox_id=self.query.sandbox_id + if self.query and self.query.sandbox_id + else UNSET, + name=self.query.name if self.query and self.query.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py index 80354a62e1..ddb0976578 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py @@ -1,5 +1,5 @@ import pytest -from e2b import AsyncSandbox +from e2b import AsyncSandbox, SnapshotQuery @pytest.mark.skip_debug() @@ -86,7 +86,7 @@ async def test_list_snapshots_for_sandbox(async_sandbox: AsyncSandbox): try: paginator = AsyncSandbox.list_snapshots( - sandbox_id=async_sandbox.sandbox_id, + query=SnapshotQuery(sandbox_id=async_sandbox.sandbox_id), ) snapshots = await paginator.next_items() @@ -105,14 +105,14 @@ async def test_list_snapshots_filtered_by_name( snapshot = await async_sandbox.create_snapshot(name=snapshot_name) try: - paginator = AsyncSandbox.list_snapshots(name=snapshot_name) + paginator = AsyncSandbox.list_snapshots(query=SnapshotQuery(name=snapshot_name)) snapshots = await paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) assert found empty_paginator = AsyncSandbox.list_snapshots( - name=f"{snapshot_name}-does-not-exist" + query=SnapshotQuery(name=f"{snapshot_name}-does-not-exist") ) empty_snapshots = await empty_paginator.next_items() assert isinstance(empty_snapshots, list) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py index 2fa6d29824..ac15d1f405 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py @@ -1,5 +1,5 @@ import pytest -from e2b import Sandbox +from e2b import Sandbox, SnapshotQuery @pytest.mark.skip_debug() @@ -85,7 +85,9 @@ def test_list_snapshots_for_sandbox(sandbox: Sandbox): snapshot = sandbox.create_snapshot() try: - paginator = Sandbox.list_snapshots(sandbox_id=sandbox.sandbox_id) + paginator = Sandbox.list_snapshots( + query=SnapshotQuery(sandbox_id=sandbox.sandbox_id) + ) snapshots = paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) @@ -101,13 +103,15 @@ def test_list_snapshots_filtered_by_name(sandbox: Sandbox, sandbox_test_id: str) snapshot = sandbox.create_snapshot(name=snapshot_name) try: - paginator = Sandbox.list_snapshots(name=snapshot_name) + paginator = Sandbox.list_snapshots(query=SnapshotQuery(name=snapshot_name)) snapshots = paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) assert found - empty_paginator = Sandbox.list_snapshots(name=f"{snapshot_name}-does-not-exist") + empty_paginator = Sandbox.list_snapshots( + query=SnapshotQuery(name=f"{snapshot_name}-does-not-exist") + ) empty_snapshots = empty_paginator.next_items() assert isinstance(empty_snapshots, list) assert len(empty_snapshots) == 0 From 52fead8afd79d2d3c913056775bdfb377fb2ee51 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:56:31 +0200 Subject: [PATCH 3/7] refactor(sdk): make snapshot sandboxId/name filters mutually exclusive Snapshots can be filtered by source sandbox ID or by name, but not both at once. JS enforces this via a discriminated union on the query object; Python raises ValueError in SnapshotQuery when both are set. The instance list_snapshots() filters by its own sandbox id only (no name). Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/snapshot-list-name-filter.md | 2 ++ packages/js-sdk/src/sandbox/index.ts | 8 ++--- packages/js-sdk/src/sandbox/sandboxApi.ts | 34 +++++++++++-------- .../python-sdk/e2b/sandbox/sandbox_api.py | 12 ++++++- packages/python-sdk/e2b/sandbox_async/main.py | 11 ++---- packages/python-sdk/e2b/sandbox_sync/main.py | 11 ++---- .../sync/sandbox_sync/test_snapshot_api.py | 5 +++ 7 files changed, 44 insertions(+), 39 deletions(-) diff --git a/.changeset/snapshot-list-name-filter.md b/.changeset/snapshot-list-name-filter.md index f4d3155b9a..9d1296b5b5 100644 --- a/.changeset/snapshot-list-name-filter.md +++ b/.changeset/snapshot-list-name-filter.md @@ -11,6 +11,8 @@ The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`). Unknown names return an empty list rather than an error. +The `sandboxId` and `name` filters are mutually exclusive — provide at most one. + Note: this reshapes the recently added snapshot list filter — the `sandboxId` filter now lives inside `query` instead of being a top-level option. diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index ed964d0596..86ca97d801 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -609,14 +609,10 @@ export class Sandbox extends SandboxApi { * * @returns paginator for listing snapshots from this sandbox. */ - listSnapshots( - opts?: Omit & { - query?: Omit, 'sandboxId'> - } - ): SnapshotPaginator { + listSnapshots(opts?: Omit): SnapshotPaginator { return SandboxApi.listSnapshots({ ...this.resolveApiOpts(opts), - query: { ...opts?.query, sandboxId: this.sandboxId }, + query: { sandboxId: this.sandboxId }, }) } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 885fd334d7..dde518b8c6 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -446,20 +446,26 @@ export interface SandboxMetricsOpts extends SandboxApiOpts { */ export interface SnapshotListOpts extends Omit { /** - * Filter the list of snapshots. If there are multiple filters they are combined with AND. - */ - query?: { - /** - * Filter snapshots by source sandbox ID. - */ - sandboxId?: string - - /** - * Filter snapshots by name or ID, optionally tag-qualified - * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). - */ - name?: string - } + * Filter the list of snapshots by source sandbox ID or by name. + * + * These filters are mutually exclusive — provide at most one of them. + */ + query?: + | { + /** + * Filter snapshots by source sandbox ID. + */ + sandboxId?: string + name?: never + } + | { + /** + * Filter snapshots by name or ID, optionally tag-qualified + * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + */ + name?: string + sandboxId?: never + } /** * Number of snapshots to return per page. diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 3af836ca51..b50e56eab9 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -602,7 +602,11 @@ class SnapshotInfo: @dataclass class SnapshotQuery: - """Query parameters for listing snapshots.""" + """ + Query parameters for listing snapshots. + + `sandbox_id` and `name` are mutually exclusive — provide at most one. + """ sandbox_id: Optional[str] = None """Filter snapshots by source sandbox ID.""" @@ -610,6 +614,12 @@ class SnapshotQuery: name: Optional[str] = None """Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1").""" + def __post_init__(self): + if self.sandbox_id is not None and self.name is not None: + raise ValueError( + "SnapshotQuery accepts either `sandbox_id` or `name`, not both." + ) + class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]): def __init__( diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 3959b891a4..c03631464a 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -770,7 +770,6 @@ async def create_snapshot( @overload def list_snapshots( self, - query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -778,7 +777,6 @@ def list_snapshots( """ List snapshots for this sandbox. - :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -797,7 +795,7 @@ def list_snapshots( """ List all snapshots. - :param query: Filter the list of snapshots by source sandbox ID or name, e.g. `SnapshotQuery(name="my-snapshot")` + :param query: Filter the list of snapshots by source sandbox ID or name (mutually exclusive), e.g. `SnapshotQuery(name="my-snapshot")` :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -808,7 +806,6 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -816,17 +813,13 @@ def list_snapshots( """ List snapshots for this sandbox. - :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination :return: Paginator for listing snapshots """ return AsyncSnapshotPaginator( - query=SnapshotQuery( - sandbox_id=self.sandbox_id, - name=query.name if query else None, - ), + query=SnapshotQuery(sandbox_id=self.sandbox_id), limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 20b3d35516..18ed5a09d3 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -760,7 +760,6 @@ def create_snapshot( @overload def list_snapshots( self, - query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -768,7 +767,6 @@ def list_snapshots( """ List snapshots for this sandbox. - :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -787,7 +785,7 @@ def list_snapshots( """ List all snapshots. - :param query: Filter the list of snapshots by source sandbox ID or name, e.g. `SnapshotQuery(name="my-snapshot")` + :param query: Filter the list of snapshots by source sandbox ID or name (mutually exclusive), e.g. `SnapshotQuery(name="my-snapshot")` :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -798,7 +796,6 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - query: Optional[SnapshotQuery] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -806,17 +803,13 @@ def list_snapshots( """ List snapshots for this sandbox. - :param query: Filter the list of snapshots, e.g. `SnapshotQuery(name="my-snapshot")`. The `sandbox_id` filter is ignored — snapshots are always scoped to this sandbox. :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination :return: Paginator for listing snapshots """ return SnapshotPaginator( - query=SnapshotQuery( - sandbox_id=self.sandbox_id, - name=query.name if query else None, - ), + query=SnapshotQuery(sandbox_id=self.sandbox_id), limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py index ac15d1f405..b54c805ae8 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py @@ -2,6 +2,11 @@ from e2b import Sandbox, SnapshotQuery +def test_snapshot_query_rejects_both_filters(): + with pytest.raises(ValueError): + SnapshotQuery(sandbox_id="sandbox-id", name="my-snapshot") + + @pytest.mark.skip_debug() def test_create_snapshot(sandbox: Sandbox): snapshot = sandbox.create_snapshot() From 1c65bc219a9621e55164e082cef1a52c06085169 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:09:13 +0200 Subject: [PATCH 4/7] refactor(sdk): revert SnapshotQuery, keep flat snapshot list filters Moving the released `sandboxId` snapshot filter into a query object was a breaking change, so revert to flat top-level `sandboxId` / `name` options. Mutual exclusivity is kept via a runtime guard (JS throws, Python raises ValueError) instead of a query type, dropping the `never` union and the SnapshotQuery dataclass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/snapshot-list-name-filter.md | 23 +++------ packages/js-sdk/src/sandbox/index.ts | 6 ++- packages/js-sdk/src/sandbox/sandboxApi.ts | 47 ++++++++++--------- .../js-sdk/tests/sandbox/snapshot-api.test.ts | 12 +++-- packages/python-sdk/e2b/__init__.py | 2 - .../python-sdk/e2b/sandbox/sandbox_api.py | 31 ++++-------- packages/python-sdk/e2b/sandbox_async/main.py | 15 +++--- .../python-sdk/e2b/sandbox_async/paginator.py | 6 +-- packages/python-sdk/e2b/sandbox_sync/main.py | 15 +++--- .../python-sdk/e2b/sandbox_sync/paginator.py | 6 +-- .../async/sandbox_async/test_snapshot_api.py | 8 ++-- .../sync/sandbox_sync/test_snapshot_api.py | 16 +++---- 12 files changed, 83 insertions(+), 104 deletions(-) diff --git a/.changeset/snapshot-list-name-filter.md b/.changeset/snapshot-list-name-filter.md index 9d1296b5b5..31124248d5 100644 --- a/.changeset/snapshot-list-name-filter.md +++ b/.changeset/snapshot-list-name-filter.md @@ -3,38 +3,27 @@ "@e2b/python-sdk": minor --- -Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()` -and move snapshot list filters into a `query` object, matching the shape of -`Sandbox.list()`. +Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`. The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`). Unknown names return an empty list rather than an error. -The `sandboxId` and `name` filters are mutually exclusive — provide at most one. - -Note: this reshapes the recently added snapshot list filter — the `sandboxId` -filter now lives inside `query` instead of being a top-level option. +The `sandboxId` / `sandbox_id` and `name` filters are mutually exclusive — +passing both raises an error. ```ts // JS/TS -const paginator = Sandbox.listSnapshots({ query: { name: 'my-snapshot' } }) +const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' }) const snapshots = await paginator.nextItems() - -// still filter by source sandbox -Sandbox.listSnapshots({ query: { sandboxId: 'sandbox-id' } }) ``` ```python # Python (sync) -from e2b import Sandbox, SnapshotQuery - -paginator = Sandbox.list_snapshots(query=SnapshotQuery(name="my-snapshot")) +paginator = Sandbox.list_snapshots(name="my-snapshot") snapshots = paginator.next_items() # Python (async) -from e2b import AsyncSandbox, SnapshotQuery - -paginator = AsyncSandbox.list_snapshots(query=SnapshotQuery(name="my-snapshot")) +paginator = AsyncSandbox.list_snapshots(name="my-snapshot") snapshots = await paginator.next_items() ``` diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 86ca97d801..b18e980c26 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -609,10 +609,12 @@ export class Sandbox extends SandboxApi { * * @returns paginator for listing snapshots from this sandbox. */ - listSnapshots(opts?: Omit): SnapshotPaginator { + listSnapshots( + opts?: Omit + ): SnapshotPaginator { return SandboxApi.listSnapshots({ ...this.resolveApiOpts(opts), - query: { sandboxId: this.sandboxId }, + sandboxId: this.sandboxId, }) } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index dde518b8c6..cf4f22eb44 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -446,26 +446,19 @@ export interface SandboxMetricsOpts extends SandboxApiOpts { */ export interface SnapshotListOpts extends Omit { /** - * Filter the list of snapshots by source sandbox ID or by name. + * Filter snapshots by source sandbox ID. * - * These filters are mutually exclusive — provide at most one of them. - */ - query?: - | { - /** - * Filter snapshots by source sandbox ID. - */ - sandboxId?: string - name?: never - } - | { - /** - * Filter snapshots by name or ID, optionally tag-qualified - * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). - */ - name?: string - sandboxId?: never - } + * Mutually exclusive with `name`. + */ + sandboxId?: string + + /** + * Filter snapshots by name or ID, optionally tag-qualified + * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + * + * Mutually exclusive with `sandboxId`. + */ + name?: string /** * Number of snapshots to return per page. @@ -1351,12 +1344,20 @@ export class SandboxPaginator extends Paginator { * ``` */ export class SnapshotPaginator extends Paginator { - private readonly query: SnapshotListOpts['query'] + private readonly sandboxId?: string + private readonly name?: string constructor(opts?: SnapshotListOpts) { super(opts, opts?.limit, opts?.nextToken) - this.query = opts?.query + if (opts?.sandboxId && opts?.name) { + throw new Error( + 'listSnapshots accepts either `sandboxId` or `name`, not both.' + ) + } + + this.sandboxId = opts?.sandboxId + this.name = opts?.name } async nextItems(opts?: SandboxApiOpts): Promise { @@ -1370,8 +1371,8 @@ export class SnapshotPaginator extends Paginator { const res = await client.api.GET('/snapshots', { params: { query: { - sandboxID: this.query?.sandboxId, - name: this.query?.name, + sandboxID: this.sandboxId, + name: this.name, limit: this.limit, nextToken: this.nextToken, }, diff --git a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts index dba97a5913..6a3ac694f5 100644 --- a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts @@ -1,8 +1,14 @@ -import { assert } from 'vitest' +import { assert, test } from 'vitest' import { sandboxTest, isDebug } from '../setup.js' import { Sandbox } from '../../src' +test('listSnapshots rejects both sandboxId and name', () => { + assert.throws(() => + Sandbox.listSnapshots({ sandboxId: 'sandbox-id', name: 'my-snapshot' }) + ) +}) + sandboxTest.skipIf(isDebug)( 'create a snapshot from sandbox', async ({ sandbox }) => { @@ -168,7 +174,7 @@ sandboxTest.skipIf(isDebug)( try { // Filtering by the snapshot name should return the snapshot - const paginator = Sandbox.listSnapshots({ query: { name: snapshotName } }) + const paginator = Sandbox.listSnapshots({ name: snapshotName }) const snapshots = await paginator.nextItems() const found = snapshots.find((s) => s.snapshotId === snapshot.snapshotId) @@ -176,7 +182,7 @@ sandboxTest.skipIf(isDebug)( // Filtering by an unknown name should return an empty list const emptyPaginator = Sandbox.listSnapshots({ - query: { name: `${snapshotName}-does-not-exist` }, + name: `${snapshotName}-does-not-exist`, }) const emptySnapshots = await emptyPaginator.nextItems() assert.isArray(emptySnapshots) diff --git a/packages/python-sdk/e2b/__init__.py b/packages/python-sdk/e2b/__init__.py index b6da96d640..c8b65aa74a 100644 --- a/packages/python-sdk/e2b/__init__.py +++ b/packages/python-sdk/e2b/__init__.py @@ -92,7 +92,6 @@ SandboxQuery, SandboxState, SnapshotInfo, - SnapshotQuery, ) from .sandbox_async.commands.command_handle import AsyncCommandHandle from .sandbox_async.filesystem.watch_handle import AsyncWatchHandle @@ -207,7 +206,6 @@ "ALL_TRAFFIC", # Snapshot "SnapshotInfo", - "SnapshotQuery", "SnapshotPaginator", "AsyncSnapshotPaginator", # Signature diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index b50e56eab9..b2b3dae054 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -600,37 +600,22 @@ class SnapshotInfo: """Full names of the snapshot template including team namespace and tag (e.g. team-slug/my-snapshot:v2).""" -@dataclass -class SnapshotQuery: - """ - Query parameters for listing snapshots. - - `sandbox_id` and `name` are mutually exclusive — provide at most one. - """ - - sandbox_id: Optional[str] = None - """Filter snapshots by source sandbox ID.""" - - name: Optional[str] = None - """Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1").""" - - def __post_init__(self): - if self.sandbox_id is not None and self.name is not None: - raise ValueError( - "SnapshotQuery accepts either `sandbox_id` or `name`, not both." - ) - - class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]): def __init__( self, - query: Optional[SnapshotQuery] = None, + sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ): super().__init__(limit=limit, next_token=next_token, **opts) - self.query = query + if sandbox_id is not None and name is not None: + raise ValueError( + "list_snapshots accepts either `sandbox_id` or `name`, not both." + ) + self.sandbox_id = sandbox_id + self.name = name class SandboxPaginatorBase(PaginatorBase[SandboxInfo, ApiParams]): diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index c03631464a..eb343a00ca 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -27,7 +27,6 @@ SandboxNetworkOpts, SandboxNetworkUpdate, SnapshotInfo, - SnapshotQuery, ) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_async.commands.command import Commands @@ -787,7 +786,8 @@ def list_snapshots( @overload @staticmethod def list_snapshots( - query: Optional[SnapshotQuery] = None, + sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -795,7 +795,8 @@ def list_snapshots( """ List all snapshots. - :param query: Filter the list of snapshots by source sandbox ID or name (mutually exclusive), e.g. `SnapshotQuery(name="my-snapshot")` + :param sandbox_id: Filter snapshots by source sandbox ID (mutually exclusive with `name`) + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") (mutually exclusive with `sandbox_id`) :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -819,7 +820,7 @@ def list_snapshots( :return: Paginator for listing snapshots """ return AsyncSnapshotPaginator( - query=SnapshotQuery(sandbox_id=self.sandbox_id), + sandbox_id=self.sandbox_id, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -827,13 +828,15 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( - query: Optional[SnapshotQuery] = None, + sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: return AsyncSnapshotPaginator( - query=query, + sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_async/paginator.py b/packages/python-sdk/e2b/sandbox_async/paginator.py index 2af3af86ae..bc8f5380a7 100644 --- a/packages/python-sdk/e2b/sandbox_async/paginator.py +++ b/packages/python-sdk/e2b/sandbox_async/paginator.py @@ -115,10 +115,8 @@ async def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: api_client = get_api_client(config) res = await get_snapshots.asyncio_detailed( client=api_client, - sandbox_id=self.query.sandbox_id - if self.query and self.query.sandbox_id - else UNSET, - name=self.query.name if self.query and self.query.name else UNSET, + sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, + name=self.name if self.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 18ed5a09d3..ea054c187b 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -25,7 +25,6 @@ SandboxNetworkOpts, SandboxNetworkUpdate, SnapshotInfo, - SnapshotQuery, ) from e2b.sandbox.utils import class_method_variant from e2b.sandbox_sync.commands.command import Commands @@ -777,7 +776,8 @@ def list_snapshots( @overload @staticmethod def list_snapshots( - query: Optional[SnapshotQuery] = None, + sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -785,7 +785,8 @@ def list_snapshots( """ List all snapshots. - :param query: Filter the list of snapshots by source sandbox ID or name (mutually exclusive), e.g. `SnapshotQuery(name="my-snapshot")` + :param sandbox_id: Filter snapshots by source sandbox ID (mutually exclusive with `name`) + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") (mutually exclusive with `sandbox_id`) :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -809,7 +810,7 @@ def list_snapshots( :return: Paginator for listing snapshots """ return SnapshotPaginator( - query=SnapshotQuery(sandbox_id=self.sandbox_id), + sandbox_id=self.sandbox_id, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), @@ -817,13 +818,15 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( - query: Optional[SnapshotQuery] = None, + sandbox_id: Optional[str] = None, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: return SnapshotPaginator( - query=query, + sandbox_id=sandbox_id, + name=name, limit=limit, next_token=next_token, **opts, diff --git a/packages/python-sdk/e2b/sandbox_sync/paginator.py b/packages/python-sdk/e2b/sandbox_sync/paginator.py index 0a89d9e4ed..aa896dd4ba 100644 --- a/packages/python-sdk/e2b/sandbox_sync/paginator.py +++ b/packages/python-sdk/e2b/sandbox_sync/paginator.py @@ -115,10 +115,8 @@ def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]: api_client = get_api_client(config) res = get_snapshots.sync_detailed( client=api_client, - sandbox_id=self.query.sandbox_id - if self.query and self.query.sandbox_id - else UNSET, - name=self.query.name if self.query and self.query.name else UNSET, + sandbox_id=self.sandbox_id if self.sandbox_id else UNSET, + name=self.name if self.name else UNSET, limit=self.limit if self.limit else UNSET, next_token=self._next_token if self._next_token else UNSET, ) diff --git a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py index ddb0976578..80354a62e1 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_snapshot_api.py @@ -1,5 +1,5 @@ import pytest -from e2b import AsyncSandbox, SnapshotQuery +from e2b import AsyncSandbox @pytest.mark.skip_debug() @@ -86,7 +86,7 @@ async def test_list_snapshots_for_sandbox(async_sandbox: AsyncSandbox): try: paginator = AsyncSandbox.list_snapshots( - query=SnapshotQuery(sandbox_id=async_sandbox.sandbox_id), + sandbox_id=async_sandbox.sandbox_id, ) snapshots = await paginator.next_items() @@ -105,14 +105,14 @@ async def test_list_snapshots_filtered_by_name( snapshot = await async_sandbox.create_snapshot(name=snapshot_name) try: - paginator = AsyncSandbox.list_snapshots(query=SnapshotQuery(name=snapshot_name)) + paginator = AsyncSandbox.list_snapshots(name=snapshot_name) snapshots = await paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) assert found empty_paginator = AsyncSandbox.list_snapshots( - query=SnapshotQuery(name=f"{snapshot_name}-does-not-exist") + name=f"{snapshot_name}-does-not-exist" ) empty_snapshots = await empty_paginator.next_items() assert isinstance(empty_snapshots, list) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py index b54c805ae8..59f94b6ccc 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py @@ -1,10 +1,10 @@ import pytest -from e2b import Sandbox, SnapshotQuery +from e2b import Sandbox -def test_snapshot_query_rejects_both_filters(): +def test_list_snapshots_rejects_both_filters(): with pytest.raises(ValueError): - SnapshotQuery(sandbox_id="sandbox-id", name="my-snapshot") + Sandbox.list_snapshots(sandbox_id="sandbox-id", name="my-snapshot") @pytest.mark.skip_debug() @@ -90,9 +90,7 @@ def test_list_snapshots_for_sandbox(sandbox: Sandbox): snapshot = sandbox.create_snapshot() try: - paginator = Sandbox.list_snapshots( - query=SnapshotQuery(sandbox_id=sandbox.sandbox_id) - ) + paginator = Sandbox.list_snapshots(sandbox_id=sandbox.sandbox_id) snapshots = paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) @@ -108,15 +106,13 @@ def test_list_snapshots_filtered_by_name(sandbox: Sandbox, sandbox_test_id: str) snapshot = sandbox.create_snapshot(name=snapshot_name) try: - paginator = Sandbox.list_snapshots(query=SnapshotQuery(name=snapshot_name)) + paginator = Sandbox.list_snapshots(name=snapshot_name) snapshots = paginator.next_items() found = any(s.snapshot_id == snapshot.snapshot_id for s in snapshots) assert found - empty_paginator = Sandbox.list_snapshots( - query=SnapshotQuery(name=f"{snapshot_name}-does-not-exist") - ) + empty_paginator = Sandbox.list_snapshots(name=f"{snapshot_name}-does-not-exist") empty_snapshots = empty_paginator.next_items() assert isinstance(empty_snapshots, list) assert len(empty_snapshots) == 0 From 773e375e44c137cf994968003d267611b1db7c1a Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:13:47 +0200 Subject: [PATCH 5/7] refactor(sdk): allow combining snapshot sandboxId and name filters The backend combines the /snapshots `sandboxID` and `name` query params with AND, so drop the client-side mutual-exclusivity guard and let both filters be used together, mirroring the API (and the metadata+state combination in Sandbox.list). Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/snapshot-list-name-filter.md | 6 ++---- packages/js-sdk/src/sandbox/index.ts | 4 +--- packages/js-sdk/src/sandbox/sandboxApi.ts | 10 ---------- packages/js-sdk/tests/sandbox/snapshot-api.test.ts | 8 +------- packages/python-sdk/e2b/sandbox/sandbox_api.py | 4 ---- packages/python-sdk/e2b/sandbox_async/main.py | 9 +++++++-- packages/python-sdk/e2b/sandbox_sync/main.py | 9 +++++++-- .../tests/sync/sandbox_sync/test_snapshot_api.py | 5 ----- 8 files changed, 18 insertions(+), 37 deletions(-) diff --git a/.changeset/snapshot-list-name-filter.md b/.changeset/snapshot-list-name-filter.md index 31124248d5..d2d9df7fea 100644 --- a/.changeset/snapshot-list-name-filter.md +++ b/.changeset/snapshot-list-name-filter.md @@ -7,10 +7,8 @@ Add a `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`. The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`). Unknown names -return an empty list rather than an error. - -The `sandboxId` / `sandbox_id` and `name` filters are mutually exclusive — -passing both raises an error. +return an empty list rather than an error. It can be combined with the existing +`sandboxId` / `sandbox_id` filter (results must match both). ```ts // JS/TS diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index b18e980c26..56caea6300 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -609,9 +609,7 @@ export class Sandbox extends SandboxApi { * * @returns paginator for listing snapshots from this sandbox. */ - listSnapshots( - opts?: Omit - ): SnapshotPaginator { + listSnapshots(opts?: Omit): SnapshotPaginator { return SandboxApi.listSnapshots({ ...this.resolveApiOpts(opts), sandboxId: this.sandboxId, diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index cf4f22eb44..2da2aafc5e 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -447,16 +447,12 @@ export interface SandboxMetricsOpts extends SandboxApiOpts { export interface SnapshotListOpts extends Omit { /** * Filter snapshots by source sandbox ID. - * - * Mutually exclusive with `name`. */ sandboxId?: string /** * Filter snapshots by name or ID, optionally tag-qualified * (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). - * - * Mutually exclusive with `sandboxId`. */ name?: string @@ -1350,12 +1346,6 @@ export class SnapshotPaginator extends Paginator { constructor(opts?: SnapshotListOpts) { super(opts, opts?.limit, opts?.nextToken) - if (opts?.sandboxId && opts?.name) { - throw new Error( - 'listSnapshots accepts either `sandboxId` or `name`, not both.' - ) - } - this.sandboxId = opts?.sandboxId this.name = opts?.name } diff --git a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts index 6a3ac694f5..3c6432af65 100644 --- a/packages/js-sdk/tests/sandbox/snapshot-api.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot-api.test.ts @@ -1,14 +1,8 @@ -import { assert, test } from 'vitest' +import { assert } from 'vitest' import { sandboxTest, isDebug } from '../setup.js' import { Sandbox } from '../../src' -test('listSnapshots rejects both sandboxId and name', () => { - assert.throws(() => - Sandbox.listSnapshots({ sandboxId: 'sandbox-id', name: 'my-snapshot' }) - ) -}) - sandboxTest.skipIf(isDebug)( 'create a snapshot from sandbox', async ({ sandbox }) => { diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index b2b3dae054..89a7070e5e 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -610,10 +610,6 @@ def __init__( **opts: Unpack[ApiParams], ): super().__init__(limit=limit, next_token=next_token, **opts) - if sandbox_id is not None and name is not None: - raise ValueError( - "list_snapshots accepts either `sandbox_id` or `name`, not both." - ) self.sandbox_id = sandbox_id self.name = name diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index eb343a00ca..f12e99a9a4 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -769,6 +769,7 @@ async def create_snapshot( @overload def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -776,6 +777,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -795,8 +797,8 @@ def list_snapshots( """ List all snapshots. - :param sandbox_id: Filter snapshots by source sandbox ID (mutually exclusive with `name`) - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") (mutually exclusive with `sandbox_id`) + :param sandbox_id: Filter snapshots by source sandbox ID + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -807,6 +809,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -814,6 +817,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -821,6 +825,7 @@ def list_snapshots( """ return AsyncSnapshotPaginator( sandbox_id=self.sandbox_id, + name=name, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index ea054c187b..e217f30986 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -759,6 +759,7 @@ def create_snapshot( @overload def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -766,6 +767,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -785,8 +787,8 @@ def list_snapshots( """ List all snapshots. - :param sandbox_id: Filter snapshots by source sandbox ID (mutually exclusive with `name`) - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") (mutually exclusive with `sandbox_id`) + :param sandbox_id: Filter snapshots by source sandbox ID + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -797,6 +799,7 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, + name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, **opts: Unpack[ApiParams], @@ -804,6 +807,7 @@ def list_snapshots( """ List snapshots for this sandbox. + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination @@ -811,6 +815,7 @@ def list_snapshots( """ return SnapshotPaginator( sandbox_id=self.sandbox_id, + name=name, limit=limit, next_token=next_token, **self.connection_config.get_api_params(**opts), diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py index 59f94b6ccc..2fa6d29824 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_snapshot_api.py @@ -2,11 +2,6 @@ from e2b import Sandbox -def test_list_snapshots_rejects_both_filters(): - with pytest.raises(ValueError): - Sandbox.list_snapshots(sandbox_id="sandbox-id", name="my-snapshot") - - @pytest.mark.skip_debug() def test_create_snapshot(sandbox: Sandbox): snapshot = sandbox.create_snapshot() From d65a13e9b2bfcedd4a11efa737a93358f04eb5ff Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:34:34 +0200 Subject: [PATCH 6/7] chore(sdk): regenerate API clients for snapshot name filter Run `make codegen` so the generated JS schema and Python client match the spec. Move the `name` param description under `schema` (matching `sandboxID`) so it surfaces in the generated Python docstring. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/js-sdk/src/api/schema.gen.ts | 1 - .../e2b/api/client/api/snapshots/get_snapshots.py | 12 ++++++++---- spec/openapi.yml | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 5908fc2606..79764409e0 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -622,7 +622,6 @@ export interface paths { query?: { /** @description Maximum number of items to return per page */ limit?: components["parameters"]["paginationLimit"]; - /** @description Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). */ name?: string; /** @description Cursor to start the list from */ nextToken?: components["parameters"]["paginationNextToken"]; diff --git a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py index 58731da4a1..13f6b1758b 100644 --- a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py +++ b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py @@ -87,7 +87,8 @@ def sync_detailed( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID - name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. + "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -125,7 +126,8 @@ def sync( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID - name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. + "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -158,7 +160,8 @@ async def asyncio_detailed( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID - name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. + "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): @@ -194,7 +197,8 @@ async def asyncio( Args: sandbox_id (Union[Unset, str]): Filter snapshots by source sandbox ID - name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + name (Union[Unset, str]): Filter snapshots by name or ID, optionally tag-qualified (e.g. + "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). limit (Union[Unset, int]): Default: 100. next_token (Union[Unset, str]): diff --git a/spec/openapi.yml b/spec/openapi.yml index e2d998d87c..f772100d1d 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -2620,10 +2620,10 @@ paths: description: Filter snapshots by source sandbox ID - name: name in: query - description: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). required: false schema: type: string + description: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). - $ref: '#/components/parameters/paginationLimit' - $ref: '#/components/parameters/paginationNextToken' responses: From 7b4420eb2768eaa993c98dad477d764b1a7f4f2d Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:25:32 +0200 Subject: [PATCH 7/7] fix(python-sdk): keep snapshot list `name` filter positionally compatible `list_snapshots` is already released with `(limit, next_token)` positional params. Append the new `name` filter after them (instead of before) so a positional call like `list_snapshots(50)` still binds `limit`, avoiding a silent breaking change. Addresses PR review feedback. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/python-sdk/e2b/sandbox/sandbox_api.py | 2 +- packages/python-sdk/e2b/sandbox_async/main.py | 14 +++++++------- packages/python-sdk/e2b/sandbox_sync/main.py | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 89a7070e5e..f5aaaffb6a 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -604,9 +604,9 @@ class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]): def __init__( self, sandbox_id: Optional[str] = None, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ): super().__init__(limit=limit, next_token=next_token, **opts) diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index f12e99a9a4..50fbdaa0f1 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -769,17 +769,17 @@ async def create_snapshot( @overload def list_snapshots( self, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -789,18 +789,18 @@ def list_snapshots( @staticmethod def list_snapshots( sandbox_id: Optional[str] = None, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: """ List all snapshots. :param sandbox_id: Filter snapshots by source sandbox ID - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -809,17 +809,17 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -834,9 +834,9 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( sandbox_id: Optional[str] = None, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> AsyncSnapshotPaginator: return AsyncSnapshotPaginator( diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index e217f30986..005e2d30a8 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -759,17 +759,17 @@ def create_snapshot( @overload def list_snapshots( self, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -779,18 +779,18 @@ def list_snapshots( @staticmethod def list_snapshots( sandbox_id: Optional[str] = None, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: """ List all snapshots. :param sandbox_id: Filter snapshots by source sandbox ID - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -799,17 +799,17 @@ def list_snapshots( @class_method_variant("_cls_list_snapshots") def list_snapshots( self, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: """ List snapshots for this sandbox. - :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :param limit: Maximum number of snapshots to return per page :param next_token: Token for pagination + :param name: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1") :return: Paginator for listing snapshots """ @@ -824,9 +824,9 @@ def list_snapshots( @staticmethod def _cls_list_snapshots( sandbox_id: Optional[str] = None, - name: Optional[str] = None, limit: Optional[int] = None, next_token: Optional[str] = None, + name: Optional[str] = None, **opts: Unpack[ApiParams], ) -> SnapshotPaginator: return SnapshotPaginator(