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