Package dbus :: Module decorators
[hide private]
[frames] | no frames]

Source Code for Module dbus.decorators

  1  """Service-side D-Bus decorators.""" 
  2   
  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/> 
  7  # 
  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: 
 15  # 
 16  # The above copyright notice and this permission notice shall be 
 17  # included in all copies or substantial portions of the Software. 
 18  # 
 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. 
 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 # non-deprecated case 312 if rel_path is None or rel_path in ('/', ''): 313 object_path = location[1] 314 else: 315 # will be validated by SignalMessage ctor in a moment 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 # end emit_signal 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