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

Filter by extension

Filter by extension

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

added getInfo methods
31 changes: 31 additions & 0 deletions apps/web/src/app/(docs)/docs/sandbox/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ sandbox.set_timeout(30)
```
</CodeGroup>

## Retrieve sandbox information

You can retrieve sandbox information like sandbox id, template, metadata, started at/end at date by calling the `getInfo` method in JavaScript or `get_info` method in Python.

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpreter'

// Create sandbox with and keep it running for 60 seconds.
const sandbox = await Sandbox.create({ timeoutMs: 60_000 })

// Retrieve sandbox information.
const info = await sandbox.getInfo()
Comment thread
mishushakov marked this conversation as resolved.

// Retrieve sandbox expiration date.
const expirationDate = info.endAt
console.log(expirationDate)
```

```python
from e2b_code_interpreter import Sandbox

# Create sandbox with and keep it running for 60 seconds.
sandbox = Sandbox(timeout=60)

# Retrieve sandbox expiration date.
expiration_date = sandbox.get_info().end_at
print(expiration_date)
```
</CodeGroup>

## Shutdown sandbox

You can shutdown the sandbox any time even before the timeout is up by calling the `kill` method.
Expand Down
31 changes: 24 additions & 7 deletions packages/js-sdk/src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class Sandbox extends SandboxApi {
logger: opts?.logger,
},
{
version: opts?.envdVersion
version: opts?.envdVersion,
}
)
this.files = new Filesystem(
Expand Down Expand Up @@ -184,12 +184,15 @@ export class Sandbox extends SandboxApi {
const config = new ConnectionConfig(sandboxOpts)

if (config.debug) {
return new this({ sandboxId: 'debug_sandbox_id', ...config }) as InstanceType<S>
} else {
const sandbox = await this.createSandbox(
template,
sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
sandboxOpts
return new this({
sandboxId: 'debug_sandbox_id',
...config,
}) as InstanceType<S>
} else {
const sandbox = await this.createSandbox(
template,
sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs,
sandboxOpts
)
return new this({ ...sandbox, ...config }) as InstanceType<S>
}
Expand Down Expand Up @@ -356,4 +359,18 @@ export class Sandbox extends SandboxApi {

return url.toString()
}

/**
* Get sandbox information like sandbox id, template, metadata, started at/end at date.
*
* @param opts connection options.
*
* @returns information about the sandbox
*/
async getInfo(opts?: Pick<SandboxOpts, 'requestTimeoutMs'>) {
return await Sandbox.getInfo(this.sandboxId, {
...this.connectionConfig,
...opts,
})
}
}
72 changes: 64 additions & 8 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface SandboxListOpts extends SandboxApiOpts {
/**
* Filter the list of sandboxes, e.g. by metadata `metadata:{"key": "value"}`, if there are multiple filters they are combined with AND.
*/
query?: {metadata?: Record<string, string>}
query?: { metadata?: Record<string, string> }
}

/**
Expand Down Expand Up @@ -46,6 +46,11 @@ export interface SandboxInfo {
* Sandbox start time.
*/
startedAt: Date

/**
* Sandbox expiration date.
*/
endAt: Date
}

export class SandboxApi {
Expand Down Expand Up @@ -94,23 +99,27 @@ export class SandboxApi {
*
* @returns list of running sandboxes.
*/
static async list(
opts?: SandboxListOpts): Promise<SandboxInfo[]> {
static async list(opts?: SandboxListOpts): Promise<SandboxInfo[]> {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)

let metadata = undefined
if (opts?.query) {
if (opts.query.metadata) {
const encodedPairs: Record<string, string> = Object.fromEntries(Object.entries(opts.query.metadata).map(([key, value]) => [encodeURIComponent(key), encodeURIComponent(value)]))
const encodedPairs: Record<string, string> = Object.fromEntries(
Object.entries(opts.query.metadata).map(([key, value]) => [
encodeURIComponent(key),
encodeURIComponent(value),
])
)
metadata = new URLSearchParams(encodedPairs).toString()
}
}

const res = await client.api.GET('/sandboxes', {
params: {
query: {metadata},
},
params: {
query: { metadata },
},
signal: config.getSignal(opts?.requestTimeoutMs),
})

Expand All @@ -129,10 +138,57 @@ export class SandboxApi {
...(sandbox.alias && { name: sandbox.alias }),
metadata: sandbox.metadata ?? {},
startedAt: new Date(sandbox.startedAt),
endAt: new Date(sandbox.endAt),
})) ?? []
)
}

/**
* Get sandbox information like sandbox id, template, metadata, started at/end at date.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns sandbox information.
*/
static async getInfo(
sandboxId: string,
opts?: SandboxApiOpts
): Promise<SandboxInfo> {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)

const res = await client.api.GET('/sandboxes/{sandboxID}', {
params: {
path: {
sandboxID: sandboxId,
},
},
signal: config.getSignal(opts?.requestTimeoutMs),
})

const err = handleApiError(res)
if (err) {
throw err
}

if (!res.data) {
throw new Error('Sandbox not found')
}

return {
sandboxId: this.getSandboxId({
sandboxId: res.data.sandboxID,
clientId: res.data.clientID,
}),
templateId: res.data.templateID,
...(res.data.alias && { name: res.data.alias }),
metadata: res.data.metadata ?? {},
startedAt: new Date(res.data.startedAt),
endAt: new Date(res.data.endAt),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the sandbox is still running? It it the expected end time then? It might be good to explain in then in the documentation as it isn't clear imo

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's returned from the API already, should be the timestamp when the sandbox is expected to expire

}
}

