diff options
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 | ||