Skip to content

Commit 56d359f

Browse files
committed
fix: #2938 make sandboxes importable on Windows
1 parent e80d2d2 commit 56d359f

File tree

3 files changed

+124
-13
lines changed

3 files changed

+124
-13
lines changed

src/agents/sandbox/sandboxes/__init__.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,27 @@
55
execution environments (e.g. Docker, local Unix).
66
"""
77

8-
from .unix_local import (
9-
UnixLocalSandboxClient,
10-
UnixLocalSandboxClientOptions,
11-
UnixLocalSandboxSession,
12-
UnixLocalSandboxSessionState,
13-
)
8+
from __future__ import annotations
9+
10+
import sys
11+
from typing import TYPE_CHECKING
12+
13+
_HAS_UNIX_LOCAL = sys.platform != "win32"
14+
15+
if _HAS_UNIX_LOCAL:
16+
from .unix_local import (
17+
UnixLocalSandboxClient,
18+
UnixLocalSandboxClientOptions,
19+
UnixLocalSandboxSession,
20+
UnixLocalSandboxSessionState,
21+
)
22+
elif TYPE_CHECKING:
23+
from .unix_local import ( # noqa: F401
24+
UnixLocalSandboxClient,
25+
UnixLocalSandboxClientOptions,
26+
UnixLocalSandboxSession,
27+
UnixLocalSandboxSessionState,
28+
)
1429

1530
try:
1631
from .docker import ( # noqa: F401
@@ -25,12 +40,17 @@
2540
# Docker is an optional extra; keep base imports working without it.
2641
_HAS_DOCKER = False
2742

28-
__all__ = [
29-
"UnixLocalSandboxClient",
30-
"UnixLocalSandboxClientOptions",
31-
"UnixLocalSandboxSession",
32-
"UnixLocalSandboxSessionState",
33-
]
43+
__all__: list[str] = []
44+
45+
if _HAS_UNIX_LOCAL:
46+
__all__.extend(
47+
[
48+
"UnixLocalSandboxClient",
49+
"UnixLocalSandboxClientOptions",
50+
"UnixLocalSandboxSession",
51+
"UnixLocalSandboxSessionState",
52+
]
53+
)
3454

3555
if _HAS_DOCKER:
3656
__all__.extend(

src/agents/sandbox/sandboxes/unix_local.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import sys
2+
3+
if sys.platform == "win32": # pragma: no cover
4+
raise ImportError(
5+
"UnixLocalSandbox is not supported on Windows. "
6+
"Use DockerSandboxClient or another sandbox backend."
7+
)
8+
19
import asyncio
210
import errno
311
import fcntl
@@ -7,7 +15,6 @@
715
import shlex
816
import shutil
917
import signal
10-
import sys
1118
import tarfile
1219
import tempfile
1320
import termios
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
3+
import importlib
4+
import sys
5+
from types import ModuleType
6+
from typing import Any
7+
8+
import pytest
9+
10+
11+
def _restore_module(name: str, original: ModuleType | None) -> None:
12+
sys.modules.pop(name, None)
13+
if original is not None:
14+
sys.modules[name] = original
15+
16+
17+
def _restore_attr(obj: Any, name: str, original: object, existed: bool) -> None:
18+
if existed:
19+
setattr(obj, name, original)
20+
else:
21+
try:
22+
delattr(obj, name)
23+
except AttributeError:
24+
pass
25+
26+
27+
def test_sandboxes_package_import_skips_unix_local_on_windows(monkeypatch) -> None:
28+
sandbox_package = importlib.import_module("agents.sandbox")
29+
original_sandboxes_module = sys.modules.pop("agents.sandbox.sandboxes", None)
30+
original_unix_local_module = sys.modules.pop("agents.sandbox.sandboxes.unix_local", None)
31+
original_sandboxes_attr = getattr(sandbox_package, "sandboxes", None)
32+
had_sandboxes_attr = hasattr(sandbox_package, "sandboxes")
33+
34+
if had_sandboxes_attr:
35+
delattr(sandbox_package, "sandboxes")
36+
monkeypatch.setattr(sys, "platform", "win32")
37+
38+
try:
39+
sandboxes = importlib.import_module("agents.sandbox.sandboxes")
40+
41+
assert sandboxes.__name__ == "agents.sandbox.sandboxes"
42+
assert "UnixLocalSandboxClient" not in sandboxes.__all__
43+
assert "UnixLocalSandboxClient" not in sandboxes.__dict__
44+
assert "agents.sandbox.sandboxes.unix_local" not in sys.modules
45+
finally:
46+
_restore_module("agents.sandbox.sandboxes", original_sandboxes_module)
47+
_restore_module("agents.sandbox.sandboxes.unix_local", original_unix_local_module)
48+
_restore_attr(
49+
sandbox_package,
50+
"sandboxes",
51+
original_sandboxes_attr,
52+
had_sandboxes_attr,
53+
)
54+
55+
56+
def test_unix_local_backend_import_raises_clear_error_on_windows(monkeypatch) -> None:
57+
parent = importlib.import_module("agents.sandbox.sandboxes")
58+
original_unix_local_module = sys.modules.pop("agents.sandbox.sandboxes.unix_local", None)
59+
original_unix_local_attr = getattr(parent, "unix_local", None)
60+
had_unix_local_attr = hasattr(parent, "unix_local")
61+
62+
if had_unix_local_attr:
63+
delattr(parent, "unix_local")
64+
monkeypatch.setattr(sys, "platform", "win32")
65+
66+
try:
67+
with pytest.raises(ImportError, match="not supported on Windows"):
68+
importlib.import_module("agents.sandbox.sandboxes.unix_local")
69+
finally:
70+
_restore_module("agents.sandbox.sandboxes.unix_local", original_unix_local_module)
71+
_restore_attr(
72+
parent,
73+
"unix_local",
74+
original_unix_local_attr,
75+
had_unix_local_attr,
76+
)
77+
78+
79+
@pytest.mark.skipif(sys.platform == "win32", reason="Unix local sandbox is unavailable on Windows")
80+
def test_sandboxes_package_exports_unix_local_on_supported_platforms() -> None:
81+
sandboxes = importlib.import_module("agents.sandbox.sandboxes")
82+
83+
assert "UnixLocalSandboxClient" in sandboxes.__all__
84+
assert sandboxes.UnixLocalSandboxClient.__name__ == "UnixLocalSandboxClient"

0 commit comments

Comments
 (0)