Implement fallback objects.
[dbus-python-phuang.git] / dbus / decorators.py
blob892a11fb46af98cf2ecb7d4dc90d5326f5282bd9
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 # Licensed under the Academic Free License version 2.1
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 __all__ = ('method', 'signal')
25 __docformat__ = 'restructuredtext'
27 import inspect
29 import _dbus_bindings
31 from dbus.exceptions import DBusException
34 def method(dbus_interface, in_signature=None, out_signature=None,
35 async_callbacks=None,
36 sender_keyword=None, path_keyword=None, destination_keyword=None,
37 message_keyword=None, connection_keyword=None,
38 utf8_strings=False, byte_arrays=False):
39 """Factory for decorators used to mark methods of a `dbus.service.Object`
40 to be exported on the D-Bus.
42 The decorated method will be exported over D-Bus as the method of the
43 same name on the given D-Bus interface.
45 :Parameters:
46 `dbus_interface` : str
47 Name of a D-Bus interface
48 `in_signature` : str or None
49 If not None, the signature of the method parameters in the usual
50 D-Bus notation
51 `out_signature` : str or None
52 If not None, the signature of the return value in the usual
53 D-Bus notation
54 `async_callbacks` : tuple containing (str,str), or None
55 If None (default) the decorated method is expected to return
56 values matching the `out_signature` as usual, or raise
57 an exception on error. If not None, the following applies:
59 `async_callbacks` contains the names of two keyword arguments to
60 the decorated function, which will be used to provide a success
61 callback and an error callback (in that order).
63 When the decorated method is called via the D-Bus, its normal
64 return value will be ignored; instead, a pair of callbacks are
65 passed as keyword arguments, and the decorated method is
66 expected to arrange for one of them to be called.
68 On success the success callback must be called, passing the
69 results of this method as positional parameters in the format
70 given by the `out_signature`.
72 On error the decorated method may either raise an exception
73 before it returns, or arrange for the error callback to be
74 called with an Exception instance as parameter.
76 `sender_keyword` : str or None
77 If not None, contains the name of a keyword argument to the
78 decorated function, conventionally ``'sender'``. When the
79 method is called, the sender's unique name will be passed as
80 this keyword argument.
82 `path_keyword` : str or None
83 If not None (the default), the decorated method will receive
84 the destination object path as a keyword argument with this
85 name. Normally you already know the object path, but in the
86 case of "fallback paths" you'll usually want to use the object
87 path in the method's implementation.
89 `destination_keyword` : str or None
90 If not None (the default), the decorated method will receive
91 the destination bus name as a keyword argument with this name.
92 Included for completeness - you shouldn't need this.
94 `message_keyword` : str or None
95 If not None (the default), the decorated method will receive
96 the `dbus.lowlevel.MethodCallMessage` as a keyword argument
97 with this name.
99 `connection_keyword` : str or None
100 If not None (the default), the decorated method will receive
101 the `dbus.connection.Connection` as a keyword argument
102 with this name. This is generally only useful for objects
103 that are available on more than one connection.
105 `utf8_strings` : bool
106 If False (default), D-Bus strings are passed to the decorated
107 method as objects of class dbus.String, a unicode subclass.
109 If True, D-Bus strings are passed to the decorated method
110 as objects of class dbus.UTF8String, a str subclass guaranteed
111 to be encoded in UTF-8.
113 This option does not affect object-paths and signatures, which
114 are always 8-bit strings (str subclass) encoded in ASCII.
116 `byte_arrays` : bool
117 If False (default), a byte array will be passed to the decorated
118 method as an `Array` (a list subclass) of `Byte` objects.
120 If True, a byte array will be passed to the decorated method as
121 a `ByteArray`, a str subclass. This is usually what you want,
122 but is switched off by default to keep dbus-python's API
123 consistent.
125 _dbus_bindings.validate_interface_name(dbus_interface)
127 def decorator(func):
128 args = inspect.getargspec(func)[0]
129 args.pop(0)
131 if async_callbacks:
132 if type(async_callbacks) != tuple:
133 raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
134 if len(async_callbacks) != 2:
135 raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
136 args.remove(async_callbacks[0])
137 args.remove(async_callbacks[1])
139 if sender_keyword:
140 args.remove(sender_keyword)
141 if path_keyword:
142 args.remove(path_keyword)
143 if destination_keyword:
144 args.remove(destination_keyword)
145 if message_keyword:
146 args.remove(message_keyword)
147 if connection_keyword:
148 args.remove(connection_keyword)
150 if in_signature:
151 in_sig = tuple(_dbus_bindings.Signature(in_signature))
153 if len(in_sig) > len(args):
154 raise ValueError, 'input signature is longer than the number of arguments taken'
155 elif len(in_sig) < len(args):
156 raise ValueError, 'input signature is shorter than the number of arguments taken'
158 func._dbus_is_method = True
159 func._dbus_async_callbacks = async_callbacks
160 func._dbus_interface = dbus_interface
161 func._dbus_in_signature = in_signature
162 func._dbus_out_signature = out_signature
163 func._dbus_sender_keyword = sender_keyword
164 func._dbus_path_keyword = path_keyword
165 func._dbus_destination_keyword = destination_keyword
166 func._dbus_message_keyword = message_keyword
167 func._dbus_connection_keyword = connection_keyword
168 func._dbus_args = args
169 func._dbus_get_args_options = {'byte_arrays': byte_arrays,
170 'utf8_strings': utf8_strings}
171 return func
173 return decorator
176 def signal(dbus_interface, signature=None, path_keyword=None,
177 rel_path_keyword=None):
178 """Factory for decorators used to mark methods of a `dbus.service.Object`
179 to emit signals on the D-Bus.
181 Whenever the decorated method is called in Python, after the method
182 body is executed, a signal with the same name as the decorated method,
183 with the given D-Bus interface, will be emitted from this object.
185 :Parameters:
186 `dbus_interface` : str
187 The D-Bus interface whose signal is emitted
188 `signature` : str
189 The signature of the signal in the usual D-Bus notation
191 `path_keyword` : str or None
192 A keyword argument to the decorated method. If not None,
193 that argument will not be emitted as an argument of
194 the signal, and when the signal is emitted, it will appear
195 to come from the object path given by the keyword argument.
197 Note that when calling the decorated method, you must always
198 pass in the object path as a keyword argument, not as a
199 positional argument.
201 This keyword argument cannot be used on objects where
202 the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
204 :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
206 `rel_path_keyword` : str or None
207 A keyword argument to the decorated method. If not None,
208 that argument will not be emitted as an argument of
209 the signal.
211 When the signal is emitted, if the named keyword argument is given,
212 the signal will appear to come from the object path obtained by
213 appending the keyword argument to the object's object path.
214 This is useful to implement "fallback objects" (objects which
215 own an entire subtree of the object-path tree).
217 If the object is available at more than one object-path on the
218 same or different connections, the signal will be emitted at
219 an appropriate object-path on each connection - for instance,
220 if the object is exported at /abc on connection 1 and at
221 /def and /x/y/z on connection 2, and the keyword argument is
222 /foo, then signals will be emitted from /abc/foo and /def/foo
223 on connection 1, and /x/y/z/foo on connection 2.
225 _dbus_bindings.validate_interface_name(dbus_interface)
227 if path_keyword is not None:
228 from warnings import warn
229 warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
230 'deprecated since dbus-python 0.82.0, and '
231 'will not work on objects that support '
232 'multiple object paths'),
233 DeprecationWarning, stacklevel=2)
234 if rel_path_keyword is not None:
235 raise TypeError('dbus.service.signal::path_keyword and '
236 'rel_path_keyword cannot both be used')
238 def decorator(func):
239 member_name = func.__name__
240 _dbus_bindings.validate_member_name(member_name)
242 def emit_signal(self, *args, **keywords):
243 abs_path = None
244 if path_keyword is not None:
245 if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
246 raise TypeError('path_keyword cannot be used on the '
247 'signals of an object that supports '
248 'multiple object paths')
249 abs_path = keywords.pop(path_keyword, None)
250 if (abs_path != self.__dbus_object_path__ and
251 not self.__dbus_object_path__.startswith(abs_path + '/')):
252 raise ValueError('Path %r is not below %r', abs_path,
253 self.__dbus_object_path__)
255 rel_path = None
256 if rel_path_keyword is not None:
257 rel_path = keywords.pop(rel_path_keyword, None)
259 func(self, *args, **keywords)
261 for location in self.locations:
262 if abs_path is None:
263 # non-deprecated case
264 if rel_path is None or rel_path in ('/', ''):
265 object_path = location[1]
266 else:
267 # will be validated by SignalMessage ctor in a moment
268 object_path = location[1] + rel_path
269 else:
270 object_path = abs_path
272 message = _dbus_bindings.SignalMessage(object_path,
273 dbus_interface,
274 member_name)
275 message.append(signature=signature, *args)
277 location[0].send_message(message)
278 # end emit_signal
280 args = inspect.getargspec(func)[0]
281 args.pop(0)
283 for keyword in rel_path_keyword, path_keyword:
284 if keyword is not None:
285 try:
286 args.remove(keyword)
287 except ValueError:
288 raise ValueError('function has no argument "%s"' % keyword)
290 if signature:
291 sig = tuple(_dbus_bindings.Signature(signature))
293 if len(sig) > len(args):
294 raise ValueError, 'signal signature is longer than the number of arguments provided'
295 elif len(sig) < len(args):
296 raise ValueError, 'signal signature is shorter than the number of arguments provided'
298 emit_signal.__name__ = func.__name__
299 emit_signal.__doc__ = func.__doc__
300 emit_signal._dbus_is_signal = True
301 emit_signal._dbus_interface = dbus_interface
302 emit_signal._dbus_signature = signature
303 emit_signal._dbus_args = args
304 return emit_signal
306 return decorator