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

Source Code for Module dbus.proxies

  1  # Copyright (C) 2003-2007 Red Hat Inc. <http://www.redhat.com/> 
  2  # Copyright (C) 2003 David Zeuthen 
  3  # Copyright (C) 2004 Rob Taylor 
  4  # Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/> 
  5  # 
  6  # Permission is hereby granted, free of charge, to any person 
  7  # obtaining a copy of this software and associated documentation 
  8  # files (the "Software"), to deal in the Software without 
  9  # restriction, including without limitation the rights to use, copy, 
 10  # modify, merge, publish, distribute, sublicense, and/or sell copies 
 11  # of the Software, and to permit persons to whom the Software is 
 12  # furnished to do so, subject to the following conditions: 
 13  # 
 14  # The above copyright notice and this permission notice shall be 
 15  # included in all copies or substantial portions of the Software. 
 16  # 
 17  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 18  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 19  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 20  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 21  # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 22  # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 23  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 24  # DEALINGS IN THE SOFTWARE. 
 25   
 26  import sys 
 27  import logging 
 28   
 29  try: 
 30      from threading import RLock 
 31  except ImportError: 
 32      from dummy_threading import RLock 
 33   
 34  import _dbus_bindings 
 35  from dbus._expat_introspect_parser import process_introspection_data 
 36  from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException 
 37   
 38  __docformat__ = 'restructuredtext' 
 39   
 40   
 41  _logger = logging.getLogger('dbus.proxies') 
 42   
 43  from _dbus_bindings import LOCAL_PATH, \ 
 44                             BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\ 
 45                             INTROSPECTABLE_IFACE 
 46   
 47   
