From 7557ac8dcaf7b87398f2cbe31ce67af137a8c0bb Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Tue, 15 Oct 2024 21:44:34 +0300 Subject: [PATCH] Provide proper support for Pyro5 in the guibot proxy interface The previously provided support wasn't complete as it needed a compatibility layer for the exception serialization. The current API use now is exclusive to Pyro5 and handles its own exposing based on class hierarchy and proxified return objects. --- guibot/guibot_proxy.py | 28 +++++++++++++++++++++++----- packaging/pip_requirements.txt | 4 ++-- tests/test_interfaces.py | 4 ++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/guibot/guibot_proxy.py b/guibot/guibot_proxy.py index 0a986e89..ae4e74e5 100644 --- a/guibot/guibot_proxy.py +++ b/guibot/guibot_proxy.py @@ -31,12 +31,12 @@ """ import re +import inspect +import logging from typing import Any -try: - import Pyro5 as pyro -except ImportError: - import Pyro4 as pyro +import Pyro5 as pyro +from Pyro5 import server from . import errors from .guibot import GuiBot @@ -44,6 +44,9 @@ from .controller import Controller +log = logging.getLogger("guibot.proxy") + + def serialize_custom_error( class_obj: type, ) -> dict[str, "str | getset_descriptor | dictproxy"]: @@ -69,7 +72,7 @@ def register_exception_serialization() -> None: for it with some extra setup steps and functions below. """ for exception in [errors.UnsupportedBackendError]: - pyro.util.SerializerBase.register_class_to_dict( + pyro.serializers.SerializerBase.register_class_to_dict( exception, serialize_custom_error ) @@ -100,7 +103,22 @@ def _proxify(self, obj: Any) -> Any: if isinstance(obj, (int, float, bool, str)) or obj is None: return obj if obj not in self._pyroDaemon.objectsById.values(): + log.info("Providing proxy alternative to %s", obj) self._pyroDaemon.register(obj) + cls = type(obj) + # counter pyro access redesigning normal python inheritance + for base_cls in cls.__mro__: + if base_cls in (object, int, float, bool, str, tuple, frozenset): + # known immutable classes should be skipped + continue + if inspect.ismethoddescriptor(base_cls): + # method descriptors should be skipped + continue + try: + server.expose(base_cls) + except (TypeError, AttributeError) as error: + log.warning("Additional class exposing error: %s", error) + continue return obj def nearby(self, *args: tuple[type, ...], **kwargs: dict[str, type]) -> str: diff --git a/packaging/pip_requirements.txt b/packaging/pip_requirements.txt index 22ae6365..cc2db072 100644 --- a/packaging/pip_requirements.txt +++ b/packaging/pip_requirements.txt @@ -16,8 +16,8 @@ vncdotool==0.12.0; sys_platform != 'win32' and platform_python_implementation != pyautogui==0.9.54; platform_python_implementation != "PyPy" # optional proxy guibot interface deps -serpent==1.40 -Pyro4==4.82 +serpent==1.41 +Pyro5==5.14 # coverage analysis to use for testing coverage diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py index c72f4efe..c709f207 100644 --- a/tests/test_interfaces.py +++ b/tests/test_interfaces.py @@ -69,13 +69,13 @@ class ProxyAPITest(TestCase): def setUp(self) -> None: # fake the remote objects dependency for this interface - sys.modules["Pyro4"] = mock.MagicMock() + sys.modules["Pyro5"] = mock.MagicMock() from guibot import guibot_proxy as remote self.interface = remote.GuiBotProxy(cv=None, dc=None) self.interface._proxify = mock.MagicMock() def tearDown(self) -> None: - del sys.modules["Pyro4"] + del sys.modules["Pyro5"] @mock.patch('guibot.guibot_proxy.super') def test_call_delegations(self, mock_super) -> None: