diff --git a/.changeset/python-sdk-dedupe-create-response.md b/.changeset/python-sdk-dedupe-create-response.md new file mode 100644 index 0000000000..ee52b1599b --- /dev/null +++ b/.changeset/python-sdk-dedupe-create-response.md @@ -0,0 +1,6 @@ +--- +'@e2b/python-sdk': patch +'e2b': patch +--- + +Share the sandbox create/connect/fork response mapping (JS and Python) and the Python `Commands.run` / `Commands.connect` handle setup diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index c9c011fad9..3b3b822a52 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -572,15 +572,27 @@ function validateForkCount(count: number | undefined) { } } -type SandboxForkResponse = - | { - sandboxId: string - sandboxDomain?: string - envdVersion: string - envdAccessToken?: string - trafficAccessToken?: string - } - | Error +type SandboxCreateResponse = { + sandboxId: string + sandboxDomain?: string + envdVersion: string + envdAccessToken?: string + trafficAccessToken?: string +} + +type SandboxForkResponse = SandboxCreateResponse | Error + +function toSandboxCreateResponse( + sandbox: components['schemas']['Sandbox'] +): SandboxCreateResponse { + return { + sandboxId: sandbox.sandboxID, + sandboxDomain: sandbox.domain || undefined, + envdVersion: sandbox.envdVersion, + envdAccessToken: sandbox.envdAccessToken, + trafficAccessToken: sandbox.trafficAccessToken || undefined, + } +} /** * Options for creating a new Sandbox. @@ -1761,13 +1773,7 @@ export class SandboxApi extends ClientFactory { ) } - return { - sandboxId: res.data!.sandboxID, - sandboxDomain: res.data!.domain || undefined, - envdVersion: res.data!.envdVersion, - envdAccessToken: res.data!.envdAccessToken, - trafficAccessToken: res.data!.trafficAccessToken || undefined, - } + return toSandboxCreateResponse(res.data!) } protected static async forkSandbox( @@ -1827,13 +1833,7 @@ export class SandboxApi extends ClientFactory { return apiErrorFromCode(result.error.code, result.error.message) } - return { - sandboxId: result.sandbox.sandboxID, - sandboxDomain: result.sandbox.domain || undefined, - envdVersion: result.sandbox.envdVersion, - envdAccessToken: result.sandbox.envdAccessToken, - trafficAccessToken: result.sandbox.trafficAccessToken || undefined, - } + return toSandboxCreateResponse(result.sandbox) } ) } @@ -1883,13 +1883,7 @@ export class SandboxApi extends ClientFactory { throw err } - return { - sandboxId: res.data!.sandboxID, - sandboxDomain: res.data!.domain || undefined, - envdVersion: res.data!.envdVersion, - envdAccessToken: res.data!.envdAccessToken, - trafficAccessToken: res.data!.trafficAccessToken || undefined, - } + return toSandboxCreateResponse(res.data!) } } diff --git a/packages/python-sdk/e2b/sandbox/sandbox_api.py b/packages/python-sdk/e2b/sandbox/sandbox_api.py index 71b5b600ca..789d3c5b3d 100644 --- a/packages/python-sdk/e2b/sandbox/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox/sandbox_api.py @@ -61,6 +61,8 @@ from e2b.api.client.models import ( SandboxNetworkUpdateConfigRules, ) +from e2b.api import SandboxCreateResponse +from e2b.api.client.models.sandbox import Sandbox as CreatedSandbox from e2b.api.client.types import UNSET, Unset from e2b.connection_config import ApiParams from e2b.exceptions import InvalidArgumentException @@ -76,6 +78,25 @@ MAX_FORK_COUNT = 20 +def to_sandbox_create_response(sandbox: CreatedSandbox) -> SandboxCreateResponse: + """Map a created, connected or forked sandbox from the API response.""" + return SandboxCreateResponse( + sandbox_id=sandbox.sandbox_id, + sandbox_domain=sandbox.domain if isinstance(sandbox.domain, str) else None, + envd_version=sandbox.envd_version, + envd_access_token=( + sandbox.envd_access_token + if isinstance(sandbox.envd_access_token, str) + else None + ), + traffic_access_token=( + sandbox.traffic_access_token + if isinstance(sandbox.traffic_access_token, str) + else None + ), + ) + + def validate_fork_count(count: Any) -> None: """Reject a fork count the SDK will not send. diff --git a/packages/python-sdk/e2b/sandbox_async/commands/command.py b/packages/python-sdk/e2b/sandbox_async/commands/command.py index 19d7e23649..f2ad186066 100644 --- a/packages/python-sdk/e2b/sandbox_async/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_async/commands/command.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Optional, Union, overload +from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Union, overload import httpx from connectrpc.code import Code @@ -315,32 +315,9 @@ async def _start( ) ) - try: - start_event = await first_event( - events, self._connection_config.get_request_timeout(request_timeout) - ) - - pid = extract_start_pid(start_event, "start process") - return AsyncCommandHandle( - pid=pid, - handle_kill=lambda: self.kill(pid), - events=events, - on_stdout=on_stdout, - on_stderr=on_stderr, - handle_send_stdin=lambda data, request_timeout=None: self.send_stdin( - pid, data, request_timeout - ), - handle_close_stdin=lambda request_timeout=None: self.close_stdin( - pid, request_timeout - ), - check_health=self._check_health, - ) - except Exception as e: - try: - await events.aclose() - except Exception: - pass - raise await ahandle_rpc_exception_with_health(e, self._check_health) + return await self._create_handle( + events, "start process", request_timeout, on_stdout, on_stderr + ) async def connect( self, @@ -374,12 +351,26 @@ async def connect( ) ) + return await self._create_handle( + events, "connect to process", request_timeout, on_stdout, on_stderr + ) + + async def _create_handle( + self, + events: AsyncGenerator[ + Union[process_pb.StartResponse, process_pb.ConnectResponse], Any + ], + action: str, + request_timeout: Optional[float], + on_stdout: Optional[OutputHandler[Stdout]], + on_stderr: Optional[OutputHandler[Stderr]], + ) -> AsyncCommandHandle: try: start_event = await first_event( events, self._connection_config.get_request_timeout(request_timeout) ) - pid = extract_start_pid(start_event, "connect to process") + pid = extract_start_pid(start_event, action) return AsyncCommandHandle( pid=pid, handle_kill=lambda: self.kill(pid), diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index ef12afba5b..fb6b29ef4b 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -62,6 +62,7 @@ build_iam_config, build_lifecycle_config, build_network_config, + to_sandbox_create_response, validate_fork_count, ) from e2b.sandbox_async.paginator import AsyncSandboxPaginator @@ -269,25 +270,7 @@ async def _create_sandbox( "You need to update the template to use the new SDK." ) - domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None - envd_token = ( - res.parsed.envd_access_token - if isinstance(res.parsed.envd_access_token, str) - else None - ) - traffic_token = ( - res.parsed.traffic_access_token - if isinstance(res.parsed.traffic_access_token, str) - else None - ) - - return SandboxCreateResponse( - sandbox_id=res.parsed.sandbox_id, - sandbox_domain=domain, - envd_version=res.parsed.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) + return to_sandbox_create_response(res.parsed) @classmethod async def _cls_get_metrics( @@ -496,27 +479,7 @@ async def _cls_fork( results.append(exception) continue - domain = sandbox.domain if isinstance(sandbox.domain, str) else None - envd_token = ( - sandbox.envd_access_token - if isinstance(sandbox.envd_access_token, str) - else None - ) - traffic_token = ( - sandbox.traffic_access_token - if isinstance(sandbox.traffic_access_token, str) - else None - ) - - results.append( - SandboxCreateResponse( - sandbox_id=sandbox.sandbox_id, - sandbox_domain=domain, - envd_version=sandbox.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) - ) + results.append(to_sandbox_create_response(sandbox)) return results @@ -556,22 +519,4 @@ async def _cls_connect( if res.parsed is None: raise Exception("Body of the request is None") - domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None - envd_token = ( - res.parsed.envd_access_token - if isinstance(res.parsed.envd_access_token, str) - else None - ) - traffic_token = ( - res.parsed.traffic_access_token - if isinstance(res.parsed.traffic_access_token, str) - else None - ) - - return SandboxCreateResponse( - sandbox_id=res.parsed.sandbox_id, - sandbox_domain=domain, - envd_version=res.parsed.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) + return to_sandbox_create_response(res.parsed) diff --git a/packages/python-sdk/e2b/sandbox_sync/commands/command.py b/packages/python-sdk/e2b/sandbox_sync/commands/command.py index 0198c42c5a..d929301e4e 100644 --- a/packages/python-sdk/e2b/sandbox_sync/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_sync/commands/command.py @@ -1,4 +1,14 @@ -from typing import Callable, Dict, List, Literal, Optional, Union, overload +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + Literal, + Optional, + Union, + overload, +) import httpx from connectrpc.code import Code @@ -315,28 +325,7 @@ def _start( ) ) - try: - start_event = events.__next__() - - pid = extract_start_pid(start_event, "start process") - return CommandHandle( - pid=pid, - handle_kill=lambda: self.kill(pid), - events=events, - handle_send_stdin=lambda data, request_timeout=None: self.send_stdin( - pid, data, request_timeout - ), - handle_close_stdin=lambda request_timeout=None: self.close_stdin( - pid, request_timeout - ), - check_health=self._check_health, - ) - except Exception as e: - try: - events.close() - except Exception: - pass - raise handle_rpc_exception_with_health(e, self._check_health) + return self._create_handle(events, "start process") def connect( self, @@ -366,10 +355,19 @@ def connect( ) ) + return self._create_handle(events, "connect to process") + + def _create_handle( + self, + events: Generator[ + Union[process_pb.StartResponse, process_pb.ConnectResponse], Any, None + ], + action: str, + ) -> CommandHandle: try: start_event = events.__next__() - pid = extract_start_pid(start_event, "connect to process") + pid = extract_start_pid(start_event, action) return CommandHandle( pid=pid, handle_kill=lambda: self.kill(pid), diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index d351e09289..013d65f941 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -61,6 +61,7 @@ build_iam_config, build_lifecycle_config, build_network_config, + to_sandbox_create_response, validate_fork_count, ) from e2b.sandbox_sync.paginator import SandboxPaginator, get_api_client @@ -268,25 +269,7 @@ def _create_sandbox( "You need to update the template to use the new SDK." ) - domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None - envd_token = ( - res.parsed.envd_access_token - if isinstance(res.parsed.envd_access_token, str) - else None - ) - traffic_token = ( - res.parsed.traffic_access_token - if isinstance(res.parsed.traffic_access_token, str) - else None - ) - - return SandboxCreateResponse( - sandbox_id=res.parsed.sandbox_id, - sandbox_domain=domain, - envd_version=res.parsed.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) + return to_sandbox_create_response(res.parsed) @classmethod def _cls_get_metrics( @@ -371,25 +354,7 @@ def _cls_connect( if res.parsed is None: raise Exception("Body of the request is None") - domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None - envd_token = ( - res.parsed.envd_access_token - if isinstance(res.parsed.envd_access_token, str) - else None - ) - traffic_token = ( - res.parsed.traffic_access_token - if isinstance(res.parsed.traffic_access_token, str) - else None - ) - - return SandboxCreateResponse( - sandbox_id=res.parsed.sandbox_id, - sandbox_domain=domain, - envd_version=res.parsed.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) + return to_sandbox_create_response(res.parsed) @classmethod def _cls_fork( @@ -450,27 +415,7 @@ def _cls_fork( results.append(exception) continue - domain = sandbox.domain if isinstance(sandbox.domain, str) else None - envd_token = ( - sandbox.envd_access_token - if isinstance(sandbox.envd_access_token, str) - else None - ) - traffic_token = ( - sandbox.traffic_access_token - if isinstance(sandbox.traffic_access_token, str) - else None - ) - - results.append( - SandboxCreateResponse( - sandbox_id=sandbox.sandbox_id, - sandbox_domain=domain, - envd_version=sandbox.envd_version, - envd_access_token=envd_token, - traffic_access_token=traffic_token, - ) - ) + results.append(to_sandbox_create_response(sandbox)) return results