summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--meta-python/conf/include/ptest-packagelists-meta-python.inc1
-rw-r--r--meta-python/recipes-core/packagegroups/packagegroup-meta-python.bb1
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus/0001-make-direction-attribute-conforming-to-introspect.dt.patch40
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus/0002-Support-asynchronous-calls-58.patch206
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch495
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus/run-ptest15
-rw-r--r--meta-python/recipes-devtools/python/python3-pydbus_0.6.0.bb26
7 files changed, 784 insertions, 0 deletions
diff --git a/meta-python/conf/include/ptest-packagelists-meta-python.inc b/meta-python/conf/include/ptest-packagelists-meta-python.inc
index 447e0b938..ec26f768e 100644
--- a/meta-python/conf/include/ptest-packagelists-meta-python.inc
+++ b/meta-python/conf/include/ptest-packagelists-meta-python.inc
@@ -53,6 +53,7 @@ PTESTS_FAST_META_PYTHON = "\
53 python3-pytest-mock \ 53 python3-pytest-mock \
54 python3-pytoml \ 54 python3-pytoml \
55 python3-pyyaml-include \ 55 python3-pyyaml-include \
56 python3-pydbus \
56 python3-rapidjson \ 57 python3-rapidjson \
57 python3-requests-file \ 58 python3-requests-file \
58 python3-requests-toolbelt \ 59 python3-requests-toolbelt \
diff --git a/meta-python/recipes-core/packagegroups/packagegroup-meta-python.bb b/meta-python/recipes-core/packagegroups/packagegroup-meta-python.bb
index eb5a26463..e0446da28 100644
--- a/meta-python/recipes-core/packagegroups/packagegroup-meta-python.bb
+++ b/meta-python/recipes-core/packagegroups/packagegroup-meta-python.bb
@@ -311,6 +311,7 @@ RDEPENDS:packagegroup-meta-python3 = "\
311 python3-pycodestyle \ 311 python3-pycodestyle \
312 python3-pyconnman \ 312 python3-pyconnman \
313 python3-pycurl \ 313 python3-pycurl \
314 python3-pydbus \
314 python3-pydicti \ 315 python3-pydicti \
315 python3-pyephem \ 316 python3-pyephem \
316 python3-pyexpect \ 317 python3-pyexpect \
diff --git a/meta-python/recipes-devtools/python/python3-pydbus/0001-make-direction-attribute-conforming-to-introspect.dt.patch b/meta-python/recipes-devtools/python/python3-pydbus/0001-make-direction-attribute-conforming-to-introspect.dt.patch
new file mode 100644
index 000000000..1bd17986e
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pydbus/0001-make-direction-attribute-conforming-to-introspect.dt.patch
@@ -0,0 +1,40 @@
1From 5fe65a35e0e7106347639f0258206fadb451c439 Mon Sep 17 00:00:00 2001
2From: Hiroaki KAWAI <hiroaki.kawai@gmail.com>
3Date: Wed, 1 Feb 2017 18:00:33 +0900
4Subject: [PATCH 1/3] make direction attribute conforming to introspect.dtd
5
6direction attribute defaults to "in" as
7in the DTD(*1), direction attribute is defined as following:
8
9```
10<!ATTRLIST arg direction (in|out) "in">
11```
12
13*1) http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd
14
15Adapted from Fedora [https://src.fedoraproject.org/cgit/rpms/python-pydbus.git/]
16
17Upstream-Status: Inactive-Upstream (Last release 12/18/2016; Last commit 05/6/2018)
18
19Signed-off-by: Derek Straka <derek@asterius.io>
20---
21 pydbus/proxy_method.py | 4 ++--
22 1 file changed, 2 insertions(+), 2 deletions(-)
23
24diff --git a/pydbus/proxy_method.py b/pydbus/proxy_method.py
25index 8798edd..3e6e6ee 100644
26--- a/pydbus/proxy_method.py
27+++ b/pydbus/proxy_method.py
28@@ -33,8 +33,8 @@ class ProxyMethod(object):
29 self.__name__ = method.attrib["name"]
30 self.__qualname__ = self._iface_name + "." + self.__name__
31
32- self._inargs = [(arg.attrib.get("name", ""), arg.attrib["type"]) for arg in method if arg.tag == "arg" and arg.attrib["direction"] == "in"]
33- self._outargs = [arg.attrib["type"] for arg in method if arg.tag == "arg" and arg.attrib["direction"] == "out"]
34+ self._inargs = [(arg.attrib.get("name", ""), arg.attrib["type"]) for arg in method if arg.tag == "arg" and arg.attrib.get("direction", "in") == "in"]
35+ self._outargs = [arg.attrib["type"] for arg in method if arg.tag == "arg" and arg.attrib.get("direction", "in") == "out"]
36 self._sinargs = "(" + "".join(x[1] for x in self._inargs) + ")"
37 self._soutargs = "(" + "".join(self._outargs) + ")"
38
39--
402.13.5
diff --git a/meta-python/recipes-devtools/python/python3-pydbus/0002-Support-asynchronous-calls-58.patch b/meta-python/recipes-devtools/python/python3-pydbus/0002-Support-asynchronous-calls-58.patch
new file mode 100644
index 000000000..b3c57edad
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pydbus/0002-Support-asynchronous-calls-58.patch
@@ -0,0 +1,206 @@
1From 31d6dd7893a5e1bb9eb14bfcee861a5b62f64960 Mon Sep 17 00:00:00 2001
2From: Vendula Poncova <vponcova@redhat.com>
3Date: Thu, 27 Jul 2017 18:41:29 +0200
4Subject: [PATCH 2/3] Support asynchronous calls (#58)
5
6Added support for asynchronous calls of methods. A method is called
7synchronously unless its callback parameter is specified. A callback
8is a function f(*args, returned=None, error=None), where args is
9callback_args specified in the method call, returned is a return
10value of the method and error is an exception raised by the method.
11
12Example of an asynchronous call:
13
14def func(x, y, returned=None, error=None):
15 pass
16
17proxy.Method(a, b, callback=func, callback_args=(x, y))
18
19Adapted from Fedora [https://src.fedoraproject.org/cgit/rpms/python-pydbus.git/]
20
21Upstream-Status: Inactive-Upstream (Last release 12/18/2016; Last commit 05/6/2018)
22
23Signed-off-by: Derek Straka <derek@asterius.io>
24---
25 doc/tutorial.rst | 11 ++++++++-
26 pydbus/proxy_method.py | 44 ++++++++++++++++++++++++++++++-----
27 tests/publish_async.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++
28 tests/run.sh | 1 +
29 4 files changed, 112 insertions(+), 7 deletions(-)
30 create mode 100644 tests/publish_async.py
31
32diff --git a/doc/tutorial.rst b/doc/tutorial.rst
33index 7474de3..b8479cf 100644
34--- a/doc/tutorial.rst
35+++ b/doc/tutorial.rst
36@@ -84,7 +84,8 @@ All objects have methods, properties and signals.
37 Setting up an event loop
38 ========================
39
40-To handle signals emitted by exported objects, or to export your own objects, you need to setup an event loop.
41+To handle signals emitted by exported objects, to asynchronously call methods
42+or to export your own objects, you need to setup an event loop.
43
44 The only main loop supported by ``pydbus`` is GLib.MainLoop.
45
46@@ -156,6 +157,14 @@ To call a method::
47
48 dev.Disconnect()
49
50+To asynchronously call a method::
51+
52+ def print_result(returned=None, error=None):
53+ print(returned, error)
54+
55+ dev.GetAppliedConnection(0, callback=print_result)
56+ loop.run()
57+
58 To read a property::
59
60 print(dev.Autoconnect)
61diff --git a/pydbus/proxy_method.py b/pydbus/proxy_method.py
62index 3e6e6ee..442fe07 100644
63--- a/pydbus/proxy_method.py
64+++ b/pydbus/proxy_method.py
65@@ -65,15 +65,34 @@ class ProxyMethod(object):
66
67 # Python 2 sux
68 for kwarg in kwargs:
69- if kwarg not in ("timeout",):
70+ if kwarg not in ("timeout", "callback", "callback_args"):
71 raise TypeError(self.__qualname__ + " got an unexpected keyword argument '{}'".format(kwarg))
72 timeout = kwargs.get("timeout", None)
73+ callback = kwargs.get("callback", None)
74+ callback_args = kwargs.get("callback_args", tuple())
75+
76+ call_args = (
77+ instance._bus_name,
78+ instance._path,
79+ self._iface_name,
80+ self.__name__,
81+ GLib.Variant(self._sinargs, args),
82+ GLib.VariantType.new(self._soutargs),
83+ 0,
84+ timeout_to_glib(timeout),
85+ None
86+ )
87+
88+ if callback:
89+ call_args += (self._finish_async_call, (callback, callback_args))
90+ instance._bus.con.call(*call_args)
91+ return None
92+ else:
93+ ret = instance._bus.con.call_sync(*call_args)
94+ return self._unpack_return(ret)
95
96- ret = instance._bus.con.call_sync(
97- instance._bus_name, instance._path,
98- self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs),
99- 0, timeout_to_glib(timeout), None).unpack()
100-
101+ def _unpack_return(self, values):
102+ ret = values.unpack()
103 if len(self._outargs) == 0:
104 return None
105 elif len(self._outargs) == 1:
106@@ -81,6 +100,19 @@ class ProxyMethod(object):
107 else:
108 return ret
109
110+ def _finish_async_call(self, source, result, user_data):
111+ error = None
112+ return_args = None
113+
114+ try:
115+ ret = source.call_finish(result)
116+ return_args = self._unpack_return(ret)
117+ except Exception as err:
118+ error = err
119+
120+ callback, callback_args = user_data
121+ callback(*callback_args, returned=return_args, error=error)
122+
123 def __get__(self, instance, owner):
124 if instance is None:
125 return self
126diff --git a/tests/publish_async.py b/tests/publish_async.py
127new file mode 100644
128index 0000000..3f79b62
129--- /dev/null
130+++ b/tests/publish_async.py
131@@ -0,0 +1,63 @@
132+from pydbus import SessionBus
133+from gi.repository import GLib
134+from threading import Thread
135+import sys
136+
137+done = 0
138+loop = GLib.MainLoop()
139+
140+class TestObject(object):
141+ '''
142+<node>
143+ <interface name='net.lew21.pydbus.tests.publish_async'>
144+ <method name='HelloWorld'>
145+ <arg type='i' name='x' direction='in'/>
146+ <arg type='s' name='response' direction='out'/>
147+ </method>
148+ </interface>
149+</node>
150+ '''
151+ def __init__(self, id):
152+ self.id = id
153+
154+ def HelloWorld(self, x):
155+ res = self.id + ": " + str(x)
156+ print(res)
157+ return res
158+
159+bus = SessionBus()
160+
161+with bus.publish("net.lew21.pydbus.tests.publish_async", TestObject("Obj")):
162+ remote = bus.get("net.lew21.pydbus.tests.publish_async")
163+
164+ def callback(x, returned=None, error=None):
165+ print("asyn: " + returned)
166+ assert (returned is not None)
167+ assert(error is None)
168+ assert(x == int(returned.split()[1]))
169+
170+ global done
171+ done += 1
172+ if done == 3:
173+ loop.quit()
174+
175+ def t1_func():
176+ remote.HelloWorld(1, callback=callback, callback_args=(1,))
177+ remote.HelloWorld(2, callback=callback, callback_args=(2,))
178+ print("sync: " + remote.HelloWorld(3))
179+ remote.HelloWorld(4, callback=callback, callback_args=(4,))
180+
181+ t1 = Thread(None, t1_func)
182+ t1.daemon = True
183+
184+ def handle_timeout():
185+ print("ERROR: Timeout.")
186+ sys.exit(1)
187+
188+ GLib.timeout_add_seconds(2, handle_timeout)
189+
190+ t1.start()
191+
192+ loop.run()
193+
194+ t1.join()
195diff --git a/tests/run.sh b/tests/run.sh
196index 8d93644..271c58a 100755
197--- a/tests/run.sh
198+++ b/tests/run.sh
199@@ -15,4 +15,5 @@ then
200 "$PYTHON" $TESTS_DIR/publish.py
201 "$PYTHON" $TESTS_DIR/publish_properties.py
202 "$PYTHON" $TESTS_DIR/publish_multiface.py
203+ "$PYTHON" $TESTS_DIR/publish_async.py
204 fi
205--
2062.13.5
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 000000000..a1b8a6c38
--- /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
diff --git a/meta-python/recipes-devtools/python/python3-pydbus/run-ptest b/meta-python/recipes-devtools/python/python3-pydbus/run-ptest
new file mode 100644
index 000000000..782ceed3b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pydbus/run-ptest
@@ -0,0 +1,15 @@
1#!/bin/sh
2
3for case in `find tests -type f -name '*.sh'`; do
4 bash $case python3 >$case.output 2>&1
5 ret=$?
6 if [ $ret -ne 0 ]; then
7 cat $case.output
8 echo "FAIL: ${case}"
9 elif grep -i 'SKIP' $case.output; then
10 echo "SKIP: ${case}"
11 else
12 echo "PASS: ${case}"
13 fi
14 rm -f $case.output
15done \ No newline at end of file
diff --git a/meta-python/recipes-devtools/python/python3-pydbus_0.6.0.bb b/meta-python/recipes-devtools/python/python3-pydbus_0.6.0.bb
new file mode 100644
index 000000000..ac9b8e8ab
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-pydbus_0.6.0.bb
@@ -0,0 +1,26 @@
1DESCRIPTION = "Pythonic DBus library"
2HOMEPAGE = "https://pypi.python.org/pypi/pydbus/"
3LICENSE = "LGPL-2.1-only"
4LIC_FILES_CHKSUM = "file://LICENSE;md5=a916467b91076e631dd8edb7424769c7"
5
6SRCREV = "f2e6355a88351e7d644ccb2b4d67b19305507312"
7SRC_URI = " \
8 git://github.com/LEW21/pydbus.git;protocol=https;branch=master \
9 file://0001-make-direction-attribute-conforming-to-introspect.dt.patch \
10 file://0002-Support-asynchronous-calls-58.patch \
11 file://0003-Support-transformation-between-D-Bus-errors-and-exce.patch \
12 file://run-ptest \
13"
14
15inherit ptest setuptools3
16
17S = "${WORKDIR}/git"
18
19RDEPENDS:${PN} = "${PYTHON_PN}-pygobject \
20 ${PYTHON_PN}-io \
21 ${PYTHON_PN}-logging"
22
23do_install_ptest() {
24 install -d ${D}${PTEST_PATH}/tests
25 cp -rf ${S}/tests/* ${D}${PTEST_PATH}/tests/
26} \ No newline at end of file