Try to avoid importing things from _dbus_bindings when they could be imported from...
[dbus-python-phuang.git] / dbus / decorators.py
blob908d055fb66edbbb614c199e66ec2dfe1c2cdd9a
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 from dbus import validate_interface_name, Signature, validate_member_name
30 from dbus.lowlevel import SignalMessage
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 :Since: 0.80.0?
91 `destination_keyword` : str or None
92 If not None (the default), the decorated method will receive
93 the destination bus name as a keyword argument with this name.
94 Included for completeness - you shouldn't need this.
96 :Since: 0.80.0?
98 `message_keyword` : str or None
99 If not None (the default), the decorated method will receive
100 the `dbus.lowlevel.MethodCallMessage` as a keyword argument
101 with this name.
103 :Since: 0.80.0?
105 `connection_keyword` : str or None
106 If not None (the default), the decorated method will receive
107 the `dbus.connection.Connection` as a keyword argument
108 with this name. This is generally only useful for objects
109 that are available on more than one connection.
111 :Since: 0.82.0
113 `utf8_strings` : bool
114 If False (default), D-Bus strings are passed to the decorated
115 method as objects of class dbus.String, a unicode subclass.
117 If True, D-Bus strings are passed to the decorated method
118 as objects of class dbus.UTF8String, a str subclass guaranteed
119 to be encoded in UTF-8.
121 This option does not affect object-paths and signatures, which
122 are always 8-bit strings (str subclass) encoded in ASCII.
124 :Since: 0.80.0
126 `byte_arrays` : bool
127 If False (default), a byte array will be passed to the decorated
128 method as an `Array` (a list subclass) of `Byte` objects.
130 If True, a byte array will be passed to the decorated method as
131 a `ByteArray`, a str subclass. This is usually what you want,
132 but is switched off by default to keep dbus-python's API
133 consistent.
135 :Since: 0.80.0
137 validate_interface_name(dbus_interface)
139 def decorator(func):
140 args = inspect.getargspec(func)[0]
141 args.pop(0)
143 if async_callbacks:
144 if type(async_callbacks) != tuple:
145 raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
146 if len(async_callbacks) != 2:
147 raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
148 args.remove(async_callbacks[0])
149 args.remove(async_callbacks[1])
151 if sender_keyword:
152 args.remove(sender_keyword)
153 if path_keyword:
154 args.remove(path_keyword)
155 if destination_keyword:
156 args.remove(destination_keyword)
157 if message_keyword:
158 args.remove(message_keyword)
159 if connection_keyword:
160 args.remove(connection_keyword)
162 if in_signature:
163 in_sig = tuple(Signature(in_signature))
165 if len(in_sig) > len(args):
166 raise ValueError, 'input signature is longer than the number of arguments taken'
167 elif len(in_sig) < len(args):
168 raise ValueError, 'input signature is shorter than the number of arguments taken'
170 func._dbus_is_method = True
171 func._dbus_async_callbacks = async_callbacks
172 func._dbus_interface = dbus_interface
173 func._dbus_in_signature = in_signature
174 func._dbus_out_signature = out_signature
175 func._dbus_sender_keyword = sender_keyword
176 func._dbus_path_keyword = path_keyword
177 func._dbus_destination_keyword = destination_keyword
178 func._dbus_message_keyword = message_keyword
179 func._dbus_connection_keyword = connection_keyword
180 func._dbus_args = args
181 func._dbus_get_args_options = {'byte_arrays': byte_arrays,
182 'utf8_strings': utf8_strings}
183 return func
185 return decorator
188 def signal(dbus_interface, signature=None, path_keyword=None,
189 rel_path_keyword=None):
190 """Factory for decorators used to mark methods of a `dbus.service.Object`
191 to emit signals on the D-Bus.
193 Whenever the decorated method is called in Python, after the method
194 body is executed, a signal with the same name as the decorated method,
195 with the given D-Bus interface, will be emitted from this object.
197 :Parameters:
198 `dbus_interface` : str
199 The D-Bus interface whose signal is emitted
200 `signature` : str
201 The signature of the signal in the usual D-Bus notation
203 `path_keyword` : str or None
204 A keyword argument to the decorated method. If not None,
205 that argument will not be emitted as an argument of
206 the signal, and when the signal is emitted, it will appear
207 to come from the object path given by the keyword argument.
209 Note that when calling the decorated method, you must always
210 pass in the object path as a keyword argument, not as a
211 positional argument.
213 This keyword argument cannot be used on objects where
214 the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
216 :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
218 `rel_path_keyword` : str or None
219 A keyword argument to the decorated method. If not None,
220 that argument will not be emitted as an argument of
221 the signal.
223 When the signal is emitted, if the named keyword argument is given,
224 the signal will appear to come from the object path obtained by
225 appending the keyword argument to the object's object path.
226 This is useful to implement "fallback objects" (objects which
227 own an entire subtree of the object-path tree).
229 If the object is available at more than one object-path on the
230 same or different connections, the signal will be emitted at
231 an appropriate object-path on each connection - for instance,
232 if the object is exported at /abc on connection 1 and at
233 /def and /x/y/z on connection 2, and the keyword argument is
234 /foo, then signals will be emitted from /abc/foo and /def/foo
235 on connection 1, and /x/y/z/foo on connection 2.
237 :Since: 0.82.0
239 validate_interface_name(dbus_interface)
241 if path_keyword is not None:
242 from warnings import warn
243 warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
244 'deprecated since dbus-python 0.82.0, and '
245 'will not work on objects that support '
246 'multiple object paths'),
247 DeprecationWarning, stacklevel=2)
248 if rel_path_keyword is not None:
249 raise TypeError('dbus.service.signal::path_keyword and '
250 'rel_path_keyword cannot both be used')
252 def decorator(func):
253 member_name = func.__name__
254 validate_member_name(member_name)
256 def emit_signal(self, *args, **keywords):
257 abs_path = None
258 if path_keyword is not None:
259 if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
260 raise TypeError('path_keyword cannot be used on the '
261 'signals of an object that supports '
262 'multiple object paths')
263 abs_path = keywords.pop(path_keyword, None)
264 if (abs_path != self.__dbus_object_path__ and
265 not self.__dbus_object_path__.startswith(abs_path + '/')):
266 raise ValueError('Path %r is not below %r', abs_path,
267 self.__dbus_object_path__)
269 rel_path = None
270 if rel_path_keyword is not None:
271 rel_path = keywords.pop(rel_path_keyword, None)
273 func(self, *args, **keywords)
275 for location in self.locations:
276 if abs_path is None:
277 # non-deprecated case
278 if rel_path is None or rel_path in ('/', ''):
279 object_path = location[1]
280 else:
281 # will be validated by SignalMessage ctor in a moment
282 object_path = location[1] + rel_path
283 else:
284 object_path = abs_path
286 message = SignalMessage(object_path,
287 dbus_interface,
288 member_name)
289 message.append(signature=signature, *args)
291 location[0].send_message(message)
292 # end emit_signal
294 args = inspect.getargspec(func)[0]
295 args.pop(0)
297 for keyword in rel_path_keyword, path_keyword:
298 if keyword is not None:
299 try:
300 args.remove(keyword)
301 except ValueError:
302 raise ValueError('function has no argument "%s"' % keyword)
304 if signature:
305 sig = tuple(Signature(signature))
307 if len(sig) > len(args):
308 raise ValueError, 'signal signature is longer than the number of arguments provided'
309 elif len(sig) < len(args):
310 raise ValueError, 'signal signature is shorter than the number of arguments provided'
312 emit_signal.__name__ = func.__name__
313 emit_signal.__doc__ = func.__doc__
314 emit_signal._dbus_is_signal = True
315 emit_signal._dbus_interface = dbus_interface
316 emit_signal._dbus_signature = signature
317 emit_signal._dbus_args = args
318 return emit_signal
320 return decorator