48 -class _DeferredMethod:
49 """A proxy method which will only get called once we have its 50 introspection reply. 51 """
52 - def __init__(self, proxy_method, append, block):
53 self._proxy_method = proxy_method 54 # the test suite relies on the existence of this property 55 self._method_name = proxy_method._method_name 56 self._append = append 57 self._block = block
58
59 - def __call__(self, *args, **keywords):
60 if (keywords.has_key('reply_handler') or 61 keywords.get('ignore_reply', False)): 62 # defer the async call til introspection finishes 63 self._append(self._proxy_method, args, keywords) 64 return None 65 else: 66 # we're being synchronous, so block 67 self._block() 68 return self._proxy_method(*args, **keywords)
69
70 - def call_async(self, *args, **keywords):
71 self._append(self._proxy_method, args, keywords)
72 73
74 -class _ProxyMethod:
75 """A proxy method. 76 77 Typically a member of a ProxyObject. Calls to the 78 method produce messages that travel over the Bus and are routed 79 to a specific named Service. 80 """
81 - def __init__(self, proxy, connection, bus_name, object_path, method_name, 82 iface):
83 if object_path == LOCAL_PATH: 84 raise DBusException('Methods may not be called on the reserved ' 85 'path %s' % LOCAL_PATH) 86 87 # trust that the proxy, and the properties it had, are OK 88 self._proxy = proxy 89 self._connection = connection 90 self._named_service = bus_name 91 self._object_path = object_path 92 # fail early if the method name is bad 93 _dbus_bindings.validate_member_name(method_name) 94 # the test suite relies on the existence of this property 95 self._method_name = method_name 96 # fail early if the interface name is bad 97 if iface is not None: 98 _dbus_bindings.validate_interface_name(iface) 99 self._dbus_interface = iface
100
101 - def __call__(self, *args, **keywords):
102 reply_handler = keywords.pop('reply_handler', None) 103 error_handler = keywords.pop('error_handler', None) 104 ignore_reply = keywords.pop('ignore_reply', False) 105 106 if reply_handler is not None or error_handler is not None: 107 if reply_handler is None: 108 raise MissingReplyHandlerException() 109 elif error_handler is None: 110 raise MissingErrorHandlerException() 111 elif ignore_reply: 112 raise TypeError('ignore_reply and reply_handler cannot be ' 113 'used together') 114 115 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) 116 117 if dbus_interface is None: 118 key = self._method_name 119 else: 120 key = dbus_interface + '.' + self._method_name 121 introspect_sig = self._proxy._introspect_method_map.get(key, None) 122 123 if ignore_reply or reply_handler is not None: 124 self._connection.call_async(self._named_service, 125 self._object_path, 126 dbus_interface, 127 self._method_name, 128 introspect_sig, 129 args, 130 reply_handler, 131 error_handler, 132 **keywords) 133 else: 134 return self._connection.call_blocking(self._named_service, 135 self._object_path, 136 dbus_interface, 137 self._method_name, 138 introspect_sig, 139 args, 140 **keywords)
141
142 - def call_async(self, *args, **keywords):
143 reply_handler = keywords.pop('reply_handler', None) 144 error_handler = keywords.pop('error_handler', None) 145 146 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) 147 148 if dbus_interface: 149 key = dbus_interface + '.' + self._method_name 150 else: 151 key = self._method_name 152 introspect_sig = self._proxy._introspect_method_map.get(key, None) 153 154 self._connection.call_async(self._named_service, 155 self._object_path, 156 dbus_interface, 157 self._method_name, 158 introspect_sig, 159 args, 160 reply_handler, 161 error_handler, 162 **keywords)
163 164
165 -class ProxyObject(object):
166 """A proxy to the remote Object. 167 168 A ProxyObject is provided by the Bus. ProxyObjects 169 have member functions, and can be called like normal Python objects. 170 """ 171 ProxyMethodClass = _ProxyMethod 172 DeferredMethodClass = _DeferredMethod 173 174 INTROSPECT_STATE_DONT_INTROSPECT = 0 175 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1 176 INTROSPECT_STATE_INTROSPECT_DONE = 2 177
178 - def __init__(self, conn=None, bus_name=None, object_path=None, 179 introspect=True, follow_name_owner_changes=False, **kwargs):
180 """Initialize the proxy object. 181 182 :Parameters: 183 `conn` : `dbus.connection.Connection` 184 The bus or connection on which to find this object. 185 The keyword argument `bus` is a deprecated alias for this. 186 `bus_name` : str 187 A bus name for the application owning the object, to be used 188 as the destination for method calls and the sender for 189 signal matches. The keyword argument ``named_service`` is a 190 deprecated alias for this. 191 `object_path` : str 192 The object path at which the application exports the object 193 `introspect` : bool 194 If true (default), attempt to introspect the remote 195 object to find out supported methods and their signatures 196 `follow_name_owner_changes` : bool 197 If true (default is false) and the `bus_name` is a 198 well-known name, follow ownership changes for that name 199 """ 200 bus = kwargs.pop('bus', None) 201 if bus is not None: 202 if conn is not None: 203 raise TypeError('conn and bus cannot both be specified') 204 conn = bus 205 from warnings import warn 206 warn('Passing the bus parameter to ProxyObject by name is ' 207 'deprecated: please use positional parameters', 208 DeprecationWarning, stacklevel=2) 209 named_service = kwargs.pop('named_service', None) 210 if named_service is not None: 211 if bus_name is not None: 212 raise TypeError('bus_name and named_service cannot both be ' 213 'specified') 214 bus_name = named_service 215 from warnings import warn 216 warn('Passing the named_service parameter to ProxyObject by name ' 217 'is deprecated: please use positional parameters', 218 DeprecationWarning, stacklevel=2) 219 if kwargs: 220 raise TypeError('ProxyObject.__init__ does not take these ' 221 'keyword arguments: %s' 222 % ', '.join(kwargs.iterkeys())) 223 224 if follow_name_owner_changes: 225 # we don't get the signals unless the Bus has a main loop 226 # XXX: using Bus internals 227 conn._require_main_loop() 228 229 self._bus = conn 230 231 if bus_name is not None: 232 _dbus_bindings.validate_bus_name(bus_name) 233 # the attribute is still called _named_service for the moment, 234 # for the benefit of telepathy-python 235 self._named_service = self._requested_bus_name = bus_name 236 237 _dbus_bindings.validate_object_path(object_path) 238 self.__dbus_object_path__ = object_path 239 240 if not follow_name_owner_changes: 241 self._named_service = conn.activate_name_owner(bus_name) 242 243 #PendingCall object for Introspect call 244 self._pending_introspect = None 245 #queue of async calls waiting on the Introspect to return 246 self._pending_introspect_queue = [] 247 #dictionary mapping method names to their input signatures 248 self._introspect_method_map = {} 249 250 # must be a recursive lock because block() is called while locked, 251 # and calls the callback which re-takes the lock 252 self._introspect_lock = RLock() 253 254 if not introspect or self.__dbus_object_path__ == LOCAL_PATH: 255 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT 256 else: 257 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS 258 259 self._pending_introspect = self._Introspect()
260 261 bus_name = property(lambda self: self._named_service, None, None, 262 """The bus name to which this proxy is bound. (Read-only, 263 may change.) 264 265 If the proxy was instantiated using a unique name, this property 266 is that unique name. 267 268 If the proxy was instantiated with a well-known name and with 269 ``follow_name_owner_changes`` set false (the default), this 270 property is the unique name of the connection that owned that 271 well-known name when the proxy was instantiated, which might 272 not actually own the requested well-known name any more. 273 274 If the proxy was instantiated with a well-known name and with 275 ``follow_name_owner_changes`` set true, this property is that 276 well-known name. 277 """) 278 279 requested_bus_name = property(lambda self: self._requested_bus_name, 280 None, None, 281 """The bus name which was requested when this proxy was 282 instantiated. 283 """) 284 285 object_path = property(lambda self: self.__dbus_object_path__, 286 None, None, 287 """The object-path of this proxy.""") 288 289 # XXX: We don't currently support this because it's the signal receiver 290 # that's responsible for tracking name owner changes, but it 291 # seems a natural thing to add in future. 292 #unique_bus_name = property(lambda self: something, None, None, 293 # """The unique name of the connection to which this proxy is 294 # currently bound. (Read-only, may change.) 295 # """) 296
297 - def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
298 """Arrange for the given function to be called when the given signal 299 is received. 300 301 :Parameters: 302 `signal_name` : str 303 The name of the signal 304 `handler_function` : callable 305 A function to be called when the signal is emitted by 306 the remote object. Its positional arguments will be the 307 arguments of the signal; optionally, it may be given 308 keyword arguments as described below. 309 `dbus_interface` : str 310 Optional interface with which to qualify the signal name. 311 If None (the default) the handler will be called whenever a 312 signal of the given member name is received, whatever 313 its interface. 314 :Keywords: 315 `utf8_strings` : bool 316 If True, the handler function will receive any string 317 arguments as dbus.UTF8String objects (a subclass of str 318 guaranteed to be UTF-8). If False (default) it will receive 319 any string arguments as dbus.String objects (a subclass of 320 unicode). 321 `byte_arrays` : bool 322 If True, the handler function will receive any byte-array 323 arguments as dbus.ByteArray objects (a subclass of str). 324 If False (default) it will receive any byte-array 325 arguments as a dbus.Array of dbus.Byte (subclasses of: 326 a list of ints). 327 `sender_keyword` : str 328 If not None (the default), the handler function will receive 329 the unique name of the sending endpoint as a keyword 330 argument with this name 331 `destination_keyword` : str 332 If not None (the default), the handler function will receive 333 the bus name of the destination (or None if the signal is a 334 broadcast, as is usual) as a keyword argument with this name. 335 `interface_keyword` : str 336 If not None (the default), the handler function will receive 337 the signal interface as a keyword argument with this name. 338 `member_keyword` : str 339 If not None (the default), the handler function will receive 340 the signal name as a keyword argument with this name. 341 `path_keyword` : str 342 If not None (the default), the handler function will receive 343 the object-path of the sending object as a keyword argument 344 with this name 345 `message_keyword` : str 346 If not None (the default), the handler function will receive 347 the `dbus.lowlevel.SignalMessage` as a keyword argument with 348 this name. 349 `arg...` : unicode or UTF-8 str 350 If there are additional keyword parameters of the form 351 ``arg``\ *n*, match only signals where the *n*\ th argument 352 is the value given for that keyword parameter. As of this time 353 only string arguments can be matched (in particular, 354 object paths and signatures can't). 355 """ 356 return \ 357 self._bus.add_signal_receiver(handler_function, 358 signal_name=signal_name, 359 dbus_interface=dbus_interface, 360 bus_name=self._named_service, 361 path=self.__dbus_object_path__, 362 **keywords)
363
364 - def _Introspect(self):
365 return self._bus.call_async(self._named_service, 366 self.__dbus_object_path__, 367 INTROSPECTABLE_IFACE, 'Introspect', '', (), 368 self._introspect_reply_handler, 369 self._introspect_error_handler, 370 utf8_strings=True, 371 require_main_loop=False)
372
374 # FIXME: potential to flood the bus 375 # We should make sure mainloops all have idle handlers 376 # and do one message per idle 377 for (proxy_method, args, keywords) in self._pending_introspect_queue: 378 proxy_method(*args, **keywords)
379
380 - def _introspect_reply_handler(self, data):
381 self._introspect_lock.acquire() 382 try: 383 try: 384 self._introspect_method_map = process_introspection_data(data) 385 except IntrospectionParserException, e: 386 self._introspect_error_handler(e) 387 return 388 389 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE 390 self._pending_introspect = None 391 self._introspect_execute_queue() 392 finally: 393 self._introspect_lock.release()
394
395 - def _introspect_error_handler(self, error):
396 logging.basicConfig() 397 _logger.error("Introspect error on %s:%s: %s.%s: %s", 398 self._named_service, self.__dbus_object_path__, 399 error.__class__.__module__, error.__class__.__name__, 400 error) 401 self._introspect_lock.acquire() 402 try: 403 _logger.debug('Executing introspect queue due to error') 404 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT 405 self._pending_introspect = None 406 self._introspect_execute_queue() 407 finally: 408 self._introspect_lock.release()
409
410 - def _introspect_block(self):
411 self._introspect_lock.acquire() 412 try: 413 if self._pending_introspect is not None: 414 self._pending_introspect.block() 415 # else someone still has a _DeferredMethod from before we 416 # finished introspection: no need to do anything special any more 417 finally: 418 self._introspect_lock.release()
419
420 - def _introspect_add_to_queue(self, callback, args, kwargs):
421 self._introspect_lock.acquire() 422 try: 423 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS: 424 self._pending_introspect_queue.append((callback, args, kwargs)) 425 else: 426 # someone still has a _DeferredMethod from before we 427 # finished introspection 428 callback(*args, **kwargs) 429 finally: 430 self._introspect_lock.release()
431
432 - def __getattr__(self, member):
433 if member.startswith('__') and member.endswith('__'): 434 raise AttributeError(member) 435 else: 436 return self.get_dbus_method(member)
437
438 - def get_dbus_method(self, member, dbus_interface=None):
439 """Return a proxy method representing the given D-Bus method. The 440 returned proxy method can be called in the usual way. For instance, :: 441 442 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123) 443 444 is equivalent to:: 445 446 proxy.Foo(123, dbus_interface='com.example.Bar') 447 448 or even:: 449 450 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar') 451 452 However, using `get_dbus_method` is the only way to call D-Bus 453 methods with certain awkward names - if the author of a service 454 implements a method called ``connect_to_signal`` or even 455 ``__getattr__``, you'll need to use `get_dbus_method` to call them. 456 457 For services which follow the D-Bus convention of CamelCaseMethodNames 458 this won't be a problem. 459 """ 460 461 ret = self.ProxyMethodClass(self, self._bus, 462 self._named_service, 463 self.__dbus_object_path__, member, 464 dbus_interface) 465 466 # this can be done without taking the lock - the worst that can 467 # happen is that we accidentally return a _DeferredMethod just after 468 # finishing introspection, in which case _introspect_add_to_queue and 469 # _introspect_block will do the right thing anyway 470 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS: 471 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue, 472 self._introspect_block) 473 474 return ret
475
476 - def __repr__(self):
477 return '<ProxyObject wrapping %s %s %s at %#x>'%( 478 self._bus, self._named_service, self.__dbus_object_path__, id(self))
479 __str__ = __repr__
480 481
482 -class Interface(object):
483 """An interface into a remote object. 484 485 An Interface can be used to wrap ProxyObjects 486 so that calls can be routed to their correct 487 D-Bus interface. 488 """ 489
490 - def __init__(self, object, dbus_interface):
491 """Construct a proxy for the given interface on the given object. 492 493 :Parameters: 494 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface` 495 The remote object or another of its interfaces 496 `dbus_interface` : str 497 An interface the `object` implements 498 """ 499 if isinstance(object, Interface): 500 self._obj = object.proxy_object 501 else: 502 self._obj = object 503 self._dbus_interface = dbus_interface
504 505 object_path = property (lambda self: self._obj.object_path, None, None, 506 "The D-Bus object path of the underlying object") 507 __dbus_object_path__ = object_path 508 bus_name = property (lambda self: self._obj.bus_name, None, None, 509 "The bus name to which the underlying proxy object " 510 "is bound") 511 requested_bus_name = property (lambda self: self._obj.requested_bus_name, 512 None, None, 513 "The bus name which was requested when the " 514 "underlying object was created") 515 proxy_object = property (lambda self: self._obj, None, None, 516 """The underlying proxy object""") 517 dbus_interface = property (lambda self: self._dbus_interface, None, None, 518 """The D-Bus interface represented""") 519
520 - def connect_to_signal(self, signal_name, handler_function, 521 dbus_interface=None, **keywords):
522 """Arrange for a function to be called when the given signal is 523 emitted. 524 525 The parameters and keyword arguments are the same as for 526 `dbus.proxies.ProxyObject.connect_to_signal`, except that if 527 `dbus_interface` is None (the default), the D-Bus interface that 528 was passed to the `Interface` constructor is used. 529 """ 530 if not dbus_interface: 531 dbus_interface = self._dbus_interface 532 533 return self._obj.connect_to_signal(signal_name, handler_function, 534 dbus_interface, **keywords)
535
536 - def __getattr__(self, member):
537 if member.startswith('__') and member.endswith('__'): 538 raise AttributeError(member) 539 else: 540 return self._obj.get_dbus_method(member, self._dbus_interface)
541
542 - def get_dbus_method(self, member, dbus_interface=None):
543 """Return a proxy method representing the given D-Bus method. 544 545 This is the same as `dbus.proxies.ProxyObject.get_dbus_method` 546 except that if `dbus_interface` is None (the default), 547 the D-Bus interface that was passed to the `Interface` constructor 548 is used. 549 """ 550 if dbus_interface is None: 551 dbus_interface = self._dbus_interface 552 return self._obj.get_dbus_method(member, dbus_interface)
553
554 - def __repr__(self):
555 return '<Interface %r implementing %r at %#x>'%( 556 self._obj, self._dbus_interface, id(self))
557 __str__ = __repr__
558