1 """Service-side D-Bus decorators."""
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 __all__ = ('method', 'signal')
29 __docformat__ = 'restructuredtext'
30
31 import inspect
32
33 from dbus import validate_interface_name, Signature, validate_member_name
34 from dbus.lowlevel import SignalMessage
35 from dbus.exceptions import DBusException
36 from dbus._compat import is_py2
37
38
39 -def method(dbus_interface, in_signature=None, out_signature=None,
40 async_callbacks=None,
41 sender_keyword=None, path_keyword=None, destination_keyword=None,
42 message_keyword=None, connection_keyword=None,
43 byte_arrays=False,
44 rel_path_keyword=None, **kwargs):
45 """Factory for decorators used to mark methods of a `dbus.service.Object`
46 to be exported on the D-Bus.
47
48 The decorated method will be exported over D-Bus as the method of the
49 same name on the given D-Bus interface.
50
51 :Parameters:
52 `dbus_interface` : str
53 Name of a D-Bus interface
54 `in_signature` : str or None
55 If not None, the signature of the method parameters in the usual
56 D-Bus notation
57 `out_signature` : str or None
58 If not None, the signature of the return value in the usual
59 D-Bus notation
60 `async_callbacks` : tuple containing (str,str), or None
61 If None (default) the decorated method is expected to return
62 values matching the `out_signature` as usual, or raise
63 an exception on error. If not None, the following applies:
64
65 `async_callbacks` contains the names of two keyword arguments to
66 the decorated function, which will be used to provide a success
67 callback and an error callback (in that order).
68
69 When the decorated method is called via the D-Bus, its normal
70 return value will be ignored; instead, a pair of callbacks are
71 passed as keyword arguments, and the decorated method is
72 expected to arrange for one of them to be called.
73
74 On success the success callback must be called, passing the
75 results of this method as positional parameters in the format
76 given by the `out_signature`.
77
78 On error the decorated method may either raise an exception
79 before it returns, or arrange for the error callback to be
80 called with an Exception instance as parameter.
81
82 `sender_keyword` : str or None
83 If not None, contains the name of a keyword argument to the
84 decorated function, conventionally ``'sender'``. When the
85 method is called, the sender's unique name will be passed as
86 this keyword argument.
87
88 `path_keyword` : str or None
89 If not None (the default), the decorated method will receive
90 the destination object path as a keyword argument with this
91 name. Normally you already know the object path, but in the
92 case of "fallback paths" you'll usually want to use the object
93 path in the method's implementation.
94
95 For fallback objects, `rel_path_keyword` (new in 0.82.2) is
96 likely to be more useful.
97
98 :Since: 0.80.0?
99
100 `rel_path_keyword` : str or None
101 If not None (the default), the decorated method will receive
102 the destination object path, relative to the path at which the
103 object was exported, as a keyword argument with this
104 name. For non-fallback objects the relative path will always be
105 '/'.
106
107 :Since: 0.82.2
108
109 `destination_keyword` : str or None
110 If not None (the default), the decorated method will receive
111 the destination bus name as a keyword argument with this name.
112 Included for completeness - you shouldn't need this.
113
114 :Since: 0.80.0?
115
116 `message_keyword` : str or None
117 If not None (the default), the decorated method will receive
118 the `dbus.lowlevel.MethodCallMessage` as a keyword argument
119 with this name.
120
121 :Since: 0.80.0?
122
123 `connection_keyword` : str or None
124 If not None (the default), the decorated method will receive
125 the `dbus.connection.Connection` as a keyword argument
126 with this name. This is generally only useful for objects
127 that are available on more than one connection.
128
129 :Since: 0.82.0
130
131 `utf8_strings` : bool
132 If False (default), D-Bus strings are passed to the decorated
133 method as objects of class dbus.String, a unicode subclass.
134
135 If True, D-Bus strings are passed to the decorated method
136 as objects of class dbus.UTF8String, a str subclass guaranteed
137 to be encoded in UTF-8.
138
139 This option does not affect object-paths and signatures, which
140 are always 8-bit strings (str subclass) encoded in ASCII.
141
142 :Since: 0.80.0
143
144 `byte_arrays` : bool
145 If False (default), a byte array will be passed to the decorated
146 method as an `Array` (a list subclass) of `Byte` objects.
147
148 If True, a byte array will be passed to the decorated method as
149 a `ByteArray`, a str subclass. This is usually what you want,
150 but is switched off by default to keep dbus-python's API
151 consistent.
152
153 :Since: 0.80.0
154 """
155 validate_interface_name(dbus_interface)
156
157 def decorator(func):
158 args = inspect.getargspec(func)[0]
159 args.pop(0)
160
161 if async_callbacks:
162 if type(async_callbacks) != tuple:
163 raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
164 if len(async_callbacks) != 2:
165 raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
166 args.remove(async_callbacks[0])
167 args.remove(async_callbacks[1])
168
169 if sender_keyword:
170 args.remove(sender_keyword)
171 if rel_path_keyword:
172 args.remove(rel_path_keyword)
173 if path_keyword:
174 args.remove(path_keyword)
175 if destination_keyword:
176 args.remove(destination_keyword)
177 if message_keyword:
178 args.remove(message_keyword)
179 if connection_keyword:
180 args.remove(connection_keyword)
181
182 if in_signature:
183 in_sig = tuple(Signature(in_signature))
184
185 if len(in_sig) > len(args):
186 raise ValueError('input signature is longer than the number of arguments taken')
187 elif len(in_sig) < len(args):
188 raise ValueError('input signature is shorter than the number of arguments taken')
189
190 func._dbus_is_method = True
191 func._dbus_async_callbacks = async_callbacks
192 func._dbus_interface = dbus_interface
193 func._dbus_in_signature = in_signature
194 func._dbus_out_signature = out_signature
195 func._dbus_sender_keyword = sender_keyword
196 func._dbus_path_keyword = path_keyword
197 func._dbus_rel_path_keyword = rel_path_keyword
198 func._dbus_destination_keyword = destination_keyword
199 func._dbus_message_keyword = message_keyword
200 func._dbus_connection_keyword = connection_keyword
201 func._dbus_args = args
202 func._dbus_get_args_options = dict(byte_arrays=byte_arrays)
203 if is_py2:
204 func._dbus_get_args_options['utf8_strings'] = kwargs.get(
205 'utf8_strings', False)
206 elif 'utf8_strings' in kwargs:
207 raise TypeError("unexpected keyword argument 'utf8_strings'")
208 return func
209
210 return decorator
211
212
213 -def signal(dbus_interface, signature=None, path_keyword=None,
214 rel_path_keyword=None):
215 """Factory for decorators used to mark methods of a `dbus.service.Object`
216 to emit signals on the D-Bus.
217
218 Whenever the decorated method is called in Python, after the method
219 body is executed, a signal with the same name as the decorated method,
220 with the given D-Bus interface, will be emitted from this object.
221
222 :Parameters:
223 `dbus_interface` : str
224 The D-Bus interface whose signal is emitted
225 `signature` : str
226 The signature of the signal in the usual D-Bus notation
227
228 `path_keyword` : str or None
229 A keyword argument to the decorated method. If not None,
230 that argument will not be emitted as an argument of
231 the signal, and when the signal is emitted, it will appear
232 to come from the object path given by the keyword argument.
233
234 Note that when calling the decorated method, you must always
235 pass in the object path as a keyword argument, not as a
236 positional argument.
237
238 This keyword argument cannot be used on objects where
239 the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
240
241 :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
242
243 `rel_path_keyword` : str or None
244 A keyword argument to the decorated method. If not None,
245 that argument will not be emitted as an argument of
246 the signal.
247
248 When the signal is emitted, if the named keyword argument is given,
249 the signal will appear to come from the object path obtained by
250 appending the keyword argument to the object's object path.
251 This is useful to implement "fallback objects" (objects which
252 own an entire subtree of the object-path tree).
253
254 If the object is available at more than one object-path on the
255 same or different connections, the signal will be emitted at
256 an appropriate object-path on each connection - for instance,
257 if the object is exported at /abc on connection 1 and at
258 /def and /x/y/z on connection 2, and the keyword argument is
259 /foo, then signals will be emitted from /abc/foo and /def/foo
260 on connection 1, and /x/y/z/foo on connection 2.
261
262 :Since: 0.82.0
263 """
264 validate_interface_name(dbus_interface)
265
266 if path_keyword is not None:
267 from warnings import warn
268 warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
269 'deprecated since dbus-python 0.82.0, and '
270 'will not work on objects that support '
271 'multiple object paths'),
272 DeprecationWarning, stacklevel=2)
273 if rel_path_keyword is not None:
274 raise TypeError('dbus.service.signal::path_keyword and '
275 'rel_path_keyword cannot both be used')
276
277 def decorator(func):
278 member_name = func.__name__
279 validate_member_name(member_name)
280
281 def emit_signal(self, *args, **keywords):
282 abs_path = None
283 if path_keyword is not None:
284 if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
285 raise TypeError('path_keyword cannot be used on the '
286 'signals of an object that supports '
287 'multiple object paths')
288 abs_path = keywords.pop(path_keyword, None)
289 if (abs_path != self.__dbus_object_path__ and
290 not self.__dbus_object_path__.startswith(abs_path + '/')):
291 raise ValueError('Path %r is not below %r', abs_path,
292 self.__dbus_object_path__)
293
294 rel_path = None
295 if rel_path_keyword is not None:
296 rel_path = keywords.pop(rel_path_keyword, None)
297
298 func(self, *args, **keywords)
299
300 for location in self.locations:
301 if abs_path is None:
302
303 if rel_path is None or rel_path in ('/', ''):
304 object_path = location[1]
305 else:
306
307 object_path = location[1] + rel_path
308 else:
309 object_path = abs_path
310
311 message = SignalMessage(object_path,
312 dbus_interface,
313 member_name)
314 message.append(signature=signature, *args)
315
316 location[0].send_message(message)
317
318
319 args = inspect.getargspec(func)[0]
320 args.pop(0)
321
322 for keyword in rel_path_keyword, path_keyword:
323 if keyword is not None:
324 try:
325 args.remove(keyword)
326 except ValueError:
327 raise ValueError('function has no argument "%s"' % keyword)
328
329 if signature:
330 sig = tuple(Signature(signature))
331
332 if len(sig) > len(args):
333 raise ValueError('signal signature is longer than the number of arguments provided')
334 elif len(sig) < len(args):
335 raise ValueError('signal signature is shorter than the number of arguments provided')
336
337 emit_signal.__name__ = func.__name__
338 emit_signal.__doc__ = func.__doc__
339 emit_signal._dbus_is_signal = True
340 emit_signal._dbus_interface = dbus_interface
341 emit_signal._dbus_signature = signature
342 emit_signal._dbus_args = args
343 return emit_signal
344
345 return decorator
346