From fac5d168873692f0ac8c0ffaacbaba52b79e3082 Mon Sep 17 00:00:00 2001 From: zeroxjf Date: Mon, 18 May 2026 14:39:49 -0400 Subject: [PATCH 1/2] Avoid native side effects during imports --- .../computer_server/__init__.py | 11 +++++-- .../computer_server/handlers/factory.py | 33 +++++++++---------- .../computer_server/handlers/macos.py | 28 +--------------- .../tests/test_import_safety.py | 33 +++++++++++++++++++ 4 files changed, 58 insertions(+), 47 deletions(-) create mode 100644 libs/python/computer-server/tests/test_import_safety.py diff --git a/libs/python/computer-server/computer_server/__init__.py b/libs/python/computer-server/computer_server/__init__.py index ef28cbb104..6ffa80b59e 100644 --- a/libs/python/computer-server/computer_server/__init__.py +++ b/libs/python/computer-server/computer_server/__init__.py @@ -7,12 +7,17 @@ __version__: str = "0.1.0" -# Explicitly export Server for static type checkers -from .server import Server as Server # noqa: F401 - __all__ = ["Server", "run_cli"] +def __getattr__(name: str): + if name == "Server": + from .server import Server + + return Server + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + def run_cli() -> None: """Entry point for CLI""" from .cli import main diff --git a/libs/python/computer-server/computer_server/handlers/factory.py b/libs/python/computer-server/computer_server/handlers/factory.py index 00029fa10c..906fbdd093 100644 --- a/libs/python/computer-server/computer_server/handlers/factory.py +++ b/libs/python/computer-server/computer_server/handlers/factory.py @@ -17,23 +17,6 @@ OS_TYPE = get_current_os() -if OS_TYPE == "android": - from .android import ( - AndroidAccessibilityHandler, - AndroidAutomationHandler, - AndroidDesktopHandler, - AndroidFileHandler, - AndroidWindowHandler, - ) -elif OS_TYPE == "darwin": - from computer_server.diorama.macos import MacOSDioramaHandler - - from .macos import MacOSAccessibilityHandler, MacOSAutomationHandler -elif OS_TYPE == "linux": - from .linux import LinuxAccessibilityHandler, LinuxAutomationHandler -elif OS_TYPE == "windows": - from .windows import WindowsAccessibilityHandler, WindowsAutomationHandler - from .generic import GenericDesktopHandler, GenericFileHandler, GenericWindowHandler @@ -81,6 +64,14 @@ def create_handlers() -> Tuple[ GenericWindowHandler(), ) elif OS_TYPE == "android": + from .android import ( + AndroidAccessibilityHandler, + AndroidAutomationHandler, + AndroidDesktopHandler, + AndroidFileHandler, + AndroidWindowHandler, + ) + return ( AndroidAccessibilityHandler(), AndroidAutomationHandler(), @@ -90,6 +81,10 @@ def create_handlers() -> Tuple[ AndroidWindowHandler(), ) elif OS_TYPE == "darwin": + from computer_server.diorama.macos import MacOSDioramaHandler + + from .macos import MacOSAccessibilityHandler, MacOSAutomationHandler + return ( MacOSAccessibilityHandler(), MacOSAutomationHandler(), @@ -99,6 +94,8 @@ def create_handlers() -> Tuple[ GenericWindowHandler(), ) elif OS_TYPE == "linux": + from .linux import LinuxAccessibilityHandler, LinuxAutomationHandler + return ( LinuxAccessibilityHandler(), LinuxAutomationHandler(), @@ -108,6 +105,8 @@ def create_handlers() -> Tuple[ GenericWindowHandler(), ) elif OS_TYPE == "windows": + from .windows import WindowsAccessibilityHandler, WindowsAutomationHandler + return ( WindowsAccessibilityHandler(), WindowsAutomationHandler(), diff --git a/libs/python/computer-server/computer_server/handlers/macos.py b/libs/python/computer-server/computer_server/handlers/macos.py index f1d339a22f..4fee050808 100644 --- a/libs/python/computer-server/computer_server/handlers/macos.py +++ b/libs/python/computer-server/computer_server/handlers/macos.py @@ -42,6 +42,7 @@ from ApplicationServices import kAXValueCGSizeType # type: ignore from ApplicationServices import kAXVisibleChildrenAttribute # type: ignore from ApplicationServices import kAXWindowsAttribute # type: ignore +from PIL import Image, ImageGrab from pynput.keyboard import Controller as KeyboardController from pynput.keyboard import Key from pynput.mouse import Button @@ -57,33 +58,6 @@ logger = logging.getLogger(__name__) -# Trigger accessibility permissions prompt on macOS -try: - # Source - https://stackoverflow.com/a/17134 - # Posted by Andreas - # Retrieved 2025-12-03, License - CC BY-SA 4.0 - # Attempt to create and post a mouse event to trigger the permissions prompt - # This will cause macOS to show "Python would like to control this computer using accessibility features" - current_pos = CGEventGetLocation(CGEventCreate(None)) - p = CGPoint() - p.x = current_pos.x - p.y = current_pos.y - - me = CGEventCreateMouseEvent(None, kCGEventMouseMoved, p, 0) - if me: - CGEventPost(kCGHIDEventTap, me) - CFRelease(me) -except Exception as e: - logger.debug(f"Failed to trigger accessibility permissions prompt: {e}") - -# Trigger screen recording prompt on macOS -try: - from PIL import Image, ImageGrab - - ImageGrab.grab() -except Exception as e: - logger.debug(f"Failed to trigger screenshot permissions prompt: {e}") - # Fix: pynput's MouseController.position setter uses CGEventPost with # kCGEventMouseMoved internally, which silently fails in macOS VMs running diff --git a/libs/python/computer-server/tests/test_import_safety.py b/libs/python/computer-server/tests/test_import_safety.py new file mode 100644 index 0000000000..0f74a1c549 --- /dev/null +++ b/libs/python/computer-server/tests/test_import_safety.py @@ -0,0 +1,33 @@ +import platform +import subprocess +import sys + +import pytest + + +def _assert_import_exits_cleanly(import_statement: str): + result = subprocess.run( + [sys.executable, "-X", "faulthandler", "-c", f"{import_statement}; print('ok')"], + capture_output=True, + text=True, + timeout=20, + ) + + assert result.returncode == 0, result.stdout + result.stderr + assert "ok" in result.stdout + + +def test_package_import_does_not_import_native_handlers_at_teardown(): + _assert_import_exits_cleanly("import computer_server") + + +def test_generic_handler_import_does_not_import_native_handlers_at_teardown(): + _assert_import_exits_cleanly("import computer_server.handlers.generic") + + +@pytest.mark.skipif(platform.system() != "Darwin", reason="macOS handler requires AppKit") +def test_macos_handler_creation_exits_cleanly_at_teardown(): + _assert_import_exits_cleanly( + "from computer_server.handlers.factory import HandlerFactory; " + "HandlerFactory.create_handlers()" + ) From 583ea928372845010ae1212a0905286f00dfcddd Mon Sep 17 00:00:00 2001 From: zeroxjf Date: Mon, 18 May 2026 14:57:48 -0400 Subject: [PATCH 2/2] Isolate import safety subprocess environment --- libs/python/computer-server/tests/test_import_safety.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/python/computer-server/tests/test_import_safety.py b/libs/python/computer-server/tests/test_import_safety.py index 0f74a1c549..1b920c3840 100644 --- a/libs/python/computer-server/tests/test_import_safety.py +++ b/libs/python/computer-server/tests/test_import_safety.py @@ -1,3 +1,4 @@ +import os import platform import subprocess import sys @@ -6,9 +7,14 @@ def _assert_import_exits_cleanly(import_statement: str): + env = os.environ.copy() + for key in ("CUA_BACKEND", "CUA_VNC_HOST", "CUA_VNC_PORT", "CUA_VNC_PASSWORD"): + env.pop(key, None) + result = subprocess.run( [sys.executable, "-X", "faulthandler", "-c", f"{import_statement}; print('ok')"], capture_output=True, + env=env, text=True, timeout=20, )