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..3ee1c72f 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"}, @@ -1373,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"}, @@ -1399,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)"] @@ -1645,7 +1659,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 +1815,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 +1928,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 +1945,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "f0f0e653869c2ee8fac2dd97f0a87f9a89c150264da41ea3d2845e6c54ad08b5" +content-hash = "f3c2463ae3e3af850183625fdf71af742aa448ecaf85261a3fce2b30cb879743" diff --git a/python/pyproject.toml b/python/pyproject.toml index 573b594f..3324b91b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,13 +14,13 @@ 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" +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/tests/conftest.py b/python/tests/conftest.py index 3c046928..bc853e85 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,19 @@ timeout = 60 +# 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 a session-scoped event loop for all async tests.""" + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + yield loop + loop.close() + + @pytest.fixture() def template(): return os.getenv("E2B_TESTS_TEMPLATE") or "code-interpreter-v1" @@ -31,20 +45,39 @@ def sandbox(template, debug): ) -@pytest_asyncio.fixture -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.""" - try: - yield async_sandbox - finally: - try: - await async_sandbox.kill() - except: # noqa: E722 - if not debug: - warning( - "Failed to kill sandbox — this is expected if the test runs with local envd." - ) + 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(): + 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