Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fix-snapshot-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"e2b": patch
"@e2b/python-sdk": patch
---

Forward optional `name` parameter in `createSnapshot` / `create_snapshot` and return `names` array in `SnapshotInfo` for both JS and Python SDKs.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ vale-styles
.vercel
.vscode
.DS_Store
.worktrees

# Logs
logs
Expand Down
1 change: 1 addition & 0 deletions packages/js-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type {
SandboxInfoLifecycle,
SnapshotInfo,
SnapshotListOpts,
SnapshotCreateOpts,
SnapshotPaginator,
} from './sandbox/sandboxApi'

Expand Down
4 changes: 2 additions & 2 deletions packages/js-sdk/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {
SandboxListOpts,
SandboxPaginator,
SandboxBetaCreateOpts,
SandboxApiOpts,
SnapshotListOpts,
SnapshotCreateOpts,
SnapshotInfo,
SnapshotPaginator,
} from './sandboxApi'
Expand Down Expand Up @@ -621,7 +621,7 @@ export class Sandbox extends SandboxApi {
* const newSandbox = await Sandbox.create(snapshot.snapshotId)
* ```
*/
async createSnapshot(opts?: SandboxApiOpts): Promise<SnapshotInfo> {
async createSnapshot(opts?: SnapshotCreateOpts): Promise<SnapshotInfo> {
return await SandboxApi.createSnapshot(
this.sandboxId,
this.resolveApiOpts(opts)
Expand Down
31 changes: 29 additions & 2 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,22 @@ export interface SnapshotListOpts extends SandboxApiOpts {
nextToken?: string
}

/**
* Options for creating a snapshot.
*/
export interface SnapshotCreateOpts extends SandboxApiOpts {
/**
* Optional human-readable name for the snapshot template.
*
* If a snapshot template with this name already exists, a new build will be
* assigned to the existing template instead of creating a new one.
*
* The name must be unique within your team. Once assigned, the snapshot can
* be used with {@link Sandbox.create} via `team-slug/name`.
*/
name?: string
}

/**
* Information about a snapshot.
*/
Expand All @@ -276,6 +292,13 @@ export interface SnapshotInfo {
* Can be used with Sandbox.create() to create a new sandbox from this snapshot.
*/
snapshotId: string

/**
* Full namespaced names assigned to this snapshot (e.g. `["team-slug/my-snapshot:default"]`).
* Present when the snapshot was created with a `name` option.
* Use any entry with {@link Sandbox.create} to start a sandbox from this snapshot by name.
*/
names: string[]
}

/**
Expand Down Expand Up @@ -687,7 +710,7 @@ export class SandboxApi {
*/
static async createSnapshot(
sandboxId: string,
opts?: SandboxApiOpts
opts?: SnapshotCreateOpts
): Promise<SnapshotInfo> {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)
Expand All @@ -698,7 +721,9 @@ export class SandboxApi {
sandboxID: sandboxId,
},
},
body: {},
body: {
...(opts?.name !== undefined && { name: opts.name }),
},
signal: config.getSignal(opts?.requestTimeoutMs),
})

Expand All @@ -713,6 +738,7 @@ export class SandboxApi {

return {
snapshotId: res.data!.snapshotID,
names: res.data!.names,
}
}

Expand Down Expand Up @@ -1038,6 +1064,7 @@ export class SnapshotPaginator extends BasePaginator<SnapshotInfo> {
return (res.data ?? []).map(
(snapshot: components['schemas']['SnapshotInfo']) => ({
snapshotId: snapshot.snapshotID,
names: snapshot.names,
})
)
}
Expand Down
3 changes: 3 additions & 0 deletions packages/python-sdk/e2b/sandbox/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ class SnapshotInfo:
snapshot_id: str
"""Snapshot identifier — template ID with tag, or namespaced name with tag (e.g. my-snapshot:latest). Can be used with Sandbox.create() to create a new sandbox from this snapshot."""

names: List[str] = field(default_factory=list)
"""Full namespaced names assigned to this snapshot (e.g. ["team-slug/my-snapshot:default"]). Present when the snapshot was created with a name. Use any entry with Sandbox.create() to start a sandbox from this snapshot by name."""


class PaginatorBase:
def __init__(
Expand Down
14 changes: 10 additions & 4 deletions packages/python-sdk/e2b/sandbox_async/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ async def beta_pause(
@overload
async def create_snapshot(
self,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -705,14 +706,16 @@ async def create_snapshot(

Use the returned `snapshot_id` with `AsyncSandbox.create(snapshot_id)` to create a new sandbox from the snapshot.

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one.
:return: Snapshot information including the snapshot ID and names
"""
...

@overload
@staticmethod
async def create_snapshot(
sandbox_id: str,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -721,14 +724,15 @@ async def create_snapshot(
The sandbox will be paused while the snapshot is being created.

:param sandbox_id: Sandbox ID

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template.
:return: Snapshot information including the snapshot ID and names
"""
...

@class_method_variant("_cls_create_snapshot")
async def create_snapshot(
self,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -740,10 +744,12 @@ async def create_snapshot(

Use the returned `snapshot_id` with `AsyncSandbox.create(snapshot_id)` to create a new sandbox from the snapshot.

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one.
:return: Snapshot information including the snapshot ID and names
"""
return await SandboxApi._cls_create_snapshot(
sandbox_id=self.sandbox_id,
name=name,
**self.connection_config.get_api_params(**opts),
)

Expand Down
3 changes: 2 additions & 1 deletion packages/python-sdk/e2b/sandbox_async/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,6 @@ async def next_items(self) -> List[SnapshotInfo]:
raise SandboxException(f"{res.parsed.message}: Request failed")

return [
SnapshotInfo(snapshot_id=snapshot.snapshot_id) for snapshot in res.parsed
SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=snapshot.names or [])
for snapshot in res.parsed
]
9 changes: 7 additions & 2 deletions packages/python-sdk/e2b/sandbox_async/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ async def _cls_get_metrics(
async def _cls_create_snapshot(
cls,
sandbox_id: str,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
config = ConnectionConfig(**opts)
Expand All @@ -305,7 +306,9 @@ async def _cls_create_snapshot(
res = await post_sandboxes_sandbox_id_snapshots.asyncio_detailed(
sandbox_id,
client=api_client,
body=PostSandboxesSandboxIDSnapshotsBody(),
body=PostSandboxesSandboxIDSnapshotsBody(
name=name if name is not None else UNSET,
),
)

if res.status_code == 404:
Expand All @@ -320,7 +323,9 @@ async def _cls_create_snapshot(
if isinstance(res.parsed, Error):
raise SandboxException(f"{res.parsed.message}: Request failed")

return SnapshotInfo(snapshot_id=res.parsed.snapshot_id)
return SnapshotInfo(
snapshot_id=res.parsed.snapshot_id, names=res.parsed.names or []
)

@classmethod
async def _cls_delete_snapshot(
Expand Down
14 changes: 10 additions & 4 deletions packages/python-sdk/e2b/sandbox_sync/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ def beta_pause(
@overload
def create_snapshot(
self,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -702,14 +703,16 @@ def create_snapshot(

Use the returned `snapshot_id` with `Sandbox.create(snapshot_id)` to create a new sandbox from the snapshot.

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one.
:return: Snapshot information including the snapshot ID and names
"""
...

@overload
@staticmethod
def create_snapshot(
sandbox_id: str,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -718,14 +721,15 @@ def create_snapshot(
The sandbox will be paused while the snapshot is being created.

:param sandbox_id: Sandbox ID

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template.
:return: Snapshot information including the snapshot ID and names
"""
...

@class_method_variant("_cls_create_snapshot")
def create_snapshot(
self,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
"""
Expand All @@ -737,10 +741,12 @@ def create_snapshot(

Use the returned `snapshot_id` with `Sandbox.create(snapshot_id)` to create a new sandbox from the snapshot.

:return: Snapshot information including the snapshot ID
:param name: Optional human-readable name for the snapshot template. If a snapshot with this name already exists, a new build is assigned to it instead of creating a new one.
:return: Snapshot information including the snapshot ID and names
"""
return SandboxApi._cls_create_snapshot(
sandbox_id=self.sandbox_id,
name=name,
**self.connection_config.get_api_params(**opts),
)

Expand Down
3 changes: 2 additions & 1 deletion packages/python-sdk/e2b/sandbox_sync/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,6 @@ def next_items(self) -> List[SnapshotInfo]:
raise SandboxException(f"{res.parsed.message}: Request failed")

return [
SnapshotInfo(snapshot_id=snapshot.snapshot_id) for snapshot in res.parsed
SnapshotInfo(snapshot_id=snapshot.snapshot_id, names=snapshot.names or [])
for snapshot in res.parsed
]
9 changes: 7 additions & 2 deletions packages/python-sdk/e2b/sandbox_sync/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ def _cls_connect(
def _cls_create_snapshot(
cls,
sandbox_id: str,
name: Optional[str] = None,
**opts: Unpack[ApiParams],
) -> SnapshotInfo:
config = ConnectionConfig(**opts)
Expand All @@ -332,7 +333,9 @@ def _cls_create_snapshot(
res = post_sandboxes_sandbox_id_snapshots.sync_detailed(
sandbox_id,
client=api_client,
body=PostSandboxesSandboxIDSnapshotsBody(),
body=PostSandboxesSandboxIDSnapshotsBody(
name=name if name is not None else UNSET,
),
)

if res.status_code == 404:
Expand All @@ -347,7 +350,9 @@ def _cls_create_snapshot(
if isinstance(res.parsed, Error):
raise SandboxException(f"{res.parsed.message}: Request failed")

return SnapshotInfo(snapshot_id=res.parsed.snapshot_id)
return SnapshotInfo(
snapshot_id=res.parsed.snapshot_id, names=res.parsed.names or []
)

@classmethod
def _cls_delete_snapshot(
Expand Down