Use MIT/X11 license as per permission given on the dbus mailing list.
[dbus-python-phuang.git] / dbus / decorators.py
bloba5ca281325f221bfbe58baffab3ffdc30fc0dd34
1 """Service-side D-Bus decorators."""
3 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
4 # Copyright (C) 2003 David Zeuthen
5 # Copyright (C) 2004 Rob Taylor
6 # Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
8 # Permission is hereby granted, free of charge, to any person
9 # obtaining a copy of this software and associated documentation
10 # files (the "Software"), to deal in the Software without
11 # restriction, including without limitation the rights to use, copy,
12 # modify, merge, publish, distribute, sublicense, and/or sell copies
13 # of the Software, and to permit persons to whom the Software is
14 # furnished to do so, subject to the following conditions:
16 # The above copyright notice and this permission notice shall be
17 # included in all copies or substantial portions of the Software.
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 # DEALINGS IN THE SOFTWARE.
28 __all__ = ('method', 'signal')
29 __docformat__ = 'restructuredtext'
31 import inspect
33 from dbus import validate_interface_name, Signature, validate_member_name
34 from dbus.lowlevel import SignalMessage
35 from dbus.exceptions import DBusException
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.
47 The decorated method will be exported over D-Bus as the method of the
48 same name on the given D-Bus interface.
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:
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).
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.
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`.
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.
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.
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.
94 For fallback objects, `rel_path_keyword` (new in 0.82.2) is
95 likely to be more useful.
97 :Since: 0.80.0?
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 '/'.
106 :Since: 0.82.2
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.
113 :Since: 0.80.0?
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.
120 :Since: 0.80.0?
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.
128 :Since: 0.82.0
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.
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.
138 This option does not affect object-paths and signatures, which
139 are always 8-bit strings (str subclass) encoded in ASCII.
141 :Since: 0.80.0
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.
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.
152 :Since: 0.80.0
154 validate_interface_name(dbus_interface)
156 def decorator(func):
157 args = inspect.getargspec(func)[0]
158 args.pop(0)
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])
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)
181 if in_signature:
182 in_sig = tuple(Signature(in_signature))
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'
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
205 return decorator
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.
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.
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
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.
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.
233 This keyword argument cannot be used on objects where
234 the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
236 :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
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.
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).
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.
257 :Since: 0.82.0
259 validate_interface_name(dbus_interface)
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')
272 def decorator(func):
273 member_name = func.__name__
274 validate_member_name(member_name)
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__)
289 rel_path = None
290 if rel_path_keyword is not None:
291 rel_path = keywords.pop(rel_path_keyword, None)
293 func(self, *args, **keywords)
295 for location in self.locations:
296 if abs_path is None:
297 # non-deprecated case
298 if rel_path is None or rel_path in ('/', ''):
299 object_path = location[1]
300 else:
301 # will be validated by SignalMessage ctor in a moment
302 object_path = location[1] + rel_path
303 else:
304 object_path = abs_path
306 message = SignalMessage(object_path,
307 dbus_interface,
308 member_name)
309 message.append(signature=signature, *args)
311 location[0].send_message(message)
312 # end emit_signal
314 args = inspect.getargspec(func)[0]
315 args.pop(0)
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)
324 if signature:
325 sig = tuple(Signature(signature))
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'
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
340 return decorator