diff options
author | Derek Straka <derek@asterius.io> | 2024-03-22 16:59:54 +0000 |
---|---|---|
committer | Khem Raj <raj.khem@gmail.com> | 2024-03-22 21:19:50 -0700 |
commit | 3c188f75eabf3cb2c976e69e4c18c401e20635dd (patch) | |
tree | 56d5082157836964843fa69dfd8d1b3b397e1bc1 /meta-python/recipes-devtools/python/python3-pydbus/0003-Support-transformation-between-D-Bus-errors-and-exce.patch | |
parent | 1cb0dae6b8bddcef854aa30a61af8213b638a63a (diff) | |
download | meta-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.patch | 495 |
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 @@ | |||
1 | From 773858e1afd21cdf3ceef2cd35509f0b4882bf16 Mon Sep 17 00:00:00 2001 | ||
2 | From: Vendula Poncova <vponcova@redhat.com> | ||
3 | Date: Tue, 1 Aug 2017 16:54:24 +0200 | ||
4 | Subject: [PATCH 3/3] Support transformation between D-Bus errors and | ||
5 | exceptions. | ||
6 | |||
7 | Exceptions can be registered with decorators, raised in a remote | ||
8 | method and recreated after return from the remote call. | ||
9 | |||
10 | Adapted from Fedora [https://src.fedoraproject.org/cgit/rpms/python-pydbus.git/] | ||
11 | |||
12 | Upstream-Status: Inactive-Upstream (Last release 12/18/2016; Last commit 05/6/2018) | ||
13 | |||
14 | Signed-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 | |||
28 | diff --git a/doc/tutorial.rst b/doc/tutorial.rst | ||
29 | index 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 | |||
86 | diff --git a/pydbus/error.py b/pydbus/error.py | ||
87 | new file mode 100644 | ||
88 | index 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() | ||
189 | diff --git a/pydbus/proxy_method.py b/pydbus/proxy_method.py | ||
190 | index 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) | ||
233 | diff --git a/pydbus/registration.py b/pydbus/registration.py | ||
234 | index 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] | ||
267 | diff --git a/tests/error.py b/tests/error.py | ||
268 | new file mode 100644 | ||
269 | index 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() | ||
340 | diff --git a/tests/publish_error.py b/tests/publish_error.py | ||
341 | new file mode 100644 | ||
342 | index 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() | ||
478 | diff --git a/tests/run.sh b/tests/run.sh | ||
479 | index 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 | -- | ||
495 | 2.13.5 | ||