From 4c037ebeeaf14b74773d21245e0d97f5ff9c79db Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 13 Aug 2026 11:44:06 +0200 Subject: [PATCH 1/2] fix(python-sdk): keep get_transport accepting the deprecated http2 argument Any fresh `pip install e2b-code-interpreter` currently raises on the first `run_code()`: TypeError: get_transport() got an unexpected keyword argument 'http2' `e2b_code_interpreter/code_interpreter_sync.py` builds its streaming client with `get_transport(self.connection_config, http2=False)`. Moving the REST API client onto pyqwest (#1601) dropped that parameter, correctly, since ALPN negotiates the HTTP version now and the flag no longer selects anything. But e2b-code-interpreter pins `e2b>=2.26.0,<3.0.0`, so pip and poetry resolve the broken pair by default, and 2.9.0, 2.8.1, 2.8.0 and 2.7.0 all call it the same way. e2b 2.37.0 works, 2.38.0 does not. `get_transport` is not re-exported at the package root, so it reads as internal, but a sibling package we ship depends on it and the version range says the combination is supported. Accepting the argument and ignoring it costs nothing (it is genuinely inert) and unbreaks every already-published code-interpreter without anyone having to upgrade two packages in lockstep. Verified end to end: with this patch installed alongside an unmodified e2b-code-interpreter 2.9.0, `Sandbox.create().run_code(...)` returns normally against live E2B. The follow-up belongs in e2b-dev/code-interpreter, which should move off the REST transport entirely: that call site is envd streaming traffic, so `get_envd_transport(config, for_streaming=True)` from #1623 is what it actually wants, and its `e2b` floor should then move to >=2.38. --- .../python-get-transport-http2-compat.md | 12 +++++++++++ .../e2b/api/client_async/__init__.py | 14 +++++++++++-- .../e2b/api/client_sync/__init__.py | 14 +++++++++++-- .../tests/test_api_client_transport.py | 21 +++++++++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 .changeset/python-get-transport-http2-compat.md diff --git a/.changeset/python-get-transport-http2-compat.md b/.changeset/python-get-transport-http2-compat.md new file mode 100644 index 0000000000..896eaf966d --- /dev/null +++ b/.changeset/python-get-transport-http2-compat.md @@ -0,0 +1,12 @@ +--- +"@e2b/python-sdk": patch +--- + +Accept and ignore a deprecated `http2` argument on `get_transport` in both the +sync and async REST API clients. The pyqwest move dropped the parameter because +ALPN negotiates the HTTP version now, but every published +`e2b-code-interpreter` calls `get_transport(config, http2=False)`, and its +`e2b>=2.26.0,<3.0.0` range resolves straight to a version that no longer accepts +it. The result was a `TypeError` on the first `run_code()` of any fresh +`pip install e2b-code-interpreter`. The flag is inert, so it is accepted and +ignored rather than removed. diff --git a/packages/python-sdk/e2b/api/client_async/__init__.py b/packages/python-sdk/e2b/api/client_async/__init__.py index d40ca257e6..0092379b0a 100644 --- a/packages/python-sdk/e2b/api/client_async/__init__.py +++ b/packages/python-sdk/e2b/api/client_async/__init__.py @@ -78,10 +78,20 @@ def retrying_http_transport( _transports: Dict[Optional[ProxyConfig], AsyncPyqwestTransport] = {} -def get_transport(config: ConnectionConfig) -> AsyncPyqwestTransport: +def get_transport( + config: ConnectionConfig, http2: Optional[bool] = None +) -> AsyncPyqwestTransport: """The shared pyqwest-backed httpx transport for REST API calls. For TLS connections ALPN negotiates the HTTP version (HTTP/2 against the E2B - API), like the http2-enabled httpx transport this replaced.""" + API), like the http2-enabled httpx transport this replaced. + + :param http2: Deprecated and ignored. The httpx transport this replaced + took an explicit HTTP/2 switch; ALPN negotiates the version now, so + the flag no longer selects anything. Accepted so that callers written + against the pre-pyqwest signature keep working - notably every + published ``e2b-code-interpreter``, which calls + ``get_transport(config, http2=False)``. + """ proxy = proxy_to_config(config.proxy) with _transport_lock: transport = _transports.get(proxy) diff --git a/packages/python-sdk/e2b/api/client_sync/__init__.py b/packages/python-sdk/e2b/api/client_sync/__init__.py index d7331d8cc4..8531bcba58 100644 --- a/packages/python-sdk/e2b/api/client_sync/__init__.py +++ b/packages/python-sdk/e2b/api/client_sync/__init__.py @@ -77,10 +77,20 @@ def retrying_http_transport( _transports: Dict[Optional[ProxyConfig], PyqwestTransport] = {} -def get_transport(config: ConnectionConfig) -> PyqwestTransport: +def get_transport( + config: ConnectionConfig, http2: Optional[bool] = None +) -> PyqwestTransport: """The shared pyqwest-backed httpx transport for REST API calls. For TLS connections ALPN negotiates the HTTP version (HTTP/2 against the E2B - API), like the http2-enabled httpx transport this replaced.""" + API), like the http2-enabled httpx transport this replaced. + + :param http2: Deprecated and ignored. The httpx transport this replaced + took an explicit HTTP/2 switch; ALPN negotiates the version now, so + the flag no longer selects anything. Accepted so that callers written + against the pre-pyqwest signature keep working - notably every + published ``e2b-code-interpreter``, which calls + ``get_transport(config, http2=False)``. + """ proxy = proxy_to_config(config.proxy) with _transport_lock: transport = _transports.get(proxy) diff --git a/packages/python-sdk/tests/test_api_client_transport.py b/packages/python-sdk/tests/test_api_client_transport.py index c37d034642..d84074acc1 100644 --- a/packages/python-sdk/tests/test_api_client_transport.py +++ b/packages/python-sdk/tests/test_api_client_transport.py @@ -610,3 +610,24 @@ def test_sync_transport_sends_multipart_bodies(test_api_key, echo_server): finally: client.close() reset_sync_api_transports() + + +def test_get_transport_accepts_the_deprecated_http2_kwarg(): + """Every published e2b-code-interpreter calls + ``get_transport(config, http2=False)``. The pyqwest move dropped that + parameter, which turned the first ``run_code()`` of any fresh + ``pip install e2b-code-interpreter`` into a TypeError, because + e2b-code-interpreter's ``e2b>=2.26.0,<3.0.0`` range resolves to a version + that no longer accepts it. The flag is inert now (ALPN negotiates), so it + is accepted and ignored rather than removed. + """ + reset_sync_api_transports() + reset_async_api_transports() + config = ConnectionConfig(api_key="test-key") + + assert get_sync_transport(config, http2=False) is get_sync_transport(config) + assert get_async_transport(config, http2=False) is get_async_transport(config) + + # Positional still works, and so does omitting it entirely. + assert isinstance(get_sync_transport(config, False), PyqwestTransport) + assert isinstance(get_async_transport(config), AsyncPyqwestTransport) From 50c8d94291d1695d2bcf735dc6f075fa7eb96aa0 Mon Sep 17 00:00:00 2001 From: Tomas Beran Date: Thu, 13 Aug 2026 11:53:35 +0200 Subject: [PATCH 2/2] test(python-sdk): follow the file's try/finally cleanup convention Every other transport-creating test in test_api_client_transport.py resets the module-global transport caches in a finally block; the new one only reset at the top, leaving a live pooled transport in _transports after it finished. Also takes the test_api_key fixture instead of hardcoding a key, matching its neighbours. --- .../tests/test_api_client_transport.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/python-sdk/tests/test_api_client_transport.py b/packages/python-sdk/tests/test_api_client_transport.py index d84074acc1..1eda42719a 100644 --- a/packages/python-sdk/tests/test_api_client_transport.py +++ b/packages/python-sdk/tests/test_api_client_transport.py @@ -612,7 +612,7 @@ def test_sync_transport_sends_multipart_bodies(test_api_key, echo_server): reset_sync_api_transports() -def test_get_transport_accepts_the_deprecated_http2_kwarg(): +def test_get_transport_accepts_the_deprecated_http2_kwarg(test_api_key): """Every published e2b-code-interpreter calls ``get_transport(config, http2=False)``. The pyqwest move dropped that parameter, which turned the first ``run_code()`` of any fresh @@ -623,11 +623,16 @@ def test_get_transport_accepts_the_deprecated_http2_kwarg(): """ reset_sync_api_transports() reset_async_api_transports() - config = ConnectionConfig(api_key="test-key") + config = ConnectionConfig(api_key=test_api_key) - assert get_sync_transport(config, http2=False) is get_sync_transport(config) - assert get_async_transport(config, http2=False) is get_async_transport(config) + try: + # Ignored, so it must not key a separate cache entry. + assert get_sync_transport(config, http2=False) is get_sync_transport(config) + assert get_async_transport(config, http2=False) is get_async_transport(config) - # Positional still works, and so does omitting it entirely. - assert isinstance(get_sync_transport(config, False), PyqwestTransport) - assert isinstance(get_async_transport(config), AsyncPyqwestTransport) + # Positional still works, and so does omitting it entirely. + assert isinstance(get_sync_transport(config, False), PyqwestTransport) + assert isinstance(get_async_transport(config), AsyncPyqwestTransport) + finally: + reset_sync_api_transports() + reset_async_api_transports()