e2b-haystack 1.0.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. e2b_haystack-2.0.0/CHANGELOG.md +29 -0
  2. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/PKG-INFO +6 -5
  3. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/README.md +2 -1
  4. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/pyproject.toml +3 -2
  5. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/bash_tool.py +8 -0
  6. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/e2b_sandbox.py +1 -13
  7. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/list_directory_tool.py +8 -0
  8. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/read_file_tool.py +8 -0
  9. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/write_file_tool.py +8 -0
  10. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/tests/test_integration.py +7 -1
  11. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/tests/test_sandbox_toolset.py +49 -30
  12. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/.gitignore +0 -0
  13. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/LICENSE.txt +0 -0
  14. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/examples/e2b_agent_example.py +0 -0
  15. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/examples/e2b_pipeline_example.py +0 -0
  16. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/pydoc/config_docusaurus.yml +0 -0
  17. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/__init__.py +0 -0
  18. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/e2b/sandbox_toolset.py +0 -0
  19. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/src/haystack_integrations/tools/py.typed +0 -0
  20. {e2b_haystack-1.0.0 → e2b_haystack-2.0.0}/tests/__init__.py +0 -0
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## [integrations/e2b-v1.1.0] - 2026-10-09
4
+
5
+ ### 🚀 Features
6
+
7
+ - E2b - add lifecycle methods to each tool (#4071)
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - Fix new issues raised by ruff 0.16.0 (#3670)
12
+
13
+ ### ⚙️ CI
14
+
15
+ - Run e2b integration tests unconditionally and skip them in the test files (#3725)
16
+ - Improve changelog generation; fix existing changelogs (#3883)
17
+
18
+ ### 🧹 Chores
19
+
20
+ - Re-standardize READMEs (#3768)
21
+
22
+
23
+ ## [integrations/e2b-v1.0.0] - 2026-04-30
24
+
25
+ ### 🚀 Features
26
+
27
+ - Add e2b-haystack integration for E2B cloud sandbox tools (#3195)
28
+
29
+ <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: e2b-haystack
3
- Version: 1.0.0
3
+ Version: 2.0.0
4
4
  Summary: Haystack integration for E2B cloud sandboxes
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/e2b#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -12,14 +12,14 @@ Keywords: E2B,Haystack,code execution,sandbox,tools
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
21
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
- Requires-Python: >=3.10
22
+ Requires-Python: >=3.11
23
23
  Requires-Dist: e2b>=2.0.0
24
24
  Requires-Dist: haystack-ai>=2.19.0
25
25
  Description-Content-Type: text/markdown
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
29
29
  [![PyPI - Version](https://img.shields.io/pypi/v/e2b-haystack.svg)](https://pypi.org/project/e2b-haystack)
30
30
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/e2b-haystack.svg)](https://pypi.org/project/e2b-haystack)
31
31
 
32
+ - [Integration page](https://haystack.deepset.ai/integrations/e2b)
32
33
  - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/e2b/CHANGELOG.md)
33
34
 
34
35
  ---
@@ -37,4 +38,4 @@ Description-Content-Type: text/markdown
37
38
 
38
39
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
39
40
 
40
- To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
41
+ To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
@@ -3,6 +3,7 @@
3
3
  [![PyPI - Version](https://img.shields.io/pypi/v/e2b-haystack.svg)](https://pypi.org/project/e2b-haystack)
4
4
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/e2b-haystack.svg)](https://pypi.org/project/e2b-haystack)
5
5
 
6
+ - [Integration page](https://haystack.deepset.ai/integrations/e2b)
6
7
  - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/e2b/CHANGELOG.md)
7
8
 
8
9
  ---
@@ -11,4 +12,4 @@
11
12
 
12
13
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
13
14
 
14
- To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
15
+ To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
@@ -7,7 +7,7 @@ name = "e2b-haystack"
7
7
  dynamic = ["version"]
8
8
  description = "Haystack integration for E2B cloud sandboxes"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.11"
11
11
  license = "Apache-2.0"
12
12
  keywords = ["E2B", "Haystack", "sandbox", "code execution", "tools"]
13
13
  authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
@@ -15,11 +15,11 @@ classifiers = [
15
15
  "License :: OSI Approved :: Apache Software License",
16
16
  "Development Status :: 4 - Beta",
17
17
  "Programming Language :: Python",
18
- "Programming Language :: Python :: 3.10",
19
18
  "Programming Language :: Python :: 3.11",
20
19
  "Programming Language :: Python :: 3.12",
21
20
  "Programming Language :: Python :: 3.13",
22
21
  "Programming Language :: Python :: 3.14",
22
+ "Programming Language :: Python :: 3.15",
23
23
  "Programming Language :: Python :: Implementation :: CPython",
24
24
  "Programming Language :: Python :: Implementation :: PyPy",
25
25
  ]
@@ -131,6 +131,7 @@ ignore = [
131
131
  "PLR0912",
132
132
  "PLR0913",
133
133
  "PLR0915",
134
+ "PLR0917",
134
135
  # Allow `Any` type - used legitimately for dynamic types and SDK boundaries
135
136
  "ANN401",
136
137
  ]
@@ -80,6 +80,14 @@ class RunBashCommandTool(Tool):
80
80
  )
81
81
  self._e2b_sandbox = sandbox
82
82
 
83
+ def warm_up(self) -> None:
84
+ """Start the sandbox if needed."""
85
+ self._e2b_sandbox.warm_up()
86
+
87
+ def close(self) -> None:
88
+ """Close the sandbox."""
89
+ self._e2b_sandbox.close()
90
+
83
91
  def to_dict(self) -> dict[str, Any]:
84
92
  """Serialize this tool to a dictionary."""
85
93
  return {
@@ -8,11 +8,9 @@ from typing import Any, ClassVar
8
8
 
9
9
  from haystack import logging
10
10
  from haystack.core.serialization import generate_qualified_class_name
11
- from haystack.lazy_imports import LazyImport
12
11
  from haystack.utils import Secret, deserialize_secrets_inplace
13
12
 
14
- with LazyImport(message="Run 'pip install e2b'") as e2b_import:
15
- from e2b import Sandbox
13
+ from e2b import Sandbox
16
14
 
17
15
  logger = logging.getLogger(__name__)
18
16
 
@@ -52,15 +50,6 @@ class E2BSandbox:
52
50
  ],
53
51
  )
54
52
  ```
55
-
56
- Lifecycle is handled automatically by the Agent's pipeline. If you use the
57
- tools standalone, call :meth:`warm_up` before the first tool invocation:
58
-
59
- ```python
60
- sandbox.warm_up()
61
- # ... use tools ...
62
- sandbox.close()
63
- ```
64
53
  """
65
54
 
66
55
  # Process-wide cache used during deserialization to keep tools that
@@ -115,7 +104,6 @@ class E2BSandbox:
115
104
  if self._sandbox is not None:
116
105
  return
117
106
 
118
- e2b_import.check()
119
107
  resolved_key = self.api_key.resolve_value()
120
108
  try:
121
109
  logger.info(
@@ -67,6 +67,14 @@ class ListDirectoryTool(Tool):
67
67
  )
68
68
  self._e2b_sandbox = sandbox
69
69
 
70
+ def warm_up(self) -> None:
71
+ """Start the sandbox if needed."""
72
+ self._e2b_sandbox.warm_up()
73
+
74
+ def close(self) -> None:
75
+ """Close the sandbox."""
76
+ self._e2b_sandbox.close()
77
+
70
78
  def to_dict(self) -> dict[str, Any]:
71
79
  """Serialize this tool to a dictionary."""
72
80
  return {
@@ -62,6 +62,14 @@ class ReadFileTool(Tool):
62
62
  )
63
63
  self._e2b_sandbox = sandbox
64
64
 
65
+ def warm_up(self) -> None:
66
+ """Start the sandbox if needed."""
67
+ self._e2b_sandbox.warm_up()
68
+
69
+ def close(self) -> None:
70
+ """Close the sandbox."""
71
+ self._e2b_sandbox.close()
72
+
65
73
  def to_dict(self) -> dict[str, Any]:
66
74
  """Serialize this tool to a dictionary."""
67
75
  return {
@@ -62,6 +62,14 @@ class WriteFileTool(Tool):
62
62
  )
63
63
  self._e2b_sandbox = sandbox
64
64
 
65
+ def warm_up(self) -> None:
66
+ """Start the sandbox if needed."""
67
+ self._e2b_sandbox.warm_up()
68
+
69
+ def close(self) -> None:
70
+ """Close the sandbox."""
71
+ self._e2b_sandbox.close()
72
+
65
73
  def to_dict(self) -> dict[str, Any]:
66
74
  """Serialize this tool to a dictionary."""
67
75
  return {
@@ -6,9 +6,13 @@
6
6
  Integration tests for the E2B sandbox tools.
7
7
 
8
8
  These tests require a valid E2B_API_KEY environment variable and will
9
- spin up a real cloud sandbox on each run.
9
+ spin up a real cloud sandbox on each run. Every test in this module skips when
10
+ the variable is unset, so the suite can always be invoked — including on fork
11
+ PRs, which have no access to repository secrets.
10
12
  """
11
13
 
14
+ import os
15
+
12
16
  import pytest
13
17
 
14
18
  from haystack_integrations.tools.e2b import (
@@ -20,6 +24,8 @@ from haystack_integrations.tools.e2b import (
20
24
  WriteFileTool,
21
25
  )
22
26
 
27
+ pytestmark = pytest.mark.skipif(not os.environ.get("E2B_API_KEY"), reason="E2B_API_KEY not set")
28
+
23
29
 
24
30
  @pytest.fixture(scope="module")
25
31
  def sandbox():
@@ -73,10 +73,8 @@ class TestE2BSandboxInit:
73
73
 
74
74
 
75
75
  class TestE2BSandboxWarmUp:
76
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
77
76
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
78
- def test_warm_up_creates_sandbox(self, mock_sandbox_create, mock_e2b_import):
79
- mock_e2b_import.check.return_value = None
77
+ def test_warm_up_creates_sandbox(self, mock_sandbox_create):
80
78
  mock_instance = _make_sandbox_mock()
81
79
  mock_sandbox_create.return_value = mock_instance
82
80
 
@@ -91,10 +89,8 @@ class TestE2BSandboxWarmUp:
91
89
  )
92
90
  assert sb._sandbox is mock_instance
93
91
 
94
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
95
92
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
96
- def test_warm_up_passes_environment_vars(self, mock_sandbox_create, mock_e2b_import):
97
- mock_e2b_import.check.return_value = None
93
+ def test_warm_up_passes_environment_vars(self, mock_sandbox_create):
98
94
  mock_sandbox_create.return_value = _make_sandbox_mock()
99
95
 
100
96
  sb = _make_sandbox(environment_vars={"MY_VAR": "value"})
@@ -103,10 +99,8 @@ class TestE2BSandboxWarmUp:
103
99
  _, kwargs = mock_sandbox_create.call_args
104
100
  assert kwargs["envs"] == {"MY_VAR": "value"}
105
101
 
106
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
107
102
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
108
- def test_warm_up_is_idempotent(self, mock_sandbox_create, mock_e2b_import):
109
- mock_e2b_import.check.return_value = None
103
+ def test_warm_up_is_idempotent(self, mock_sandbox_create):
110
104
  mock_sandbox_create.return_value = _make_sandbox_mock()
111
105
 
112
106
  sb = _make_sandbox()
@@ -115,10 +109,8 @@ class TestE2BSandboxWarmUp:
115
109
 
116
110
  mock_sandbox_create.assert_called_once()
117
111
 
118
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
119
112
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
120
- def test_warm_up_raises_on_sandbox_error(self, mock_sandbox_create, mock_e2b_import):
121
- mock_e2b_import.check.return_value = None
113
+ def test_warm_up_raises_on_sandbox_error(self, mock_sandbox_create):
122
114
  mock_sandbox_create.side_effect = Exception("connection refused")
123
115
 
124
116
  sb = _make_sandbox()
@@ -343,12 +335,47 @@ class TestToolClasses:
343
335
  ts = E2BToolset()
344
336
  assert ts.sandbox.api_key is not None
345
337
 
346
- def test_tools_from_same_sandbox_share_state(self):
347
- """Tools instantiated with the same sandbox share state."""
348
- sb = _make_sandbox()
349
- bash_tool = RunBashCommandTool(sandbox=sb)
350
- read_tool = ReadFileTool(sandbox=sb)
351
- assert bash_tool._e2b_sandbox is read_tool._e2b_sandbox
338
+ @pytest.mark.parametrize("tool_class", [RunBashCommandTool, ReadFileTool, WriteFileTool, ListDirectoryTool])
339
+ def test_warm_up_and_close(self, tool_class):
340
+ sandbox = MagicMock(spec=E2BSandbox)
341
+ tool = tool_class(sandbox=sandbox)
342
+
343
+ tool.warm_up()
344
+ sandbox.warm_up.assert_called_once_with()
345
+ tool.close()
346
+ sandbox.close.assert_called_once_with()
347
+
348
+ @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
349
+ def test_tools_share_sandbox_lifecycle(self, create):
350
+ first, second = _make_sandbox_mock(), _make_sandbox_mock()
351
+ create.side_effect = [first, second]
352
+ sandbox = _make_sandbox()
353
+ tools = [ReadFileTool(sandbox=sandbox), ListDirectoryTool(sandbox=sandbox)]
354
+
355
+ for tool in tools:
356
+ tool.close()
357
+ create.assert_not_called()
358
+
359
+ tools[1].invoke(path="/")
360
+ first.files.list.assert_called_once_with("/")
361
+ for tool in tools:
362
+ tool.warm_up()
363
+ create.assert_called_once()
364
+
365
+ for tool in tools:
366
+ tool.close()
367
+ first.kill.assert_called_once_with()
368
+ assert sandbox._sandbox is None
369
+
370
+ for tool in tools:
371
+ tool.warm_up()
372
+ assert create.call_count == 2
373
+ assert sandbox._sandbox is second
374
+
375
+ for tool in tools:
376
+ tool.close()
377
+ first.kill.assert_called_once_with()
378
+ second.kill.assert_called_once_with()
352
379
 
353
380
 
354
381
  # ---------------------------------------------------------------------------
@@ -378,10 +405,8 @@ class TestRunBashCommandTool:
378
405
 
379
406
  mock.commands.run.assert_called_once_with("sleep 5", timeout=30)
380
407
 
381
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
382
408
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
383
- def test_wraps_warm_up_failure(self, mock_sandbox_create, mock_e2b_import):
384
- mock_e2b_import.check.return_value = None
409
+ def test_wraps_warm_up_failure(self, mock_sandbox_create):
385
410
  mock_sandbox_create.side_effect = Exception("connection refused")
386
411
  sb = _make_sandbox()
387
412
  tool = RunBashCommandTool(sandbox=sb)
@@ -421,10 +446,8 @@ class TestReadFileTool:
421
446
 
422
447
  assert result == "binary content"
423
448
 
424
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
425
449
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
426
- def test_wraps_warm_up_failure(self, mock_sandbox_create, mock_e2b_import):
427
- mock_e2b_import.check.return_value = None
450
+ def test_wraps_warm_up_failure(self, mock_sandbox_create):
428
451
  mock_sandbox_create.side_effect = Exception("connection refused")
429
452
  sb = _make_sandbox()
430
453
  tool = ReadFileTool(sandbox=sb)
@@ -454,10 +477,8 @@ class TestWriteFileTool:
454
477
  assert "/output/result.txt" in result
455
478
  mock.files.write.assert_called_once_with("/output/result.txt", "hello")
456
479
 
457
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
458
480
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
459
- def test_wraps_warm_up_failure(self, mock_sandbox_create, mock_e2b_import):
460
- mock_e2b_import.check.return_value = None
481
+ def test_wraps_warm_up_failure(self, mock_sandbox_create):
461
482
  mock_sandbox_create.side_effect = Exception("connection refused")
462
483
  sb = _make_sandbox()
463
484
  tool = WriteFileTool(sandbox=sb)
@@ -507,10 +528,8 @@ class TestListDirectoryTool:
507
528
 
508
529
  assert result == "(empty directory)"
509
530
 
510
- @patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
511
531
  @patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
512
- def test_wraps_warm_up_failure(self, mock_sandbox_create, mock_e2b_import):
513
- mock_e2b_import.check.return_value = None
532
+ def test_wraps_warm_up_failure(self, mock_sandbox_create):
514
533
  mock_sandbox_create.side_effect = Exception("connection refused")
515
534
  sb = _make_sandbox()
516
535
  tool = ListDirectoryTool(sandbox=sb)
File without changes
File without changes