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 if hasattr(inspect, 'Signature'):
159 args = []
160
161 for arg in inspect.signature(func).parameters.values():
162 if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
163 inspect.Parameter.POSITIONAL_OR_KEYWORD):
164 args.append(arg.name)
165 else:
166 args = inspect.getargspec(func)[0]
167
168 args.pop(0)
169
170 if async_callbacks:
171 if type(async_callbacks) != tuple:
172 raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
173 if len(async_callbacks) != 2:
174 raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
175 args.remove(async_callbacks[0])
176 args.remove(async_callbacks[1])
177
178 if sender_keyword:
179 args.remove(sender_keyword)
180 if rel_path_keyword:
181 args.remove(rel_path_keyword)
182 if path_keyword:
183 args.remove(path_keyword)
184 if destination_keyword:
185 args.remove(destination_keyword)
186 if message_keyword:
187 args.remove(message_keyword)
188 if connection_keyword:
189 args.remove(connection_keyword)
190
191 if in_signature:
192 in_sig = tuple(Signature(in_signature))
193
194 if len(in_sig) > len(args):
195 raise ValueError('input signature is longer than the number of arguments taken')
196 elif len(in_sig) < len(args):
197 raise ValueError('input signature is shorter than the number of arguments taken')
198
199 func._dbus_is_method = True
200 func._dbus_async_callbacks = async_callbacks
201 func._dbus_interface = dbus_interface
202 func._dbus_in_signature = in_signature
203 func._dbus_out_signature = out_signature
204 func._dbus_sender_keyword = sender_keyword
205 func._dbus_path_keyword = path_keyword
206 func._dbus_rel_path_keyword = rel_path_keyword
207 func._dbus_destination_keyword = destination_keyword
208 func._dbus_message_keyword = message_keyword
209 func._dbus_connection_keyword = connection_keyword
210 func._dbus_args = args
211 func._dbus_get_args_options = dict(byte_arrays=byte_arrays)
212 if is_py2:
213 func._dbus_get_args_options['utf8_strings'] = kwargs.get(
214 'utf8_strings', False)
215 elif 'utf8_strings' in kwargs:
216 raise TypeError("unexpected keyword argument 'utf8_strings'")
217 return func
218
219 return decorator
220
221
222 -def signal(dbus_interface, signature=None, path_keyword=None,
223 rel_path_keyword=None):
224 """Factory for decorators used to mark methods of a `dbus.service.Object`
225 to emit signals on the D-Bus.
226
227 Whenever the decorated method is called in Python, after the method
228 body is executed, a signal with the same name as the decorated method,
229 with the given D-Bus interface, will be emitted from this object.
230
231 :Parameters:
232 `dbus_interface` : str
233 The D-Bus interface whose signal is emitted
234 `signature` : str
235 The signature of the signal in the usual D-Bus notation
236
237 `path_keyword` : str or None
238 A keyword argument to the decorated method. If not None,
239 that argument will not be emitted as an argument of
240 the signal, and when the signal is emitted, it will appear
241 to come from the object path given by the keyword argument.
242
243 Note that when calling the decorated method, you must always
244 pass in the object path as a keyword argument, not as a
245 positional argument.
246
247 This keyword argument cannot be used on objects where
248 the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
249
250 :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
251
252 `rel_path_keyword` : str or None
253 A keyword argument to the decorated method. If not None,
254 that argument will not be emitted as an argument of
255 the signal.
256
257 When the signal is emitted, if the named keyword argument is given,
258 the signal will appear to come from the object path obtained by
259 appending the keyword argument to the object's object path.
260 This is useful to implement "fallback objects" (objects which
261 own an entire subtree of the object-path tree).
262
263 If the object is available at more than one object-path on the
264 same or different connections, the signal will be emitted at
265 an appropriate object-path on each connection - for instance,
266 if the object is exported at /abc on connection 1 and at
267 /def and /x/y/z on connection 2, and the keyword argument is
268 /foo, then signals will be emitted from /abc/foo and /def/foo
269 on connection 1, and /x/y/z/foo on connection 2.
270
271 :Since: 0.82.0
272 """
273 validate_interface_name(dbus_interface)
274
275 if path_keyword is not None:
276 from warnings import warn
277 warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
278 'deprecated since dbus-python 0.82.0, and '
279 'will not work on objects that support '
280 'multiple object paths'),
281 DeprecationWarning, stacklevel=2)
282 if rel_path_keyword is not None:
283 raise TypeError('dbus.service.signal::path_keyword and '
284 'rel_path_keyword cannot both be used')
285
286 def decorator(func):
287 member_name = func.__name__
288 validate_member_name(member_name)
289
290 def emit_signal(self, *args, **keywords):
291 abs_path = None
292 if path_keyword is not None:
293 if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
294 raise TypeError('path_keyword cannot be used on the '
295 'signals of an object that supports '
296 'multiple object paths')
297 abs_path = keywords.pop(path_keyword, None)
298 if (abs_path != self.__dbus_object_path__ and
299 not self.__dbus_object_path__.startswith(abs_path + '/')):
300 raise ValueError('Path %r is not below %r', abs_path,
301 self.__dbus_object_path__)
302
303 rel_path = None
304 if rel_path_keyword is not None:
305 rel_path = keywords.pop(rel_path_keyword, None)
306
307 func(self, *args, **keywords)
308
309 for location in self.locations:
310 if abs_path is None:
311
312 if rel_path is None or rel_path in ('/', ''):
313 object_path = location[1]
314 else:
315
316 object_path = location[1] + rel_path
317 else:
318 object_path = abs_path
319
320 message = SignalMessage(object_path,
321 dbus_interface,
322 member_name)
323 message.append(signature=signature, *args)
324
325 location[0].send_message(message)
326
327
328 args = inspect.getargspec(func)[0]
329 args.pop(0)
330
331 for keyword in rel_path_keyword, path_keyword:
332 if keyword is not None:
333 try:
334 args.remove(keyword)
335 except ValueError:
336 raise ValueError('function has no argument "%s"' % keyword)
337
338 if signature:
339 sig = tuple(Signature(signature))
340
341 if len(sig) > len(args):
342 raise ValueError('signal signature is longer than the number of arguments provided')
343 elif len(sig) < len(args):
344 raise ValueError('signal signature is shorter than the number of arguments provided')
345
346 emit_signal.__name__ = func.__name__
347 emit_signal.__doc__ = func.__doc__
348 emit_signal._dbus_is_signal = True
349 emit_signal._dbus_interface = dbus_interface
350 emit_signal._dbus_signature = signature
351 emit_signal._dbus_args = args
352 return emit_signal
353
354 return decorator
355