/**
* Set the timeout of the specified sandbox.
* After the timeout expires the sandbox will be automatically killed.
Expand Down Expand Up @@ -219,7 +275,7 @@ export class SandboxApi {
sandboxId: res.data!.sandboxID,
clientId: res.data!.clientID,
}),
envdVersion: res.data!.envdVersion
envdVersion: res.data!.envdVersion,
}
}

Expand Down
7 changes: 6 additions & 1 deletion packages/js-sdk/tests/sandbox/timeout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ sandboxTest.skipIf(isDebug)('shorten then lenghten timeout', async ({ sandbox })

await wait(6000)

await sandbox.isRunning()
expect(await sandbox.isRunning()).toBeTruthy()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before, I believe the test would pass even if the promise resolved with false, it should however check that the Promise resolves with true, explicitly

})

sandboxTest.skipIf(isDebug)('get sandbox timeout', async ({ sandbox }) => {
const { endAt } = await sandbox.getInfo()
expect(endAt).toBeInstanceOf(Date)
})
2 changes: 2 additions & 0 deletions packages/python-sdk/e2b/sandbox/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class SandboxInfo:
"""Saved sandbox metadata."""
started_at: datetime
"""Sandbox start time."""
end_at: datetime
"""Sandbox expiration date."""


@dataclass
Expand Down
24 changes: 23 additions & 1 deletion packages/python-sdk/e2b/sandbox_async/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from e2b.sandbox_async.filesystem.filesystem import Filesystem
from e2b.sandbox_async.commands.command import Commands
from e2b.sandbox_async.commands.pty import Pty
from e2b.sandbox_async.sandbox_api import SandboxApi
from e2b.sandbox_async.sandbox_api import SandboxApi, SandboxInfo

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -371,3 +371,25 @@ async def set_timeout( # type: ignore
timeout=timeout,
**self.connection_config.__dict__,
)

async def get_info( # type: ignore
self,
request_timeout: Optional[float] = None,
) -> SandboxInfo:
"""
Get sandbox information like sandbox id, template, metadata, started at/end at date.
:param request_timeout: Timeout for the request in **seconds**
:return: Sandbox info
"""

config_dict = self.connection_config.__dict__
config_dict.pop("access_token", None)
config_dict.pop("api_url", None)

if request_timeout:
config_dict["request_timeout"] = request_timeout

return await SandboxApi.get_info(
sandbox_id=self.sandbox_id,
**self.connection_config.__dict__,
)
53 changes: 53 additions & 0 deletions packages/python-sdk/e2b/sandbox_async/sandbox_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from e2b.api import AsyncApiClient, SandboxCreateResponse
from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody
from e2b.api.client.api.sandboxes import (
get_sandboxes_sandbox_id,
post_sandboxes_sandbox_id_timeout,
get_sandboxes,
delete_sandboxes_sandbox_id,
Expand Down Expand Up @@ -78,9 +79,61 @@ async def list(
sandbox.metadata if isinstance(sandbox.metadata, dict) else {}
),
started_at=sandbox.started_at,
end_at=sandbox.end_at,
)
for sandbox in res.parsed
]

@classmethod
async def get_info(
cls,
sandbox_id: str,
api_key: Optional[str] = None,
domain: Optional[str] = None,
debug: Optional[bool] = None,
request_timeout: Optional[float] = None,
) -> SandboxInfo:
"""
Get the sandbox info.
:param sandbox_id: Sandbox ID
:param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable
:param domain: Domain to use for the request, defaults to `E2B_DOMAIN` environment variable
:param debug: Debug mode, defaults to `E2B_DEBUG` environment variable
:param request_timeout: Timeout for the request in **seconds**
:return: Sandbox info
"""
config = ConnectionConfig(
api_key=api_key,
domain=domain,
debug=debug,
request_timeout=request_timeout,
)

async with AsyncApiClient(config) as api_client:
res = await get_sandboxes_sandbox_id.asyncio_detailed(
sandbox_id,
client=api_client,
)

if res.status_code >= 300:
raise handle_api_exception(res)

if res.parsed is None:
raise Exception("Body of the request is None")

return SandboxInfo(
sandbox_id=SandboxApi._get_sandbox_id(
res.parsed.sandbox_id,
res.parsed.client_id,
),
template_id=res.parsed.template_id,
name=res.parsed.alias if isinstance(res.parsed.alias, str) else None,
metadata=(
res.parsed.metadata if isinstance(res.parsed.metadata, dict) else {}
),
started_at=res.parsed.started_at,
end_at=res.parsed.end_at,
)

@classmethod
async def _cls_kill(
Expand Down
23 changes: 22 additions & 1 deletion packages/python-sdk/e2b/sandbox_sync/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from e2b.sandbox_sync.filesystem.filesystem import Filesystem
from e2b.sandbox_sync.commands.command import Commands
from e2b.sandbox_sync.commands.pty import Pty
from e2b.sandbox_sync.sandbox_api import SandboxApi
from e2b.sandbox_sync.sandbox_api import SandboxApi, SandboxInfo

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -361,3 +361,24 @@ def set_timeout( # type: ignore
timeout=timeout,
**self.connection_config.__dict__,
)

def get_info( # type: ignore
self,
request_timeout: Optional[float] = None,
) -> SandboxInfo:
"""
Get sandbox information like sandbox id, template, metadata, started at/end at date.
:param request_timeout: Timeout for the request in **seconds**
:return: Sandbox info
"""
config_dict = self.connection_config.__dict__
config_dict.pop("access_token", None)
config_dict.pop("api_url", None)

if request_timeout:
config_dict["request_timeout"] = request_timeout

return SandboxApi.get_info(
sandbox_id=self.sandbox_id,
**self.connection_config.__dict__,
)
Loading
Loading