15 KiB
@e2b/python-sdk
2.40.0
Minor Changes
-
6248b12: Remove the deprecatedaccessToken/access_tokenoption and itsE2B_ACCESS_TOKENenvironment fallback. E2B access tokens are no longer accepted for API authentication, so the SDKs no longer resolve one or send it as anAuthorization: Bearerheader — requests authenticate with the API key alone.If you were relying on the option to send a bearer token to a custom deployment, pass the header directly, which is what the deprecation notice already pointed to:
// Before const sandbox = await Sandbox.create({ accessToken: token }) // After const sandbox = await Sandbox.create({ apiHeaders: { Authorization: `Bearer ${token}` }, })# Before config = ConnectionConfig(access_token=token) # After config = ConnectionConfig(api_headers={"Authorization": f"Bearer {token}"})Note that
Sandbox.envd_access_token/traffic_access_tokenare unrelated per-sandbox tokens and are unaffected.
2.39.1
Patch Changes
0d507cd: Restore thehttp2parameter onget_transportandget_envd_transport, which the pyqwest migration dropped in 2.38.0.http2=Falseagain returns a transport pinned to HTTP/1.1, on its own connection pool.
2.39.0
Minor Changes
-
07eb9be: Allow a network rule'stransformto be a callback, so a workload identity token from theiamoption can be injected into egress requests without the SDK ever seeing its value. The callback receives placeholder strings that the egress proxy resolves per request —iam.tokens.awsis${e2b.identity.tokens.aws}on the wire — and referencing a token that is not registered iniam.tokensfails withInvalidArgumentError/InvalidArgumentExceptioninstead of silently sending a placeholder no token will ever replace.updateNetwork/update_networkaccepts the same callbacks, but its payload carries noiamconfig, so token names cannot be checked there and every name resolves to its placeholder.Token names are validated where they are registered and again before they are interpolated: a name cannot be empty or contain
{,}or control characters, since the proxy reads a placeholder up to its first}and a brace in the name would resolve a different token than the one referenced.import { Sandbox, Secret } from 'e2b' const sandbox = await Sandbox.create({ iam: { tokens: { aws: Secret.iamToken({ audience: 'sts.amazonaws.com', tokenType: 'JWT-SVID', }), }, }, network: { allowOut: ({ rules }) => [...rules.keys()], rules: { 'api.internal.example.com': [ { transform: ({ iam }) => ({ headers: { Authorization: `Bearer ${iam.tokens.aws}` }, }), }, ], }, }, })from e2b import Sandbox, Secret sandbox = Sandbox.create( iam={ "tokens": { "aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"), }, }, network={ "allow_out": lambda ctx: list(ctx.rules.keys()), "rules": { "api.internal.example.com": [ { "transform": lambda ctx: { "headers": {"Authorization": f"Bearer {ctx.iam.tokens['aws']}"}, }, }, ], }, }, ) -
64b25bb: Add theiamoption toSandbox.createfor configuring sandbox workload identity, and aSecretclass with aniamToken/iam_tokenmethod for defining the workload tokens. Passing a non-emptytokensmap (name →{ audience, tokenType }) enables workload identity for the sandbox:import { Sandbox, Secret } from 'e2b' const sandbox = await Sandbox.create({ iam: { tokens: { aws: Secret.iamToken({ audience: 'sts.amazonaws.com', tokenType: 'JWT-SVID', }), }, }, })from e2b import Sandbox, Secret sandbox = Sandbox.create( iam={ "tokens": { "aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"), }, }, )Plain
{ audience, tokenType }objects ({"audience": ..., "token_type": ...}dicts in Python) are accepted as token values too.
Patch Changes
11912ff: Build the envd HTTP API client once per syncSandboxand share it across the filesystem, commands, and PTY modules, which now receive it instead of each constructing their own — matchingAsyncSandbox. No behavior change: the pyqwest transport underneath is already cached process-wide per(proxy, for_streaming), so the separate clients shared one connection pool either way.Filesystemstill builds the streaming sibling client whose transport carries the idle read timeout, in both flavors.
2.38.0
Minor Changes
-
b048369: Move the envd HTTP API client (sandbox file transfers, health checks) ontopyqwestvia its httpx-compatible transport adapter. envd RPC already runs on pyqwest throughconnectrpc, so all sandbox traffic now shares one HTTP stack built from the same transport pieces (with separate connection pools per use).The per-thread (sync) and per-loop (async) envd httpx clients are gone: the pyqwest transports are thread-safe and loop-independent, so a single client per module serves all threads and event loops.
Timeout semantics through the adapter:
- Streamed downloads (
files.read(format="stream")): arequest_timeoutset explicitly for the call is the deadline for the whole transfer — by default the transfer is unbounded in total, as before. A stalled stream is reclaimed by a 60-second idle read timeout that resets on every chunk.stream_idle_timeoutkeeps working on the async client (applied per read); the sync client cannot interrupt a blocking read, so it relies on the transport-wide idle bound and now ignores the parameter. - Uploads: a buffered upload is bounded by
request_timeoutas a whole-request deadline, and a streamed (file-like) upload carries no client-side timeout (a stalled one is bounded server-side by envd's idle read timeout) — both matching the JS SDK. - Non-streamed reads (
files.read()as text or bytes) and buffered uploads are bounded byrequest_timeoutfor the whole transfer (default 60 seconds), where the previous transport bounded each socket operation and left total duration unbounded. Reading or writing a file too large to transfer inside the deadline now raiseshttpx.ReadTimeout— pass a largerrequest_timeout(or0to disable), or useformat="stream"/file-like data, for large transfers.
E2B_MAX_CONNECTIONSis no longer read: it configured httpx's global connection cap, and the last transport that took one is gone (reqwest has no counterpart — it does not cap concurrent connections).E2B_KEEPALIVE_EXPIRYandE2B_MAX_KEEPALIVE_CONNECTIONSkeep tuning the pools. - Streamed downloads (
-
a874ced: Move the REST API client (sandbox lifecycle, listing, templates, volumes control plane) ontopyqwest(Rust reqwest/hyper) via its httpx-compatible transport adapter, replacing the httpx-nativeHTTPTransport/AsyncHTTPTransport. The generated httpx client API is unchanged — only the transport underneath is swapped — so logging event hooks, headers, and redirect handling (follow_redirects,response.history) behave as before.One timeout semantics change: through the adapter,
request_timeoutis a deadline for the whole API call, where the previous transports applied it to each phase (connect, read, write) separately — a slow request could exceed it in total. For the REST API's small JSON exchanges this tightening is whatrequest_timeoutreads as promising;0still disables it.Because pyqwest transports are thread-safe and loop-independent (I/O runs on a Rust runtime), the API connection pool is now shared process-wide per proxy, instead of one pool per thread (sync) or per event loop (async), and
ApiClientno longer maintains per-thread/per-loop httpx client caches — a single httpx client serves all threads and event loops. Connection-establishment failures are retried with backoff (E2B_CONNECTION_RETRIES, default 3), matching the connect-only retries of the previous transports. Timeouts keep raisinghttpx.ReadTimeout(anhttpx.TimeoutException), as before, whether they fire while waiting for the response head or while reading the response body, and connection, network, and protocol failures keep raising theirhttpxcounterparts (httpx.ConnectError,httpx.ReadError,httpx.RemoteProtocolError).proxyfor API calls takes a URL string (e.g.proxy="http://user:pass@localhost:8030", scheme http, https, socks5, or socks5h), anhttpx.URL, or anhttpx.Proxy— including its credentials (sent asProxy-Authorization) and any headers configured for the proxy. The onehttpx.Proxyoption pyqwest cannot express, a per-proxyssl_context, raisesInvalidArgumentExceptionrather than being silently dropped.Low-level HTTP logs stay available: where enabling the
httpcorelogger used to show connection-level detail, pyqwest logs one line per request on thepyqwest.accesslogger and request lifecycle records onpyqwest, both atDEBUGand off unless enabled:import logging logging.basicConfig() logging.getLogger("pyqwest.access").setLevel(logging.DEBUG) # DEBUG pyqwest.access - HTTP Request: POST https://api.e2b.app/sandboxes "HTTP/2 201 Created"The SDK's own
loggeroption is unchanged and independent of these.envd traffic is not affected: RPC (commands, PTY, filesystem watch) already runs on pyqwest via
connectrpc, and the envd HTTP API (file transfers, health checks) keeps its httpx transports. -
b3a7c9f: Move template build-context uploads (to S3 presigned URLs) ontopyqwestvia its httpx-compatible transport adapter. Content-Length framing for the streamed archive body is preserved (S3 rejects chunked transfer encoding), and redirects stay with the httpx client instead of being followed inside the transport. The 1-hour upload timeout now bounds the entire upload rather than each socket operation, andverify_ssl=Falseon the client is no longer honored for uploads (pyqwest has no insecure-TLS option). -
458c2c4: Move the volume content client (Volume/AsyncVolumefile operations) ontopyqwestvia its httpx-compatible transport adapter, the same stack the REST API client uses. The connection pool is shared process-wide per proxy instead of one pool per thread (sync) or per event loop (async), and connection-establishment failures are retried with backoff (E2B_CONNECTION_RETRIES, default 3), as before.For streamed volume reads (
Volume.read_file(format="stream")), a stalled stream is by default bounded by a transport-wide idle read timeout of 60 seconds that resets on every chunk (still surfaced ashttpx.ReadTimeout; matches the JS SDK's default stream idle timeout).AsyncVolume.read_filekeeps honoring an explicitstream_idle_timeoutper read (including0to disable); the sync client ignores it — it cannot interrupt a blocking read. Passingrequest_timeoutto a streamed read now bounds the whole transfer rather than individual socket operations.The same whole-transfer semantics apply to non-streamed operations:
read_file(format="text"/"bytes")and uploads are bounded byrequest_timeoutas a total deadline (default 1 hour for file content operations), where the previous transports bounded each socket operation and left total duration unbounded. Pass a largerrequest_timeout(or0to disable) for very large transfers on slow links.
Patch Changes
cab27aa: Kill newly created sandboxes when MCP gateway startup fails. The failure now surfaces asSandboxError(JS) /SandboxException(Python) with aFailed to start MCP gateway: <stderr>message instead of a bare command exit error.
2.37.1
Patch Changes
88f41f3: Align ANSI stripping of template build log messages across both SDKs. The Python SDK'sstrip_ansi_escape_codesnow ports the JS SDK'sstripAnsiregex: OSC sequences (hyperlinks, window titles) are matched non-greedily up to the first string terminator — including sequences spanning newlines — and CSI sequences are stripped without requiring a terminator. Both implementations additionally strip the remaining ECMA-48 string controls (DCS/Sixel, SOS, PM, APC) through their string terminator so control payloads no longer leak into cleaned logs.998e560: Relax the Python SDK'swcmatchrequirement from>=10.1,<11to>=10.1,<12soe2bcan be installed alongside packages that already requirewcmatch>=11(for exampledeepagents>=0.7.0), which previously failed to resolve. The SDK only callsglob.glob()withGLOBSTAR | DOTMATCHfor template context matching; wcmatch 11.0's single breaking change affectstranslate()callers using extended-glob capture groups, so it is a no-op here. The template glob test suite passes against 10.1, 10.2.1 and 11.0.
2.37.0
Minor Changes
2821fb0: Route volume content requests to a team's custom (BYOC) cluster. When a team is connected to a custom cluster, the volume create and get endpoints now return that cluster'sdomain, and the SDK uses it as the destination for volume content requests instead of the defaultapi.<E2B_DOMAIN>host. Teams on the default cluster are unaffected and keep their configured domain.
2.36.0
Minor Changes
1504fbc: AddfromFedoraImage,fromAlpineImage, andfromArchImagebase-image helpers to theTemplatebuilder (from_fedora_image,from_alpine_image,from_arch_imagein the Python SDK), alongside the existingfromUbuntuImage/fromDebianImage/etc. Templates can now start from Fedora, Alpine, and Arch base images (the orchestrator identifies the distro from/etc/os-release). Fedora and Alpine default to pinned tags (fedora:44,alpine:3.24) so builds stay reproducible; Arch defaults tolatestbecause it is a rolling release and provisioning runspacman -Syuregardless.
Patch Changes
6733f36: Align the Python SDK'sfrom_fedora_imageandfrom_alpine_imagedefaults with the JS SDK:fedora:44andalpine:3.24, replacingfedora:42(end-of-life, so its repositories leave the normal mirror network and provisioning can fail) andalpine:3.22. Callers that omit the variant now get the same base image in both SDKs, and both tags are the ones the orchestrator's distro build tests cover. Also corrects the JSTemplateFromImagetype docs, which still named the old defaults.45d2679: Regeneratee2b/sandbox/mcp.pywithdatamodel-code-generator0.64.0: the MCP server option types now use builtin generics (list[str],dict[str, Any]) and are closedTypedDicts, mirroring the spec'sadditionalProperties: false. Raises thetyping-extensionsfloor to>=4.10.0, the first release accepting PEP 728'sclosed.ee0ad25: Update snapshot docstrings to use project terminology instead of team (e.g. "my-project/my-snapshot", project slug)