From a76cac78402df7ad01841c93fb64b02af4700b07 Mon Sep 17 00:00:00 2001 From: Vendula Poncova Date: Thu, 9 Jun 2022 15:41:08 +0200 Subject: [PATCH] Raise TimeoutError if a DBus call times out * Raise the TimeoutError exception instead of the original GLib.Error. * Improve the documentation of the timeout and add an example. --- dasbus/client/handler.py | 49 ++++++++++++++++++++++++++++------------ docs/examples.rst | 11 +++++++++ tests/test_client.py | 18 ++++++++++++++- tests/test_dbus.py | 21 +++++++++++++++++ 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/dasbus/client/handler.py b/dasbus/client/handler.py index 839f5a6..43b22f6 100644 --- a/dasbus/client/handler.py +++ b/dasbus/client/handler.py @@ -137,6 +137,12 @@ class GLibClient(object): """Unsubscribe from a signal.""" connection.signal_unsubscribe(subscription_id) + @classmethod + def is_timeout_error(cls, error): + """Is it a timeout error?""" + return isinstance(error, GLib.Error) \ + and error.matches(Gio.io_error_quark(), Gio.IOErrorEnum.TIMED_OUT) + @classmethod def is_remote_error(cls, error): """Is it a remote DBus error?""" @@ -479,22 +485,37 @@ class ClientObjectHandler(AbstractClientObjectHandler): def _handle_method_error(self, error): """Handle an error of a DBus call. + If the call returned a DBus error, it will be mapped + to a Python exception based on the rules defined in + the available error mapper. It should be a subclass + of DBusError. + :param error: an exception raised during the call + :raise Exception: if the call unexpectedly failed + :raise TimeoutError: if the DBus call timed out + :raise DBusError: if the call returned a DBus error """ - # Re-raise if it is not a remote DBus error. - if not self._client.is_remote_error(error): - raise error - - name = self._client.get_remote_error_name(error) - cls = self._error_mapper.get_exception_type(name) - message = self._client.get_remote_error_message(error) - - # Create a new exception. - exception = cls(message) - exception.dbus_name = name - - # Raise a new instance of the exception class. - raise exception from None + if self._client.is_remote_error(error): + # Handle a remote DBus error. + name = self._client.get_remote_error_name(error) + cls = self._error_mapper.get_exception_type(name) + message = self._client.get_remote_error_message(error) + + # Create a new exception. + exception = cls(message) + exception.dbus_name = name + + # Raise a new instance of the exception class. + raise exception from None + + if self._client.is_timeout_error(error): + # Handle a timeout error. + raise TimeoutError( + "The DBus call timeout was reached." + ) from None + + # Or re-raise the original error. + raise error def _handle_method_result(self, result): """Handle a result of a DBus call. diff --git a/docs/examples.rst b/docs/examples.rst index 8d272a8..176c0b4 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -129,6 +129,17 @@ mapper to transform Python exceptions to DBus errors and back. class InvalidArgs(DBusError): pass +Call DBus methods with a timeout (specified in milliseconds). + +.. code-block:: python + + proxy = NETWORK_MANAGER.get_proxy() + + try: + proxy.CheckConnectivity(timeout=3) + except TimeoutError: + print("The call timed out!") + Call DBus methods asynchronously. .. code-block:: python diff --git a/tests/test_client.py b/tests/test_client.py index 03890ea..dcbdbb9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -30,7 +30,8 @@ from dasbus.typing import get_variant, get_variant_type, VariantType import gi gi.require_version("Gio", "2.0") -from gi.repository import Gio +gi.require_version("GLib", "2.0") +from gi.repository import Gio, GLib class FakeException(Exception): @@ -230,6 +231,21 @@ class DBusClientTestCase(unittest.TestCase): self.assertEqual(str(cm.exception), "My message.") + # Handle timeout exception. + self._set_reply(GLib.Error.new_literal( + Gio.io_error_quark(), + "Timeout was reached", + Gio.IOErrorEnum.TIMED_OUT + )) + + with self.assertRaises(TimeoutError) as cm: + self.proxy.Method1() + + self.assertEqual( + str(cm.exception), + "The DBus call timeout was reached." + ) + # Handle local exception. self._set_reply(Exception("My message.")) diff --git a/tests/test_dbus.py b/tests/test_dbus.py index d62e25c..4e372dc 100644 --- a/tests/test_dbus.py +++ b/tests/test_dbus.py @@ -283,6 +283,27 @@ class DBusTestCase(unittest.TestCase): self.assertEqual(sorted(self.service._names), ["Bar", "Foo"]) + def test_timeout(self): + """Call a DBus method with a timeout.""" + self._set_service(ExampleInterface()) + self.assertEqual(self.service._names, []) + + def test1(): + proxy = self._get_proxy() + proxy.Hello("Foo", timeout=1000) + + def test2(): + proxy = self._get_proxy() + + with self.assertRaises(TimeoutError): + proxy.Hello("Bar", timeout=0) + + self._add_client(test1) + self._add_client(test2) + self._run_test() + + self.assertEqual(sorted(self.service._names), ["Bar", "Foo"]) + def test_name(self): """Use a DBus read-only property.""" self._set_service(ExampleInterface()) -- 2.33.0