summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch
diff options
context:
space:
mode:
authorDerek Straka <derek@asterius.io>2024-03-22 16:59:54 +0000
committerKhem Raj <raj.khem@gmail.com>2024-03-22 21:19:50 -0700
commit3c188f75eabf3cb2c976e69e4c18c401e20635dd (patch)
tree56d5082157836964843fa69dfd8d1b3b397e1bc1 /meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch
parent1cb0dae6b8bddcef854aa30a61af8213b638a63a (diff)
downloadmeta-openembedded-3c188f75eabf3cb2c976e69e4c18c401e20635dd.tar.gz
python3-dbus: re-add recipe with latest patches and add ptest
The python3-dbus package was removed in (dac933e). While the upstream project isn't active, other distributions (e.g. Fedora, Debian, etc) continue to offer the package and apply patches to resolve reported issues. While other packages offer similar functionality (e.g. dasbus), they are not drop in replacements and the general dbus functionality works out of the box. The python package has accomplished it's goal of providing useful functionality, and the proposal is to continue to have it available in meta-python for use. Signed-off-by: Derek Straka <derek@asterius.io> Signed-off-by: Khem Raj <raj.khem@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch')
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch495
1 files changed, 495 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch b/meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch
new file mode 100644
index 0000000000..a1b8a6c38c
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch
@@ -0,0 +1,495 @@
1From 773858e1afd21cdf3ceef2cd35509f0b4882bf16 Mon Sep 17 00:00:00 2001
2From: Vendula Poncova <vponcova@redhat.com>
3Date: Tue, 1 Aug 2017 16:54:24 +0200
4Subject: [PATCH 3/3] Support transformation between D-Bus errors and
5 exceptions.
6
7Exceptions can be registered with decorators, raised in a remote
8method and recreated after return from the remote call.
9
10Adapted from Fedora [https://src.fedoraproject.org/cgit/rpms/python-pydbus.git/]
11
12Upstream-Status: Inactive-Upstream (Last release 12/18/2016; Last commit 05/6/2018)
13
14Signed-off-by: Derek Straka <derek@asterius.io>
15---
16 doc/tutorial.rst | 47 ++++++++++++++++++
17 pydbus/error.py | 97 ++++++++++++++++++++++++++++++++++++
18 pydbus/proxy_method.py | 18 +++++--
19 pydbus/registration.py | 16 ++++--
20 tests/error.py | 67 +++++++++++++++++++++++++
21 tests/publish_error.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++++
22 tests/run.sh | 2 +
23 7 files changed, 371 insertions(+), 8 deletions(-)
24 create mode 100644 pydbus/error.py
25 create mode 100644 tests/error.py
26 create mode 100644 tests/publish_error.py
27
28diff --git a/doc/tutorial.rst b/doc/tutorial.rst
29index b8479cf..7fe55e1 100644
30--- a/doc/tutorial.rst
31+++ b/doc/tutorial.rst
32@@ -341,6 +341,53 @@ See ``help(bus.request_name)`` and ``help(bus.register_object)`` for details.
33
34 .. --------------------------------------------------------------------
35
36+Error handling
37+==============
38+
39+You can map D-Bus errors to your exception classes for better error handling.
40+To handle D-Bus errors, use the ``@map_error`` decorator::
41+
42+ from pydbus.error import map_error
43+
44+ @map_error("org.freedesktop.DBus.Error.InvalidArgs")
45+ class InvalidArgsException(Exception):
46+ pass
47+
48+ try:
49+ ...
50+ catch InvalidArgsException as e:
51+ print(e)
52+
53+To register new D-Bus errors, use the ``@register_error`` decorator::
54+
55+ from pydbus.error import register_error
56+
57+ @map_error("net.lew21.pydbus.TutorialExample.MyError", MY_DOMAIN, MY_EXCEPTION_CODE)
58+ class MyException(Exception):
59+ pass
60+
61+Then you can raise ``MyException`` from the D-Bus method of the remote object::
62+
63+ def Method():
64+ raise MyException("Message")
65+
66+And catch the same exception on the client side::
67+
68+ try:
69+ proxy.Method()
70+ catch MyException as e:
71+ print(e)
72+
73+To handle all unknown D-Bus errors, use the ``@map_by_default`` decorator to specify the default exception::
74+
75+ from pydbus.error import map_by_default
76+
77+ @map_by_default
78+ class DefaultException(Exception):
79+ pass
80+
81+.. --------------------------------------------------------------------
82+
83 Data types
84 ==========
85
86diff --git a/pydbus/error.py b/pydbus/error.py
87new file mode 100644
88index 0000000..aaa3510
89--- /dev/null
90+++ b/pydbus/error.py
91@@ -0,0 +1,97 @@
92+from gi.repository import GLib, Gio
93+
94+
95+def register_error(name, domain, code):
96+ """Register and map decorated exception class to a DBus error."""
97+ def decorated(cls):
98+ error_registration.register_error(cls, name, domain, code)
99+ return cls
100+
101+ return decorated
102+
103+
104+def map_error(error_name):
105+ """Map decorated exception class to a DBus error."""
106+ def decorated(cls):
107+ error_registration.map_error(cls, error_name)
108+ return cls
109+
110+ return decorated
111+
112+
113+def map_by_default(cls):
114+ """Map decorated exception class to all unknown DBus errors."""
115+ error_registration.map_by_default(cls)
116+ return cls
117+
118+
119+class ErrorRegistration(object):
120+ """Class for mapping exceptions to DBus errors."""
121+
122+ _default = None
123+ _map = dict()
124+ _reversed_map = dict()
125+
126+ def map_by_default(self, exception_cls):
127+ """Set the exception class as a default."""
128+ self._default = exception_cls
129+
130+ def map_error(self, exception_cls, name):
131+ """Map the exception class to a DBus name."""
132+ self._map[name] = exception_cls
133+ self._reversed_map[exception_cls] = name
134+
135+ def register_error(self, exception_cls, name, domain, code):
136+ """Map and register the exception class to a DBus name."""
137+ self.map_error(exception_cls, name)
138+ return Gio.DBusError.register_error(domain, code, name)
139+
140+ def is_registered_exception(self, obj):
141+ """Is the exception registered?"""
142+ return obj.__class__ in self._reversed_map
143+
144+ def get_dbus_name(self, obj):
145+ """Get the DBus name of the exception."""
146+ return self._reversed_map.get(obj.__class__)
147+
148+ def get_exception_class(self, name):
149+ """Get the exception class mapped to the DBus name."""
150+ return self._map.get(name, self._default)
151+
152+ def transform_message(self, name, message):
153+ """Transform the message of the exception."""
154+ prefix = "{}:{}: ".format("GDBus.Error", name)
155+
156+ if message.startswith(prefix):
157+ return message[len(prefix):]
158+
159+ return message
160+
161+ def transform_exception(self, e):
162+ """Transform the remote error to the exception."""
163+ if not isinstance(e, GLib.Error):
164+ return e
165+
166+ if not Gio.DBusError.is_remote_error(e):
167+ return e
168+
169+ # Get DBus name of the error.
170+ name = Gio.DBusError.get_remote_error(e)
171+ # Get the exception class.
172+ exception_cls = self.get_exception_class(name)
173+
174+ # Return the original exception.
175+ if not exception_cls:
176+ return e
177+
178+ # Return new exception.
179+ message = self.transform_message(name, e.message)
180+ exception = exception_cls(message)
181+ exception.dbus_name = name
182+ exception.dbus_domain = e.domain
183+ exception.dbus_code = e.code
184+ return exception
185+
186+
187+# Default error registration.
188+error_registration = ErrorRegistration()
189diff --git a/pydbus/proxy_method.py b/pydbus/proxy_method.py
190index 442fe07..a73f9eb 100644
191--- a/pydbus/proxy_method.py
192+++ b/pydbus/proxy_method.py
193@@ -2,6 +2,7 @@ from gi.repository import GLib
194 from .generic import bound_method
195 from .identifier import filter_identifier
196 from .timeout import timeout_to_glib
197+from .error import error_registration
198
199 try:
200 from inspect import Signature, Parameter
201@@ -87,9 +88,20 @@ class ProxyMethod(object):
202 call_args += (self._finish_async_call, (callback, callback_args))
203 instance._bus.con.call(*call_args)
204 return None
205+
206 else:
207- ret = instance._bus.con.call_sync(*call_args)
208- return self._unpack_return(ret)
209+ result = None
210+ error = None
211+
212+ try:
213+ result = instance._bus.con.call_sync(*call_args)
214+ except Exception as e:
215+ error = error_registration.transform_exception(e)
216+
217+ if error:
218+ raise error
219+
220+ return self._unpack_return(result)
221
222 def _unpack_return(self, values):
223 ret = values.unpack()
224@@ -108,7 +120,7 @@ class ProxyMethod(object):
225 ret = source.call_finish(result)
226 return_args = self._unpack_return(ret)
227 except Exception as err:
228- error = err
229+ error = error_registration.transform_exception(err)
230
231 callback, callback_args = user_data
232 callback(*callback_args, returned=return_args, error=error)
233diff --git a/pydbus/registration.py b/pydbus/registration.py
234index f531539..1d2cbcb 100644
235--- a/pydbus/registration.py
236+++ b/pydbus/registration.py
237@@ -5,6 +5,7 @@ from . import generic
238 from .exitable import ExitableWithAliases
239 from functools import partial
240 from .method_call_context import MethodCallContext
241+from .error import error_registration
242 import logging
243
244 try:
245@@ -91,11 +92,16 @@ class ObjectWrapper(ExitableWithAliases("unwrap")):
246 logger = logging.getLogger(__name__)
247 logger.exception("Exception while handling %s.%s()", interface_name, method_name)
248
249- #TODO Think of a better way to translate Python exception types to DBus error types.
250- e_type = type(e).__name__
251- if not "." in e_type:
252- e_type = "unknown." + e_type
253- invocation.return_dbus_error(e_type, str(e))
254+ if error_registration.is_registered_exception(e):
255+ name = error_registration.get_dbus_name(e)
256+ invocation.return_dbus_error(name, str(e))
257+ else:
258+ logger.info("name is not registered")
259+ e_type = type(e).__name__
260+ if not "." in e_type:
261+ e_type = "unknown." + e_type
262+
263+ invocation.return_dbus_error(e_type, str(e))
264
265 def Get(self, interface_name, property_name):
266 type = self.readable_properties[interface_name + "." + property_name]
267diff --git a/tests/error.py b/tests/error.py
268new file mode 100644
269index 0000000..3ec507d
270--- /dev/null
271+++ b/tests/error.py
272@@ -0,0 +1,67 @@
273+from pydbus.error import ErrorRegistration
274+
275+
276+class ExceptionA(Exception):
277+ pass
278+
279+
280+class ExceptionB(Exception):
281+ pass
282+
283+
284+class ExceptionC(Exception):
285+ pass
286+
287+
288+class ExceptionD(Exception):
289+ pass
290+
291+
292+class ExceptionE(Exception):
293+ pass
294+
295+
296+def test_error_mapping():
297+ r = ErrorRegistration()
298+ r.map_error(ExceptionA, "net.lew21.pydbus.tests.ErrorA")
299+ r.map_error(ExceptionB, "net.lew21.pydbus.tests.ErrorB")
300+ r.map_error(ExceptionC, "net.lew21.pydbus.tests.ErrorC")
301+
302+ assert r.is_registered_exception(ExceptionA("Test"))
303+ assert r.is_registered_exception(ExceptionB("Test"))
304+ assert r.is_registered_exception(ExceptionC("Test"))
305+ assert not r.is_registered_exception(ExceptionD("Test"))
306+ assert not r.is_registered_exception(ExceptionE("Test"))
307+
308+ assert r.get_dbus_name(ExceptionA("Test")) == "net.lew21.pydbus.tests.ErrorA"
309+ assert r.get_dbus_name(ExceptionB("Test")) == "net.lew21.pydbus.tests.ErrorB"
310+ assert r.get_dbus_name(ExceptionC("Test")) == "net.lew21.pydbus.tests.ErrorC"
311+
312+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorA") == ExceptionA
313+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorB") == ExceptionB
314+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorC") == ExceptionC
315+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorD") is None
316+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorE") is None
317+
318+ r.map_by_default(ExceptionD)
319+ assert not r.is_registered_exception(ExceptionD("Test"))
320+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorD") == ExceptionD
321+ assert r.get_exception_class("net.lew21.pydbus.tests.ErrorE") == ExceptionD
322+
323+
324+def test_transform_message():
325+ r = ErrorRegistration()
326+ n1 = "net.lew21.pydbus.tests.ErrorA"
327+ m1 = "GDBus.Error:net.lew21.pydbus.tests.ErrorA: Message1"
328+
329+ n2 = "net.lew21.pydbus.tests.ErrorB"
330+ m2 = "GDBus.Error:net.lew21.pydbus.tests.ErrorB: Message2"
331+
332+ assert r.transform_message(n1, m1) == "Message1"
333+ assert r.transform_message(n2, m2) == "Message2"
334+ assert r.transform_message(n1, m2) == m2
335+ assert r.transform_message(n2, m1) == m1
336+
337+
338+test_error_mapping()
339+test_transform_message()
340diff --git a/tests/publish_error.py b/tests/publish_error.py
341new file mode 100644
342index 0000000..aa8a18a
343--- /dev/null
344+++ b/tests/publish_error.py
345@@ -0,0 +1,132 @@
346+import sys
347+from threading import Thread
348+from gi.repository import GLib, Gio
349+from pydbus import SessionBus
350+from pydbus.error import register_error, map_error, map_by_default, error_registration
351+
352+import logging
353+logger = logging.getLogger('pydbus.registration')
354+logger.disabled = True
355+
356+loop = GLib.MainLoop()
357+DOMAIN = Gio.DBusError.quark() # TODO: Register new domain.
358+
359+
360+@register_error("net.lew21.pydbus.tests.ErrorA", DOMAIN, 1000)
361+class ExceptionA(Exception):
362+ pass
363+
364+
365+@register_error("net.lew21.pydbus.tests.ErrorB", DOMAIN, 2000)
366+class ExceptionB(Exception):
367+ pass
368+
369+
370+@map_error("org.freedesktop.DBus.Error.InvalidArgs")
371+class ExceptionC(Exception):
372+ pass
373+
374+
375+@map_by_default
376+class ExceptionD(Exception):
377+ pass
378+
379+
380+class ExceptionE(Exception):
381+ pass
382+
383+
384+class TestObject(object):
385+ '''
386+<node>
387+ <interface name='net.lew21.pydbus.tests.TestInterface'>
388+ <method name='RaiseA'>
389+ <arg type='s' name='msg' direction='in'/>
390+ </method>
391+ <method name='RaiseB'>
392+ <arg type='s' name='msg' direction='in'/>
393+ </method>
394+ <method name='RaiseD'>
395+ <arg type='s' name='msg' direction='in'/>
396+ </method>
397+ <method name='RaiseE'>
398+ <arg type='s' name='msg' direction='in'/>
399+ </method>
400+ </interface>
401+</node>
402+ '''
403+
404+ def RaiseA(self, msg):
405+ raise ExceptionA(msg)
406+
407+ def RaiseB(self, msg):
408+ raise ExceptionB(msg)
409+
410+ def RaiseD(self, msg):
411+ raise ExceptionD(msg)
412+
413+ def RaiseE(self, msg):
414+ raise ExceptionE(msg)
415+
416+bus = SessionBus()
417+
418+with bus.publish("net.lew21.pydbus.tests.Test", TestObject()):
419+ remote = bus.get("net.lew21.pydbus.tests.Test")
420+
421+ def t_func():
422+ # Test new registered errors.
423+ try:
424+ remote.RaiseA("Test A")
425+ except ExceptionA as e:
426+ assert str(e) == "Test A"
427+
428+ try:
429+ remote.RaiseB("Test B")
430+ except ExceptionB as e:
431+ assert str(e) == "Test B"
432+
433+ # Test mapped errors.
434+ try:
435+ remote.Get("net.lew21.pydbus.tests.TestInterface", "Foo")
436+ except ExceptionC as e:
437+ assert str(e) == "No such property 'Foo'"
438+
439+ # Test default errors.
440+ try:
441+ remote.RaiseD("Test D")
442+ except ExceptionD as e:
443+ assert str(e) == "Test D"
444+
445+ try:
446+ remote.RaiseE("Test E")
447+ except ExceptionD as e:
448+ assert str(e) == "Test E"
449+
450+ # Test with no default errors.
451+ error_registration.map_by_default(None)
452+
453+ try:
454+ remote.RaiseD("Test D")
455+ except Exception as e:
456+ assert not isinstance(e, ExceptionD)
457+
458+ try:
459+ remote.RaiseE("Test E")
460+ except Exception as e:
461+ assert not isinstance(e, ExceptionD)
462+ assert not isinstance(e, ExceptionE)
463+
464+ loop.quit()
465+
466+ t = Thread(None, t_func)
467+ t.daemon = True
468+
469+ def handle_timeout():
470+ print("ERROR: Timeout.")
471+ sys.exit(1)
472+
473+ GLib.timeout_add_seconds(4, handle_timeout)
474+
475+ t.start()
476+ loop.run()
477+ t.join()
478diff --git a/tests/run.sh b/tests/run.sh
479index 271c58a..a08baf8 100755
480--- a/tests/run.sh
481+++ b/tests/run.sh
482@@ -10,10 +10,11 @@ PYTHON=${1:-python}
483
484 "$PYTHON" $TESTS_DIR/context.py
485 "$PYTHON" $TESTS_DIR/identifier.py
486+"$PYTHON" $TESTS_DIR/error.py
487 if [ "$2" != "dontpublish" ]
488 then
489 "$PYTHON" $TESTS_DIR/publish.py
490 "$PYTHON" $TESTS_DIR/publish_properties.py
491 "$PYTHON" $TESTS_DIR/publish_multiface.py
492 "$PYTHON" $TESTS_DIR/publish_async.py
493 fi
494--
4952.13.5