From 16f03d1dcc84768eae2258937b88936c0c5d04b4 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Fri, 10 Jan 2025 13:28:04 +0100 Subject: [PATCH 01/10] Add filtering support to sandbox API list method --- .gitignore | 4 +- packages/js-sdk/src/api/schema.gen.ts | 168 ++++++++++++++- packages/js-sdk/src/sandbox/sandboxApi.ts | 19 +- packages/js-sdk/tests/api/list.test.ts | 18 ++ .../sandboxes/delete_sandboxes_sandbox_id.py | 22 +- .../api/client/api/sandboxes/get_sandboxes.py | 68 +++--- .../api/sandboxes/get_sandboxes_sandbox_id.py | 14 +- .../get_sandboxes_sandbox_id_logs.py | 16 +- .../client/api/sandboxes/post_sandboxes.py | 16 +- .../post_sandboxes_sandbox_id_pause.py | 24 +-- .../post_sandboxes_sandbox_id_refreshes.py | 26 +-- .../post_sandboxes_sandbox_id_resume.py | 18 +- .../post_sandboxes_sandbox_id_timeout.py | 28 +-- packages/python-sdk/e2b/api/client/client.py | 62 ++---- .../e2b/api/client/models/__init__.py | 8 +- .../python-sdk/e2b/api/client/models/error.py | 12 +- .../e2b/api/client/models/new_sandbox.py | 12 +- .../python-sdk/e2b/api/client/models/node.py | 12 +- .../e2b/api/client/models/node_detail.py | 22 +- .../api/client/models/node_status_change.py | 12 +- ...ost_sandboxes_sandbox_id_refreshes_body.py | 12 +- .../post_sandboxes_sandbox_id_timeout_body.py | 12 +- .../e2b/api/client/models/resumed_sandbox.py | 12 +- .../e2b/api/client/models/running_sandbox.py | 12 +- .../e2b/api/client/models/sandbox.py | 12 +- .../e2b/api/client/models/sandbox_log.py | 12 +- .../e2b/api/client/models/sandbox_logs.py | 16 +- .../python-sdk/e2b/api/client/models/team.py | 12 +- .../e2b/api/client/models/team_user.py | 21 +- .../e2b/api/client/models/template.py | 22 +- .../e2b/api/client/models/template_build.py | 18 +- .../client/models/template_build_request.py | 12 +- .../client/models/template_update_request.py | 12 +- packages/python-sdk/e2b/api/client/types.py | 7 +- .../e2b/sandbox_async/sandbox_api.py | 55 +++-- .../e2b/sandbox_sync/sandbox_api.py | 20 +- .../tests/async/api_async/test_sbx_list.py | 16 ++ .../tests/sync/api_sync/test_sbx_list.py | 12 ++ spec/openapi.yml | 199 +++++++++++++++++- 39 files changed, 738 insertions(+), 337 deletions(-) diff --git a/.gitignore b/.gitignore index f887809420..7ccb79cdee 100644 --- a/.gitignore +++ b/.gitignore @@ -292,4 +292,6 @@ cython_debug/ # SDK reference artifacts sdk_ref/ sdkRefRoutes.json -spec/openapi_generated.yml \ No newline at end of file + +# SDK Client generated spec +spec/openapi_generated.yml diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 0baa9a07f2..16ab38cdaa 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -15,8 +15,8 @@ export interface paths { get: { parameters: { query?: { - /** @description A list of filters with key-value pairs (e.g. user:abc, app:prod). */ - filter?: string[]; + /** @description A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. */ + query?: string; }; header?: never; path?: never; @@ -183,6 +183,47 @@ export interface paths { patch?: never; trace?: never; }; + "/sandboxes/{sandboxID}/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** @description Get sandbox metrics */ + get: { + parameters: { + query?: never; + header?: never; + path: { + sandboxID: components["parameters"]["sandboxID"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successfully returned the sandbox metrics */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SandboxMetric"][]; + }; + }; + 401: components["responses"]["401"]; + 404: components["responses"]["404"]; + 500: components["responses"]["500"]; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/sandboxes/{sandboxID}/pause": { parameters: { query?: never; @@ -363,6 +404,48 @@ export interface paths { patch?: never; trace?: never; }; + "/sandboxes/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** @description List all running sandboxes with metrics */ + get: { + parameters: { + query?: { + /** @description A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. */ + query?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successfully returned all running sandboxes with metrics */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RunningSandboxWithMetrics"][]; + }; + }; + 400: components["responses"]["400"]; + 401: components["responses"]["401"]; + 500: components["responses"]["500"]; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/templates": { parameters: { query?: never; @@ -633,6 +716,11 @@ export interface components { */ MemoryMB: number; NewSandbox: { + /** + * @description Automatically pauses the sandbox after the timeout + * @default false + */ + autoPause: boolean; envVars?: components["schemas"]["EnvVars"]; metadata?: components["schemas"]["SandboxMetadata"]; /** @description Identifier of the required template */ @@ -655,6 +743,11 @@ export interface components { * @description Amount of allocated memory in MiB */ allocatedMemoryMiB: number; + /** + * Format: uint64 + * @description Number of sandbox create fails + */ + createFails: number; /** @description Identifier of the node */ nodeID: string; /** @@ -662,11 +755,21 @@ export interface components { * @description Number of sandboxes running on the node */ sandboxCount: number; + /** + * Format: int + * @description Number of starting Sandboxes + */ + sandboxStartingCount: number; status: components["schemas"]["NodeStatus"]; }; NodeDetail: { /** @description List of cached builds id on the node */ cachedBuilds: string[]; + /** + * Format: uint64 + * @description Number of sandbox create fails + */ + createFails: number; /** @description Identifier of the node */ nodeID: string; /** @description List of sandboxes running on the node */ @@ -677,11 +780,16 @@ export interface components { * @description Status of the node * @enum {string} */ - NodeStatus: "ready" | "draining"; + NodeStatus: "ready" | "draining" | "connecting" | "unhealthy"; NodeStatusChange: { status: components["schemas"]["NodeStatus"]; }; ResumedSandbox: { + /** + * @description Automatically pauses the sandbox after the timeout + * @default false + */ + autoPause: boolean; /** * Format: int32 * @description Time to live for the sandbox in seconds. @@ -712,6 +820,30 @@ export interface components { /** @description Identifier of the template from which is the sandbox created */ templateID: string; }; + RunningSandboxWithMetrics: { + /** @description Alias of the template */ + alias?: string; + /** @description Identifier of the client */ + clientID: string; + cpuCount: components["schemas"]["CPUCount"]; + /** + * Format: date-time + * @description Time when the sandbox will expire + */ + endAt: string; + memoryMB: components["schemas"]["MemoryMB"]; + metadata?: components["schemas"]["SandboxMetadata"]; + metrics?: components["schemas"]["SandboxMetric"][]; + /** @description Identifier of the sandbox */ + sandboxID: string; + /** + * Format: date-time + * @description Time when the sandbox was started + */ + startedAt: string; + /** @description Identifier of the template from which is the sandbox created */ + templateID: string; + }; Sandbox: { /** @description Alias of the template */ alias?: string; @@ -741,6 +873,34 @@ export interface components { SandboxMetadata: { [key: string]: string; }; + /** @description Metric entry with timestamp and line */ + SandboxMetric: { + /** + * Format: int32 + * @description Number of CPU cores + */ + cpuCount: number; + /** + * Format: float + * @description CPU usage percentage + */ + cpuUsedPct: number; + /** + * Format: int64 + * @description Total memory in MiB + */ + memTotalMiB: number; + /** + * Format: int64 + * @description Memory used in MiB + */ + memUsedMiB: number; + /** + * Format: date-time + * @description Timestamp of the metric entry + */ + timestamp: string; + }; Team: { /** @description API key for the team */ apiKey: string; @@ -810,7 +970,7 @@ export interface components { * @description Status of the template * @enum {string} */ - status: "building" | "ready" | "error"; + status: "building" | "waiting" | "ready" | "error"; /** @description Identifier of the template */ templateID: string; }; diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index edddc15e22..26093488e1 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -11,6 +11,13 @@ export interface SandboxApiOpts Pick > {} +export interface SandboxListOpts extends SandboxApiOpts { + /** + * Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + */ + filters?: Record +} + /** * Information about a sandbox. */ @@ -87,11 +94,21 @@ export class SandboxApi { * * @returns list of running sandboxes. */ - static async list(opts?: SandboxApiOpts): Promise { + static async list( + opts?: SandboxListOpts): Promise { const config = new ConnectionConfig(opts) const client = new ApiClient(config) + let query = undefined + if (opts?.filters) { + const encodedPairs: Record = Object.fromEntries(Object.entries(opts.filters).map(([key, value]) => [encodeURIComponent(key),encodeURIComponent(value)])) + query = new URLSearchParams(encodedPairs).toString() + } + const res = await client.api.GET('/sandboxes', { + params: { + query: {query}, + }, signal: config.getSignal(opts?.requestTimeoutMs), }) diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index 3b720ffdd2..2671b7cd47 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -20,3 +20,21 @@ sandboxTest.skipIf(isDebug)('list sandboxes', async ({ sandbox }) => { ) } }) + +sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => { + const uniqueId = Date.now().toString() + // Create an extra sandbox with a uniqueId + const extraSbx = await Sandbox.create({ }) + try { + const sbx = await Sandbox.create({metadata: {uniqueId: uniqueId}}) + try { + const sandboxes = await Sandbox.list({filters: {uniqueId}}) + assert.equal(sandboxes.length, 1) + assert.equal(sandboxes[0].sandboxId, sbx.sandboxId) + } finally { + await sbx.kill() + } + } finally { + await extraSbx.kill() + } +}) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py index 11b5a4cda9..b5deb5cff9 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx @@ -10,8 +10,8 @@ def _get_kwargs( sandbox_id: str, -) -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "delete", "url": f"/sandboxes/{sandbox_id}", } @@ -19,16 +19,14 @@ def _get_kwargs( return _kwargs -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Any]: - if response.status_code == HTTPStatus.NO_CONTENT: +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == 204: return None - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: return None - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: return None - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: return None if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -36,9 +34,7 @@ def _parse_response( return None -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Any]: +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py index 71539e473b..39ff16fb9a 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, Optional, Union, cast import httpx @@ -11,19 +11,15 @@ def _get_kwargs( *, - filter_: Union[Unset, List[str]] = UNSET, -) -> Dict[str, Any]: - params: Dict[str, Any] = {} + query: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} - json_filter_: Union[Unset, List[str]] = UNSET - if not isinstance(filter_, Unset): - json_filter_ = filter_ - - params["filter"] = json_filter_ + params["query"] = query params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "get", "url": "/sandboxes", "params": params, @@ -34,8 +30,8 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Union[Any, List["RunningSandbox"]]]: - if response.status_code == HTTPStatus.OK: +) -> Optional[Union[Any, list["RunningSandbox"]]]: + if response.status_code == 200: response_200 = [] _response_200 = response.json() for response_200_item_data in _response_200: @@ -44,13 +40,13 @@ def _parse_response( response_200.append(response_200_item) return response_200 - if response.status_code == HTTPStatus.BAD_REQUEST: + if response.status_code == 400: response_400 = cast(Any, None) return response_400 - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: response_401 = cast(Any, None) return response_401 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: response_500 = cast(Any, None) return response_500 if client.raise_on_unexpected_status: @@ -61,7 +57,7 @@ def _parse_response( def _build_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Union[Any, List["RunningSandbox"]]]: +) -> Response[Union[Any, list["RunningSandbox"]]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -73,23 +69,23 @@ def _build_response( def sync_detailed( *, client: AuthenticatedClient, - filter_: Union[Unset, List[str]] = UNSET, -) -> Response[Union[Any, List["RunningSandbox"]]]: + query: Union[Unset, str] = UNSET, +) -> Response[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - filter_ (Union[Unset, List[str]]): + query (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, List['RunningSandbox']]] + Response[Union[Any, list['RunningSandbox']]] """ kwargs = _get_kwargs( - filter_=filter_, + query=query, ) response = client.get_httpx_client().request( @@ -102,47 +98,47 @@ def sync_detailed( def sync( *, client: AuthenticatedClient, - filter_: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[Any, List["RunningSandbox"]]]: + query: Union[Unset, str] = UNSET, +) -> Optional[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - filter_ (Union[Unset, List[str]]): + query (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, List['RunningSandbox']] + Union[Any, list['RunningSandbox']] """ return sync_detailed( client=client, - filter_=filter_, + query=query, ).parsed async def asyncio_detailed( *, client: AuthenticatedClient, - filter_: Union[Unset, List[str]] = UNSET, -) -> Response[Union[Any, List["RunningSandbox"]]]: + query: Union[Unset, str] = UNSET, +) -> Response[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - filter_ (Union[Unset, List[str]]): + query (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Union[Any, List['RunningSandbox']]] + Response[Union[Any, list['RunningSandbox']]] """ kwargs = _get_kwargs( - filter_=filter_, + query=query, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -153,24 +149,24 @@ async def asyncio_detailed( async def asyncio( *, client: AuthenticatedClient, - filter_: Union[Unset, List[str]] = UNSET, -) -> Optional[Union[Any, List["RunningSandbox"]]]: + query: Union[Unset, str] = UNSET, +) -> Optional[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - filter_ (Union[Unset, List[str]]): + query (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Union[Any, List['RunningSandbox']] + Union[Any, list['RunningSandbox']] """ return ( await asyncio_detailed( client=client, - filter_=filter_, + query=query, ) ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py index 7ab59ee7e7..a03cae73e7 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union, cast +from typing import Any, Optional, Union, cast import httpx @@ -11,8 +11,8 @@ def _get_kwargs( sandbox_id: str, -) -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "get", "url": f"/sandboxes/{sandbox_id}", } @@ -23,17 +23,17 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Any, RunningSandbox]]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: response_200 = RunningSandbox.from_dict(response.json()) return response_200 - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: response_401 = cast(Any, None) return response_401 - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: response_404 = cast(Any, None) return response_404 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: response_500 = cast(Any, None) return response_500 if client.raise_on_unexpected_status: diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py index 7c70e28b73..11d6656123 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union, cast +from typing import Any, Optional, Union, cast import httpx @@ -14,8 +14,8 @@ def _get_kwargs( *, start: Union[Unset, int] = UNSET, limit: Union[Unset, int] = 1000, -) -> Dict[str, Any]: - params: Dict[str, Any] = {} +) -> dict[str, Any]: + params: dict[str, Any] = {} params["start"] = start @@ -23,7 +23,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "get", "url": f"/sandboxes/{sandbox_id}/logs", "params": params, @@ -35,17 +35,17 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Any, SandboxLogs]]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: response_200 = SandboxLogs.from_dict(response.json()) return response_200 - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: response_401 = cast(Any, None) return response_401 - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: response_404 = cast(Any, None) return response_404 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: response_500 = cast(Any, None) return response_500 if client.raise_on_unexpected_status: diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py index 70fa540daa..0a4000b749 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union, cast +from typing import Any, Optional, Union, cast import httpx @@ -13,10 +13,10 @@ def _get_kwargs( *, body: NewSandbox, -) -> Dict[str, Any]: - headers: Dict[str, Any] = {} +) -> dict[str, Any]: + headers: dict[str, Any] = {} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "post", "url": "/sandboxes", } @@ -33,17 +33,17 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Any, Sandbox]]: - if response.status_code == HTTPStatus.CREATED: + if response.status_code == 201: response_201 = Sandbox.from_dict(response.json()) return response_201 - if response.status_code == HTTPStatus.BAD_REQUEST: + if response.status_code == 400: response_400 = cast(Any, None) return response_400 - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: response_401 = cast(Any, None) return response_401 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: response_500 = cast(Any, None) return response_500 if client.raise_on_unexpected_status: diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py index e47e052296..8635ebc60a 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx @@ -10,8 +10,8 @@ def _get_kwargs( sandbox_id: str, -) -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "post", "url": f"/sandboxes/{sandbox_id}/pause", } @@ -19,18 +19,16 @@ def _get_kwargs( return _kwargs -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Any]: - if response.status_code == HTTPStatus.NO_CONTENT: +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == 204: return None - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: return None - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: return None - if response.status_code == HTTPStatus.CONFLICT: + if response.status_code == 409: return None - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: return None if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -38,9 +36,7 @@ def _parse_response( return None -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Any]: +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py index 39dfc678a0..ab90668d15 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py @@ -1,13 +1,11 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_sandboxes_sandbox_id_refreshes_body import ( - PostSandboxesSandboxIDRefreshesBody, -) +from ...models.post_sandboxes_sandbox_id_refreshes_body import PostSandboxesSandboxIDRefreshesBody from ...types import Response @@ -15,10 +13,10 @@ def _get_kwargs( sandbox_id: str, *, body: PostSandboxesSandboxIDRefreshesBody, -) -> Dict[str, Any]: - headers: Dict[str, Any] = {} +) -> dict[str, Any]: + headers: dict[str, Any] = {} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "post", "url": f"/sandboxes/{sandbox_id}/refreshes", } @@ -32,14 +30,12 @@ def _get_kwargs( return _kwargs -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Any]: - if response.status_code == HTTPStatus.NO_CONTENT: +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == 204: return None - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: return None - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: return None if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -47,9 +43,7 @@ def _parse_response( return None -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Any]: +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py index 1433aa5134..c50fc9c07d 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union, cast +from typing import Any, Optional, Union, cast import httpx @@ -14,10 +14,10 @@ def _get_kwargs( sandbox_id: str, *, body: ResumedSandbox, -) -> Dict[str, Any]: - headers: Dict[str, Any] = {} +) -> dict[str, Any]: + headers: dict[str, Any] = {} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "post", "url": f"/sandboxes/{sandbox_id}/resume", } @@ -34,20 +34,20 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Any, Sandbox]]: - if response.status_code == HTTPStatus.CREATED: + if response.status_code == 201: response_201 = Sandbox.from_dict(response.json()) return response_201 - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: response_401 = cast(Any, None) return response_401 - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: response_404 = cast(Any, None) return response_404 - if response.status_code == HTTPStatus.CONFLICT: + if response.status_code == 409: response_409 = cast(Any, None) return response_409 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: response_500 = cast(Any, None) return response_500 if client.raise_on_unexpected_status: diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py index 615963abf8..63beecdcb5 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py @@ -1,13 +1,11 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_sandboxes_sandbox_id_timeout_body import ( - PostSandboxesSandboxIDTimeoutBody, -) +from ...models.post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody from ...types import Response @@ -15,10 +13,10 @@ def _get_kwargs( sandbox_id: str, *, body: PostSandboxesSandboxIDTimeoutBody, -) -> Dict[str, Any]: - headers: Dict[str, Any] = {} +) -> dict[str, Any]: + headers: dict[str, Any] = {} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "post", "url": f"/sandboxes/{sandbox_id}/timeout", } @@ -32,16 +30,14 @@ def _get_kwargs( return _kwargs -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Any]: - if response.status_code == HTTPStatus.NO_CONTENT: +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: + if response.status_code == 204: return None - if response.status_code == HTTPStatus.UNAUTHORIZED: + if response.status_code == 401: return None - if response.status_code == HTTPStatus.NOT_FOUND: + if response.status_code == 404: return None - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: + if response.status_code == 500: return None if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -49,9 +45,7 @@ def _parse_response( return None -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Any]: +def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/client.py b/packages/python-sdk/e2b/api/client/client.py index 38b07d0575..e80446f108 100644 --- a/packages/python-sdk/e2b/api/client/client.py +++ b/packages/python-sdk/e2b/api/client/client.py @@ -1,5 +1,5 @@ import ssl -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx from attrs import define, evolve, field @@ -36,22 +36,16 @@ class Client: raise_on_unexpected_status: bool = field(default=False, kw_only=True) _base_url: str = field(alias="base_url") - _cookies: Dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") - _headers: Dict[str, str] = field(factory=dict, kw_only=True, alias="headers") - _timeout: Optional[httpx.Timeout] = field( - default=None, kw_only=True, alias="timeout" - ) - _verify_ssl: Union[str, bool, ssl.SSLContext] = field( - default=True, kw_only=True, alias="verify_ssl" - ) - _follow_redirects: bool = field( - default=False, kw_only=True, alias="follow_redirects" - ) - _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") + _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") + _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers") + _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") + _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") + _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) - def with_headers(self, headers: Dict[str, str]) -> "Client": + def with_headers(self, headers: dict[str, str]) -> "Client": """Get a new client matching this one with additional headers""" if self._client is not None: self._client.headers.update(headers) @@ -59,7 +53,7 @@ def with_headers(self, headers: Dict[str, str]) -> "Client": self._async_client.headers.update(headers) return evolve(self, headers={**self._headers, **headers}) - def with_cookies(self, cookies: Dict[str, str]) -> "Client": + def with_cookies(self, cookies: dict[str, str]) -> "Client": """Get a new client matching this one with additional cookies""" if self._client is not None: self._client.cookies.update(cookies) @@ -76,7 +70,7 @@ def with_timeout(self, timeout: httpx.Timeout) -> "Client": return evolve(self, timeout=timeout) def set_httpx_client(self, client: httpx.Client) -> "Client": - """Manually the underlying httpx.Client + """Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. """ @@ -172,18 +166,12 @@ class AuthenticatedClient: raise_on_unexpected_status: bool = field(default=False, kw_only=True) _base_url: str = field(alias="base_url") - _cookies: Dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") - _headers: Dict[str, str] = field(factory=dict, kw_only=True, alias="headers") - _timeout: Optional[httpx.Timeout] = field( - default=None, kw_only=True, alias="timeout" - ) - _verify_ssl: Union[str, bool, ssl.SSLContext] = field( - default=True, kw_only=True, alias="verify_ssl" - ) - _follow_redirects: bool = field( - default=False, kw_only=True, alias="follow_redirects" - ) - _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") + _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") + _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers") + _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") + _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") + _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) @@ -191,7 +179,7 @@ class AuthenticatedClient: prefix: str = "Bearer" auth_header_name: str = "Authorization" - def with_headers(self, headers: Dict[str, str]) -> "AuthenticatedClient": + def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient": """Get a new client matching this one with additional headers""" if self._client is not None: self._client.headers.update(headers) @@ -199,7 +187,7 @@ def with_headers(self, headers: Dict[str, str]) -> "AuthenticatedClient": self._async_client.headers.update(headers) return evolve(self, headers={**self._headers, **headers}) - def with_cookies(self, cookies: Dict[str, str]) -> "AuthenticatedClient": + def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient": """Get a new client matching this one with additional cookies""" if self._client is not None: self._client.cookies.update(cookies) @@ -216,7 +204,7 @@ def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient": return evolve(self, timeout=timeout) def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient": - """Manually the underlying httpx.Client + """Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. """ @@ -226,9 +214,7 @@ def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient": def get_httpx_client(self) -> httpx.Client: """Get the underlying httpx.Client, constructing a new one if not previously set""" if self._client is None: - self._headers[self.auth_header_name] = ( - f"{self.prefix} {self.token}" if self.prefix else self.token - ) + self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token self._client = httpx.Client( base_url=self._base_url, cookies=self._cookies, @@ -249,9 +235,7 @@ def __exit__(self, *args: Any, **kwargs: Any) -> None: """Exit a context manager for internal httpx.Client (see httpx docs)""" self.get_httpx_client().__exit__(*args, **kwargs) - def set_async_httpx_client( - self, async_client: httpx.AsyncClient - ) -> "AuthenticatedClient": + def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient": """Manually the underlying httpx.AsyncClient **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. @@ -262,9 +246,7 @@ def set_async_httpx_client( def get_async_httpx_client(self) -> httpx.AsyncClient: """Get the underlying httpx.AsyncClient, constructing a new one if not previously set""" if self._async_client is None: - self._headers[self.auth_header_name] = ( - f"{self.prefix} {self.token}" if self.prefix else self.token - ) + self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token self._async_client = httpx.AsyncClient( base_url=self._base_url, cookies=self._cookies, diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index bccd7f68bb..ec61ba3955 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -2,9 +2,11 @@ from .error import Error from .new_sandbox import NewSandbox -from .post_sandboxes_sandbox_id_refreshes_body import ( - PostSandboxesSandboxIDRefreshesBody, -) +from .node import Node +from .node_detail import NodeDetail +from .node_status import NodeStatus +from .node_status_change import NodeStatusChange +from .post_sandboxes_sandbox_id_refreshes_body import PostSandboxesSandboxIDRefreshesBody from .post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody from .resumed_sandbox import ResumedSandbox from .running_sandbox import RunningSandbox diff --git a/packages/python-sdk/e2b/api/client/models/error.py b/packages/python-sdk/e2b/api/client/models/error.py index b9680dc0c8..1dcf14741d 100644 --- a/packages/python-sdk/e2b/api/client/models/error.py +++ b/packages/python-sdk/e2b/api/client/models/error.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,14 +16,14 @@ class Error: code: int message: str - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: code = self.code message = self.message - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -35,7 +35,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() code = d.pop("code") @@ -50,7 +50,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return error @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/new_sandbox.py b/packages/python-sdk/e2b/api/client/models/new_sandbox.py index 10cf613103..0045e898c0 100644 --- a/packages/python-sdk/e2b/api/client/models/new_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/new_sandbox.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -22,9 +22,9 @@ class NewSandbox: env_vars: Union[Unset, Any] = UNSET metadata: Union[Unset, Any] = UNSET timeout: Union[Unset, int] = 15 - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: template_id = self.template_id env_vars = self.env_vars @@ -33,7 +33,7 @@ def to_dict(self) -> Dict[str, Any]: timeout = self.timeout - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -50,7 +50,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() template_id = d.pop("templateID") @@ -71,7 +71,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return new_sandbox @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/node.py b/packages/python-sdk/e2b/api/client/models/node.py index 8c207759f3..7ecdbd5461 100644 --- a/packages/python-sdk/e2b/api/client/models/node.py +++ b/packages/python-sdk/e2b/api/client/models/node.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -24,9 +24,9 @@ class Node: node_id: str sandbox_count: int status: NodeStatus - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: allocated_cpu = self.allocated_cpu allocated_memory_mi_b = self.allocated_memory_mi_b @@ -37,7 +37,7 @@ def to_dict(self) -> Dict[str, Any]: status = self.status.value - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -52,7 +52,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() allocated_cpu = d.pop("allocatedCPU") @@ -76,7 +76,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return node @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/node_detail.py b/packages/python-sdk/e2b/api/client/models/node_detail.py index a2326e0543..9eb4b9ac5b 100644 --- a/packages/python-sdk/e2b/api/client/models/node_detail.py +++ b/packages/python-sdk/e2b/api/client/models/node_detail.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,19 +16,19 @@ class NodeDetail: """ Attributes: - cached_builds (List[str]): List of cached builds id on the node + cached_builds (list[str]): List of cached builds id on the node node_id (str): Identifier of the node - sandboxes (List['RunningSandbox']): List of sandboxes running on the node + sandboxes (list['RunningSandbox']): List of sandboxes running on the node status (NodeStatus): Status of the node """ - cached_builds: List[str] + cached_builds: list[str] node_id: str - sandboxes: List["RunningSandbox"] + sandboxes: list["RunningSandbox"] status: NodeStatus - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: cached_builds = self.cached_builds node_id = self.node_id @@ -40,7 +40,7 @@ def to_dict(self) -> Dict[str, Any]: status = self.status.value - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -54,11 +54,11 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: from ..models.running_sandbox import RunningSandbox d = src_dict.copy() - cached_builds = cast(List[str], d.pop("cachedBuilds")) + cached_builds = cast(list[str], d.pop("cachedBuilds")) node_id = d.pop("nodeID") @@ -82,7 +82,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return node_detail @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/node_status_change.py b/packages/python-sdk/e2b/api/client/models/node_status_change.py index 43628b8093..c22453be21 100644 --- a/packages/python-sdk/e2b/api/client/models/node_status_change.py +++ b/packages/python-sdk/e2b/api/client/models/node_status_change.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +16,12 @@ class NodeStatusChange: """ status: NodeStatus - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: status = self.status.value - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -32,7 +32,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() status = NodeStatus(d.pop("status")) @@ -44,7 +44,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return node_status_change @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py b/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py index 39536231ae..5cb0807cf9 100644 --- a/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +++ b/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +16,12 @@ class PostSandboxesSandboxIDRefreshesBody: """ duration: Union[Unset, int] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: duration = self.duration - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if duration is not UNSET: @@ -30,7 +30,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() duration = d.pop("duration", UNSET) @@ -42,7 +42,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return post_sandboxes_sandbox_id_refreshes_body @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_timeout_body.py b/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_timeout_body.py index 5d0b876076..77907033b4 100644 --- a/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +++ b/packages/python-sdk/e2b/api/client/models/post_sandboxes_sandbox_id_timeout_body.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -14,12 +14,12 @@ class PostSandboxesSandboxIDTimeoutBody: """ timeout: int - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: timeout = self.timeout - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -30,7 +30,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() timeout = d.pop("timeout") @@ -42,7 +42,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return post_sandboxes_sandbox_id_timeout_body @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py index ae872a6d12..7815653397 100644 --- a/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +16,12 @@ class ResumedSandbox: """ timeout: Union[Unset, int] = 15 - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: timeout = self.timeout - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if timeout is not UNSET: @@ -30,7 +30,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() timeout = d.pop("timeout", UNSET) @@ -42,7 +42,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return resumed_sandbox @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/running_sandbox.py b/packages/python-sdk/e2b/api/client/models/running_sandbox.py index c329511b5e..f4b2d772d1 100644 --- a/packages/python-sdk/e2b/api/client/models/running_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/running_sandbox.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -34,9 +34,9 @@ class RunningSandbox: template_id: str alias: Union[Unset, str] = UNSET metadata: Union[Unset, Any] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: client_id = self.client_id cpu_count = self.cpu_count @@ -55,7 +55,7 @@ def to_dict(self) -> Dict[str, Any]: metadata = self.metadata - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -76,7 +76,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() client_id = d.pop("clientID") @@ -112,7 +112,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return running_sandbox @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/sandbox.py b/packages/python-sdk/e2b/api/client/models/sandbox.py index baf5b4890e..3514d71b1e 100644 --- a/packages/python-sdk/e2b/api/client/models/sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/sandbox.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -24,9 +24,9 @@ class Sandbox: sandbox_id: str template_id: str alias: Union[Unset, str] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: client_id = self.client_id envd_version = self.envd_version @@ -37,7 +37,7 @@ def to_dict(self) -> Dict[str, Any]: alias = self.alias - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -53,7 +53,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() client_id = d.pop("clientID") @@ -77,7 +77,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return sandbox @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_log.py b/packages/python-sdk/e2b/api/client/models/sandbox_log.py index 44c990e9a8..ea192436dd 100644 --- a/packages/python-sdk/e2b/api/client/models/sandbox_log.py +++ b/packages/python-sdk/e2b/api/client/models/sandbox_log.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -19,14 +19,14 @@ class SandboxLog: line: str timestamp: datetime.datetime - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: line = self.line timestamp = self.timestamp.isoformat() - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -38,7 +38,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() line = d.pop("line") @@ -53,7 +53,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return sandbox_log @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_logs.py b/packages/python-sdk/e2b/api/client/models/sandbox_logs.py index 0594fdcc8a..0766a371b2 100644 --- a/packages/python-sdk/e2b/api/client/models/sandbox_logs.py +++ b/packages/python-sdk/e2b/api/client/models/sandbox_logs.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -14,19 +14,19 @@ class SandboxLogs: """ Attributes: - logs (List['SandboxLog']): Logs of the sandbox + logs (list['SandboxLog']): Logs of the sandbox """ - logs: List["SandboxLog"] - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + logs: list["SandboxLog"] + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: logs = [] for logs_item_data in self.logs: logs_item = logs_item_data.to_dict() logs.append(logs_item) - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -37,7 +37,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: from ..models.sandbox_log import SandboxLog d = src_dict.copy() @@ -56,7 +56,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return sandbox_logs @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/team.py b/packages/python-sdk/e2b/api/client/models/team.py index c957f9384e..f17cedd147 100644 --- a/packages/python-sdk/e2b/api/client/models/team.py +++ b/packages/python-sdk/e2b/api/client/models/team.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -20,9 +20,9 @@ class Team: is_default: bool name: str team_id: str - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: api_key = self.api_key is_default = self.is_default @@ -31,7 +31,7 @@ def to_dict(self) -> Dict[str, Any]: team_id = self.team_id - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -45,7 +45,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() api_key = d.pop("apiKey") @@ -66,7 +66,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return team @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/team_user.py b/packages/python-sdk/e2b/api/client/models/team_user.py index 9aa62dfb59..b52b82ab46 100644 --- a/packages/python-sdk/e2b/api/client/models/team_user.py +++ b/packages/python-sdk/e2b/api/client/models/team_user.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, List, Type, TypeVar +from typing import Any, TypeVar +from uuid import UUID from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -11,19 +12,19 @@ class TeamUser: """ Attributes: email (str): Email of the user - id (str): Identifier of the user + id (UUID): Identifier of the user """ email: str - id: str - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + id: UUID + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: email = self.email - id = self.id + id = str(self.id) - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -35,11 +36,11 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() email = d.pop("email") - id = d.pop("id") + id = UUID(d.pop("id")) team_user = cls( email=email, @@ -50,7 +51,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return team_user @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/template.py b/packages/python-sdk/e2b/api/client/models/template.py index 7e76153e97..4c65a62def 100644 --- a/packages/python-sdk/e2b/api/client/models/template.py +++ b/packages/python-sdk/e2b/api/client/models/template.py @@ -1,5 +1,5 @@ import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -29,7 +29,7 @@ class Template: spawn_count (int): Number of times the template was used template_id (str): Identifier of the template updated_at (datetime.datetime): Time when the template was last updated - aliases (Union[Unset, List[str]]): Aliases of the template + aliases (Union[Unset, list[str]]): Aliases of the template """ build_count: int @@ -43,10 +43,10 @@ class Template: spawn_count: int template_id: str updated_at: datetime.datetime - aliases: Union[Unset, List[str]] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + aliases: Union[Unset, list[str]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: from ..models.team_user import TeamUser build_count = self.build_count @@ -57,7 +57,7 @@ def to_dict(self) -> Dict[str, Any]: created_at = self.created_at.isoformat() - created_by: Union[Dict[str, Any], None] + created_by: Union[None, dict[str, Any]] if isinstance(self.created_by, TeamUser): created_by = self.created_by.to_dict() else: @@ -75,11 +75,11 @@ def to_dict(self) -> Dict[str, Any]: updated_at = self.updated_at.isoformat() - aliases: Union[Unset, List[str]] = UNSET + aliases: Union[Unset, list[str]] = UNSET if not isinstance(self.aliases, Unset): aliases = self.aliases - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -102,7 +102,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: from ..models.team_user import TeamUser d = src_dict.copy() @@ -141,7 +141,7 @@ def _parse_created_by(data: object) -> Union["TeamUser", None]: updated_at = isoparse(d.pop("updatedAt")) - aliases = cast(List[str], d.pop("aliases", UNSET)) + aliases = cast(list[str], d.pop("aliases", UNSET)) template = cls( build_count=build_count, @@ -162,7 +162,7 @@ def _parse_created_by(data: object) -> Union["TeamUser", None]: return template @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/template_build.py b/packages/python-sdk/e2b/api/client/models/template_build.py index ed05e1114d..34bb32aae5 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build.py +++ b/packages/python-sdk/e2b/api/client/models/template_build.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, cast +from typing import Any, TypeVar, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -13,18 +13,18 @@ class TemplateBuild: """ Attributes: build_id (str): Identifier of the build - logs (List[str]): Build logs + logs (list[str]): Build logs status (TemplateBuildStatus): Status of the template template_id (str): Identifier of the template """ build_id: str - logs: List[str] + logs: list[str] status: TemplateBuildStatus template_id: str - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: build_id = self.build_id logs = self.logs @@ -33,7 +33,7 @@ def to_dict(self) -> Dict[str, Any]: template_id = self.template_id - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -47,11 +47,11 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() build_id = d.pop("buildID") - logs = cast(List[str], d.pop("logs")) + logs = cast(list[str], d.pop("logs")) status = TemplateBuildStatus(d.pop("status")) @@ -68,7 +68,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return template_build @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/template_build_request.py b/packages/python-sdk/e2b/api/client/models/template_build_request.py index 08ac58490f..18f1fb8f32 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build_request.py +++ b/packages/python-sdk/e2b/api/client/models/template_build_request.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -26,9 +26,9 @@ class TemplateBuildRequest: memory_mb: Union[Unset, int] = UNSET start_cmd: Union[Unset, str] = UNSET team_id: Union[Unset, str] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: dockerfile = self.dockerfile alias = self.alias @@ -41,7 +41,7 @@ def to_dict(self) -> Dict[str, Any]: team_id = self.team_id - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( { @@ -62,7 +62,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() dockerfile = d.pop("dockerfile") @@ -89,7 +89,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return template_build_request @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/models/template_update_request.py b/packages/python-sdk/e2b/api/client/models/template_update_request.py index 66e235c417..17df72644b 100644 --- a/packages/python-sdk/e2b/api/client/models/template_update_request.py +++ b/packages/python-sdk/e2b/api/client/models/template_update_request.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +16,12 @@ class TemplateUpdateRequest: """ public: Union[Unset, bool] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: public = self.public - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if public is not UNSET: @@ -30,7 +30,7 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() public = d.pop("public", UNSET) @@ -42,7 +42,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return template_update_request @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/packages/python-sdk/e2b/api/client/types.py b/packages/python-sdk/e2b/api/client/types.py index 21fac106f3..b9ed58b8aa 100644 --- a/packages/python-sdk/e2b/api/client/types.py +++ b/packages/python-sdk/e2b/api/client/types.py @@ -1,7 +1,8 @@ """Contains some shared types for properties""" +from collections.abc import MutableMapping from http import HTTPStatus -from typing import BinaryIO, Generic, Literal, MutableMapping, Optional, Tuple, TypeVar +from typing import BinaryIO, Generic, Literal, Optional, TypeVar from attrs import define @@ -13,7 +14,7 @@ def __bool__(self) -> Literal[False]: UNSET: Unset = Unset() -FileJsonType = Tuple[Optional[str], BinaryIO, Optional[str]] +FileJsonType = tuple[Optional[str], BinaryIO, Optional[str]] @define @@ -42,4 +43,4 @@ class Response(Generic[T]): parsed: Optional[T] -__all__ = ["File", "Response", "FileJsonType", "Unset", "UNSET"] +__all__ = ["UNSET", "File", "FileJsonType", "Response", "Unset"] diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index e353a54779..b89911bf89 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -1,4 +1,5 @@ -from typing import Optional, Dict, List, Tuple +import urllib.parse +from typing import Optional, Dict, List from packaging.version import Version from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase @@ -20,6 +21,7 @@ class SandboxApi(SandboxApiBase): async def list( cls, api_key: Optional[str] = None, + filters: Optional[Dict[str, str]] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -28,6 +30,9 @@ async def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable + :param filters: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param domain: Domain to use for the request, only relevant for self-hosted environments + :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** :return: List of running sandboxes @@ -39,32 +44,40 @@ async def list( request_timeout=request_timeout, ) + query = None + if filters: + filters = { + urllib.parse.quote(k): urllib.parse.quote(v) for k, v in filters.items() + } + query = urllib.parse.urlencode(filters) + async with AsyncApiClient(config) as api_client: res = await get_sandboxes.asyncio_detailed( client=api_client, + query=query, ) - if res.status_code >= 300: - raise handle_api_exception(res) + if res.status_code >= 300: + raise handle_api_exception(res) - if res.parsed is None: - return [] - - return [ - SandboxInfo( - sandbox_id=SandboxApi._get_sandbox_id( - sandbox.sandbox_id, - sandbox.client_id, - ), - template_id=sandbox.template_id, - name=sandbox.alias if isinstance(sandbox.alias, str) else None, - metadata=( - sandbox.metadata if isinstance(sandbox.metadata, dict) else {} - ), - started_at=sandbox.started_at, - ) - for sandbox in res.parsed - ] + if res.parsed is None: + return [] + + return [ + SandboxInfo( + sandbox_id=SandboxApi._get_sandbox_id( + sandbox.sandbox_id, + sandbox.client_id, + ), + template_id=sandbox.template_id, + name=sandbox.alias if isinstance(sandbox.alias, str) else None, + metadata=( + sandbox.metadata if isinstance(sandbox.metadata, dict) else {} + ), + started_at=sandbox.started_at, + ) + for sandbox in res.parsed + ] @classmethod async def _cls_kill( diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index ea1efe1acd..c7ad00272c 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -1,3 +1,5 @@ +import urllib.parse + from httpx import HTTPTransport from typing import Optional, Dict, List, Tuple from packaging.version import Version @@ -21,6 +23,7 @@ class SandboxApi(SandboxApiBase): def list( cls, api_key: Optional[str] = None, + filters: Optional[Dict[str, str]] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -29,9 +32,12 @@ def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable + :param filters: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param domain: Domain to use for the request, only relevant for self-hosted environments + :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** - :return: List of sandbox info + :return: List of running sandboxes """ config = ConnectionConfig( api_key=api_key, @@ -40,12 +46,18 @@ def list( request_timeout=request_timeout, ) + # Convert filters to the format expected by the API + query = None + if filters: + filters = { + urllib.parse.quote(k): urllib.parse.quote(v) for k, v in filters.items() + } + query = urllib.parse.urlencode(filters) + with ApiClient( config, transport=HTTPTransport(limits=SandboxApiBase._limits) ) as api_client: - res = get_sandboxes.sync_detailed( - client=api_client, - ) + res = get_sandboxes.sync_detailed(client=api_client, query=query) if res.status_code >= 300: raise handle_api_exception(res) diff --git a/packages/python-sdk/tests/async/api_async/test_sbx_list.py b/packages/python-sdk/tests/async/api_async/test_sbx_list.py index 0388c6c9ba..4e58e736bc 100644 --- a/packages/python-sdk/tests/async/api_async/test_sbx_list.py +++ b/packages/python-sdk/tests/async/api_async/test_sbx_list.py @@ -1,3 +1,6 @@ +import random +import string + import pytest from e2b import AsyncSandbox @@ -8,3 +11,16 @@ async def test_list_sandboxes(async_sandbox: AsyncSandbox): sandboxes = await AsyncSandbox.list() assert len(sandboxes) > 0 assert async_sandbox.sandbox_id in [sbx.sandbox_id for sbx in sandboxes] + + +@pytest.mark.skip_debug() +async def test_list_sandboxes_with_filter(async_sandbox: AsyncSandbox): + unique_id = "".join(random.choices(string.ascii_letters, k=5)) + sbx = await AsyncSandbox.create(metadata={"unique_id": unique_id}) + try: + # There's an extra sandbox created by the test runner + sandboxes = await AsyncSandbox.list(filters={"unique_id": unique_id}) + assert len(sandboxes) == 1 + assert sandboxes[0].metadata["unique_id"] == unique_id + finally: + await sbx.kill() diff --git a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py index 91d8b19a34..cc2541a5fb 100644 --- a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py +++ b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py @@ -1,3 +1,6 @@ +import random +import string + import pytest from e2b import Sandbox @@ -8,3 +11,12 @@ def test_list_sandboxes(sandbox: Sandbox): sandboxes = Sandbox.list() assert len(sandboxes) > 0 assert sandbox.sandbox_id in [sbx.sandbox_id for sbx in sandboxes] + + +@pytest.mark.skip_debug() +def test_list_sandboxes_with_filter(sandbox: Sandbox): + unique_id = "".join(random.choices(string.ascii_letters, k=5)) + Sandbox(metadata={"unique_id": unique_id}) + sandboxes = Sandbox.list(filters={"unique_id": unique_id}) + assert len(sandboxes) == 1 + assert sandboxes[0].metadata["unique_id"] == unique_id diff --git a/spec/openapi.yml b/spec/openapi.yml index 32bbc87630..8eaaca9932 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -16,6 +16,17 @@ components: type: http scheme: bearer bearerFormat: access_token + # Generated code uses security schemas in the alphabetical order. + # In order to check first the token, and then the team (so we can already use the user), + # there is a 1 and 2 present in the names of the security schemas. + Supabase1TokenAuth: + type: apiKey + in: header + name: X-Supabase-Token + Supabase2TeamAuth: + type: apiKey + in: header + name: X-Supabase-Team AdminTokenAuth: type: apiKey in: header @@ -167,6 +178,36 @@ components: items: $ref: "#/components/schemas/SandboxLog" + SandboxMetric: + description: Metric entry with timestamp and line + required: + - timestamp + - cpuCount + - cpuUsedPct + - memUsedMiB + - memTotalMiB + properties: + timestamp: + type: string + format: date-time + description: Timestamp of the metric entry + cpuCount: + type: integer + format: int32 + description: Number of CPU cores + cpuUsedPct: + type: number + format: float + description: CPU usage percentage + memUsedMiB: + type: integer + format: int64 + description: Memory used in MiB + memTotalMiB: + type: integer + format: int64 + description: Total memory in MiB + Sandbox: required: - templateID @@ -227,6 +268,47 @@ components: metadata: $ref: "#/components/schemas/SandboxMetadata" + RunningSandboxWithMetrics: + required: + - templateID + - sandboxID + - clientID + - startedAt + - cpuCount + - memoryMB + - endAt + properties: + templateID: + type: string + description: Identifier of the template from which is the sandbox created + alias: + type: string + description: Alias of the template + sandboxID: + type: string + description: Identifier of the sandbox + clientID: + type: string + description: Identifier of the client + startedAt: + type: string + format: date-time + description: Time when the sandbox was started + endAt: + type: string + format: date-time + description: Time when the sandbox will expire + cpuCount: + $ref: "#/components/schemas/CPUCount" + memoryMB: + $ref: "#/components/schemas/MemoryMB" + metadata: + $ref: "#/components/schemas/SandboxMetadata" + metrics: + type: array + items: + $ref: "#/components/schemas/SandboxMetric" + NewSandbox: required: - templateID @@ -240,6 +322,10 @@ components: minimum: 0 default: 15 description: Time to live for the sandbox in seconds. + autoPause: + type: boolean + default: false + description: Automatically pauses the sandbox after the timeout metadata: $ref: "#/components/schemas/SandboxMetadata" envVars: @@ -253,6 +339,10 @@ components: minimum: 0 default: 15 description: Time to live for the sandbox in seconds. + autoPause: + type: boolean + default: false + description: Automatically pauses the sandbox after the timeout Template: required: @@ -356,6 +446,7 @@ components: description: Status of the template enum: - building + - waiting - ready - error @@ -365,6 +456,8 @@ components: enum: - ready - draining + - connecting + - unhealthy NodeStatusChange: required: @@ -380,6 +473,8 @@ components: - sandboxCount - allocatedCPU - allocatedMemoryMiB + - createFails + - sandboxStartingCount properties: nodeID: type: string @@ -398,6 +493,14 @@ components: type: integer format: int32 description: Amount of allocated memory in MiB + createFails: + type: integer + format: uint64 + description: Number of sandbox create fails + sandboxStartingCount: + type: integer + format: int + description: Number of starting Sandboxes NodeDetail: required: @@ -405,6 +508,7 @@ components: - status - sandboxes - cachedBuilds + - createFails properties: nodeID: type: string @@ -421,6 +525,10 @@ components: description: List of cached builds id on the node items: type: string + createFails: + type: integer + format: uint64 + description: Number of sandbox create fails Error: @@ -457,6 +565,7 @@ paths: tags: [auth] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] responses: "200": description: Successfully returned all teams @@ -478,15 +587,15 @@ paths: tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - - name: filter + - name: query in: query - description: A list of filters with key-value pairs (e.g. user:abc, app:prod). + description: A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. required: false schema: - type: array - items: - type: string + type: string responses: "200": description: Successfully returned all running sandboxes @@ -508,6 +617,8 @@ paths: tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] requestBody: required: true content: @@ -528,12 +639,44 @@ paths: "500": $ref: "#/components/responses/500" + /sandboxes/metrics: + get: + description: List all running sandboxes with metrics + tags: [sandboxes] + security: + - ApiKeyAuth: [] + parameters: + - name: query + in: query + description: A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. + required: false + schema: + type: string + responses: + "200": + description: Successfully returned all running sandboxes with metrics + content: + application/json: + schema: + type: array + items: + allOf: + - $ref: "#/components/schemas/RunningSandboxWithMetrics" + "401": + $ref: "#/components/responses/401" + "400": + $ref: "#/components/responses/400" + "500": + $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/logs: get: description: Get sandbox logs tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" - in: query @@ -565,12 +708,41 @@ paths: "500": $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/metrics: + get: + description: Get sandbox metrics + tags: [sandboxes] + security: + - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + responses: + "200": + description: Successfully returned the sandbox metrics + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/SandboxMetric" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + + /sandboxes/{sandboxID}: get: description: Get a sandbox by id tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -592,6 +764,8 @@ paths: tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -611,6 +785,8 @@ paths: tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -631,6 +807,8 @@ paths: tags: [sandboxes] security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" requestBody: @@ -660,6 +838,8 @@ paths: description: Set the timeout for the sandbox. The sandbox will expire x seconds from the time of the request. Calling this method multiple times overwrites the TTL, each time using the current timestamp as the starting point to measure the timeout duration. security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] tags: [sandboxes] requestBody: content: @@ -691,6 +871,8 @@ paths: description: Refresh the sandbox extending its time to live security: - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] tags: [sandboxes] requestBody: content: @@ -719,6 +901,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - in: query required: false @@ -745,6 +928,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] requestBody: required: true content: @@ -770,6 +954,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - $ref: "#/components/parameters/templateID" requestBody: @@ -795,6 +980,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - $ref: "#/components/parameters/templateID" responses: @@ -809,6 +995,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - $ref: "#/components/parameters/templateID" requestBody: @@ -833,6 +1020,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" @@ -850,6 +1038,7 @@ paths: tags: [templates] security: - AccessTokenAuth: [] + - Supabase1TokenAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" From 7be39a462d40c0ebdbd1b103023e9a2b084ec918 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 13:52:11 +0100 Subject: [PATCH 02/10] Update filter to metadata --- packages/js-sdk/src/api/schema.gen.ts | 86 +++++- packages/js-sdk/src/sandbox/sandboxApi.ts | 13 +- packages/js-sdk/tests/api/list.test.ts | 4 +- .../api/client/api/sandboxes/get_sandboxes.py | 28 +- .../e2b/api/client/models/__init__.py | 16 ++ .../e2b/api/client/models/new_sandbox.py | 9 + .../python-sdk/e2b/api/client/models/node.py | 16 ++ .../e2b/api/client/models/node_detail.py | 8 + .../e2b/api/client/models/node_status.py | 2 + .../e2b/api/client/models/resumed_sandbox.py | 9 + .../client/models/template_build_status.py | 1 + .../e2b/sandbox_async/sandbox_api.py | 14 +- .../e2b/sandbox_sync/sandbox_api.py | 16 +- .../tests/async/api_async/test_sbx_list.py | 2 +- .../tests/sync/api_sync/test_sbx_list.py | 2 +- spec/openapi.yml | 269 +++++++++++++++++- 16 files changed, 448 insertions(+), 47 deletions(-) diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 16ab38cdaa..f4ea32fccf 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -15,8 +15,8 @@ export interface paths { get: { parameters: { query?: { - /** @description A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. */ - query?: string; + /** @description Metadata query used to filter the sandboxes (e.g. "user=abc&app=prod"). Each key and values must be URL encoded. */ + metadata?: string; }; header?: never; path?: never; @@ -415,8 +415,8 @@ export interface paths { get: { parameters: { query?: { - /** @description A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. */ - query?: string; + /** @description Metadata query used to filter the sandboxes (e.g. "user=abc&app=prod"). Each key and values must be URL encoded. */ + metadata?: string; }; header?: never; path?: never; @@ -698,6 +698,48 @@ export interface components { * @description CPU cores for the sandbox */ CPUCount: number; + CreatedAccessToken: { + /** + * Format: date-time + * @description Timestamp of access token creation + */ + createdAt: string; + /** + * Format: uuid + * @description Identifier of the access token + */ + id: string; + /** @description Name of the access token */ + name: string; + /** @description Raw value of the access token */ + token: string; + /** @description Mask of the access token */ + tokenMask: string; + }; + CreatedTeamAPIKey: { + /** + * Format: date-time + * @description Timestamp of API key creation + */ + createdAt: string; + createdBy: components["schemas"]["TeamUser"] | null; + /** + * Format: uuid + * @description Identifier of the API key + */ + id: string; + /** @description Raw value of the API key */ + key: string; + /** @description Mask of the API key */ + keyMask: string; + /** + * Format: date-time + * @description Last time this API key was used + */ + lastUsed: string | null; + /** @description Name of the API key */ + name: string; + }; EnvVars: { [key: string]: string; }; @@ -715,6 +757,10 @@ export interface components { * @description Memory for the sandbox in MB */ MemoryMB: number; + NewAccessToken: { + /** @description Name of the access token */ + name: string; + }; NewSandbox: { /** * @description Automatically pauses the sandbox after the timeout @@ -732,6 +778,10 @@ export interface components { */ timeout: number; }; + NewTeamAPIKey: { + /** @description Name of the API key */ + name: string; + }; Node: { /** * Format: int32 @@ -911,6 +961,28 @@ export interface components { /** @description Identifier of the team */ teamID: string; }; + TeamAPIKey: { + /** + * Format: date-time + * @description Timestamp of API key creation + */ + createdAt: string; + createdBy: components["schemas"]["TeamUser"] | null; + /** + * Format: uuid + * @description Identifier of the API key + */ + id: string; + /** @description Mask of the API key */ + keyMask: string; + /** + * Format: date-time + * @description Last time this API key was used + */ + lastUsed: string | null; + /** @description Name of the API key */ + name: string; + }; TeamUser: { /** @description Email of the user */ email: string; @@ -990,6 +1062,10 @@ export interface components { /** @description Whether the template is public or only accessible by the team */ public?: boolean; }; + UpdateTeamAPIKey: { + /** @description New name for the API key */ + name: string; + }; }; responses: { /** @description Bad request */ @@ -1039,6 +1115,8 @@ export interface components { }; }; parameters: { + accessTokenID: string; + apiKeyID: string; buildID: string; nodeID: string; sandboxID: string; diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 26093488e1..420c0e335b 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -15,7 +15,7 @@ export interface SandboxListOpts extends SandboxApiOpts { /** * Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. */ - filters?: Record + metadata?: Record } /** @@ -99,15 +99,15 @@ export class SandboxApi { const config = new ConnectionConfig(opts) const client = new ApiClient(config) - let query = undefined - if (opts?.filters) { - const encodedPairs: Record = Object.fromEntries(Object.entries(opts.filters).map(([key, value]) => [encodeURIComponent(key),encodeURIComponent(value)])) - query = new URLSearchParams(encodedPairs).toString() + let metadata = undefined + if (opts?.metadata) { + const encodedPairs: Record = Object.fromEntries(Object.entries(opts.metadata).map(([key, value]) => [encodeURIComponent(key),encodeURIComponent(value)])) + metadata = new URLSearchParams(encodedPairs).toString() } const res = await client.api.GET('/sandboxes', { params: { - query: {query}, + query: {metadata}, }, signal: config.getSignal(opts?.requestTimeoutMs), }) @@ -185,6 +185,7 @@ export class SandboxApi { const res = await client.api.POST('/sandboxes', { body: { + autoPause: false, templateID: template, metadata: opts?.metadata, envVars: opts?.envs, diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index 2671b7cd47..d651dfcece 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -21,14 +21,14 @@ sandboxTest.skipIf(isDebug)('list sandboxes', async ({ sandbox }) => { } }) -sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => { +sandboxTest.skipIf(isDebug)('list sandboxes with metadata filter', async () => { const uniqueId = Date.now().toString() // Create an extra sandbox with a uniqueId const extraSbx = await Sandbox.create({ }) try { const sbx = await Sandbox.create({metadata: {uniqueId: uniqueId}}) try { - const sandboxes = await Sandbox.list({filters: {uniqueId}}) + const sandboxes = await Sandbox.list({metadata: {uniqueId}}) assert.equal(sandboxes.length, 1) assert.equal(sandboxes[0].sandboxId, sbx.sandboxId) } finally { diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py index 39ff16fb9a..ede6837446 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py @@ -11,11 +11,11 @@ def _get_kwargs( *, - query: Union[Unset, str] = UNSET, + metadata: Union[Unset, str] = UNSET, ) -> dict[str, Any]: params: dict[str, Any] = {} - params["query"] = query + params["metadata"] = metadata params = {k: v for k, v in params.items() if v is not UNSET and v is not None} @@ -69,12 +69,12 @@ def _build_response( def sync_detailed( *, client: AuthenticatedClient, - query: Union[Unset, str] = UNSET, + metadata: Union[Unset, str] = UNSET, ) -> Response[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - query (Union[Unset, str]): + metadata (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -85,7 +85,7 @@ def sync_detailed( """ kwargs = _get_kwargs( - query=query, + metadata=metadata, ) response = client.get_httpx_client().request( @@ -98,12 +98,12 @@ def sync_detailed( def sync( *, client: AuthenticatedClient, - query: Union[Unset, str] = UNSET, + metadata: Union[Unset, str] = UNSET, ) -> Optional[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - query (Union[Unset, str]): + metadata (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -115,19 +115,19 @@ def sync( return sync_detailed( client=client, - query=query, + metadata=metadata, ).parsed async def asyncio_detailed( *, client: AuthenticatedClient, - query: Union[Unset, str] = UNSET, + metadata: Union[Unset, str] = UNSET, ) -> Response[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - query (Union[Unset, str]): + metadata (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -138,7 +138,7 @@ async def asyncio_detailed( """ kwargs = _get_kwargs( - query=query, + metadata=metadata, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -149,12 +149,12 @@ async def asyncio_detailed( async def asyncio( *, client: AuthenticatedClient, - query: Union[Unset, str] = UNSET, + metadata: Union[Unset, str] = UNSET, ) -> Optional[Union[Any, list["RunningSandbox"]]]: """List all running sandboxes Args: - query (Union[Unset, str]): + metadata (Union[Unset, str]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -167,6 +167,6 @@ async def asyncio( return ( await asyncio_detailed( client=client, - query=query, + metadata=metadata, ) ).parsed diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index ec61ba3955..017a40937e 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -1,7 +1,11 @@ """Contains all the data models used in inputs/outputs""" +from .created_access_token import CreatedAccessToken +from .created_team_api_key import CreatedTeamAPIKey from .error import Error +from .new_access_token import NewAccessToken from .new_sandbox import NewSandbox +from .new_team_api_key import NewTeamAPIKey from .node import Node from .node_detail import NodeDetail from .node_status import NodeStatus @@ -10,20 +14,28 @@ from .post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody from .resumed_sandbox import ResumedSandbox from .running_sandbox import RunningSandbox +from .running_sandbox_with_metrics import RunningSandboxWithMetrics from .sandbox import Sandbox from .sandbox_log import SandboxLog from .sandbox_logs import SandboxLogs +from .sandbox_metric import SandboxMetric from .team import Team +from .team_api_key import TeamAPIKey from .team_user import TeamUser from .template import Template from .template_build import TemplateBuild from .template_build_request import TemplateBuildRequest from .template_build_status import TemplateBuildStatus from .template_update_request import TemplateUpdateRequest +from .update_team_api_key import UpdateTeamAPIKey __all__ = ( + "CreatedAccessToken", + "CreatedTeamAPIKey", "Error", + "NewAccessToken", "NewSandbox", + "NewTeamAPIKey", "Node", "NodeDetail", "NodeStatus", @@ -32,14 +44,18 @@ "PostSandboxesSandboxIDTimeoutBody", "ResumedSandbox", "RunningSandbox", + "RunningSandboxWithMetrics", "Sandbox", "SandboxLog", "SandboxLogs", + "SandboxMetric", "Team", + "TeamAPIKey", "TeamUser", "Template", "TemplateBuild", "TemplateBuildRequest", "TemplateBuildStatus", "TemplateUpdateRequest", + "UpdateTeamAPIKey", ) diff --git a/packages/python-sdk/e2b/api/client/models/new_sandbox.py b/packages/python-sdk/e2b/api/client/models/new_sandbox.py index 0045e898c0..aa79cac3f6 100644 --- a/packages/python-sdk/e2b/api/client/models/new_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/new_sandbox.py @@ -13,12 +13,14 @@ class NewSandbox: """ Attributes: template_id (str): Identifier of the required template + auto_pause (Union[Unset, bool]): Automatically pauses the sandbox after the timeout Default: False. env_vars (Union[Unset, Any]): metadata (Union[Unset, Any]): timeout (Union[Unset, int]): Time to live for the sandbox in seconds. Default: 15. """ template_id: str + auto_pause: Union[Unset, bool] = False env_vars: Union[Unset, Any] = UNSET metadata: Union[Unset, Any] = UNSET timeout: Union[Unset, int] = 15 @@ -27,6 +29,8 @@ class NewSandbox: def to_dict(self) -> dict[str, Any]: template_id = self.template_id + auto_pause = self.auto_pause + env_vars = self.env_vars metadata = self.metadata @@ -40,6 +44,8 @@ def to_dict(self) -> dict[str, Any]: "templateID": template_id, } ) + if auto_pause is not UNSET: + field_dict["autoPause"] = auto_pause if env_vars is not UNSET: field_dict["envVars"] = env_vars if metadata is not UNSET: @@ -54,6 +60,8 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() template_id = d.pop("templateID") + auto_pause = d.pop("autoPause", UNSET) + env_vars = d.pop("envVars", UNSET) metadata = d.pop("metadata", UNSET) @@ -62,6 +70,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: new_sandbox = cls( template_id=template_id, + auto_pause=auto_pause, env_vars=env_vars, metadata=metadata, timeout=timeout, diff --git a/packages/python-sdk/e2b/api/client/models/node.py b/packages/python-sdk/e2b/api/client/models/node.py index 7ecdbd5461..40b9510479 100644 --- a/packages/python-sdk/e2b/api/client/models/node.py +++ b/packages/python-sdk/e2b/api/client/models/node.py @@ -14,15 +14,19 @@ class Node: Attributes: allocated_cpu (int): Number of allocated CPU cores allocated_memory_mi_b (int): Amount of allocated memory in MiB + create_fails (int): Number of sandbox create fails node_id (str): Identifier of the node sandbox_count (int): Number of sandboxes running on the node + sandbox_starting_count (int): Number of starting Sandboxes status (NodeStatus): Status of the node """ allocated_cpu: int allocated_memory_mi_b: int + create_fails: int node_id: str sandbox_count: int + sandbox_starting_count: int status: NodeStatus additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -31,10 +35,14 @@ def to_dict(self) -> dict[str, Any]: allocated_memory_mi_b = self.allocated_memory_mi_b + create_fails = self.create_fails + node_id = self.node_id sandbox_count = self.sandbox_count + sandbox_starting_count = self.sandbox_starting_count + status = self.status.value field_dict: dict[str, Any] = {} @@ -43,8 +51,10 @@ def to_dict(self) -> dict[str, Any]: { "allocatedCPU": allocated_cpu, "allocatedMemoryMiB": allocated_memory_mi_b, + "createFails": create_fails, "nodeID": node_id, "sandboxCount": sandbox_count, + "sandboxStartingCount": sandbox_starting_count, "status": status, } ) @@ -58,17 +68,23 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: allocated_memory_mi_b = d.pop("allocatedMemoryMiB") + create_fails = d.pop("createFails") + node_id = d.pop("nodeID") sandbox_count = d.pop("sandboxCount") + sandbox_starting_count = d.pop("sandboxStartingCount") + status = NodeStatus(d.pop("status")) node = cls( allocated_cpu=allocated_cpu, allocated_memory_mi_b=allocated_memory_mi_b, + create_fails=create_fails, node_id=node_id, sandbox_count=sandbox_count, + sandbox_starting_count=sandbox_starting_count, status=status, ) diff --git a/packages/python-sdk/e2b/api/client/models/node_detail.py b/packages/python-sdk/e2b/api/client/models/node_detail.py index 9eb4b9ac5b..91a1c738f7 100644 --- a/packages/python-sdk/e2b/api/client/models/node_detail.py +++ b/packages/python-sdk/e2b/api/client/models/node_detail.py @@ -17,12 +17,14 @@ class NodeDetail: """ Attributes: cached_builds (list[str]): List of cached builds id on the node + create_fails (int): Number of sandbox create fails node_id (str): Identifier of the node sandboxes (list['RunningSandbox']): List of sandboxes running on the node status (NodeStatus): Status of the node """ cached_builds: list[str] + create_fails: int node_id: str sandboxes: list["RunningSandbox"] status: NodeStatus @@ -31,6 +33,8 @@ class NodeDetail: def to_dict(self) -> dict[str, Any]: cached_builds = self.cached_builds + create_fails = self.create_fails + node_id = self.node_id sandboxes = [] @@ -45,6 +49,7 @@ def to_dict(self) -> dict[str, Any]: field_dict.update( { "cachedBuilds": cached_builds, + "createFails": create_fails, "nodeID": node_id, "sandboxes": sandboxes, "status": status, @@ -60,6 +65,8 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() cached_builds = cast(list[str], d.pop("cachedBuilds")) + create_fails = d.pop("createFails") + node_id = d.pop("nodeID") sandboxes = [] @@ -73,6 +80,7 @@ def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: node_detail = cls( cached_builds=cached_builds, + create_fails=create_fails, node_id=node_id, sandboxes=sandboxes, status=status, diff --git a/packages/python-sdk/e2b/api/client/models/node_status.py b/packages/python-sdk/e2b/api/client/models/node_status.py index 87a77905b9..4529e3b542 100644 --- a/packages/python-sdk/e2b/api/client/models/node_status.py +++ b/packages/python-sdk/e2b/api/client/models/node_status.py @@ -2,8 +2,10 @@ class NodeStatus(str, Enum): + CONNECTING = "connecting" DRAINING = "draining" READY = "ready" + UNHEALTHY = "unhealthy" def __str__(self) -> str: return str(self.value) diff --git a/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py index 7815653397..c8481750b8 100644 --- a/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py +++ b/packages/python-sdk/e2b/api/client/models/resumed_sandbox.py @@ -12,18 +12,24 @@ class ResumedSandbox: """ Attributes: + auto_pause (Union[Unset, bool]): Automatically pauses the sandbox after the timeout Default: False. timeout (Union[Unset, int]): Time to live for the sandbox in seconds. Default: 15. """ + auto_pause: Union[Unset, bool] = False timeout: Union[Unset, int] = 15 additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + auto_pause = self.auto_pause + timeout = self.timeout field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if auto_pause is not UNSET: + field_dict["autoPause"] = auto_pause if timeout is not UNSET: field_dict["timeout"] = timeout @@ -32,9 +38,12 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: d = src_dict.copy() + auto_pause = d.pop("autoPause", UNSET) + timeout = d.pop("timeout", UNSET) resumed_sandbox = cls( + auto_pause=auto_pause, timeout=timeout, ) diff --git a/packages/python-sdk/e2b/api/client/models/template_build_status.py b/packages/python-sdk/e2b/api/client/models/template_build_status.py index 9ecd819410..6cae835d59 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build_status.py +++ b/packages/python-sdk/e2b/api/client/models/template_build_status.py @@ -5,6 +5,7 @@ class TemplateBuildStatus(str, Enum): BUILDING = "building" ERROR = "error" READY = "ready" + WAITING = "waiting" def __str__(self) -> str: return str(self.value) diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index b89911bf89..ac3c89043c 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -21,7 +21,7 @@ class SandboxApi(SandboxApiBase): async def list( cls, api_key: Optional[str] = None, - filters: Optional[Dict[str, str]] = None, + metadata: Optional[Dict[str, str]] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -44,17 +44,17 @@ async def list( request_timeout=request_timeout, ) - query = None - if filters: - filters = { - urllib.parse.quote(k): urllib.parse.quote(v) for k, v in filters.items() + if metadata: + quoted_metadata = { + urllib.parse.quote(k): urllib.parse.quote(v) + for k, v in metadata.items() } - query = urllib.parse.urlencode(filters) + metadata = urllib.parse.urlencode(quoted_metadata) async with AsyncApiClient(config) as api_client: res = await get_sandboxes.asyncio_detailed( client=api_client, - query=query, + metadata=metadata, ) if res.status_code >= 300: diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index c7ad00272c..6664c698ab 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -23,7 +23,7 @@ class SandboxApi(SandboxApiBase): def list( cls, api_key: Optional[str] = None, - filters: Optional[Dict[str, str]] = None, + metadata: Optional[Dict[str, str]] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -32,7 +32,7 @@ def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param filters: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param metadata: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** @@ -47,17 +47,17 @@ def list( ) # Convert filters to the format expected by the API - query = None - if filters: - filters = { - urllib.parse.quote(k): urllib.parse.quote(v) for k, v in filters.items() + if metadata: + quoted_metadata = { + urllib.parse.quote(k): urllib.parse.quote(v) + for k, v in metadata.items() } - query = urllib.parse.urlencode(filters) + metadata = urllib.parse.urlencode(quoted_metadata) with ApiClient( config, transport=HTTPTransport(limits=SandboxApiBase._limits) ) as api_client: - res = get_sandboxes.sync_detailed(client=api_client, query=query) + res = get_sandboxes.sync_detailed(client=api_client, metadata=metadata) if res.status_code >= 300: raise handle_api_exception(res) diff --git a/packages/python-sdk/tests/async/api_async/test_sbx_list.py b/packages/python-sdk/tests/async/api_async/test_sbx_list.py index 4e58e736bc..4e50eef527 100644 --- a/packages/python-sdk/tests/async/api_async/test_sbx_list.py +++ b/packages/python-sdk/tests/async/api_async/test_sbx_list.py @@ -19,7 +19,7 @@ async def test_list_sandboxes_with_filter(async_sandbox: AsyncSandbox): sbx = await AsyncSandbox.create(metadata={"unique_id": unique_id}) try: # There's an extra sandbox created by the test runner - sandboxes = await AsyncSandbox.list(filters={"unique_id": unique_id}) + sandboxes = await AsyncSandbox.list(metadata={"unique_id": unique_id}) assert len(sandboxes) == 1 assert sandboxes[0].metadata["unique_id"] == unique_id finally: diff --git a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py index cc2541a5fb..dd5bdc9e8a 100644 --- a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py +++ b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py @@ -17,6 +17,6 @@ def test_list_sandboxes(sandbox: Sandbox): def test_list_sandboxes_with_filter(sandbox: Sandbox): unique_id = "".join(random.choices(string.ascii_letters, k=5)) Sandbox(metadata={"unique_id": unique_id}) - sandboxes = Sandbox.list(filters={"unique_id": unique_id}) + sandboxes = Sandbox.list(metadata={"unique_id": unique_id}) assert len(sandboxes) == 1 assert sandboxes[0].metadata["unique_id"] == unique_id diff --git a/spec/openapi.yml b/spec/openapi.yml index 8eaaca9932..f01c467790 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -57,6 +57,18 @@ components: required: true schema: type: string + apiKeyID: + name: apiKeyID + in: path + required: true + schema: + type: string + accessTokenID: + name: accessTokenID + in: path + required: true + schema: + type: string responses: "400": @@ -530,6 +542,125 @@ components: format: uint64 description: Number of sandbox create fails + CreatedAccessToken: + required: + - id + - name + - token + - tokenMask + - createdAt + properties: + id: + type: string + format: uuid + description: Identifier of the access token + name: + type: string + description: Name of the access token + token: + type: string + description: Raw value of the access token + tokenMask: + type: string + description: Mask of the access token + createdAt: + type: string + format: date-time + description: Timestamp of access token creation + + NewAccessToken: + required: + - name + properties: + name: + type: string + description: Name of the access token + + TeamAPIKey: + required: + - id + - name + - keyMask + - createdAt + - createdBy + - lastUsed + properties: + id: + type: string + format: uuid + description: Identifier of the API key + name: + type: string + description: Name of the API key + keyMask: + type: string + description: Mask of the API key + createdAt: + type: string + format: date-time + description: Timestamp of API key creation + createdBy: + allOf: + - $ref: "#/components/schemas/TeamUser" + nullable: true + lastUsed: + type: string + format: date-time + description: Last time this API key was used + nullable: true + + CreatedTeamAPIKey: + required: + - id + - name + - key + - keyMask + - createdAt + - createdBy + - lastUsed + properties: + id: + type: string + format: uuid + description: Identifier of the API key + name: + type: string + description: Name of the API key + key: + type: string + description: Raw value of the API key + keyMask: + type: string + description: Mask of the API key + createdAt: + type: string + format: date-time + description: Timestamp of API key creation + createdBy: + allOf: + - $ref: "#/components/schemas/TeamUser" + nullable: true + lastUsed: + type: string + format: date-time + description: Last time this API key was used + nullable: true + + NewTeamAPIKey: + required: + - name + properties: + name: + type: string + description: Name of the API key + + UpdateTeamAPIKey: + required: + - name + properties: + name: + type: string + description: New name for the API key Error: required: @@ -548,6 +679,8 @@ tags: - name: templates - name: sandboxes - name: auth + - name: access-tokens + - name: api-keys paths: /health: @@ -590,9 +723,9 @@ paths: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] parameters: - - name: query + - name: metadata in: query - description: A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. + description: Metadata query used to filter the sandboxes (e.g. "user=abc&app=prod"). Each key and values must be URL encoded. required: false schema: type: string @@ -646,9 +779,9 @@ paths: security: - ApiKeyAuth: [] parameters: - - name: query + - name: metadata in: query - description: A query used to filter the sandboxes (e.g. "user=abc&app=prod"). Query and each key and values must be URL encoded. + description: Metadata query used to filter the sandboxes (e.g. "user=abc&app=prod"). Each key and values must be URL encoded. required: false schema: type: string @@ -1125,5 +1258,133 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + + /access-tokens: + post: + description: Create a new access token + tags: [access-tokens] + security: + - Supabase1TokenAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewAccessToken" + responses: + "201": + description: Access token created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/CreatedAccessToken" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + + /access-tokens/{accessTokenID}: + delete: + description: Delete an access token + tags: [access-tokens] + security: + - Supabase1TokenAuth: [] + parameters: + - $ref: "#/components/parameters/accessTokenID" + responses: + "204": + description: Access token deleted successfully + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + + /api-keys: + get: + description: List all team API keys + tags: [api-keys] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + responses: + "200": + description: Successfully returned all team API keys + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TeamAPIKey" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + post: + description: Create a new team API key + tags: [api-keys] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewTeamAPIKey" + responses: + "201": + description: Team API key created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/CreatedTeamAPIKey" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + + /api-keys/{apiKeyID}: + patch: + description: Update a team API key + tags: [api-keys] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/apiKeyID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateTeamAPIKey" + responses: + "200": + description: Team API key updated successfully + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + delete: + description: Delete a team API key + tags: [api-keys] + security: + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/apiKeyID" + responses: + "204": + description: Team API key deleted successfully + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" "500": $ref: "#/components/responses/500" \ No newline at end of file From 634bd3d8c389d397d4b95a65c9d86218d73620b7 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 13:55:16 +0100 Subject: [PATCH 03/10] Update docs --- apps/web/src/app/(docs)/docs/sandbox/list/page.mdx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx index 7aa2366952..778bf625ab 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx @@ -63,9 +63,6 @@ Running sandbox template id: 3e4rngfa34txe0gxc1zf ## Filtering sandboxes - -This feature is in a private beta. - You can filter sandboxes by specifying Metadata key value pairs. Specifying multiple key value pairs will return sandboxes that match all of them. @@ -87,7 +84,7 @@ const sandbox = await Sandbox.create({ // List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. const runningSandboxes = await Sandbox.list({ - filters: { userId: '123', env: 'dev' } // $HighlightLine + metadata: { userId: '123', env: 'dev' } // $HighlightLine }) ``` ```python @@ -103,7 +100,7 @@ sandbox = Sandbox( ) # List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. -running_sandboxes = Sandbox.list(filters={ +running_sandboxes = Sandbox.list(metadata={ "userId": "123", "env": "dev" # $HighlightLine }) ``` From 39c8e1eec63f2efdb73a1b548b6007994c432aa2 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 13:57:25 +0100 Subject: [PATCH 04/10] Add changeset --- .changeset/tame-needles-fry.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tame-needles-fry.md diff --git a/.changeset/tame-needles-fry.md b/.changeset/tame-needles-fry.md new file mode 100644 index 0000000000..085fb08c03 --- /dev/null +++ b/.changeset/tame-needles-fry.md @@ -0,0 +1,6 @@ +--- +'@e2b/python-sdk': patch +'e2b': patch +--- + +Add filtering by metadata From 348441b58e9ddc06b2778aeb9b46918672fe13b1 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 14:14:47 +0100 Subject: [PATCH 05/10] Fix docstring --- packages/python-sdk/e2b/sandbox_async/sandbox_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index ac3c89043c..6606916e5f 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -30,7 +30,7 @@ async def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param filters: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param metadata: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** From f88bb87ca44f5d0e27a750e9fe918a12bba048af Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 15:05:40 +0100 Subject: [PATCH 06/10] Add missing files --- .../api/sandboxes/get_sandboxes_metrics.py | 172 ++++++++++++++++++ .../get_sandboxes_sandbox_id_metrics.py | 164 +++++++++++++++++ .../api/client/models/created_access_token.py | 93 ++++++++++ .../api/client/models/created_team_api_key.py | 151 +++++++++++++++ .../e2b/api/client/models/new_access_token.py | 58 ++++++ .../e2b/api/client/models/new_team_api_key.py | 58 ++++++ .../models/running_sandbox_with_metrics.py | 153 ++++++++++++++++ .../e2b/api/client/models/sandbox_metric.py | 93 ++++++++++ .../e2b/api/client/models/team_api_key.py | 143 +++++++++++++++ .../api/client/models/update_team_api_key.py | 58 ++++++ 10 files changed, 1143 insertions(+) create mode 100644 packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py create mode 100644 packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py create mode 100644 packages/python-sdk/e2b/api/client/models/created_access_token.py create mode 100644 packages/python-sdk/e2b/api/client/models/created_team_api_key.py create mode 100644 packages/python-sdk/e2b/api/client/models/new_access_token.py create mode 100644 packages/python-sdk/e2b/api/client/models/new_team_api_key.py create mode 100644 packages/python-sdk/e2b/api/client/models/running_sandbox_with_metrics.py create mode 100644 packages/python-sdk/e2b/api/client/models/sandbox_metric.py create mode 100644 packages/python-sdk/e2b/api/client/models/team_api_key.py create mode 100644 packages/python-sdk/e2b/api/client/models/update_team_api_key.py diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py new file mode 100644 index 0000000000..689551f265 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py @@ -0,0 +1,172 @@ +from http import HTTPStatus +from typing import Any, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.running_sandbox_with_metrics import RunningSandboxWithMetrics +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + metadata: Union[Unset, str] = UNSET, +) -> dict[str, Any]: + params: dict[str, Any] = {} + + params["metadata"] = metadata + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/sandboxes/metrics", + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, list["RunningSandboxWithMetrics"]]]: + if response.status_code == 200: + response_200 = [] + _response_200 = response.json() + for response_200_item_data in _response_200: + response_200_item = RunningSandboxWithMetrics.from_dict(response_200_item_data) + + response_200.append(response_200_item) + + return response_200 + if response.status_code == 400: + response_400 = cast(Any, None) + return response_400 + if response.status_code == 401: + response_401 = cast(Any, None) + return response_401 + if response.status_code == 500: + response_500 = cast(Any, None) + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, list["RunningSandboxWithMetrics"]]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient, + metadata: Union[Unset, str] = UNSET, +) -> Response[Union[Any, list["RunningSandboxWithMetrics"]]]: + """List all running sandboxes with metrics + + Args: + metadata (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, list['RunningSandboxWithMetrics']]] + """ + + kwargs = _get_kwargs( + metadata=metadata, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient, + metadata: Union[Unset, str] = UNSET, +) -> Optional[Union[Any, list["RunningSandboxWithMetrics"]]]: + """List all running sandboxes with metrics + + Args: + metadata (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, list['RunningSandboxWithMetrics']] + """ + + return sync_detailed( + client=client, + metadata=metadata, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient, + metadata: Union[Unset, str] = UNSET, +) -> Response[Union[Any, list["RunningSandboxWithMetrics"]]]: + """List all running sandboxes with metrics + + Args: + metadata (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, list['RunningSandboxWithMetrics']]] + """ + + kwargs = _get_kwargs( + metadata=metadata, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient, + metadata: Union[Unset, str] = UNSET, +) -> Optional[Union[Any, list["RunningSandboxWithMetrics"]]]: + """List all running sandboxes with metrics + + Args: + metadata (Union[Unset, str]): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, list['RunningSandboxWithMetrics']] + """ + + return ( + await asyncio_detailed( + client=client, + metadata=metadata, + ) + ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py new file mode 100644 index 0000000000..76905092d8 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py @@ -0,0 +1,164 @@ +from http import HTTPStatus +from typing import Any, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.sandbox_metric import SandboxMetric +from ...types import Response + + +def _get_kwargs( + sandbox_id: str, +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": f"/sandboxes/{sandbox_id}/metrics", + } + + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, list["SandboxMetric"]]]: + if response.status_code == 200: + response_200 = [] + _response_200 = response.json() + for response_200_item_data in _response_200: + response_200_item = SandboxMetric.from_dict(response_200_item_data) + + response_200.append(response_200_item) + + return response_200 + if response.status_code == 401: + response_401 = cast(Any, None) + return response_401 + if response.status_code == 404: + response_404 = cast(Any, None) + return response_404 + if response.status_code == 500: + response_500 = cast(Any, None) + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, list["SandboxMetric"]]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Response[Union[Any, list["SandboxMetric"]]]: + """Get sandbox metrics + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, list['SandboxMetric']]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Optional[Union[Any, list["SandboxMetric"]]]: + """Get sandbox metrics + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, list['SandboxMetric']] + """ + + return sync_detailed( + sandbox_id=sandbox_id, + client=client, + ).parsed + + +async def asyncio_detailed( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Response[Union[Any, list["SandboxMetric"]]]: + """Get sandbox metrics + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Any, list['SandboxMetric']]] + """ + + kwargs = _get_kwargs( + sandbox_id=sandbox_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + sandbox_id: str, + *, + client: AuthenticatedClient, +) -> Optional[Union[Any, list["SandboxMetric"]]]: + """Get sandbox metrics + + Args: + sandbox_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Any, list['SandboxMetric']] + """ + + return ( + await asyncio_detailed( + sandbox_id=sandbox_id, + client=client, + ) + ).parsed diff --git a/packages/python-sdk/e2b/api/client/models/created_access_token.py b/packages/python-sdk/e2b/api/client/models/created_access_token.py new file mode 100644 index 0000000000..31f22a3c02 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/created_access_token.py @@ -0,0 +1,93 @@ +import datetime +from typing import Any, TypeVar +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +T = TypeVar("T", bound="CreatedAccessToken") + + +@_attrs_define +class CreatedAccessToken: + """ + Attributes: + created_at (datetime.datetime): Timestamp of access token creation + id (UUID): Identifier of the access token + name (str): Name of the access token + token (str): Raw value of the access token + token_mask (str): Mask of the access token + """ + + created_at: datetime.datetime + id: UUID + name: str + token: str + token_mask: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + created_at = self.created_at.isoformat() + + id = str(self.id) + + name = self.name + + token = self.token + + token_mask = self.token_mask + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "createdAt": created_at, + "id": id, + "name": name, + "token": token, + "tokenMask": token_mask, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + created_at = isoparse(d.pop("createdAt")) + + id = UUID(d.pop("id")) + + name = d.pop("name") + + token = d.pop("token") + + token_mask = d.pop("tokenMask") + + created_access_token = cls( + created_at=created_at, + id=id, + name=name, + token=token, + token_mask=token_mask, + ) + + created_access_token.additional_properties = d + return created_access_token + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/created_team_api_key.py b/packages/python-sdk/e2b/api/client/models/created_team_api_key.py new file mode 100644 index 0000000000..3a6f2aa2b7 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/created_team_api_key.py @@ -0,0 +1,151 @@ +import datetime +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +if TYPE_CHECKING: + from ..models.team_user import TeamUser + + +T = TypeVar("T", bound="CreatedTeamAPIKey") + + +@_attrs_define +class CreatedTeamAPIKey: + """ + Attributes: + created_at (datetime.datetime): Timestamp of API key creation + created_by (Union['TeamUser', None]): + id (UUID): Identifier of the API key + key (str): Raw value of the API key + key_mask (str): Mask of the API key + last_used (Union[None, datetime.datetime]): Last time this API key was used + name (str): Name of the API key + """ + + created_at: datetime.datetime + created_by: Union["TeamUser", None] + id: UUID + key: str + key_mask: str + last_used: Union[None, datetime.datetime] + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.team_user import TeamUser + + created_at = self.created_at.isoformat() + + created_by: Union[None, dict[str, Any]] + if isinstance(self.created_by, TeamUser): + created_by = self.created_by.to_dict() + else: + created_by = self.created_by + + id = str(self.id) + + key = self.key + + key_mask = self.key_mask + + last_used: Union[None, str] + if isinstance(self.last_used, datetime.datetime): + last_used = self.last_used.isoformat() + else: + last_used = self.last_used + + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "createdAt": created_at, + "createdBy": created_by, + "id": id, + "key": key, + "keyMask": key_mask, + "lastUsed": last_used, + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + from ..models.team_user import TeamUser + + d = src_dict.copy() + created_at = isoparse(d.pop("createdAt")) + + def _parse_created_by(data: object) -> Union["TeamUser", None]: + if data is None: + return data + try: + if not isinstance(data, dict): + raise TypeError() + created_by_type_1 = TeamUser.from_dict(data) + + return created_by_type_1 + except: # noqa: E722 + pass + return cast(Union["TeamUser", None], data) + + created_by = _parse_created_by(d.pop("createdBy")) + + id = UUID(d.pop("id")) + + key = d.pop("key") + + key_mask = d.pop("keyMask") + + def _parse_last_used(data: object) -> Union[None, datetime.datetime]: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + last_used_type_0 = isoparse(data) + + return last_used_type_0 + except: # noqa: E722 + pass + return cast(Union[None, datetime.datetime], data) + + last_used = _parse_last_used(d.pop("lastUsed")) + + name = d.pop("name") + + created_team_api_key = cls( + created_at=created_at, + created_by=created_by, + id=id, + key=key, + key_mask=key_mask, + last_used=last_used, + name=name, + ) + + created_team_api_key.additional_properties = d + return created_team_api_key + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/new_access_token.py b/packages/python-sdk/e2b/api/client/models/new_access_token.py new file mode 100644 index 0000000000..18cfb30b53 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/new_access_token.py @@ -0,0 +1,58 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="NewAccessToken") + + +@_attrs_define +class NewAccessToken: + """ + Attributes: + name (str): Name of the access token + """ + + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + name = d.pop("name") + + new_access_token = cls( + name=name, + ) + + new_access_token.additional_properties = d + return new_access_token + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/new_team_api_key.py b/packages/python-sdk/e2b/api/client/models/new_team_api_key.py new file mode 100644 index 0000000000..f6826f59c1 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/new_team_api_key.py @@ -0,0 +1,58 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="NewTeamAPIKey") + + +@_attrs_define +class NewTeamAPIKey: + """ + Attributes: + name (str): Name of the API key + """ + + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + name = d.pop("name") + + new_team_api_key = cls( + name=name, + ) + + new_team_api_key.additional_properties = d + return new_team_api_key + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/running_sandbox_with_metrics.py b/packages/python-sdk/e2b/api/client/models/running_sandbox_with_metrics.py new file mode 100644 index 0000000000..95925acac5 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/running_sandbox_with_metrics.py @@ -0,0 +1,153 @@ +import datetime +from typing import TYPE_CHECKING, Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.sandbox_metric import SandboxMetric + + +T = TypeVar("T", bound="RunningSandboxWithMetrics") + + +@_attrs_define +class RunningSandboxWithMetrics: + """ + Attributes: + client_id (str): Identifier of the client + cpu_count (int): CPU cores for the sandbox + end_at (datetime.datetime): Time when the sandbox will expire + memory_mb (int): Memory for the sandbox in MB + sandbox_id (str): Identifier of the sandbox + started_at (datetime.datetime): Time when the sandbox was started + template_id (str): Identifier of the template from which is the sandbox created + alias (Union[Unset, str]): Alias of the template + metadata (Union[Unset, Any]): + metrics (Union[Unset, list['SandboxMetric']]): + """ + + client_id: str + cpu_count: int + end_at: datetime.datetime + memory_mb: int + sandbox_id: str + started_at: datetime.datetime + template_id: str + alias: Union[Unset, str] = UNSET + metadata: Union[Unset, Any] = UNSET + metrics: Union[Unset, list["SandboxMetric"]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + client_id = self.client_id + + cpu_count = self.cpu_count + + end_at = self.end_at.isoformat() + + memory_mb = self.memory_mb + + sandbox_id = self.sandbox_id + + started_at = self.started_at.isoformat() + + template_id = self.template_id + + alias = self.alias + + metadata = self.metadata + + metrics: Union[Unset, list[dict[str, Any]]] = UNSET + if not isinstance(self.metrics, Unset): + metrics = [] + for metrics_item_data in self.metrics: + metrics_item = metrics_item_data.to_dict() + metrics.append(metrics_item) + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "clientID": client_id, + "cpuCount": cpu_count, + "endAt": end_at, + "memoryMB": memory_mb, + "sandboxID": sandbox_id, + "startedAt": started_at, + "templateID": template_id, + } + ) + if alias is not UNSET: + field_dict["alias"] = alias + if metadata is not UNSET: + field_dict["metadata"] = metadata + if metrics is not UNSET: + field_dict["metrics"] = metrics + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + from ..models.sandbox_metric import SandboxMetric + + d = src_dict.copy() + client_id = d.pop("clientID") + + cpu_count = d.pop("cpuCount") + + end_at = isoparse(d.pop("endAt")) + + memory_mb = d.pop("memoryMB") + + sandbox_id = d.pop("sandboxID") + + started_at = isoparse(d.pop("startedAt")) + + template_id = d.pop("templateID") + + alias = d.pop("alias", UNSET) + + metadata = d.pop("metadata", UNSET) + + metrics = [] + _metrics = d.pop("metrics", UNSET) + for metrics_item_data in _metrics or []: + metrics_item = SandboxMetric.from_dict(metrics_item_data) + + metrics.append(metrics_item) + + running_sandbox_with_metrics = cls( + client_id=client_id, + cpu_count=cpu_count, + end_at=end_at, + memory_mb=memory_mb, + sandbox_id=sandbox_id, + started_at=started_at, + template_id=template_id, + alias=alias, + metadata=metadata, + metrics=metrics, + ) + + running_sandbox_with_metrics.additional_properties = d + return running_sandbox_with_metrics + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/sandbox_metric.py b/packages/python-sdk/e2b/api/client/models/sandbox_metric.py new file mode 100644 index 0000000000..54cea611c0 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/sandbox_metric.py @@ -0,0 +1,93 @@ +import datetime +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +T = TypeVar("T", bound="SandboxMetric") + + +@_attrs_define +class SandboxMetric: + """Metric entry with timestamp and line + + Attributes: + cpu_count (int): Number of CPU cores + cpu_used_pct (float): CPU usage percentage + mem_total_mi_b (int): Total memory in MiB + mem_used_mi_b (int): Memory used in MiB + timestamp (datetime.datetime): Timestamp of the metric entry + """ + + cpu_count: int + cpu_used_pct: float + mem_total_mi_b: int + mem_used_mi_b: int + timestamp: datetime.datetime + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + cpu_count = self.cpu_count + + cpu_used_pct = self.cpu_used_pct + + mem_total_mi_b = self.mem_total_mi_b + + mem_used_mi_b = self.mem_used_mi_b + + timestamp = self.timestamp.isoformat() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "cpuCount": cpu_count, + "cpuUsedPct": cpu_used_pct, + "memTotalMiB": mem_total_mi_b, + "memUsedMiB": mem_used_mi_b, + "timestamp": timestamp, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + cpu_count = d.pop("cpuCount") + + cpu_used_pct = d.pop("cpuUsedPct") + + mem_total_mi_b = d.pop("memTotalMiB") + + mem_used_mi_b = d.pop("memUsedMiB") + + timestamp = isoparse(d.pop("timestamp")) + + sandbox_metric = cls( + cpu_count=cpu_count, + cpu_used_pct=cpu_used_pct, + mem_total_mi_b=mem_total_mi_b, + mem_used_mi_b=mem_used_mi_b, + timestamp=timestamp, + ) + + sandbox_metric.additional_properties = d + return sandbox_metric + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/team_api_key.py b/packages/python-sdk/e2b/api/client/models/team_api_key.py new file mode 100644 index 0000000000..9726b62524 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/team_api_key.py @@ -0,0 +1,143 @@ +import datetime +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field +from dateutil.parser import isoparse + +if TYPE_CHECKING: + from ..models.team_user import TeamUser + + +T = TypeVar("T", bound="TeamAPIKey") + + +@_attrs_define +class TeamAPIKey: + """ + Attributes: + created_at (datetime.datetime): Timestamp of API key creation + created_by (Union['TeamUser', None]): + id (UUID): Identifier of the API key + key_mask (str): Mask of the API key + last_used (Union[None, datetime.datetime]): Last time this API key was used + name (str): Name of the API key + """ + + created_at: datetime.datetime + created_by: Union["TeamUser", None] + id: UUID + key_mask: str + last_used: Union[None, datetime.datetime] + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.team_user import TeamUser + + created_at = self.created_at.isoformat() + + created_by: Union[None, dict[str, Any]] + if isinstance(self.created_by, TeamUser): + created_by = self.created_by.to_dict() + else: + created_by = self.created_by + + id = str(self.id) + + key_mask = self.key_mask + + last_used: Union[None, str] + if isinstance(self.last_used, datetime.datetime): + last_used = self.last_used.isoformat() + else: + last_used = self.last_used + + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "createdAt": created_at, + "createdBy": created_by, + "id": id, + "keyMask": key_mask, + "lastUsed": last_used, + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + from ..models.team_user import TeamUser + + d = src_dict.copy() + created_at = isoparse(d.pop("createdAt")) + + def _parse_created_by(data: object) -> Union["TeamUser", None]: + if data is None: + return data + try: + if not isinstance(data, dict): + raise TypeError() + created_by_type_1 = TeamUser.from_dict(data) + + return created_by_type_1 + except: # noqa: E722 + pass + return cast(Union["TeamUser", None], data) + + created_by = _parse_created_by(d.pop("createdBy")) + + id = UUID(d.pop("id")) + + key_mask = d.pop("keyMask") + + def _parse_last_used(data: object) -> Union[None, datetime.datetime]: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + last_used_type_0 = isoparse(data) + + return last_used_type_0 + except: # noqa: E722 + pass + return cast(Union[None, datetime.datetime], data) + + last_used = _parse_last_used(d.pop("lastUsed")) + + name = d.pop("name") + + team_api_key = cls( + created_at=created_at, + created_by=created_by, + id=id, + key_mask=key_mask, + last_used=last_used, + name=name, + ) + + team_api_key.additional_properties = d + return team_api_key + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/update_team_api_key.py b/packages/python-sdk/e2b/api/client/models/update_team_api_key.py new file mode 100644 index 0000000000..03bae6c5a6 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/update_team_api_key.py @@ -0,0 +1,58 @@ +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="UpdateTeamAPIKey") + + +@_attrs_define +class UpdateTeamAPIKey: + """ + Attributes: + name (str): New name for the API key + """ + + name: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + name = self.name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "name": name, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T: + d = src_dict.copy() + name = d.pop("name") + + update_team_api_key = cls( + name=name, + ) + + update_team_api_key.additional_properties = d + return update_team_api_key + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties From 469e70642f2f06336996f230754abb9cbeeaf5d7 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 16:34:09 +0100 Subject: [PATCH 07/10] Add query as top level parameter --- packages/js-sdk/src/sandbox/sandboxApi.ts | 12 ++++++----- packages/js-sdk/tests/api/list.test.ts | 11 +++++++++- .../python-sdk/e2b/sandbox/sandbox_api.py | 10 ++++++++- .../e2b/sandbox_async/sandbox_api.py | 21 +++++++++++-------- .../e2b/sandbox_sync/sandbox_api.py | 20 ++++++++++-------- .../tests/async/api_async/test_sbx_list.py | 12 ++++++++++- .../tests/sync/api_sync/test_sbx_list.py | 10 ++++++++- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 420c0e335b..a18429a59a 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -13,9 +13,9 @@ export interface SandboxApiOpts export interface SandboxListOpts extends SandboxApiOpts { /** - * Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + * Filter the list of sandboxes, e.g. by metadata `metadata:{"key": "value"}`, if there are multiple filters they are combined with AND. */ - metadata?: Record + query?: {metadata?: Record} } /** @@ -100,9 +100,11 @@ export class SandboxApi { const client = new ApiClient(config) let metadata = undefined - if (opts?.metadata) { - const encodedPairs: Record = Object.fromEntries(Object.entries(opts.metadata).map(([key, value]) => [encodeURIComponent(key),encodeURIComponent(value)])) - metadata = new URLSearchParams(encodedPairs).toString() + if (opts?.query) { + if (opts.query.metadata) { + const encodedPairs: Record = 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', { diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index d651dfcece..91550646b7 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -28,7 +28,7 @@ sandboxTest.skipIf(isDebug)('list sandboxes with metadata filter', async () => { try { const sbx = await Sandbox.create({metadata: {uniqueId: uniqueId}}) try { - const sandboxes = await Sandbox.list({metadata: {uniqueId}}) + const sandboxes = await Sandbox.list({query:{metadata: {uniqueId}}}) assert.equal(sandboxes.length, 1) assert.equal(sandboxes[0].sandboxId, sbx.sandboxId) } finally { @@ -38,3 +38,12 @@ sandboxTest.skipIf(isDebug)('list sandboxes with metadata filter', async () => { await extraSbx.kill() } }) + +sandboxTest.skipIf(isDebug)('list sandboxes empty filter', async ({ sandbox }) => { + const sandboxes = await Sandbox.list() + assert.isAtLeast(sandboxes.length, 1) + assert.include( + sandboxes.map((s) => s.sandboxId), + sandbox.sandboxId + ) +}) diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index cbf876053c..4f85d89a0d 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -1,6 +1,6 @@ from abc import ABC from dataclasses import dataclass -from typing import Optional, Dict +from typing import Optional, Dict, TypedDict from datetime import datetime from httpx import Limits @@ -21,6 +21,14 @@ class SandboxInfo: """Sandbox start time.""" +@dataclass +class SandboxQuery: + """Query parameters for listing sandboxes.""" + + metadata: Optional[dict[str, str]] = None + """Filter sandboxes by metadata.""" + + class SandboxApiBase(ABC): _limits = Limits( max_keepalive_connections=10, diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 6606916e5f..6b25c7e6f5 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -2,7 +2,7 @@ from typing import Optional, Dict, List from packaging.version import Version -from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase +from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase, SandboxQuery from e2b.exceptions import TemplateException from e2b.api import AsyncApiClient, SandboxCreateResponse from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody @@ -21,7 +21,7 @@ class SandboxApi(SandboxApiBase): async def list( cls, api_key: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + query: Optional[SandboxQuery] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -30,7 +30,7 @@ async def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param metadata: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param query: Filter the list of sandboxes, e.g. by metadata `metadata={"key": "value"}`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** @@ -44,12 +44,15 @@ async def list( request_timeout=request_timeout, ) - if metadata: - quoted_metadata = { - urllib.parse.quote(k): urllib.parse.quote(v) - for k, v in metadata.items() - } - metadata = urllib.parse.urlencode(quoted_metadata) + # Convert filters to the format expected by the API + metadata = None + if query: + if query.metadata: + quoted_metadata = { + urllib.parse.quote(k): urllib.parse.quote(v) + for k, v in query.metadata.items() + } + metadata = urllib.parse.urlencode(quoted_metadata) async with AsyncApiClient(config) as api_client: res = await get_sandboxes.asyncio_detailed( diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 6664c698ab..b2d755a397 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -4,7 +4,7 @@ from typing import Optional, Dict, List, Tuple from packaging.version import Version -from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase +from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase, SandboxQuery from e2b.exceptions import TemplateException from e2b.api import ApiClient, SandboxCreateResponse from e2b.api.client.models import NewSandbox, PostSandboxesSandboxIDTimeoutBody @@ -23,7 +23,7 @@ class SandboxApi(SandboxApiBase): def list( cls, api_key: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + query: Optional[SandboxQuery] = None, domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, @@ -32,7 +32,7 @@ def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param metadata: Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND. + :param query: Filter the list of sandboxes, e.g. by metadata `metadata={"key": "value"}`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** @@ -47,12 +47,14 @@ def list( ) # Convert filters to the format expected by the API - if metadata: - quoted_metadata = { - urllib.parse.quote(k): urllib.parse.quote(v) - for k, v in metadata.items() - } - metadata = urllib.parse.urlencode(quoted_metadata) + metadata = None + if query: + if query.metadata: + quoted_metadata = { + urllib.parse.quote(k): urllib.parse.quote(v) + for k, v in query.metadata.items() + } + metadata = urllib.parse.urlencode(quoted_metadata) with ApiClient( config, transport=HTTPTransport(limits=SandboxApiBase._limits) diff --git a/packages/python-sdk/tests/async/api_async/test_sbx_list.py b/packages/python-sdk/tests/async/api_async/test_sbx_list.py index 4e50eef527..9274e3068b 100644 --- a/packages/python-sdk/tests/async/api_async/test_sbx_list.py +++ b/packages/python-sdk/tests/async/api_async/test_sbx_list.py @@ -4,6 +4,7 @@ import pytest from e2b import AsyncSandbox +from e2b.sandbox.sandbox_api import SandboxQuery @pytest.mark.skip_debug() @@ -19,8 +20,17 @@ async def test_list_sandboxes_with_filter(async_sandbox: AsyncSandbox): sbx = await AsyncSandbox.create(metadata={"unique_id": unique_id}) try: # There's an extra sandbox created by the test runner - sandboxes = await AsyncSandbox.list(metadata={"unique_id": unique_id}) + sandboxes = await AsyncSandbox.list( + query=SandboxQuery(metadata={"unique_id": unique_id}) + ) assert len(sandboxes) == 1 assert sandboxes[0].metadata["unique_id"] == unique_id finally: await sbx.kill() + + +@pytest.mark.skip_debug() +async def test_list_sandboxes_with_empty_filter(async_sandbox: AsyncSandbox): + sandboxes = await AsyncSandbox.list(query=SandboxQuery()) + assert len(sandboxes) > 0 + assert async_sandbox.sandbox_id in [sbx.sandbox_id for sbx in sandboxes] diff --git a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py index dd5bdc9e8a..0b78a1ee55 100644 --- a/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py +++ b/packages/python-sdk/tests/sync/api_sync/test_sbx_list.py @@ -4,6 +4,7 @@ import pytest from e2b import Sandbox +from e2b.sandbox.sandbox_api import SandboxQuery @pytest.mark.skip_debug() @@ -17,6 +18,13 @@ def test_list_sandboxes(sandbox: Sandbox): def test_list_sandboxes_with_filter(sandbox: Sandbox): unique_id = "".join(random.choices(string.ascii_letters, k=5)) Sandbox(metadata={"unique_id": unique_id}) - sandboxes = Sandbox.list(metadata={"unique_id": unique_id}) + sandboxes = Sandbox.list(query=SandboxQuery(metadata={"unique_id": unique_id})) assert len(sandboxes) == 1 assert sandboxes[0].metadata["unique_id"] == unique_id + + +@pytest.mark.skip_debug() +def test_list_sandboxes_with_empty_filter(sandbox: Sandbox): + sandboxes = Sandbox.list(query=SandboxQuery()) + assert len(sandboxes) > 0 + assert sandbox.sandbox_id in [sbx.sandbox_id for sbx in sandboxes] From 0725395d070d01e1d90de24cd4628b3c9109f52e Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 20 Mar 2025 18:27:33 +0100 Subject: [PATCH 08/10] Update docs --- .../src/app/(docs)/docs/sandbox/list/page.mdx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx index 778bf625ab..89e73989d5 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx @@ -84,7 +84,9 @@ const sandbox = await Sandbox.create({ // List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. const runningSandboxes = await Sandbox.list({ - metadata: { userId: '123', env: 'dev' } // $HighlightLine + query: { + metadata: { userId: '123', env: 'dev' }, // $HighlightLine + }, }) ``` ```python @@ -93,15 +95,20 @@ from e2b_code_interpreter import Sandbox # Create sandbox with metadata. sandbox = Sandbox( metadata={ - "env": "dev", # $HighlightLine - "app": "my-app", # $HighlightLine - "user_id": "123", # $HighlightLine + 'env": "dev", # $HighlightLine + "app": "my-app", # $HighlightLine + "user_id": "123", # $HighlightLine }, ) # List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. -running_sandboxes = Sandbox.list(metadata={ - "userId": "123", "env": "dev" # $HighlightLine -}) +running_sandboxes = Sandbox.list( + query=SandboxQuery( + metadata={ + "userId": "123", # $HighlightLine + "env": "dev", # $HighlightLine + } + ), +) ``` From 5b670e79eb070716b5852c4e2ceadb21c468c432 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:47:45 +0100 Subject: [PATCH 09/10] Fix quote in docs --- apps/web/src/app/(docs)/docs/sandbox/list/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx index 89e73989d5..b75205530d 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/list/page.mdx @@ -95,7 +95,7 @@ from e2b_code_interpreter import Sandbox # Create sandbox with metadata. sandbox = Sandbox( metadata={ - 'env": "dev", # $HighlightLine + "env": "dev", # $HighlightLine "app": "my-app", # $HighlightLine "user_id": "123", # $HighlightLine }, From 505a53f88dc53026f35590c06f2083f7e8ce5856 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:54:12 +0100 Subject: [PATCH 10/10] Remove unused import and update docstrings --- packages/python-sdk/e2b/sandbox/sandbox_api.py | 2 +- packages/python-sdk/e2b/sandbox_async/sandbox_api.py | 2 +- packages/python-sdk/e2b/sandbox_sync/sandbox_api.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 4f85d89a0d..c746de575f 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -1,6 +1,6 @@ from abc import ABC from dataclasses import dataclass -from typing import Optional, Dict, TypedDict +from typing import Optional, Dict from datetime import datetime from httpx import Limits diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index 6b25c7e6f5..9268024767 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -30,7 +30,7 @@ async def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param query: Filter the list of sandboxes, e.g. by metadata `metadata={"key": "value"}`, if there are multiple filters they are combined with AND. + :param query: Filter the list of sandboxes, e.g. by metadata `SandboxQuery(metadata={"key": "value"})`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds** diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index b2d755a397..92460e7526 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -32,7 +32,7 @@ def list( List all running sandboxes. :param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable - :param query: Filter the list of sandboxes, e.g. by metadata `metadata={"key": "value"}`, if there are multiple filters they are combined with AND. + :param query: Filter the list of sandboxes, e.g. by metadata `SandboxQuery(metadata={"key": "value"})`, if there are multiple filters they are combined with AND. :param domain: Domain to use for the request, only relevant for self-hosted environments :param debug: Enable debug mode, all requested are then sent to localhost :param request_timeout: Timeout for the request in **seconds**