From 0d48cbc416a23ca0de3ee672a92f5a605a8b6ba7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Nov 2025 23:11:46 +0000 Subject: [PATCH 1/5] Bump e2b dependency to version 2.7.0 Co-authored-by: jakub.dobry --- js/package.json | 2 +- pnpm-lock.yaml | 31 ++++++++---------------------- python/poetry.lock | 44 +++++++++++++++++++++++++++++++++++-------- python/pyproject.toml | 2 +- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/js/package.json b/js/package.json index 21c7f587..027e4242 100644 --- a/js/package.json +++ b/js/package.json @@ -74,6 +74,6 @@ "defaults" ], "dependencies": { - "e2b": "^2.6.0" + "e2b": "^2.7.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58c58a7b..7e3f8587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: js: dependencies: e2b: - specifier: ^2.6.0 - version: 2.6.0 + specifier: ^2.7.0 + version: 2.7.0 devDependencies: '@types/node': specifier: ^20.19.19 @@ -880,8 +880,8 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} - e2b@2.6.0: - resolution: {integrity: sha512-7hzfZGVZNwh2YU+aviOVRZ0HjFBnKFFQKTNq9CtXuJ0tHlDqeu5G5ZcryejGejnYrgAloxarThK9vE4srqXamQ==} + e2b@2.7.0: + resolution: {integrity: sha512-pbCbkkdkkY+yIhhtdSE7lM/vhIROtHNI0hNpj8lBphDILNH2qmmjhxU7/wam8/xWRbiWbfuQaOsv100lD32nag==} engines: {node: '>=20'} eastasianwidth@0.2.0: @@ -1029,10 +1029,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1307,10 +1303,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -2689,7 +2681,7 @@ snapshots: dotenv@16.4.7: {} - e2b@2.6.0: + e2b@2.7.0: dependencies: '@bufbuild/protobuf': 2.9.0 '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.9.0) @@ -2890,11 +2882,6 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -2927,10 +2914,10 @@ snapshots: glob@10.3.10: dependencies: - foreground-child: 3.1.1 + foreground-child: 3.3.1 jackspeak: 2.3.6 minimatch: 9.0.5 - minipass: 7.0.4 + minipass: 7.1.2 path-scurry: 1.10.1 glob@11.0.3: @@ -3194,8 +3181,6 @@ snapshots: minimist@1.2.8: {} - minipass@7.0.4: {} - minipass@7.1.2: {} minizlib@3.1.0: @@ -3288,7 +3273,7 @@ snapshots: path-scurry@1.10.1: dependencies: lru-cache: 10.2.0 - minipass: 7.0.4 + minipass: 7.1.2 path-scurry@2.0.0: dependencies: diff --git a/python/poetry.lock b/python/poetry.lock index 7a4186d0..2a5d6b14 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "anyio" @@ -90,6 +90,18 @@ d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \" jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "bracex" +version = "2.6" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952"}, + {file = "bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7"}, +] + [[package]] name = "certifi" version = "2024.7.4" @@ -476,14 +488,14 @@ test = ["black", "pytest"] [[package]] name = "e2b" -version = "2.6.0" +version = "2.7.0" description = "E2B SDK that give agents cloud environments" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "e2b-2.6.0-py3-none-any.whl", hash = "sha256:74fbf9adf5d651862780d281e5f50cfb0286adbeaba6dceac86f26e5e1c0ad8c"}, - {file = "e2b-2.6.0.tar.gz", hash = "sha256:3c6a8e315748d39efc2b0ff69aa47af548fd9da64be64f6797e5998db324e791"}, + {file = "e2b-2.7.0-py3-none-any.whl", hash = "sha256:cc3d0a2def205e3e3e2ac635c59d12f41adea3c32a479e578716e4abcdd8898d"}, + {file = "e2b-2.7.0.tar.gz", hash = "sha256:e276101c017ab4425688a9935a91d79c8ea43fc73bf7089d10028a4b21915016"}, ] [package.dependencies] @@ -496,6 +508,7 @@ protobuf = ">=4.21.0" python-dateutil = ">=2.8.2" rich = ">=14.0.0" typing-extensions = ">=4.1.0" +wcmatch = ">=10.1,<11.0" [[package]] name = "exceptiongroup" @@ -687,7 +700,7 @@ description = "Read resources from Python packages" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, @@ -1645,7 +1658,7 @@ description = "Easily download, build, install, upgrade, and uninstall Python pa optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, @@ -1801,6 +1814,21 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wcmatch" +version = "10.1" +description = "Wildcard/glob file name matcher." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a"}, + {file = "wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af"}, +] + +[package.dependencies] +bracex = ">=2.1.1" + [[package]] name = "wrapt" version = "1.17.0" @@ -1899,7 +1927,7 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, @@ -1916,4 +1944,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "f0f0e653869c2ee8fac2dd97f0a87f9a89c150264da41ea3d2845e6c54ad08b5" +content-hash = "c88dc13f1327a5e18dd75aa1285634970ad043cfb758dcb3647e74f38686aa95" diff --git a/python/pyproject.toml b/python/pyproject.toml index 573b594f..9c6f5613 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,7 +14,7 @@ python = "^3.9" httpx = ">=0.20.0, <1.0.0" attrs = ">=21.3.0" -e2b = "^2.6.0" +e2b = "^2.7.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" From d3499e9d3df07d593baefc325e5fd2171f1ebc3d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Nov 2025 23:19:18 +0000 Subject: [PATCH 2/5] Update pytest and dependencies, add asyncio loop scope Co-authored-by: jakub.dobry --- python/poetry.lock | 35 ++++++++++++++++++----------------- python/pyproject.toml | 4 ++-- python/pytest.ini | 1 + 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/python/poetry.lock b/python/poetry.lock index 2a5d6b14..3ee1c72f 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -1386,7 +1386,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -1412,41 +1412,42 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.23.8" +version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, - {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] -pytest = ">=7.0.0,<9" +pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] @@ -1944,4 +1945,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "c88dc13f1327a5e18dd75aa1285634970ad043cfb758dcb3647e74f38686aa95" +content-hash = "f3c2463ae3e3af850183625fdf71af742aa448ecaf85261a3fce2b30cb879743" diff --git a/python/pyproject.toml b/python/pyproject.toml index 9c6f5613..3324b91b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -17,10 +17,10 @@ attrs = ">=21.3.0" e2b = "^2.7.0" [tool.poetry.group.dev.dependencies] -pytest = "^7.4.0" +pytest = "^8.2.0" python-dotenv = "^1.0.0" pytest-dotenv = "^0.5.2" -pytest-asyncio = "^0.23.7" +pytest-asyncio = "^0.24.0" pytest-xdist = "^3.6.1" pydoc-markdown = "^4.8.2" matplotlib = "^3.8.0" diff --git a/python/pytest.ini b/python/pytest.ini index 7695413b..9ecc7dec 100644 --- a/python/pytest.ini +++ b/python/pytest.ini @@ -3,5 +3,6 @@ markers = skip_debug: skip test if E2B_DEBUG is set. asyncio_mode=auto +asyncio_default_fixture_loop_scope=function addopts = "--import-mode=importlib" "--numprocesses=2" From 5f7df48e1390a6f615a05a7900bc82444003e04d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Nov 2025 23:25:22 +0000 Subject: [PATCH 3/5] Refactor: Use function-scoped event loop for tests Co-authored-by: jakub.dobry --- python/pytest.ini | 1 - python/tests/conftest.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/python/pytest.ini b/python/pytest.ini index 9ecc7dec..7695413b 100644 --- a/python/pytest.ini +++ b/python/pytest.ini @@ -3,6 +3,5 @@ markers = skip_debug: skip test if E2B_DEBUG is set. asyncio_mode=auto -asyncio_default_fixture_loop_scope=function addopts = "--import-mode=importlib" "--numprocesses=2" diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 3c046928..695a80e9 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,6 +1,7 @@ import pytest import pytest_asyncio import os +import asyncio from logging import warning @@ -10,6 +11,28 @@ timeout = 60 +@pytest_asyncio.fixture(scope="function") +def event_loop(): + """Create an instance of the default event loop for each test case.""" + policy = asyncio.get_event_loop_policy() + loop = policy.new_event_loop() + yield loop + # Clean up any remaining tasks + try: + pending = asyncio.all_tasks(loop) + for task in pending: + task.cancel() + if pending: + loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + except Exception: + pass + # Close the loop gracefully + try: + loop.close() + except Exception: + pass + + @pytest.fixture() def template(): return os.getenv("E2B_TESTS_TEMPLATE") or "code-interpreter-v1" @@ -31,7 +54,7 @@ def sandbox(template, debug): ) -@pytest_asyncio.fixture +@pytest_asyncio.fixture(loop_scope="function") async def async_sandbox(template, debug): async_sandbox = await AsyncSandbox.create(template, timeout=timeout, debug=debug) @@ -40,6 +63,10 @@ async def async_sandbox(template, debug): finally: try: await async_sandbox.kill() + except RuntimeError as e: + # Ignore "Event loop is closed" errors during cleanup in pytest-xdist + if "Event loop is closed" not in str(e): + raise except: # noqa: E722 if not debug: warning( From 52c28fbf0c4e4d648252c25df6e66cb60866ea9b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Nov 2025 23:28:32 +0000 Subject: [PATCH 4/5] Refactor sandbox fixtures for better async handling Co-authored-by: jakub.dobry --- python/tests/conftest.py | 75 +++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 695a80e9..f4f246b9 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -11,26 +11,17 @@ timeout = 60 -@pytest_asyncio.fixture(scope="function") +# Override the event loop so it never closes during test execution +# This helps with pytest-xdist and prevents "Event loop is closed" errors +@pytest.fixture(scope="session") def event_loop(): - """Create an instance of the default event loop for each test case.""" - policy = asyncio.get_event_loop_policy() - loop = policy.new_event_loop() - yield loop - # Clean up any remaining tasks - try: - pending = asyncio.all_tasks(loop) - for task in pending: - task.cancel() - if pending: - loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) - except Exception: - pass - # Close the loop gracefully + """Create a session-scoped event loop for all async tests.""" try: - loop.close() - except Exception: - pass + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + yield loop + loop.close() @pytest.fixture() @@ -54,24 +45,38 @@ def sandbox(template, debug): ) -@pytest_asyncio.fixture(loop_scope="function") -async def async_sandbox(template, debug): - async_sandbox = await AsyncSandbox.create(template, timeout=timeout, debug=debug) +@pytest.fixture +def async_sandbox_factory(request, template, debug, event_loop): + """Factory for creating async sandboxes with proper cleanup.""" + async def factory(template_override=None, **kwargs): + template_name = template_override or template + kwargs.setdefault("timeout", timeout) + kwargs.setdefault("debug", debug) + + sandbox = await AsyncSandbox.create(template_name, **kwargs) - try: - yield async_sandbox - finally: - try: - await async_sandbox.kill() - except RuntimeError as e: - # Ignore "Event loop is closed" errors during cleanup in pytest-xdist - if "Event loop is closed" not in str(e): - raise - except: # noqa: E722 - if not debug: - warning( - "Failed to kill sandbox — this is expected if the test runs with local envd." - ) + def kill(): + async def _kill(): + try: + await sandbox.kill() + except: # noqa: E722 + if not debug: + warning( + "Failed to kill sandbox — this is expected if the test runs with local envd." + ) + + event_loop.run_until_complete(_kill()) + + request.addfinalizer(kill) + return sandbox + + return factory + + +@pytest.fixture +async def async_sandbox(async_sandbox_factory): + """Default async sandbox fixture.""" + return await async_sandbox_factory() @pytest.fixture From b32245ffb840f3cc0cf50bcedeb493414906534c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Nov 2025 23:31:10 +0000 Subject: [PATCH 5/5] Refactor async_sandbox_factory to use default kwargs Co-authored-by: jakub.dobry --- python/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index f4f246b9..bc853e85 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -48,11 +48,12 @@ def sandbox(template, debug): @pytest.fixture def async_sandbox_factory(request, template, debug, event_loop): """Factory for creating async sandboxes with proper cleanup.""" + async def factory(template_override=None, **kwargs): template_name = template_override or template kwargs.setdefault("timeout", timeout) kwargs.setdefault("debug", debug) - + sandbox = await AsyncSandbox.create(template_name, **kwargs) def kill():