1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
From 69968dec867053e38de0b91d76ac41d5a5735e36 Mon Sep 17 00:00:00 2001
From: Vendula Poncova <vponcova@redhat.com>
Date: Thu, 2 Aug 2018 15:31:56 +0800
Subject: [PATCH 2/2] Support transformation between D-Bus errors and
exceptions.
Exceptions can be registered with decorators, raised in a remote
method and recreated after return from the remote call.
Upstream-Status: Backport [https://src.fedoraproject.org/cgit/rpms/python-pydbus.git/]
Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
---
pydbus/error.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
pydbus/proxy_method.py | 18 ++++++++--
pydbus/registration.py | 16 ++++++---
3 files changed, 123 insertions(+), 8 deletions(-)
create mode 100644 pydbus/error.py
diff --git a/pydbus/error.py b/pydbus/error.py
new file mode 100644
index 0000000..aaa3510
--- /dev/null
+++ b/pydbus/error.py
@@ -0,0 +1,97 @@
+from gi.repository import GLib, Gio
+
+
+def register_error(name, domain, code):
+ """Register and map decorated exception class to a DBus error."""
+ def decorated(cls):
+ error_registration.register_error(cls, name, domain, code)
+ return cls
+
+ return decorated
+
+
+def map_error(error_name):
+ """Map decorated exception class to a DBus error."""
+ def decorated(cls):
+ error_registration.map_error(cls, error_name)
+ return cls
+
+ return decorated
+
+
+def map_by_default(cls):
+ """Map decorated exception class to all unknown DBus errors."""
+ error_registration.map_by_default(cls)
+ return cls
+
+
+class ErrorRegistration(object):
+ """Class for mapping exceptions to DBus errors."""
+
+ _default = None
+ _map = dict()
+ _reversed_map = dict()
+
+ def map_by_default(self, exception_cls):
+ """Set the exception class as a default."""
+ self._default = exception_cls
+
+ def map_error(self, exception_cls, name):
+ """Map the exception class to a DBus name."""
+ self._map[name] = exception_cls
+ self._reversed_map[exception_cls] = name
+
+ def register_error(self, exception_cls, name, domain, code):
+ """Map and register the exception class to a DBus name."""
+ self.map_error(exception_cls, name)
+ return Gio.DBusError.register_error(domain, code, name)
+
+ def is_registered_exception(self, obj):
+ """Is the exception registered?"""
+ return obj.__class__ in self._reversed_map
+
+ def get_dbus_name(self, obj):
+ """Get the DBus name of the exception."""
+ return self._reversed_map.get(obj.__class__)
+
+ def get_exception_class(self, name):
+ """Get the exception class mapped to the DBus name."""
+ return self._map.get(name, self._default)
+
+ def transform_message(self, name, message):
+ """Transform the message of the exception."""
+ prefix = "{}:{}: ".format("GDBus.Error", name)
+
+ if message.startswith(prefix):
+ return message[len(prefix):]
+
+ return message
+
+ def transform_exception(self, e):
+ """Transform the remote error to the exception."""
+ if not isinstance(e, GLib.Error):
+ return e
+
+ if not Gio.DBusError.is_remote_error(e):
+ return e
+
+ # Get DBus name of the error.
+ name = Gio.DBusError.get_remote_error(e)
+ # Get the exception class.
+ exception_cls = self.get_exception_class(name)
+
+ # Return the original exception.
+ if not exception_cls:
+ return e
+
+ # Return new exception.
+ message = self.transform_message(name, e.message)
+ exception = exception_cls(message)
+ exception.dbus_name = name
+ exception.dbus_domain = e.domain
+ exception.dbus_code = e.code
+ return exception
+
+
+# Default error registration.
+error_registration = ErrorRegistration()
diff --git a/pydbus/proxy_method.py b/pydbus/proxy_method.py
index 4ea4304..e9496f5 100644
--- a/pydbus/proxy_method.py
+++ b/pydbus/proxy_method.py
@@ -2,6 +2,7 @@ from gi.repository import GLib
from .generic import bound_method
from .identifier import filter_identifier
from .timeout import timeout_to_glib
+from .error import error_registration
try:
from inspect import Signature, Parameter
@@ -87,9 +88,20 @@ class ProxyMethod(object):
call_args += (self._finish_async_call, (callback, callback_args))
instance._bus.con.call(*call_args)
return None
+
else:
- ret = instance._bus.con.call_sync(*call_args)
- return self._unpack_return(ret)
+ result = None
+ error = None
+
+ try:
+ result = instance._bus.con.call_sync(*call_args)
+ except Exception as e:
+ error = error_registration.transform_exception(e)
+
+ if error:
+ raise error
+
+ return self._unpack_return(result)
def _unpack_return(self, values):
ret = values.unpack()
@@ -108,7 +120,7 @@ class ProxyMethod(object):
ret = source.call_finish(result)
return_args = self._unpack_return(ret)
except Exception as err:
- error = err
+ error = error_registration.transform_exception(err)
callback, callback_args = user_data
callback(*callback_args, returned=return_args, error=error)
diff --git a/pydbus/registration.py b/pydbus/registration.py
index f531539..1d2cbcb 100644
--- a/pydbus/registration.py
+++ b/pydbus/registration.py
@@ -5,6 +5,7 @@ from . import generic
from .exitable import ExitableWithAliases
from functools import partial
from .method_call_context import MethodCallContext
+from .error import error_registration
import logging
try:
@@ -91,11 +92,16 @@ class ObjectWrapper(ExitableWithAliases("unwrap")):
logger = logging.getLogger(__name__)
logger.exception("Exception while handling %s.%s()", interface_name, method_name)
- #TODO Think of a better way to translate Python exception types to DBus error types.
- e_type = type(e).__name__
- if not "." in e_type:
- e_type = "unknown." + e_type
- invocation.return_dbus_error(e_type, str(e))
+ if error_registration.is_registered_exception(e):
+ name = error_registration.get_dbus_name(e)
+ invocation.return_dbus_error(name, str(e))
+ else:
+ logger.info("name is not registered")
+ e_type = type(e).__name__
+ if not "." in e_type:
+ e_type = "unknown." + e_type
+
+ invocation.return_dbus_error(e_type, str(e))
def Get(self, interface_name, property_name):
type = self.readable_properties[interface_name + "." + property_name]
--
2.7.4
|