@e2b/python-sdk@2.39.0
1015 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ce634ab5f2 | [skip ci] Release new versions | ||
|
|
07eb9be196 |
feat(sdk): resolve iam token placeholders in network transform callbacks (#1616)
Stacked on #1606 (`iam-sdk-feature`) — merge that one first. This is the second half of SDK-245: it makes the workload tokens registered by `Sandbox.create`'s `iam` option usable, by letting a network rule's `transform` be a **callback** that receives placeholder strings the egress proxy resolves per request. `iam.tokens.aws` is the literal string `${e2b.identity.tokens.aws}` (the frozen backend spelling — a placeholder can only select a persisted named token, never an inline audience or claim). The SDK never resolves it: the wire payload carries the placeholder and the proxy substitutes a freshly minted JWT-SVID when it forwards the request, so the token value never reaches SDK-side code or the sandbox. Referencing a name that isn't registered in `iam.tokens` fails with `InvalidArgumentError` / `InvalidArgumentException` listing the names that are — the proxy never turns an unregistered name into a token, so a typo would otherwise surface as a confusing auth failure at the destination. `updateNetwork` / `update_network` accepts the same callbacks, but its payload carries no `iam` config, so token names can't be validated client-side there and any name resolves to its placeholder. Static `transform: { headers }` objects keep working unchanged (including hand-written `${e2b.identity.tokens.<name>}` strings, which stay the escape hatch for tokens the SDK doesn't know about). Only `{ iam }` is exposed on the context for now — `${e2b.sandboxId}` / `${e2b.teamId}` / `${e2b.executionId}` from the older prototype are not part of the current backend design, so `sandbox` can be added later when there is something to resolve. ## Usage ```ts import { Sandbox, Secret } from 'e2b' const sandbox = await Sandbox.create({ iam: { tokens: { aws: Secret.iamToken({ audience: 'sts.amazonaws.com', tokenType: 'JWT-SVID' }), }, }, network: { // Only allow egress to hosts that have rules registered. allowOut: ({ rules }) => [...rules.keys()], rules: { 'api.internal.example.com': [ { transform: ({ iam }) => ({ headers: { Authorization: `Bearer ${iam.tokens.aws}` }, }), }, ], }, }, }) ``` ```python 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']}"}, }, }, ], }, }, ) ``` Both send: ```json { "iam": { "tokens": { "aws": { "audience": "sts.amazonaws.com", "tokenType": "JWT-SVID" } } }, "network": { "allowOut": ["api.internal.example.com"], "rules": { "api.internal.example.com": [ { "transform": { "headers": { "Authorization": "Bearer ${e2b.identity.tokens.aws}" } } } ] } } } ``` ## Notes - `allowOut` / `deny_out` selectors run **before** transforms are resolved, so `ctx.rules` still hands back the rules you passed — a rule's `transform` there is the union (object or callback), not the materialized object. The `getInfo` view keeps its own narrowed `SandboxNetworkRuleInfo` type. - Token names are validated where they are registered and again before interpolation: a name cannot be empty or contain `{`, `}` or control characters. The proxy reads a placeholder up to its first `}`, so `a}b` would mint the unrelated token `a` and leave `b}` as literal text, and a `{` in a name can open a second placeholder. The interpolation check is what covers `updateNetwork`, where any name the callback looks up becomes a placeholder without passing through the `iam` config. - Every lookup form on `iam.tokens` is guarded, not just `[name]`: Python's map is a `Mapping` whose `__getitem__` owns resolution (so `.get('typo')` raises instead of returning `None`), and membership (`'aws' in ctx.iam.tokens` / `'aws' in iam.tokens`) answers "is it registered?" without raising so a callback can branch on it. Lookup checks own keys only, so an unregistered name colliding with an object member (`constructor`, `__proto__`) reports as unregistered instead of resolving to a built-in; the four properties the runtime itself reads (`toJSON`, `then`, `toString`, `valueOf`) still resolve normally, so serializing, awaiting or coercing the map does not trip the guard. - A callback must be synchronous and return a plain transform object; a promise (from an `async` callback), an array, a `Map`/`Date`/class instance, or a missing return value is rejected with an actionable error rather than silently creating a rule with no headers. The awaitable is closed/caught so you don't also get an unawaited-coroutine warning or an unhandled rejection. ## Tests New payload-level tests: JS `tests/sandbox/networkTransform.test.ts` (msw), Python `tests/shared/sandbox/test_network_transform.py` — shared rather than mirrored into the sync and async suites, since they only exercise the shared builders. They cover placeholder resolution, enumerating and membership-testing registered tokens, `JSON.stringify` of the context not tripping the guard, static transforms staying byte-identical, `transform: null`, the unregistered-name rejection through both `[name]` and `.get()`, the no-`iam` rejection, non-transform and `async` return values, unusable token names (both braces, a smuggled placeholder, a newline, empty) at registration and on the update path, and the permissive `updateNetwork` path. Verified against production on all three surfaces (JS, sync Python, async Python), where: 1. a static transform carrying `Bearer ${e2b.identity.tokens.aws}` is accepted by `validateNetworkRules` and round-trips through `getInfo` / `get_info` unchanged; 2. the callback-resolved payload reaches the API and is answered with the expected team-gating error (`400: Sandbox IAM workload tokens are not available for your team.`), since `iam` is still feature-flagged; 3. a misspelled or unusable token name is rejected client-side before any request is made. Proxy-side substitution of the placeholder ships separately in belt (EN-1864); until then the header value is forwarded verbatim, which is why there is no end-to-end injection test here. Part of SDK-245. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
64b25bb37b |
feat(sdk): add iam workload identity option and Secret.iamToken helper (#1606)
Implements the sandbox workload identity (IAM) feature from the [infra spec](https://github.com/e2b-dev/belt/blob/main/spec/openapi-infra.yml) (`SandboxIam` / `SandboxIamTokens` / `SandboxIamToken`, already present in the pinned spec and generated clients) across the JS SDK and the sync and async Python SDKs. `Sandbox.create` gains an `iam` option whose non-empty `tokens` map enables workload identity, and a new `Secret` class (exported from both main packages) provides `iamToken` / `iam_token` to define the token values, per the SDK design. The design doc's `filePath` field is deliberately omitted until it lands in the OpenAPI spec, and plain `{ audience, tokenType }` objects are accepted alongside `Secret.iamToken` results. The SDK builds the request body from only the known token fields (stray properties never reach the wire, undefined-valued map entries count as empty) and rejects tokens missing `audience`/`tokenType` (`token_type` in Python) with `InvalidArgumentError` / `InvalidArgumentException`. Covered by request-body tests (msw in JS, `NewSandbox` payload tests in Python) since the backend feature is team-gated; all three surfaces were also smoke-tested end-to-end against production, where the payload is parsed and answered with the expected team-gating error. Fixes SDK-245. ## Usage ```ts import { Sandbox, Secret } from 'e2b' const sandbox = await Sandbox.create({ iam: { tokens: { aws: Secret.iamToken({ audience: 'sts.amazonaws.com', tokenType: 'JWT-SVID' }), }, }, }) ``` ```python from e2b import Sandbox, Secret sandbox = Sandbox.create( iam={ "tokens": { "aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"), }, }, ) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
11912ffa04 |
refactor(python-sdk): share one envd HTTP client across the sync sandbox modules (#1655)
The sync flavor built four envd HTTP clients per sandbox — `Filesystem`, `Commands` and `Pty` each constructed their own — while the async flavor built one in `Sandbox.__init__` and threaded it down; this builds it once on the sync side too and passes it into the three modules. No functional change: `get_envd_transport` already caches the pyqwest transport per `(proxy, for_streaming)` process-wide, so those four clients already shared one connection pool — the cost was a few `httpx.Client` wrappers per sandbox, plus a sync/async divergence that CLAUDE.md and TASTE.md both ask us to avoid. It also clears the last cosmetic differences between the two flavors: async `Commands`/`Pty` swap their `_check_health` lambda closure for the sync side's attribute + method, sync `Commands`/`Pty` drop a write-only `_envd_api_url`, and async `Filesystem` builds its RPC client first to match sync — the three constructor pairs now differ only in the sync/async client and RPC class names. All constructors touched are internal, so there is no public API change and nothing to show as a usage example. Verified with 336 unit tests, 92 sync and 89 async integration tests against prod (`commands`, `pty`, `files`), plus `make lint` and `make typecheck`. Closes [SDK-322](https://linear.app/e2b/issue/SDK-322/python-sdk-share-one-envd-http-client-across-the-sync-sandbox-modules) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6acbeb39ee | [skip ci] Release new versions | ||
|
|
b048369307 |
feat(python-sdk): move the envd HTTP API client onto pyqwest (#1623)
## What Tracked in [SDK-265](https://linear.app/e2b/issue/SDK-265) (part of the [SDK-268](https://linear.app/e2b/issue/SDK-268) stack). Stacked on #1603, at the top of the pyqwest stack (#1601 → #1602 → #1603 → this). Migrate the envd HTTP API client — sandbox file transfers (`files.read`/`write`), health checks — from httpx-native transports to pyqwest via the httpx adapter, and dedupe the transport plumbing that #1558 (envd RPC) and #1601 (REST) each carried a copy of. With this, all Python SDK traffic runs on pyqwest: REST control plane (#1601), envd RPC (#1558, connectrpc), envd HTTP API (this PR); the volume content client (#1602) and template build uploads (#1603) sit below this one in the stack. ## How **Shared plumbing** (first commit): `e2b.api` becomes the canonical home for the proxy narrowing (`proxy_to_config`, with stack-neutral error messages), the pool tuning, and the flavor `ConnectionRetryTransport` + a new `retrying_http_transport(proxy, read_timeout=None)` factory; `e2b.envd.client_sync/client_async` import them instead of defining their own (envd RPC behavior unchanged, pools stay separate — unification is SDK-291). **envd HTTP API** (second commit): - `get_envd_transport(config, for_streaming=False)` returns pyqwest-adapter transports cached per `(proxy, streaming)`; `get_envd_api(config, base_url, for_streaming=False)` builds the httpx client with sandbox headers + logging hooks. The per-thread (sync) / per-loop (async) client caching in `Filesystem`/`Commands`/`Pty`/`AsyncSandbox` is gone — one shared client per module, same rationale as the `ApiClient` simplification in #1601. - **Streamed downloads**: the streaming transport carries a 60s `read_timeout` — an idle bound that resets on every read, capping stalls without limiting total transfer time. It gets a dedicated pool because reqwest's read timer keeps ticking while a request body is sent and while waiting for the response head, so on the shared transport it would cut off uploads and slow unary responses. An explicit `request_timeout` becomes the whole-transfer deadline (adapter semantics) and is sent only when the caller set one; `stream_idle_timeout` stays honored on the async client via `wait_for` per read (so values above 60s work and `0` disables), and is documented as ignored on the sync client, which cannot interrupt a blocking read. Mirrors #1602's volume design. - **Uploads**: buffered uploads keep `request_timeout` as a whole-request deadline; streamed (file-like) uploads carry no client-side timeout and are bounded server-side (envd's idle read timeout) — both exactly the JS SDK's behavior (`getSignal` for buffered, no signal for streams). - **Multipart**: `files=` uploads go out as httpx's `MultipartStream`, which implements *both* `SyncByteStream` and `AsyncByteStream`. The pyqwest 0.7 adapter's sync content conversion matched `AsyncByteStream` first and raised `TypeError("unreachable")` from inside the body iterator, surfacing as a `WriteError` mid-request ("http2 error: stream error sent by user"). Fixed upstream in [pyqwest#196](https://github.com/curioswitch/pyqwest/pull/196), which matches the sync case first — so this PR carries no workaround (the stack requires **pyqwest 0.9**, set in #1601). The regression test stays, now covering the upstream fix. - The stream readers map the transport's idle timeout (builtin `TimeoutError` under pyqwest) to the documented `httpx.ReadTimeout`; `handle_envd_api_transport_exception`'s health-probe path keeps working because the adapter maps HTTP/2 stream resets to `httpx.RemoteProtocolError`. - **RPC logging**: the `LoggingInterceptor` docstring no longer promises its own removal. pyqwest does log requests ([pyqwest#197](https://github.com/curioswitch/pyqwest/pull/197)), but on process-wide `pyqwest`/`pyqwest.access` loggers that can't carry the per-sandbox `logger` and don't see streamed messages or the Connect error code of a stream that fails inside a `200 OK` — so the interceptor stays, with those loggers below it. [pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192), the middleware it referenced, was closed in favor of #197. - **Transports**: rebased onto #1603 on pyqwest 0.9, so the envd HTTP API transports are the stock `PyqwestTransport`/`AsyncPyqwestTransport` (the SDK's adapter subclasses are gone as of #1601 — 0.9 strips the `Host` header and maps timeouts itself) with `follow_redirects=False` and, for the streaming pool, the transport-wide `read_timeout`. ## Testing - Unit: envd transport keying (streaming vs regular vs REST pools), `get_envd_api` wiring (headers, transports), multipart regression through a local server, stream-reader timeout mapping + per-read idle bound (`tests/test_file_stream_reader.py`), rewritten client-lifecycle tests (shared across threads). 236 unit tests green; lint + typecheck green. - Integration against production sandboxes: full `files` suites sync+async (123 tests — these caught the multipart bug), `commands` + `pty` suites both flavors (57 tests). All green. ## Usage example No API changes: ```python sbx = Sandbox.create() sbx.files.write("hello.txt", "hi") # multipart/octet-stream over pyqwest with sbx.files.read("hello.txt", format="stream") as stream: for chunk in stream: # stalls bounded by 60s idle read timeout ... ``` Only visible behavior shift: on the **sync** client, `files.read(..., format="stream", stream_idle_timeout=...)` is now a documented no-op (the transport-wide 60s idle bound applies); the async client honors it as before. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b3a7c9f44a |
feat(python-sdk): move template build-context uploads onto pyqwest (#1603)
## What Stacked on #1602 (which is stacked on #1601). Migrates the **template build-context uploads** (streaming the build archive to S3 presigned URLs in `build_api.upload_file`) onto [pyqwest](https://github.com/curioswitch/pyqwest) via its httpx-compatible transport adapter. Originally deferred from #1601 because S3 presigned URLs reject chunked transfer encoding and Content-Length framing through reqwest was unverified. Verified at the wire level (raw-socket capture server): httpx's Content-Length — derived from the spooled archive (sync) or set explicitly on the async-iterator body (async) — is forwarded by the adapter and reqwest keeps Content-Length framing for streamed bodies, no chunked fallback. > [!NOTE] > Rebased onto #1601, which locks **pyqwest 0.9.0**. Two knock-on changes here: the upload client uses the stock `PyqwestTransport`/`AsyncPyqwestTransport` (0.9.0's adapter subsumes what the SDK's transport subclasses did, so #1601 deleted them), and it builds its proxy from `proxy_to_config(...)` following #1601's rename. ## How - `e2b/template_sync/build_api.py` / `template_async/build_api.py`: `upload_file` uses a one-off pyqwest transport instead of the generated client's httpx transport. - **Redirects stay with the httpx client.** pyqwest 0.9.0 makes reqwest's internal redirect following configurable, so it's turned off on the upload transport: otherwise reqwest would replay the entire archive body against a new location without httpx knowing. The httpx client inherits the API client's `follow_redirects` (off), matching the httpx transport this replaced — so an unexpected hop surfaces as a failed upload rather than a silent re-upload. - `verify_ssl=False` on the generated client is no longer honored for uploads (pyqwest has no insecure-TLS option), and `http2=False` is gone (S3 negotiates HTTP/1.1 via ALPN anyway). - The 1-hour upload timeout now bounds the entire upload rather than each socket write — arguably the intended meaning for that endpoint. ## Testing - `tests/{sync,async}/*/test_upload_file.py` (the #1243 regression tests — Content-Length present and equal to the body, no chunked encoding) pass through pyqwest; the capture handlers now compare header names case-insensitively since hyper lowercases them where httpcore title-cased. - New in both mirrors: `test_upload_file_leaves_redirects_to_httpx` — a 307 on the upload URL surfaces as `FileUploadException` and the capture server sees exactly one PUT, guarding against reqwest silently following the hop and replaying the archive. - Lint (`ruff`), typecheck (`ty`), upload-file suites: green (10/10). ## Usage example No API changes — template builds upload their context exactly as before: ```python from e2b import Template template = Template().from_image("ubuntu:22.04").copy("data/", "/data") Template.build(template, alias="my-template") # archive upload now goes through pyqwest ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
458c2c4362 |
feat(python-sdk): move the volume content client onto pyqwest (#1602)
## What Stacked on #1601. Migrates the **volume content client** (`Volume`/`AsyncVolume` file operations) onto [pyqwest](https://github.com/curioswitch/pyqwest) via its httpx-compatible transport adapter — the same stock httpx transport adapter + connection-retry stack the REST API client uses after #1601. Originally deferred from #1601 because `Volume.read_file(format="stream")` relied on httpx's per-read `read` timeout as an *idle* timeout, which the adapter can't express per request (it converts the httpx timeout dict into a whole-request deadline, and the sync adapter doesn't bound body reads at all). Unblocked by pyqwest's transport-constructor `read_timeout`, which maps to reqwest's `ClientBuilder::read_timeout` — verified behaviorally (local slow-chunk server, sync + async) to be a true per-read idle timeout: it resets after each successful read, covers body reads, and a healthy stream longer than the timeout completes untouched. > [!NOTE] > Rebased onto #1601, which maps `httpx.Proxy` onto pyqwest's `Proxy` object and locks pyqwest 0.9.0. Following that: this PR builds its transport from `proxy_to_config(...)` instead of `proxy_to_url(...)`, uses the stock `PyqwestTransport`/`AsyncPyqwestTransport` (0.9.0's adapter drops the redundant `Host` header and maps pyqwest's timeouts and connection, network, and protocol failures to their httpx counterparts, so the SDK's transport subclasses are gone), and turns reqwest's internal redirects off so httpx owns them, as the generated volume client expects. ## How - `e2b/volume/client_sync/__init__.py` / `client_async/__init__.py` move to the same stock adapter + connection-retry stack as the API client. Caches become process-global, keyed by (proxy, streaming) — previously one pool per thread (sync) / per event loop (async). - Streamed downloads go through a **dedicated streaming transport** with `read_timeout=60s`. It can't live on the shared transport: reqwest's read timer keeps running while a request body is sent and while waiting for the response head (verified empirically — a 2.4 s upload against a 0.5 s `read_timeout` dies mid-send), so a shared `read_timeout` would cut off `write_file` uploads and slow unary responses longer than the idle bound. Uploads and unary calls stay on a transport without it, bounded by their whole-request deadlines as before. - The 60 s default matches the JS SDK exactly: JS bounds stream start by `requestTimeoutMs` (60 s default) and idle gaps by `streamIdleTimeoutMs ?? requestTimeoutMs`; the Python streaming transport's `read_timeout` bounds the response head and each idle gap at 60 s, resetting on every chunk, wire-only (a slow consumer doesn't trip it — verified). - `AsyncVolume.read_file` keeps honoring an explicit `stream_idle_timeout` **per call**, the same way JS honors `streamIdleTimeoutMs` and #1558 bounds stream setup: `asyncio.wait_for` around each read (response head and every chunk). Explicit values run on the *regular* transport, so a value above the 60 s transport bound isn't capped by it and `0` disables idle bounding entirely, restoring the previous contract. The sync client keeps the parameter but **ignores** it — it has no way to interrupt a blocking read into the Rust transport, so its bound must live in the transport. - Streamed reads are sent without a per-request timeout so the adapter imposes no whole-request deadline on long downloads; an explicitly passed `request_timeout` becomes the total-transfer deadline. - A stalled read surfaces as `httpx.ReadTimeout`, keeping the established contract: the 0.9.0 adapter maps its own timeouts, and the async flavor remaps the per-read `stream_idle_timeout` (an `asyncio.wait_for` expiry) to match. - Proxy narrowing follows #1601: `str`, `httpx.URL`, and reducible `httpx.Proxy` values work; inexpressible extras raise `InvalidArgumentException`. ## Testing - `tests/test_volume_client.py` rewritten: process-global transport caching (shared across threads and event loops), streaming vs regular transport separation, plus end-to-end streamed reads through `Volume.read_file`/`AsyncVolume.read_file` against a local chunked server — a healthy stream longer than the idle timeout completes (proves the timeout resets per read), a mid-body stall raises `httpx.ReadTimeout`, a slow response head on a *non-streamed* read is not cut off by the idle bound, and a slow response head on a streamed read is (JS handshake-timeout parity). Async `stream_idle_timeout`: an explicit value aborts a stall, a value above the transport bound isn't capped by it, and `0` disables idle bounding. - Volume content integration tests couldn't run end-to-end (the test team's key gets `403: use of volumes is not enabled`); the mock-transport volume content tests and the local-server stream tests cover that path. - Lint (`ruff`), typecheck (`ty`), unit suite: green. ## Usage example No API changes for the common path: ```python volume = Volume.connect(volume_id, token=token) stream = volume.read_file("big.bin", format="stream") # stalls bounded by the for chunk in stream: # transport-wide idle read ... # timeout (httpx.ReadTimeout) volume.read_file("big.bin", format="stream", stream_idle_timeout=5) # sync: accepted, ignored async_volume = await AsyncVolume.connect(volume_id, token=token) stream = await async_volume.read_file( "big.bin", format="stream", stream_idle_timeout=5 # async: honored per read, ) # 0 disables idle bounding ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a874ced97a |
feat(python-sdk): move the REST API client onto pyqwest's httpx transport adapter (#1601)
## What Migrate all httpx REST API client traffic in the Python SDK — the E2B control plane (sandbox lifecycle, listing, templates, volumes control plane) — to [pyqwest](https://github.com/curioswitch/pyqwest) (Rust reqwest/hyper), using its httpx-compatible transport adapter (`pyqwest.httpx.PyqwestTransport` / `AsyncPyqwestTransport`). The generated openapi client and `ApiClient`/`AsyncApiClient` keep their httpx surface — logging event hooks, per-request timeouts, headers, and redirects behave as before — only the transport underneath is swapped. envd RPC already runs on pyqwest via connectrpc (#1558). This PR touches only the control-plane client; the rest of the stack builds on it: #1623 (envd HTTP API client), #1602 (volume content client), #1603 (template uploads). Requires **pyqwest 0.9** — pinned in `pyproject.toml` (`>=0.9.0,<0.10`) with `uv.lock` refreshed. 0.8 brought the `Proxy` object ([pyqwest#194](https://github.com/curioswitch/pyqwest/pull/194)) and request loggers ([pyqwest#197](https://github.com/curioswitch/pyqwest/pull/197)); 0.9 ([release notes](https://github.com/curioswitch/pyqwest/discussions/214)) folds the two adapter workarounds this PR used to carry into the adapter itself and makes redirect handling configurable, so the SDK no longer subclasses the adapter at all. ## How - `e2b/api/client_sync/__init__.py` / `client_async/__init__.py`: `get_transport` now returns a pyqwest-backed httpx transport — a `SyncHTTPTransport`/`HTTPTransport` (`tls_include_system_certs=True`, proxy, pool tuning mapped from `E2B_KEEPALIVE_EXPIRY`/`E2B_MAX_KEEPALIVE_CONNECTIONS`), wrapped in a `ConnectionRetryTransport` for connect-only retries honoring `E2B_CONNECTION_RETRIES`, wrapped in the stock `PyqwestTransport`/`AsyncPyqwestTransport` httpx adapter. - pyqwest transports are thread-safe and loop-independent (I/O runs on a Rust tokio runtime), so the caches are process-global keyed by proxy — previously one pool per thread (sync) / per event loop (async). - **`ApiClient` sheds its threading machinery**: the `transport_factory`/`async_transport_factory` plumbing, the thread-local `httpx.Client` cache, and the per-loop `WeakKeyDictionary` of `AsyncClient`s are gone. A single lazily-created httpx client (the generated base behavior, the same shape the volume client already uses) serves all threads and event loops; `httpx.Client` is documented thread-safe and nothing below it is loop-bound. Closing that client can't tear down the shared pool — the adapter transports don't override `close()`/`aclose()`. - **Host header** (upstream in 0.9): sending the `Host` header httpx auto-adds on an HTTP/2 connection makes the E2B API edge reset the stream with `PROTOCOL_ERROR` (reproduced with plain pyqwest against `api.e2b.app`); hyper derives `Host`/`:authority` from the URL. The adapter now skips a `host` header matching the URL, so the SDK-side strip is gone — and unlike that strip, a genuinely custom `Host` override is still forwarded. - **Timeout exceptions** (upstream in 0.9): pyqwest raises the builtin `TimeoutError`; the adapter maps it to `httpx.ReadTimeout` both while awaiting the response head and while reading the body, preserving the `httpx.TimeoutException` contract for callers. Connection, network, and protocol failures likewise arrive as `httpx.ConnectError`/`ConnectTimeout`, `httpx.ReadError`/`WriteError`, and `httpx.RemoteProtocolError` instead of leaking pyqwest/builtin types. - **Redirects**: the pyqwest transports are built with `follow_redirects=False` (0.9 made it configurable; reqwest's default is to follow). Otherwise redirects are followed inside the transport, hiding 3xx responses from httpx and leaving `response.history` empty — even though the generated clients ask for no redirect following. httpx owns them again, as with the transports this replaced. - **Proxy**: `proxy=` accepts a URL string, `httpx.URL`, or an `httpx.Proxy` — including its credentials (sent as `Proxy-Authorization`) and any headers configured for the proxy, via pyqwest's `Proxy` object. `proxy_to_config` normalizes all three into a `ProxyConfig` tuple that both keys the transport cache and builds the `pyqwest.Proxy`, so the same proxy URL with different credentials or headers gets its own pool. A per-proxy `ssl_context` has no counterpart and raises `InvalidArgumentException` rather than being silently dropped. (`ProxyConfig` is a `NamedTuple`, not a frozen dataclass: `tests/test_env_var_parsing.py` reloads `e2b.api`, and a dataclass `__eq__` compares class identity, so keys built before and after a reload would silently stop matching.) - **`ProxyTypes` is ours now**: the public type of the `proxy` option (already exported from `e2b`) used to be imported at runtime from httpx's private `_types` module in eleven modules. It is defined there as `Union[str, URL, Proxy]` — exactly the three forms the SDK's two narrowers accept — so it's spelled out once in `e2b.connection_config` and imported from there. Same public name, same type to a type checker, no private-module dependency, and a place for a pyqwest proxy type to land as the remaining transports move off httpx. `e2b.envd.client_shared.proxy_to_url` took a bare `object` while `e2b.api.proxy_to_config` took `Optional[ProxyTypes]`; both now say the same thing. `isinstance` narrowing stays rather than duck-typing `.url`/`.auth` — httpx is a required dependency here (the generated REST client *is* an httpx client, and envd file transfers use httpx directly), so probing attributes would trade a clear `InvalidArgumentException` on a mistyped argument for no dependency savings. - **Request logs**: pyqwest logs one line per request on the `pyqwest.access` logger and lifecycle records on `pyqwest`, both at `DEBUG` — the transport-level diagnostics httpcore used to provide, now that httpcore is out of the path. Noted on `get_transport`; the SDK's own `logger` option is unchanged and sits above it on the httpx client. - **HTTP/2**: negotiated via ALPN for TLS connections (reqwest default), equivalent to the `http2=True` transports this replaces. ## What stays behind (handled by the stacked PRs) - **envd HTTP API client** (file transfers, health checks): #1623, which also dedupes the transport plumbing this PR and #1558 each carry a copy of (the proxy narrowing, pool tuning, retry transport — envd keeps byte-identical duplicates until then). - **Volume content client**: its streaming download relies on httpx's per-read `read` timeout as an *idle* timeout, which the adapter can't express per request — #1602. - **Template build context upload**: one-off httpx client PUTing to S3 presigned URLs — #1603. ## Timeout semantics note `request_timeout` was previously httpx's per-phase timeout (connect/read/write each bounded separately, so a slow multi-phase request could exceed it in total). Through the adapter it becomes an overall deadline per API call (async: headers + body; sync: up to response headers). For the SDK's REST calls — all unary with small JSON bodies — this is a tightening, arguably closer to what `request_timeout` promises. ## Testing - `tests/test_api_client_transport.py` rewritten for the new semantics: global per-proxy transport caching, a single httpx client shared across threads/loops (including 32-way concurrent request tests against a local server), timeout → `httpx.ReadTimeout` mapping for both the response head and a stalled body (slow/stalling local server), redirects surfacing to httpx (302 returned as-is, `response.history` populated when the caller opts in), the connection-only retry policy, `proxy_to_config` conversion, and sync+async round-trips through a real local HTTP server exercising pyqwest end to end. The two host-header unit tests are gone with the subclasses they tested — that behavior is the adapter's now. - Two tests cover the pyqwest proxy/logging surface: an echo server standing in for a proxy asserts that the absolute-form request target, `Proxy-Authorization`, and the extra proxy header actually arrive, and the `pyqwest.access` record is asserted for an API call. - On pyqwest 0.9.0 from PyPI: `uv sync --locked`, unit suite (`tests/*.py`, 238 passed), `ruff check`, `ty check` — all green. - Integration against the production API (real key) was run on 0.8.0: `tests/sync/api_sync`, `tests/async/api_async`, create/kill/timeout/connect — all green. (These initially failed with `RemoteProtocolError: StreamReset` until the host header stopped being forwarded, so they genuinely exercise the new stack; that fix now comes from the adapter.) ## Usage example No API changes for the common path: ```python from e2b import Sandbox sbx = Sandbox.create() # control-plane calls now go through pyqwest Sandbox.list() sbx.kill() ``` Proxy handling — URL strings and `httpx.Proxy` objects work, credentials and proxy headers included: ```python Sandbox.create(proxy="http://user:pass@localhost:8030") # ok (unchanged) Sandbox.create(proxy=httpx.Proxy("http://localhost:8030", auth=("user", "pass"))) # sent as Proxy-Authorization Sandbox.create(proxy=httpx.Proxy("http://localhost:8030", headers={"X-Auth": "t"})) # sent to the proxy Sandbox.create(proxy=httpx.Proxy("https://localhost:8030", ssl_context=ctx)) # raises InvalidArgumentException ``` `ProxyTypes` — already exported from `e2b` — is now defined by the SDK rather than re-exported from `httpx._types`, with the same three members: ```python from e2b import ProxyTypes # Union[str, httpx.URL, httpx.Proxy] ``` Transport-level HTTP logs, replacing the httpcore records this migration removes: ```python import logging logging.basicConfig() logging.getLogger("pyqwest.access").setLevel(logging.DEBUG) Sandbox.create() # DEBUG pyqwest.access - HTTP Request: POST https://api.e2b.app/sandboxes "HTTP/2 201 Created" ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cab27aa6fa |
fix(sdk): clean up sandbox when MCP gateway startup fails (#1548)
## Problem Fixes #1498. `Sandbox.create` allocates a remote sandbox before starting `mcp-gateway`. If gateway startup fails, creation throws before the sandbox object is returned. As a result, the caller has no sandbox ID to clean up, and the orphaned sandbox continues consuming resources until it times out. This state transition exists in synchronous Python, asynchronous Python, and JavaScript/TypeScript. ## Changes - Add a rollback boundary around MCP gateway startup in all three SDK implementations: on failure, best-effort kill the newly allocated sandbox, then re-raise. - Surface gateway startup failure as `SandboxError` (JS) / `SandboxException` (Python) with a `Failed to start MCP gateway: <stderr>` message. Previously the intended message was unreachable dead code — foreground `commands.run` already throws on non-zero exit — so callers got a bare `CommandExitError`/`CommandExitException`. - In async Python, re-raise `asyncio.CancelledError` from the best-effort `kill()` so caller cancellation (e.g. `asyncio.timeout`) is honored; only ordinary cleanup failures are suppressed and never mask the original error. - Add integration coverage for synchronous Python, asynchronous Python, and TypeScript. The tests pin the sandbox to the base template (which has no `mcp-gateway` binary) so gateway startup genuinely fails after allocation. - Add a patch changeset for `e2b` and `@e2b/python-sdk`. ## Usage Behavior No API changes. A failed creation no longer leaves a sandbox behind, and the error is now descriptive: ```ts try { const sandbox = await Sandbox.create({ mcp: { ... } }) } catch (err) { // err is SandboxError: "Failed to start MCP gateway: <stderr>" // the allocated sandbox has already been killed — no orphan is left running } ``` ## Validation All three integration tests verified against real infra: creation rejects with the documented error and no sandbox remains. ## Notes Supersedes #1547 by @hxaxd (squash-merged into this branch to preserve attribution). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: 苏紫辰 <155808914+hxaxd@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2d2823c94a | [skip ci] Release new versions | ||
|
|
88f41f3927 |
fix(python-sdk): port current JS stripAnsi regex to strip_ansi_escape_codes (#1545)
## Summary The Python SDK's `strip_ansi_escape_codes` (used to clean template build log messages) still used the old ansi-regex pattern, while the JS SDK's `stripAnsi` was rewritten in #895. This ports the current JS regex to Python so both SDKs clean logs identically: OSC sequences (hyperlinks, window titles) are matched non-greedily up to the first string terminator — including content spanning newlines — and CSI sequences are stripped without requiring a terminator. Following review feedback, both implementations now also strip the remaining ECMA-48 string controls — DCS (Sixel, tmux passthrough), SOS, PM, and APC — through their string terminator, so control payloads don't leak into cleaned logs. This goes beyond upstream `chalk/ansi-regex`, click, and Rich, none of which fully strip DCS payloads, and restores what the old Python pattern handled. Also mirrors the Python test suite into the JS SDK (which previously had no `stripAnsi` tests) — 20 identical cases per side — and verified byte-for-byte identical output between the two implementations on all of them. Includes a patch changeset for `e2b` and `@e2b/python-sdk`. ## Example Log messages that previously leaked OSC or DCS sequences into template build output are now cleaned: ```python from e2b.template.utils import strip_ansi_escape_codes strip_ansi_escape_codes("\x1b]8;;https://e2b.dev\x07E2B\x1b]8;;\x07") # "E2B" strip_ansi_escape_codes("\x1b]0;title\nstill title\x07done") # "done" strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") # "RED" strip_ansi_escape_codes("\x1bPq#0;2;0;0;0~~@@\x1b\\image") # "image" (Sixel DCS) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
998e560a1a |
fix(python-sdk): relax wcmatch constraint to >=10.1,<12 (#1638)
|
||
|
|
7a1fe4528c | [skip ci] Release new versions | ||
|
|
2821fb0b69 |
feat(sdk): route volume content to BYOC cluster domain (#1634)
When a team is connected to a custom (BYOC) cluster, the volume API now returns that cluster's domain in the create and get responses. The JS and Python (sync + async) SDKs use this domain as the destination for volume content requests instead of the default api.<E2B_DOMAIN> host, falling back to the configured domain when none is returned. The domain field is read defensively from the response until spec/infra-ref is bumped to the infra commit that adds it and `make codegen` regenerates the typed schema. Claude-Session: https://claude.ai/code/session_01212WCmNz1prPKrjhTv2PDj --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Matt Brockman <matt.brockman@e2b.dev> |
||
|
|
9ef3f1dbbe | [skip ci] Release new versions | ||
|
|
2df7651ee6 |
test(sdk): run firewall transform tests against an httpbin sidecar sandbox (#1631)
Follow-up to #1632, which added the template this depends on. Now rebased onto `main`, so this is just the test change. ## Problem The firewall transform tests asserted header injection by curling `httpbin.e2b.team`, an externally hosted service the suite had to keep alive. ## Fix Starts a sidecar sandbox from the `httpbin` template instead: the rule is keyed on the sidecar's `getHost(8080)` and the assertion reads the injected header back from `/headers`, in the JS, sync Python, and async Python suites. The sidecar's ready command has already passed by the time `create` resolves, so the server is serving and no readiness polling is needed. The template name lives in one fixture per SDK — `httpbinTemplate` in `tests/template.ts` and the `httpbin_template` fixture in `conftest.py`. Also drops two comments merged in #1632 that claimed the tests spawn `e2b/httpbin`. The bare alias is what resolves, same as `base` — the team slug only appears in the display name. ⚠️ Do not merge before **Build and push prepared templates** has been dispatched with `template: httpbin` — the tests resolve the template by name and fail until it exists on the E2B team. Verified against production: all three tests pass with the injected header reflected by the sidecar, spawning the template by its bare alias with a key that owns it — the same situation as CI. SDK-304 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
45d26792f1 |
chore(deps-dev): bump datamodel-code-generator from 0.34.0 to 0.64.0 in /packages/python-sdk in the uv group across 1 directory (#1621)
> [!NOTE]
> Manual follow-up commit on top of Dependabot's bump (addressing review
feedback): the codegen image pin was out of sync, so this PR also bumps
it and carries the regenerated output.
### Manual changes on top of the bump
- `codegen.Dockerfile` bumped from `datamodel-code-generator==0.34.0` to
`0.64.0`. `pyproject.toml`'s `codegen` group and the Dockerfile must
stay in sync (the comment above the group says so) — CI's `Generated
files` check regenerates from the image, so leaving the image at
`0.34.0` would make `make init` produce output CI rejects.
- `packages/python-sdk/e2b/sandbox/mcp.py` regenerated with `0.64.0`.
Two output changes:
- builtin generics (`list[str]`, `dict[str, Any]` instead of
`List`/`Dict`), fine on the SDK's `>=3.10` floor;
- `additionalProperties: false` in `spec/mcp-server.json` is now honored
as PEP 728 `closed=True` (`0.34.0` silently dropped it), and `TypedDict`
is imported from `typing_extensions` accordingly.
- `typing-extensions>=4.1.0` → `>=4.10.0`. `closed=True` is evaluated at
class-creation time, i.e. on `import e2b`, and 4.10.0 is the first
release whose `TypedDict` accepts the keyword (4.9.0 raises `TypeError:
_TypedDictMeta.__new__() got an unexpected keyword argument 'closed'`).
- Changeset added (`patch` for `@e2b/python-sdk`), since the regenerated
file and the dependency floor ship to users.
Nothing changes for callers at runtime — `McpServer` is still a plain
dict at the call site:
```python
from e2b import Sandbox
sbx = Sandbox.create(mcp={"duckduckgo": {}, "brave": {"braveApiKey": "..."}})
```
The `closed` types are also inert for type checkers in practice, because
the public `McpServer` is `Union[BaseMcpServer, GitHubMcpServer]` and
the second arm is a `Dict[str, ...]`. Verified: `pyright` and `mypy`
both clean against the snippet above, `ruff`/`ty`/`pnpm typecheck`
clean, 283 offline Python unit tests pass, and regenerating with the
full pinned toolchain (`python:3.10` + `black==26.3.1` + the other
Dockerfile pins) reproduces the committed file byte-for-byte.
---
Bumps the uv group with 1 update in the /packages/python-sdk directory:
[datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator).
Updates `datamodel-code-generator` from 0.34.0 to 0.64.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/releases">datamodel-code-generator's
releases</a>.</em></p>
<blockquote>
<h2>0.64.0</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python < 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List["Node"]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/blob/main/CHANGELOG.md">datamodel-code-generator's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.64.0">0.64.0</a>
- 2026-06-14</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python < 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List["Node"]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/53a25ab8ddb132ac68a2795247fc855b8f445d84"><code>53a25ab</code></a>
Fast path schema output (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3410">#3410</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ee2087f32e6100f5c3642e7ea8506aa38e9df26c"><code>ee2087f</code></a>
Skip discriminator import scan (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3411">#3411</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bdf5ddfc27f94a06ba8d289759193bb09daadd34"><code>bdf5ddf</code></a>
fix: quote self-referencing fields when --disable-future-imports is set
(<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ad4ec877fa6708baebdaaf820171d24bfe5bf0cb"><code>ad4ec87</code></a>
Cache payload validation strategies (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3409">#3409</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b191d52a0a1d83edeac9553119f70b2f5c131126"><code>b191d52</code></a>
Shard Python tests (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3408">#3408</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/29dd6d74c95dd7799d51f2c707db24862809eb23"><code>29dd6d7</code></a>
Cache parsed sources (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3407">#3407</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/93e2fe3cf5774d5e4d2083fac365e5bcbf0a647a"><code>93e2fe3</code></a>
Defer generation refresh (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3406">#3406</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bb01d9c628f9077cc5dd72320ae60a18abd5b790"><code>bb01d9c</code></a>
Lazy root format exports (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3405">#3405</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/48237ed8c3af3bb58b5e6b274925ebb646412d3a"><code>48237ed</code></a>
Fast path JSON schemas (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3404">#3404</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b21d106c88ac22f137cd4562389ad95a50c2e912"><code>b21d106</code></a>
Slot generation facts (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3403">#3403</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/koxudaxi/datamodel-code-generator/compare/0.34.0...0.64.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/e2b-dev/E2B/network/alerts).
</details>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
6733f36755 |
fix(sdk): align Python Fedora/Alpine image defaults with JS (#1625)
Python `from_fedora_image` defaulted to `fedora:42` (end-of-life) and `from_alpine_image` to `alpine:3.22`, while JS already pinned `fedora:44`/`alpine:3.24` — the same call produced a different base image per SDK. Aligns Python; also fixes the JS type docs, which still named the old defaults. ```python Template().from_fedora_image() # fedora:44 (was fedora:42) Template().from_alpine_image() # alpine:3.24 (was alpine:3.22) ``` Follow-up to #1612; both defaults are still unreleased. |
||
|
|
1504fbc843 |
SDK: fromFedoraImage/fromAlpineImage/fromArchImage helpers (#1612)
## What Adds the missing non-Debian base-image convenience helpers to **both SDKs**, mirroring the existing `fromUbuntuImage`/`fromDebianImage`/`fromPythonImage`/`fromNodeImage`/`fromBunImage`: - **JS/TS** (`packages/js-sdk`): `fromFedoraImage(variant?)`, `fromAlpineImage(variant?)`, `fromArchImage(variant?)` + unit tests - **Python** (`packages/python-sdk`): `from_fedora_image(variant)`, `from_alpine_image(variant)`, `from_arch_image(variant)` + sync/async unit tests ## Why This is the **customer-facing half** of infra **#3381** (distro-aware template provisioning). The engine now builds + boots Ubuntu/Debian/Fedora/RHEL-family/Arch/Alpine on real KVM; before this PR the SDK exposed distro helpers for the Debian family only, so Fedora/Alpine/Arch were reachable only via the generic `fromImage()`. These give them first-class parity. ## Verification (honest) - **New helper unit tests pass locally** — JS `fromDistroImages.test.ts` → 6/6 green (`vitest`, no auth). Python `test_from_distro_images.py` (sync + async) committed. - **Full integration suite**: requires E2B API keys — fails locally with `AuthenticationError` **identically on `main`** (215/187/29), i.e. **zero regression** from this change; CI runs it with secrets. - Lint scoped to the touched files. ## Not in this PR The public **docs** still state *"only Debian-based images … Alpine/RedHat not supported"* — but that text lives in **`e2b-dev/docs`**, not this monorepo, so it's a **separate docs PR** (being opened against `e2b-dev/docs`). Flagging so this + that land together. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
ee0ad25117 |
docs(sdk): rename team to project in snapshot docstrings (#1562)
Renames team → project terminology in the JS and Python SDK snapshot docstrings: the `list_snapshots`/snapshot list `name` filter example now reads `"my-project/my-snapshot"`, and `SnapshotInfo.names` is documented as "including project slug and tag (e.g. project-slug/my-snapshot:v2)". Documentation-only — no exported names, runtime behavior, or wire protocol change; generated API clients and `spec/openapi.yml` are intentionally untouched until the backend exposes project-named endpoints. Includes a patch changeset for `e2b` and `@e2b/python-sdk`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
59c6996a1e | [skip ci] Release new versions | ||
|
|
4fcf7cb150 |
feat: sync API specs from infra and belt with Copybara (#1564)
The specs in `spec/` were copied from their source repos by hand and had
drifted ~2,400 lines behind infra, so they are now imported with
Copybara (`copy.bara.sky`, run in a pinned Docker image by
`scripts/fetch-spec.sh`): `make codegen` re-fetches them at the commits
pinned in `spec/infra-ref` and `spec/belt-ref` before generating, and
the generated-files CI check fails if the tracked copies don't match the
pins. Regenerating from the current pins picks up the accumulated spec
changes in the generated JS/Python clients (renamed request schemas,
`SandboxNetworkConfig`, `SandboxIam` workload identity,
`FILE_TYPE_SYMLINK`, access-token auth deprecation, volume path-metadata
tweaks). The one handwritten SDK change follows from that: the public
`FileType` enums gain a `SYMLINK` member (JS and both Python surfaces)
so entries envd reports as symlinks show up in `files.list()` and
`getInfo()`/`get_info()` instead of being silently skipped as unknown
types. The custom `spec/remove_extra_tags.py` tag-filtering script is
replaced by Redocly CLI's `filter-in` decorator (`redocly.yaml`), which
produces identical generated JS output; a `filter-out` decorator
additionally drops any operation or component schema the upstream specs
mark `x-not-implemented: true` (currently the SOCKS5
`SandboxEgressProxyConfig`/`egressProxy` surface, which infra flagged as
spec-only); each SDK's bundle now goes to its own gitignored
`spec/openapi_generated.<api>.yml` instead of both pipelines overwriting
one shared file; Python client models now list fields in spec order
instead of alphabetical (mechanical reordering only — construct models
with keyword args). Spec fetches try whatever GitHub token is available
and fall back to the tracked copies with a warning (the public infra
specs also fetch anonymously); in CI a short-lived belt-scoped token is
minted from the org-wide Autofixer GitHub App (no new secrets), so fork
PRs simply fall back for the belt spec; the CI workflows also cache the
Copybara image alongside the codegen image, and the previously ignored
`CODEGEN_IMAGE` env is honored by the Makefile.
## Usage
```sh
# update the specs: bump a pin, then regenerate
echo <infra-commit-sha> > spec/infra-ref
make codegen
# fetch a single spec without regenerating
pnpm fetch:api-spec # spec/openapi.yml from infra
pnpm fetch:envd-spec # spec/envd/ from infra
pnpm fetch:volume-spec # spec/openapi-volumecontent.yml from belt
# try the latest spec without touching the pin
E2B_INFRA_REF=main pnpm fetch:api-spec
# change which endpoint tags an SDK exposes
$EDITOR redocly.yaml && make codegen
```
```ts
// symlinks are now visible in the filesystem API (JS; same shape in Python)
const entries = await sandbox.files.list('/home/user')
const link = entries.find((e) => e.type === FileType.SYMLINK)
console.log(link?.symlinkTarget)
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
3f46d56026 |
fix(sdk): select stack-trace frames by SDK boundary instead of fixed depth (#1599)
## Description Template build stack traces were captured by walking a fixed number of frames (`STACK_TRACE_DEPTH` plus `±1` arithmetic at ~15 call sites), which broke whenever the frame count between `new Error()` and user code shifted — TS class-field initializer frames (#1539) and Bun's tail-call frame elision were both this bug. This PR makes two related changes: 1. **Boundary-based frame selection.** The caller's frame is now the first one whose file lies outside the SDK package, making extra transpiler frames and elided delegating frames irrelevant. In the JS SDK, frame parsing is delegated to `error-stack-parser-es` (ESM-only, so it's a devDependency inlined into both dist formats via tsdown `noExternal` — the engines range includes Node versions without `require(esm)`); the Python SDK equivalently walks `f_back` until `co_filename` leaves the `e2b` package root, in the shared builder used by both sync and async. If no user frame is identifiable (e.g. the SDK is bundled into the caller's own file), capture degrades to no trace rather than a wrong frame. 2. **Dead machinery removed.** Because boundary capture resolves through SDK-internal delegation (`remove()` → `runCmd()`, `fromDockerfile()` → parser) to the user's call site on its own, the suppress/override collection machinery (`runInNewStackTraceContext`, `runInStackTraceOverrideContext`, the enabled/override flags, and their Python equivalents) became redundant and is removed — superseding the approach in #1596. Error `.stack` synthesis (keeping the `Name: message` header and the throw site on `cause`) was prototyped here and backed out — it will come as a follow-up PR. ## Usage No API changes — build errors now point at the user's call site regardless of runtime or transpiler: ```ts const template = Template() .fromBaseImage() .runCmd('./does-not-exist') // ← build failures point exactly here await Template.build(template, 'my-template') ``` ## Testing - JS: `unit` + `template` vitest projects green against the real API (incl. 27 per-method stacktrace tests pinning exact call-site line/columns, `bunInstall` now covered); edge-compat bundle test and CLI build verified; built CJS/ESM dists smoke-tested with `require()`/`import()`. - Python: all 184 template tests green (shared + sync + async, incl. both `test_stacktrace.py` suites, `bun_install` now covered); `ruff` and `ty` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00253c39cc |
feat(python-sdk): migrate envd RPC to the official connectrpc client (#1558)
Replaces the vendored `e2b_connect` client and the custom Go `protoc-gen-connect-python` plugin with the official Connect RPC client for Python ([`connectrpc`](https://github.com/connectrpc/connect-py), transport: `pyqwest`/Rust hyper), and switches the envd messages from Google's `protobuf` runtime to Buf's [`protobuf-py`](https://github.com/bufbuild/protobuf-py) (which `connectrpc` already requires) — the SDK no longer depends on the conflict-prone `protobuf` package at all, and the protoc binary drops out of the codegen image. The wire format (same protos, same JSON) is unchanged. Closing a command or watch stream early now sends `RST_STREAM`, fixing abandoned streams leaking on the shared HTTP/2 connection, and peer resets surface as typed `ConnectError`s. The plumbing mirrors the `e2b.api` layout: shared pieces (a JSON codec that ignores unknown response fields, proxy narrowing, pool tuning) live in `e2b/envd/client_shared.py`, the flavor-specific pyqwest transports (wrapped in pyqwest's retry middleware, see the retry note below) and `create_rpc_client` factories in `e2b/envd/client_sync/` and `e2b/envd/client_async/`, and the default-header/logging interceptors in `e2b/envd/interceptors.py`; `e2b/envd/rpc.py` maps `connectrpc` error codes onto the existing SDK exceptions, so the public API is unchanged (`sandbox.commands.run(...)`, `files.watch_dir(...)`, etc. work exactly as before). The REST API and file upload/download keep using `httpx`. The `proxy` connection option now applies to sandbox RPC calls too — [pyqwest 0.7.0](https://github.com/curioswitch/pyqwest/releases/tag/v0.7.0) added an httpx-style `proxy` parameter to its transports, so commands, PTY, and filesystem watch traffic follow the same proxy as the REST API and file transfers (an earlier revision of this PR could only fall back to `http_proxy`/`https_proxy` env vars for RPC): ```python sandbox = Sandbox.create(proxy="http://user:pass@localhost:8030") # REST *and* RPC (commands, PTY, watch) traffic goes through the proxy result = sandbox.commands.run("echo through-the-proxy") ``` Notes: - `e2b_connect` is no longer shipped in the wheel; code importing it directly should switch to `connectrpc` (`ConnectError`, `Code`) — SDK exception types are unchanged. - The generated `e2b.envd.*.*_pb2` modules are replaced by `protobuf-py` equivalents (`process_pb`, `filesystem_pb`) with a different message API (`Oneof` objects, `has_field`); these are internal modules — `e2b-code-interpreter` and `e2b-desktop` were verified not to import them. - RPC transports are cached per proxy URL. `httpx.URL` and `httpx.Proxy` proxies keep working for RPC calls when they reduce to a proxy URL (`httpx.Proxy` auth is folded back into the URL userinfo); `httpx.Proxy` extras that pyqwest can't express — custom headers, an `ssl_context` — raise `InvalidArgumentException` rather than being silently dropped. - Plain (non-Connect-encoded) HTTP error responses — an edge proxy or gateway answering for envd — keep the vendored client's status mapping even when they carry a JSON body that isn't a valid Connect error (e.g. a gateway's `{"code": 429}` raises `RateLimitException`, not a misleading sandbox-timeout); only JSON bodies with a valid Connect `code` string are left to connectrpc to parse. An envd response that fails to decode surfaces as a `SandboxException` with a clear message — the SDK's JSON codec raises a typed `ConnectError(INTERNAL)` at the source (connectrpc re-raises codec-raised `ConnectError`s unchanged), rather than the error being reconstructed from `__cause__` heuristics in the exception mapper. - pyqwest 0.7.0 explicit transports default to an **empty TLS root store** (0.6.2 used reqwest's defaults), so the envd transports pass `tls_include_system_certs=True`; the dependency floor is `pyqwest>=0.7.0` accordingly. - Connection retries (`E2B_CONNECTION_RETRIES`, default 3) use pyqwest's transport-level retry middleware (`pyqwest.middleware.retry`), narrowed to retry only the builtin `ConnectionError` — raised solely while establishing the connection, before the request could have reached envd — with exponential backoff. A retry can therefore never replay a delivered request, for unary and streaming RPCs alike; the previous stack's replay of unary calls whose connection dropped mid-request is dropped deliberately, since it could re-execute a delivered call (e.g. `SendInput`). Pinned by unit tests plus end-to-end tests driving the generated stubs through the middleware (`tests/test_envd_retry_transport.py`). - For async streaming calls (`commands.run`/`connect`, PTY, `watch_dir`), `request_timeout` bounds opening the stream — the wait until envd confirms with a start event, matching the JS SDK's `requestTimeoutMs` — raising `TimeoutException` and cancelling the HTTP/2 stream when exceeded (pinned frame-level in `tests/test_envd_stream_reset.py`). The running stream stays bounded by the command/watch `timeout`. The sync SDK cannot interrupt its blocking wait, so `request_timeout` is not applied to sync stream setup — both setup and the running stream are bounded by `timeout` (unlimited when `0`). - The RPC logging interceptor was upstreamed to pyqwest as a logging middleware ([curioswitch/pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192)); the SDK keeps its own `LoggingInterceptor` until that merges and ships in a release the SDK can depend on. - `pyqwest` ships binary wheels for manylinux/musllinux (x86_64, aarch64), macOS arm64 + x86_64 (Intel wheels landed in 0.7.0), Windows x64, and PyPy. - The `RST_STREAM`-on-early-close behavior is pinned by frame-level regression tests (`tests/test_envd_stream_reset.py`): a plaintext HTTP/2 server records the frames the real generated clients (with the SDK's codec and interceptors) send — early close via `disconnect()`, close through the logging interceptor, and abandoning the stream must all send `RST_STREAM(CANCEL)`; normal completion must send none (sync + async). - `E2B_MAX_CONNECTIONS` no longer applies to sandbox RPC traffic: reqwest's pool bounds only idle connections per host (`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`), not the total number of open connections. It still applies to the REST API and file transfers. - The sync sandbox modules build one RPC client each and share it across threads — the connectrpc sync client is stateless per call over the process-global transport (verified with a 16-thread frame-level test); only the httpx envd API clients stay per-thread with their transports. - Also fixes numeric env-var parsing (`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`, `E2B_MAX_CONNECTIONS`, `E2B_CONNECTION_RETRIES`): an empty-string value now falls back to the default instead of raising `ValueError` at import time. |
||
|
|
50de0af442 | [skip ci] Release new versions | ||
|
|
95e4dc2832 |
feat(sdk): add sandbox fork to JS and Python SDKs (#1554)
## Summary
Adds SDK support for the new `POST /sandboxes/{sandboxID}/fork` endpoint
(e2b-dev/infra#3202): checkpoint a running sandbox in place (briefly
paused, snapshotted with full memory state, and resumed — its ID and
expiration stay untouched) and boot `count` new sandboxes from that
snapshot.
- **spec**: adds `SandboxForkRequest` / `SandboxForkResult` schemas and
the `/sandboxes/{sandboxID}/fork` path (mirroring the infra spec); JS
and Python API clients regenerated via `make codegen`.
- **js-sdk**: `sandbox.fork(opts)` instance method and
`Sandbox.fork(sandboxId, opts)` static method. Returns
`Promise<Array<Sandbox | Error>>` — one entry per requested fork, each
either a connected `Sandbox` instance or an `Error` describing why that
fork failed to start (`Promise.allSettled`-style, matching the per-fork
results of the API). Per-fork error codes go through the same code→class
mapping as other API errors (extracted from `handleApiError` into
`apiErrorFromCode`), so e.g. a per-fork 429 (sandbox limit) surfaces as
`RateLimitError`. `SandboxForkOpts` extends the full `ConnectionOpts`
(like `SandboxConnectOpts`), so `proxy`, `logger`, `apiUrl`, etc. work
with fork-by-ID. `timeoutMs` defaults to 5 minutes like
`create`/`connect`; `count` defaults to 1 and is validated client-side
(`InvalidArgumentError` for `count < 1`); a whole-request 404 maps to
`SandboxNotFoundError` (the source sandbox is the missing resource —
same semantics as `pause`/`connect`/`setTimeout`), carrying the API
error message when present; per-fork 404 error codes map to generic
`NotFoundError` (the missing resource is fork-internal, e.g. the
snapshot).
- **python-sdk**: `sandbox.fork(timeout=..., count=...)` /
`Sandbox.fork(sandbox_id, ...)` and the `AsyncSandbox` equivalents (same
`@class_method_variant` instance/static pattern as `connect`/`pause`),
returning `List[Union[Sandbox, Exception]]`. Per-fork errors map through
the shared `api_exception_from_code` (extracted from
`handle_api_exception`). `timeout` is in seconds per Python SDK
convention; an explicit `timeout=0` is preserved. Whole-request 404
raises `SandboxNotFoundException`; per-fork 404 codes map to generic
`NotFoundException`.
- **changesets**: minor bumps for `e2b` and `@e2b/python-sdk`.
## Usage
JS:
```ts
const sandbox = await Sandbox.create()
const [fork1, fork2] = await sandbox.fork({ count: 2, timeoutMs: 60_000 })
if (fork1 instanceof Sandbox) {
await fork1.commands.run('echo "hello from fork"')
}
// or by ID
const forks = await Sandbox.fork(sandbox.sandboxId, { count: 2 })
```
Python (sync / async):
```python
sandbox = Sandbox.create()
fork1, fork2 = sandbox.fork(count=2, timeout=60)
if isinstance(fork1, Sandbox):
fork1.commands.run('echo "hello from fork"')
# or by ID
forks = Sandbox.fork(sandbox.sandbox_id, count=2)
```
```python
sandbox = await AsyncSandbox.create()
fork1, fork2 = await sandbox.fork(count=2)
```
## Notes
- The JS option is named `timeoutMs` (milliseconds) to match
`SandboxOpts.timeoutMs` / `SandboxConnectOpts.timeoutMs`; the API
receives seconds via `timeoutToSeconds` as elsewhere.
- Failed forks are returned as error **values** in the array rather than
rejected promises, so a partial failure doesn't throw away the
successful forks and there are no unhandled-rejection hazards. A
per-fork error message includes the API error code only when the API
returned one.
## Test plan
- [x] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` pass at
the repo root (`ty` diagnostics identical to baseline)
- [x] Offline tests pass: `count < 1` → `InvalidArgumentError` /
`InvalidArgumentException` in JS, Python sync, and Python async;
`handleApiError` suite passes after the `apiErrorFromCode` extraction
(plus a behavior-parity check of the Python `handle_api_exception`
refactor)
- [ ] Integration tests (single fork with FS state inheritance +
independence, multi-fork with unique IDs, fork-by-ID, fork of killed
sandbox → `SandboxNotFoundError`) are written but currently fail against
prod with 404 because the fork endpoint (e2b-dev/infra#3202) is not
deployed yet — they should pass once it lands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8c87016a57 | [skip ci] Release new versions | ||
|
|
2c77fc00bb |
feat(sdk): add name filter to snapshot list (#1523)
Adds an optional `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`, mirroring the infra snapshots list endpoint ([e2b-dev/infra#3184](https://github.com/e2b-dev/infra/pull/3184)). The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`); unknown names return an empty list. It's a flat top-level option alongside the existing `sandboxId` filter (non-breaking) and can be combined with it — the backend applies both with AND, matching the `metadata`+`state` behavior of `Sandbox.list()`. Applied equivalently across the OpenAPI spec, generated clients, and the JS + Python sync/async SDKs, with tests and a changeset. ## Usage ```ts // JS/TS const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' }) const snapshots = await paginator.nextItems() // combine filters (snapshots from a sandbox matching a name) Sandbox.listSnapshots({ sandboxId: 'sandbox-id', name: 'my-snapshot' }) ``` ```python # Python (sync) paginator = Sandbox.list_snapshots(name="my-snapshot") snapshots = paginator.next_items() # Python (async) paginator = AsyncSandbox.list_snapshots(name="my-snapshot") snapshots = await paginator.next_items() ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
78a91ab72f | [skip ci] Release new versions | ||
|
|
7474d904a2 |
fix(python-sdk): correct inverted no_install_recommends docstring (#1533)
Promotes the merged #1532 (by @anxkhn) from the staging branch `fix/no-install-recommends-docstring` into `main`. `TemplateBuilder.apt_install()` documents its `no_install_recommends` parameter as "Whether to install recommended packages", but the generated command does the opposite. In `packages/python-sdk/e2b/template/main.py` the command adds apt-get's `--no-install-recommends` flag when the argument is `True`: ```python f"... apt-get install -y {'--no-install-recommends ' if no_install_recommends else ''}..." ``` `--no-install-recommends` tells apt to *skip* recommended packages, so `no_install_recommends=True` skips them rather than installing them. A user who follows the docstring gets the inverse of the documented behavior. The parameter name and apt-get's own semantics confirm the code is correct and the docstring was wrong; this rewords the docstring line to match the real behavior. The `--no-install-recommends` flag was introduced in #983; the docstring has been inverted since then. This is Python-only. The JS twin `aptInstall` applies the same flag but has no per-parameter JSDoc for `noInstallRecommends` (it appears only inside an `@example`), so there is nothing contradictory to fix on the JS side. There is a single Python definition (no sync/async mirror for the template builder). No behavior change; documentation-only, plus a `@e2b/python-sdk: patch` changeset. ### Usage ```python from e2b import Template template = Template().from_image("ubuntu:22.04") # Install recommended packages as well (apt-get default): template.apt_install("vim") # Skip recommended packages (adds apt-get's --no-install-recommends): template.apt_install("vim", no_install_recommends=True) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> Co-authored-by: Anas Khan <anxkhn28@gmail.com> |
||
|
|
99e536f6eb |
fix(python-sdk): stop leaking per-call proxy pools in volume content clients (#1534)
The Python volume content client factories passed both `proxy` and the shared cached `transport` to httpx, so with a proxy configured (e.g. `Volume.connect(volume_id, proxy="http://user:pass@127.0.0.1:8080")`), every volume operation mounted a fresh, never-closed proxy transport that bypassed the cached connection pool. The client-level `proxy` argument is now dropped — the proxy is already baked into the cached transport, so proxied requests keep working but reuse one pooled transport per thread/event loop. The volume transports also gained connect-level retries (`E2B_CONNECTION_RETRIES`, default 3), matching the core API and envd transports. Includes a changeset for a `@e2b/python-sdk` patch release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dbc6bfa161 | [skip ci] Release new versions | ||
|
|
09e12b3f65 |
feat(sdk): set-once integration attribution via ConnectionConfig.setIntegration (#1524)
Replaces the per-call `integration` connection option with a set-once, process-wide setter — `ConnectionConfig.setIntegration()` in JS and `ConnectionConfig.set_integration()` in Python — so integrations wrapping the SDK tag themselves once at startup and every request carries the identifier in the `User-Agent` header, with no threading through individual SDK calls. The setter is internal and hidden from generated docs; the `integration` option is removed from `ConnectionConfigOpts` (kept as a deprecated alias of `ConnectionOpts`) and from the Python constructor, and the round-trip machinery from #1459 is no longer needed since rebuilt configs read the process-wide value. User-Agent handling now follows a single rule in both SDKs via one shared helper per SDK: an explicitly provided `User-Agent` always wins, otherwise the SDK sends its own tagged with the current integration — and SDK-built values are recomputed whenever a config is rebuilt, so clearing or changing the integration propagates. Tests cover attribution, clearing, config rebuilds, and custom User-Agent precedence in both SDKs, with changesets for `e2b` and `@e2b/python-sdk` (minor). CLI attribution using this setter will follow in a separate PR. Usage (internal integrations only): ```ts import { ConnectionConfig } from 'e2b' ConnectionConfig.setIntegration('e2b-code-interpreter/0.1.0') // once at startup ``` ```python from e2b import ConnectionConfig ConnectionConfig.set_integration("e2b-code-interpreter/0.1.0") # once at startup ``` A caller-supplied `User-Agent` (via `headers`/`apiHeaders`) is preserved in both SDKs: ```ts const sbx = await Sandbox.create({ apiHeaders: { 'User-Agent': 'my-app/1.0' } }) // requests carry: my-app/1.0 ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
07041ccffc |
test: skip live volume tests unless ENABLE_VOLUME_TESTS is set (#1526)
Live volume tests create real volumes against the API; this gates them
behind an `ENABLE_VOLUME_TESTS` env var so they skip by default. In the
JS SDK, the `volumeTest` fixture is chained with
`.skipIf(process.env.ENABLE_VOLUME_TESTS === undefined)`, skipping all
of `tests/volume/file.test.ts`. In the Python SDK, the `volume` and
`async_volume` fixtures call `pytest.skip` when the env var is unset,
gating `tests/{sync/volume_sync,async/volume_async}/test_file.py`.
Mocked and unit volume tests (msw-based `volume.test.ts`,
`test_volume.py`, `test_volume_content.py`, `test_volume_client.py`,
`test_volume_connection_config.py`) still run unconditionally. To run
the live tests: `ENABLE_VOLUME_TESTS=1 pnpm run test` or
`ENABLE_VOLUME_TESTS=1 poetry run pytest`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0feb926937 | [skip ci] Release new versions | ||
|
|
5d84a8e7d2 |
chore(deps-dev): bump black from 23.7.0 to 26.3.1 in /packages/python-sdk in the uv group across 1 directory (#1530)
Bumps the uv group with 1 update in the /packages/python-sdk directory: [black](https://github.com/psf/black). Updates `black` from 23.7.0 to 26.3.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/psf/black/releases">black's releases</a>.</em></p> <blockquote> <h2>26.3.1</h2> <h3>Stable style</h3> <ul> <li>Prevent Jupyter notebook magic masking collisions from corrupting cells by using exact-length placeholders for short magics and aborting if a placeholder can no longer be unmasked safely (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3>Configuration</h3> <ul> <li>Always hash cache filename components derived from <code>--python-cell-magics</code> so custom magic names cannot affect cache paths (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3><em>Blackd</em></h3> <ul> <li>Disable browser-originated requests by default, add configurable origin allowlisting and request body limits, and bound executor submissions to improve backpressure (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> </ul> <h2>26.3.0</h2> <h3>Stable style</h3> <ul> <li>Don't double-decode input, causing non-UTF-8 files to be corrupted (<a href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li> <li>Fix crash on standalone comment in lambda default arguments (<a href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li> <li>Preserve parentheses when <code># type: ignore</code> comments would be merged with other comments on the same line, preventing AST equivalence failures (<a href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li> </ul> <h3>Preview style</h3> <ul> <li>Fix bug where <code>if</code> guards in <code>case</code> blocks were incorrectly split when the pattern had a trailing comma (<a href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li> <li>Fix <code>string_processing</code> crashing on unassigned long string literals with trailing commas (one-item tuples) (<a href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li> <li>Simplify implementation of the power operator "hugging" logic (<a href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li> </ul> <h3>Packaging</h3> <ul> <li>Fix shutdown errors in PyInstaller builds on macOS by disabling multiprocessing in frozen environments (<a href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Introduce winloop for windows as an alternative to uvloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Remove deprecated function <code>uvloop.install()</code> in favor of <code>uvloop.new_event_loop()</code> (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Rename <code>maybe_install_uvloop</code> function to <code>maybe_use_uvloop</code> to simplify loop installation and creation of either a uvloop/winloop evenloop or default eventloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> </ul> <h3>Output</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/psf/black/blob/main/CHANGES.md">black's changelog</a>.</em></p> <blockquote> <h2>Version 26.3.1</h2> <h3>Stable style</h3> <ul> <li>Prevent Jupyter notebook magic masking collisions from corrupting cells by using exact-length placeholders for short magics and aborting if a placeholder can no longer be unmasked safely (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3>Configuration</h3> <ul> <li>Always hash cache filename components derived from <code>--python-cell-magics</code> so custom magic names cannot affect cache paths (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3><em>Blackd</em></h3> <ul> <li>Disable browser-originated requests by default, add configurable origin allowlisting and request body limits, and bound executor submissions to improve backpressure (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> </ul> <h2>Version 26.3.0</h2> <h3>Stable style</h3> <ul> <li>Don't double-decode input, causing non-UTF-8 files to be corrupted (<a href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li> <li>Fix crash on standalone comment in lambda default arguments (<a href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li> <li>Preserve parentheses when <code># type: ignore</code> comments would be merged with other comments on the same line, preventing AST equivalence failures (<a href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li> </ul> <h3>Preview style</h3> <ul> <li>Fix bug where <code>if</code> guards in <code>case</code> blocks were incorrectly split when the pattern had a trailing comma (<a href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li> <li>Fix <code>string_processing</code> crashing on unassigned long string literals with trailing commas (one-item tuples) (<a href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li> <li>Simplify implementation of the power operator "hugging" logic (<a href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li> </ul> <h3>Packaging</h3> <ul> <li>Fix shutdown errors in PyInstaller builds on macOS by disabling multiprocessing in frozen environments (<a href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Introduce winloop for windows as an alternative to uvloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Remove deprecated function <code>uvloop.install()</code> in favor of <code>uvloop.new_event_loop()</code> (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Rename <code>maybe_install_uvloop</code> function to <code>maybe_use_uvloop</code> to simplify loop installation and creation of either a uvloop/winloop eventloop or default eventloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/psf/black/commit/c6755bb741b6481d6b3d3bb563c83fa060db96c9"><code>c6755bb</code></a> Prepare release 26.3.1 (<a href="https://redirect.github.com/psf/black/issues/5046">#5046</a>)</li> <li><a href="https://github.com/psf/black/commit/69973fd6950985fbeb1090d96da717dc4d8380b0"><code>69973fd</code></a> Harden blackd browser-facing request handling (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> <li><a href="https://github.com/psf/black/commit/4937fe6cf241139ddbfc16b0bdbb5b422798909d"><code>4937fe6</code></a> Fix some shenanigans with the cache file and IPython (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> <li><a href="https://github.com/psf/black/commit/2e641d174469c505d5ae905e75d4c769597e681f"><code>2e641d1</code></a> docs: remove outdated Black Playground references (<a href="https://redirect.github.com/psf/black/issues/5044">#5044</a>)</li> <li><a href="https://github.com/psf/black/commit/c014b22a2d5e0632587b47b81151658bddfa0b88"><code>c014b22</code></a> Remove unused internal code (<a href="https://redirect.github.com/psf/black/issues/5041">#5041</a>)</li> <li><a href="https://github.com/psf/black/commit/0dae20b2d009f2f03de8696d06b0c947d3abafc9"><code>0dae20b</code></a> Add new changelog (<a href="https://redirect.github.com/psf/black/issues/5036">#5036</a>)</li> <li><a href="https://github.com/psf/black/commit/c5c1cbddd92cecb554ac2a77a24139dd76831030"><code>c5c1cbd</code></a> Minor release patches (<a href="https://redirect.github.com/psf/black/issues/5035">#5035</a>)</li> <li><a href="https://github.com/psf/black/commit/7e5a828c37d71b6a6666e28eed444816def6a8f4"><code>7e5a828</code></a> docs: clarify relationship between Black style and PEP 8 (<a href="https://redirect.github.com/psf/black/issues/5025">#5025</a>)</li> <li><a href="https://github.com/psf/black/commit/69705deb8776e7c5e585668da106d1abe2cb8d77"><code>69705de</code></a> docs: add clearer pyproject configuration guidance (<a href="https://redirect.github.com/psf/black/issues/5026">#5026</a>)</li> <li><a href="https://github.com/psf/black/commit/35ea67920b7f6ac8e09be1c47278752b1e827f76"><code>35ea679</code></a> Prepare release 26.3.0 (<a href="https://redirect.github.com/psf/black/issues/5032">#5032</a>)</li> <li>Additional commits viewable in <a href="https://github.com/psf/black/compare/23.7.0...26.3.1">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/e2b-dev/E2B/network/alerts). </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be4eb5fd96 |
chore(python-sdk): migrate from Poetry to uv (#1513)
Migrates the Python SDK's packaging and CI from Poetry to [uv](https://docs.astral.sh/uv/): `pyproject.toml` is converted to PEP 621 metadata using uv's native `uv_build` backend (verified to produce a byte-equivalent wheel containing both `e2b` and `e2b_connect`), `poetry.lock` is replaced with `uv.lock`, and the `Makefile`, `package.json` scripts, `.tool-versions`, `CLAUDE.md`, and all six GitHub workflows now use `uv` (`astral-sh/setup-uv` + `uv sync`/`build`/`version`/`publish`). It also drops the now-redundant explicit sync steps (since `uv run` auto-syncs) and removes the orphaned `pydoc-markdown` dev dependency, whose only consumer was deleted long ago — trimming 58 packages from the dev lockfile. ## Usage ```sh cd packages/python-sdk uv sync # install deps (replaces `poetry install`) uv run pytest # run tests uv build # build the wheel/sdist make lint # ruff (run via `uv run`) ``` No user-facing SDK change — packaging/tooling only — so no changeset is included; the published package contents are unchanged. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a6b1cf4bcf |
fix(python-sdk): strip colon-separated SGR escape codes in build logs (#1522)
### What Cherry-picks the fix from #1519. `strip_ansi_escape_codes` in the Python SDK only matched semicolon-separated CSI parameters, so colon-separated SGR sequences leaked literal escape garbage into template build-log messages. This widens the parameter class from `;` to `[;:]` so colon-separated sequences are stripped too, matching the JS SDK's `stripAnsi`. Modern terminals emit colon-separated SGR sequences: - 256-color: `\x1b[38:5:82m` - truecolor: `\x1b[38:2::255:0:0m` - curly underline: `\x1b[4:3m` The two SDKs share one source (chalk/ansi-regex) and the JS twin was already updated to support colons (`packages/js-sdk/src/utils.ts:95`, comment: "supports ; and :"); the Python port lagged behind. `strip_ansi_escape_codes` is consumed by `LogEntry.__post_init__` (`packages/python-sdk/e2b/template/logger.py`), so the leftover escape bytes showed up in Python build logs only. ### The one-line fix ```python # packages/python-sdk/e2b/template/utils.py:319 - r"(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", + r"(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", ``` ### Usage example (before / after) ```python from e2b.template.utils import strip_ansi_escape_codes # 256-color, colon-separated strip_ansi_escape_codes("\x1b[38:5:82mX\x1b[0m") # before: ":5:82mX" after: "X" # truecolor, colon-separated strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") # before: ":2::255:0:0mRED" after: "RED" # semicolon variants already worked and still do strip_ansi_escape_codes("\x1b[38;5;82mX\x1b[0m") # "X" (unchanged) ``` ### Tests Unit tests at `packages/python-sdk/tests/shared/template/utils/test_strip_ansi_escape_codes.py` (no API key / sandbox): colon-256, colon-truecolor, curly-underline, plus basic/semicolon regressions. All 7 pass locally. ### Changeset `.changeset/python-strip-ansi-colon.md` (patch on `@e2b/python-sdk`). ### Notes Original PR: #1519 (by @anxkhn). Opened against a fresh branch off `main` per request, rather than merging #1519 directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> |
||
|
|
2b7dd17f10 |
feat(sdk): add gzip option to template copy layer (#1482)
Adds a `gzip` option to the template `.copy()` / `copyItems` layer that
controls whether copied files are gzipped before upload, threaded from
the copy call through the build-time tar stream in both the JS SDK and
the sync/async Python SDKs. It is enabled by default to preserve
existing behavior, so passing `gzip: false` (`gzip=False`) uploads an
uncompressed tar — useful for already-compressed payloads where gzip
adds CPU cost without shrinking the upload. The option name matches
node-tar's own `gzip` option and the existing sandbox filesystem `gzip`
kwarg. Gzip is deliberately excluded from the file cache hash, so
toggling it does not bust the build cache. Tests in both SDKs were
updated for the new argument and extended with `gzip: false` cases
asserting the archive is not gzipped yet still extracts, and a changeset
(`minor` for both packages) is included.
> [!NOTE]
> The server that extracts these uploaded archives lives in another repo
and must auto-detect compression (peek the gzip `0x1f 0x8b` magic)
rather than assuming gzip; confirm it handles plain tars before release.
## Usage
```ts
// JS/TS
template.copy('model.bin', '/app/', { gzip: false })
template.copyItems([{ src: 'a.bin', dest: '/app/', gzip: false }])
```
```python
# Python (sync & async)
template.copy('model.bin', '/app/', gzip=False)
template.copy_items([{ 'src': 'a.bin', 'dest': '/app/', 'gzip': False }])
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c385566c29 |
fix(python-sdk): correct Sandbox.list() docstring (also lists paused) (#1511)
Integration branch PR for #1500. Merges the docstring fix into `main`. Once #1500 is merged into `python-sdk-list-docstring-base`, this PR will carry those changes into `main`. --------- Co-authored-by: Leinux <tristone13th@outlook.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f160f08c7b |
Keep integration attribution on connection config (#1459)
moves integration attirbution to more private thing to avoid confusing people with first class kwargs |
||
|
|
bb45f185f1 |
Introduce generic paginator base class for JS and Python SDKs (#1491)
Extracts the cursor-based pagination state machine into a reusable base
class — `Paginator` in the JS SDK's `utils`, `PaginatorBase` in
`e2b/utils.py` — that owns `hasNext`/`nextToken` and the `x-next-token`
header handling, and migrates the sandbox and snapshot paginators onto
it. Each concrete paginator now just implements `nextItems`/`next_items`
to fetch its own page, so future list endpoints (templates, builds,
etc.) can add pagination by subclassing without reimplementing the
bookkeeping. Applied equivalently to the JS SDK and both Python sync and
async implementations, with unit tests covering the shared base. There
are no public API changes — `Sandbox.list()` / `listSnapshots()` and the
existing paginator types behave identically.
## Usage (unchanged)
```ts
const paginator = Sandbox.list()
while (paginator.hasNext) {
const sandboxes = await paginator.nextItems()
console.log(sandboxes)
}
```
```python
paginator = Sandbox.list()
while paginator.has_next:
sandboxes = paginator.next_items()
print(sandboxes)
```
|
||
|
|
bb1696871b |
Stream template build-context upload from disk instead of buffering in memory (#1435)
## Summary Template builds previously buffered the entire gzipped build-context tar archive in memory before uploading it. This PR spools the archive to a temporary file and streams it from disk during upload — in the JS SDK and both sync and async Python SDKs — so memory usage no longer scales with the size of the build context. The upload keeps an explicit `Content-Length` header (taken from the spooled file's size), which S3 presigned PUT URLs require — they reject `Transfer-Encoding: chunked` with `501 NotImplemented` (#1243). ## Changes - **JS** (`packages/js-sdk/src/template/`): `tarFileStream`/`tarFileStreamUpload` are replaced by `tarFileToStream`, which writes the archive to a temp file and returns a self-cleaning read stream plus its `size`. The spooled temp file deletes itself once the stream is closed (consumed, errored, or destroyed) via the stream's `close` event — mirroring the Python SDK's `tar_file_stream`. `buildApi` streams this body with `duplex: 'half'` and an explicit `Content-Length` from `size`; if `fetch` throws before consuming the body, it destroys the stream to trigger the same cleanup. There is no separate cleanup callback, so a cleanup failure can no longer mask the upload result. - **Python** (`packages/python-sdk/e2b/template/utils.py`, `template_async/build_api.py`, `template_sync/build_api.py`): `tar_file_stream` now writes to a `tempfile.TemporaryFile` instead of `io.BytesIO` and returns the file object positioned at the start; the upload streams from it with an explicit `Content-Length` and closes it (deleting the temp file) when done. - Tests updated for the new return shapes (JS `tarFileToStream.test.ts`, `uploadFile.test.ts`; Python upload/tar tests), including assertions that the spooled archive is removed on both the consume and destroy paths. ## Usage No API changes — `Template.build()` / template builds behave the same, just without holding the build context in memory: ```ts await Template.build(template, { alias: 'my-template' }) ``` ```python Template.build(template, alias="my-template") ``` Split out of #1433, which covers streaming for sandbox/volume file uploads and downloads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8b8a224f8b |
feat(python-sdk): add logger option for request/debug logging (#1409)
Adds a `logger` option (a standard library `logging.Logger`) to
`Sandbox.create`/`AsyncSandbox.create` and the static
`Sandbox.connect(sandbox_id, ...)`, wired into the API client, the envd
client, the volume content client, and the RPC (ConnectRPC) path. The
logger is stored on the sandbox and propagates to all of its later
operations — including control-plane calls like
`kill`/`pause`/`set_timeout`/`get_info` (via `get_api_params`) — so
logging keeps working after construction; mirroring the JS SDK, `logger`
is a construction-time option and not a public per-request parameter
those methods accept from the caller, and nothing is logged unless a
logger is supplied. The stdlib `logging.Logger` is used directly as the
adapter (no ported JS `Logger` interface), and log levels match JS:
requests at `INFO`, successful API and unary RPC responses at `INFO`,
streamed RPC messages at `DEBUG`, failed API responses (status >= 400)
at `ERROR`. The always-on module-level (`e2b.*`) request logging at the
transport layer was removed in favor of this opt-in client-layer
logging, and volume content operations continue to accept `logger` per
call via `VolumeApiParams` to match the JS Volume API. Includes a
changeset and unit tests in `tests/test_logging_option.py`.
## Usage
```python
import logging
from e2b import Sandbox
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("my-app.e2b")
sbx = Sandbox.create(logger=logger)
sbx.commands.run("echo hello") # RPC logged via `logger`
sbx.set_timeout(60) # control-plane call also logged via `logger`
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Matt Brockman <matt.brockman@e2b.dev>
|
||
|
|
ec260376dc | [skip ci] Release new versions | ||
|
|
de0c401626 | fix(sdk): correct filesystem watch handle callback and timeout behavior (#1480) | ||
|
|
7e7e9514df |
feat(sdk): filesystem-only auto-pause via lifecycle.onTimeout object form (#1471)
## Filesystem-only auto-pause (`onTimeout` object form)
Adds an object form to the sandbox **lifecycle** `onTimeout`
(`on_timeout` in Python) that controls the snapshot kind taken when a
sandbox auto-pauses on timeout, via `keepMemory` (`keep_memory`).
`onTimeout` now accepts either the existing bare action (`'pause'` /
`'kill'`) or the object form `{ action, keepMemory }`. When `keepMemory`
is `false` (with `action: 'pause'`), a timeout auto-pause takes a
**filesystem-only** snapshot (no memory) instead of a full memory one,
so the sandbox cold-boots (reboots) from disk on resume — losing running
processes and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected. **The bare string form is
unchanged.**
It's the create-time / auto-pause counterpart to the explicit
`pause(keepMemory=false)` from #1465: same `keepMemory` naming, mapped
onto the `autoPauseMemory` create field.
### Type safety
The object form is a **discriminated union** on `action`: `keepMemory`
is only valid with `action: 'pause'`. Pairing it with `action: 'kill'`
is a **compile-time type error** (TS) / static error (`ty`), and is
additionally rejected at runtime (`InvalidArgumentError` /
`InvalidArgumentException`) for untyped callers.
### Behavior & validation
- `keepMemory` only applies to a `pause` action.
- **Incompatible with auto-resume** — auto-resume wakes a paused sandbox
on inbound traffic by restoring its memory snapshot in place; a
filesystem-only snapshot has no memory to restore (resuming cold-boots
it), so it must be resumed explicitly via `connect()`. Combining
`keepMemory: false` with `autoResume` is rejected client-side.
### Usage
```ts
// JS/TS — filesystem-only auto-pause on timeout
const sbx = await Sandbox.create({
lifecycle: { onTimeout: { action: 'pause', keepMemory: false } },
})
// bare string form still works (full memory snapshot)
const sbx2 = await Sandbox.create({ lifecycle: { onTimeout: 'pause' } })
```
```python
# Python
sbx = Sandbox.create(
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}}
)
```
### Changes
- `spec/openapi.yml`: `autoPauseMemory` on the create body (+
regenerated JS/Python clients).
- JS `SandboxOnTimeout` discriminated union (`'pause' | 'kill' | {
action: 'pause'; keepMemory? } | { action: 'kill' }`) and the Python
`SandboxOnTimeoutPause` / `SandboxOnTimeoutKill` TypedDicts, wired
through `createSandbox` / `_create_sandbox` (sync + async) to
`autoPauseMemory`, with the client-side guards.
- Tests: payload serialization + validation (offline, incl. the `action:
'kill'` type/runtime guard) and live cold-boot e2e in both SDKs;
changeset (`e2b` + `@e2b/python-sdk`, minor).
### Backend dependency
The live e2e tests exercise the real auto-pause→cold-boot path and
require the infra-side `autoPauseMemory` support (e2b-dev/infra#3055),
now merged and deployed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
cb5a3870b6 |
feat(sdk): filesystem-only snapshots (pause memory:false) (#1465)
## Summary
Adds an optional **`memory`** flag to `pause` in both the JS and Python
SDKs. When `memory` is `false`, the pause captures **only the
filesystem** (no memory snapshot); resuming such a snapshot **cold-boots
(reboots)** the sandbox from disk — losing in-memory state, running
processes, and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected.
This is the SDK surface for the filesystem-only snapshot feature on the
infra side.
## Usage
```ts
// JS / TS
const sbx = await Sandbox.create()
await sbx.pause({ memory: false }) // filesystem-only snapshot
const resumed = await sbx.connect() // resumes by cold-booting from disk
```
```python
# Python (sync)
sbx = Sandbox()
sbx.pause(memory=False) # filesystem-only snapshot
resumed = sbx.connect() # resumes by cold-booting from disk
# Python (async)
sbx = await AsyncSandbox.create()
await sbx.pause(memory=False)
resumed = await sbx.connect()
```
`memory` defaults to `true` — `pause()` / `pause({})` behave exactly as
before.
## What changed
- **spec**: optional `memory: boolean` (default `true`) on `POST
/sandboxes/{sandboxID}/pause` (`SandboxPauseRequest`); both API clients
regenerated via `make codegen`.
- **JS**: `Sandbox.pause` / `betaPause` accept `{ memory }` →
`SandboxApi.pause` sends the request body.
- **Python**: `pause(memory=...)` / `beta_pause` → `_cls_pause` (sync +
async) sends `SandboxPauseRequest(memory=...)`.
- **Tests**: filesystem-only pause+resume reboots the guest while the
filesystem survives — JS (`tests/sandbox/snapshot.test.ts`) and Python
sync + async. All pass against a local stack; `format` / `lint` /
`typecheck` clean.
- **Changeset**: `minor` for `e2b` and `@e2b/python-sdk`.
## Note (related infra observation, not addressed here)
While testing, a filesystem-only **resume cold-boots into a different
default exec context** (`root` / `/root`) than a memory resume (`user` /
`/home/user`). The filesystem itself is fully intact; tests use absolute
paths to be robust to this. Worth confirming on the infra reboot path
whether the template's default user should be restored after a cold
boot.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
31a93bed0c | [skip ci] Release new versions |