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 signature = keywords.pop('signature', None) 106 107 if reply_handler is not None or error_handler is not None: 108 if reply_handler is None: 109 raise MissingReplyHandlerException() 110 elif error_handler is None: 111 raise MissingErrorHandlerException() 112 elif ignore_reply: 113 raise TypeError('ignore_reply and reply_handler cannot be ' 114 'used together') 115 116 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) 117 118 if signature is None: 119 if dbus_interface is None: 120 key = self._method_name 121 else: 122 key = dbus_interface + '.' + self._method_name 123 124 signature = self._proxy._introspect_method_map.get(key, None) 125 126 if ignore_reply or reply_handler is not None: 127 self._connection.call_async(self._named_service, 128 self._object_path, 129 dbus_interface, 130 self._method_name, 131 signature, 132 args, 133 reply_handler, 134 error_handler, 135 **keywords) 136 else: 137 return self._connection.call_blocking(self._named_service, 138 self._object_path, 139 dbus_interface, 140 self._method_name, 141 signature, 142 args, 143 **keywords)
144
145 - def call_async(self, *args, **keywords):
146 reply_handler = keywords.pop('reply_handler', None) 147 error_handler = keywords.pop('error_handler', None) 148 signature = keywords.pop('signature', None) 149 150 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface) 151 152 if signature is None: 153 if dbus_interface: 154 key = dbus_interface + '.' + self._method_name 155 else: 156 key = self._method_name 157 signature = self._proxy._introspect_method_map.get(key, None) 158 159 self._connection.call_async(self._named_service, 160 self._object_path, 161 dbus_interface, 162 self._method_name, 163 signature, 164 args, 165 reply_handler, 166 error_handler, 167 **keywords)
168 169
170 -class ProxyObject(object):
171 """A proxy to the remote Object. 172 173 A ProxyObject is provided by the Bus. ProxyObjects 174 have member functions, and can be called like normal Python objects. 175 """ 176 ProxyMethodClass = _ProxyMethod 177 DeferredMethodClass = _DeferredMethod 178 179 INTROSPECT_STATE_DONT_INTROSPECT = 0 180 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1 181 INTROSPECT_STATE_INTROSPECT_DONE = 2 182
183 - def __init__(self, conn=None, bus_name=None, object_path=None, 184 introspect=True, follow_name_owner_changes=False, **kwargs):
185 """Initialize the proxy object. 186 187 :Parameters: 188 `conn` : `dbus.connection.Connection` 189 The bus or connection on which to find this object. 190 The keyword argument `bus` is a deprecated alias for this. 191 `bus_name` : str 192 A bus name for the application owning the object, to be used 193 as the destination for method calls and the sender for 194 signal matches. The keyword argument ``named_service`` is a 195 deprecated alias for this. 196 `object_path` : str 197 The object path at which the application exports the object 198 `introspect` : bool 199 If true (default), attempt to introspect the remote 200 object to find out supported methods and their signatures 201 `follow_name_owner_changes` : bool 202 If true (default is false) and the `bus_name` is a 203 well-known name, follow ownership changes for that name 204 """ 205 bus = kwargs.pop('bus', None) 206 if bus is not None: 207 if conn is not None: 208 raise TypeError('conn and bus cannot both be specified') 209 conn = bus 210 from warnings import warn 211 warn('Passing the bus parameter to ProxyObject by name is ' 212 'deprecated: please use positional parameters', 213 DeprecationWarning, stacklevel=2) 214 named_service = kwargs.pop('named_service', None) 215 if named_service is not None: 216 if bus_name is not None: 217 raise TypeError('bus_name and named_service cannot both be ' 218 'specified') 219 bus_name = named_service 220 from warnings import warn 221 warn('Passing the named_service parameter to ProxyObject by name ' 222 'is deprecated: please use positional parameters', 223 DeprecationWarning, stacklevel=2) 224 if kwargs: 225 raise TypeError('ProxyObject.__init__ does not take these ' 226 'keyword arguments: %s' 227 % ', '.join(kwargs.iterkeys())) 228 229 if follow_name_owner_changes: 230 # we don't get the signals unless the Bus has a main loop 231 # XXX: using Bus internals 232 conn._require_main_loop() 233 234 self._bus = conn 235 236 if bus_name is not None: 237 _dbus_bindings.validate_bus_name(bus_name) 238 # the attribute is still called _named_service for the moment, 239 # for the benefit of telepathy-python 240 self._named_service = self._requested_bus_name = bus_name 241 242 _dbus_bindings.validate_object_path(object_path) 243 self.__dbus_object_path__ = object_path 244 245 if not follow_name_owner_changes: 246 self._named_service = conn.activate_name_owner(bus_name) 247 248 #PendingCall object for Introspect call 249 self._pending_introspect = None 250 #queue of async calls waiting on the Introspect to return 251 self._pending_introspect_queue = [] 252 #dictionary mapping method names to their input signatures 253 self._introspect_method_map = {} 254 255 # must be a recursive lock because block() is called while locked, 256 # and calls the callback which re-takes the lock 257 self._introspect_lock = RLock() 258 259 if not introspect or self.__dbus_object_path__ == LOCAL_PATH: 260 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT 261 else: 262 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS 263 264 self._pending_introspect = self._Introspect()
265 266 bus_name = property(lambda self: self._named_service, None, None, 267 """The bus name to which this proxy is bound. (Read-only, 268 may change.) 269 270 If the proxy was instantiated using a unique name, this property 271 is that unique name. 272 273 If the proxy was instantiated with a well-known name and with 274 ``follow_name_owner_changes`` set false (the default), this 275 property is the unique name of the connection that owned that 276 well-known name when the proxy was instantiated, which might 277 not actually own the requested well-known name any more. 278 279 If the proxy was instantiated with a well-known name and with 280 ``follow_name_owner_changes`` set true, this property is that 281 well-known name. 282 """) 283 284 requested_bus_name = property(lambda self: self._requested_bus_name, 285 None, None, 286 """The bus name which was requested when this proxy was 287 instantiated. 288 """) 289 290 object_path = property(lambda self: self.__dbus_object_path__, 291 None, None, 292 """The object-path of this proxy.""") 293 294 # XXX: We don't currently support this because it's the signal receiver 295 # that's responsible for tracking name owner changes, but it 296 # seems a natural thing to add in future. 297 #unique_bus_name = property(lambda self: something, None, None, 298 # """The unique name of the connection to which this proxy is 299 # currently bound. (Read-only, may change.) 300 # """) 301
302 - def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
303 """Arrange for the given function to be called when the given signal 304 is received. 305 306 :Parameters: 307 `signal_name` : str 308 The name of the signal 309 `handler_function` : callable 310 A function to be called when the signal is emitted by 311 the remote object. Its positional arguments will be the 312 arguments of the signal; optionally, it may be given 313 keyword arguments as described below. 314 `dbus_interface` : str 315 Optional interface with which to qualify the signal name. 316 If None (the default) the handler will be called whenever a 317 signal of the given member name is received, whatever 318 its interface. 319 :Keywords: 320 `utf8_strings` : bool 321 If True, the handler function will receive any string 322 arguments as dbus.UTF8String objects (a subclass of str 323 guaranteed to be UTF-8). If False (default) it will receive 324 any string arguments as dbus.String objects (a subclass of 325 unicode). 326 `byte_arrays` : bool 327 If True, the handler function will receive any byte-array 328 arguments as dbus.ByteArray objects (a subclass of str). 329 If False (default) it will receive any byte-array 330 arguments as a dbus.Array of dbus.Byte (subclasses of: 331 a list of ints). 332 `sender_keyword` : str 333 If not None (the default), the handler function will receive 334 the unique name of the sending endpoint as a keyword 335 argument with this name 336 `destination_keyword` : str 337 If not None (the default), the handler function will receive 338 the bus name of the destination (or None if the signal is a 339 broadcast, as is usual) as a keyword argument with this name. 340 `interface_keyword` : str 341 If not None (the default), the handler function will receive 342 the signal interface as a keyword argument with this name. 343 `member_keyword` : str 344 If not None (the default), the handler function will receive 345 the signal name as a keyword argument with this name. 346 `path_keyword` : str 347 If not None (the default), the handler function will receive 348 the object-path of the sending object as a keyword argument 349 with this name 350 `message_keyword` : str 351 If not None (the default), the handler function will receive 352 the `dbus.lowlevel.SignalMessage` as a keyword argument with 353 this name. 354 `arg...` : unicode or UTF-8 str 355 If there are additional keyword parameters of the form 356 ``arg``\ *n*, match only signals where the *n*\ th argument 357 is the value given for that keyword parameter. As of this time 358 only string arguments can be matched (in particular, 359 object paths and signatures can't). 360 """ 361 return \ 362 self._bus.add_signal_receiver(handler_function, 363 signal_name=signal_name, 364 dbus_interface=dbus_interface, 365 bus_name=self._named_service, 366 path=self.__dbus_object_path__, 367 **keywords)
368
369 - def _Introspect(self):
370 return self._bus.call_async(self._named_service, 371 self.__dbus_object_path__, 372 INTROSPECTABLE_IFACE, 'Introspect', '', (), 373 self._introspect_reply_handler, 374 self._introspect_error_handler, 375 utf8_strings=True, 376 require_main_loop=False)
377
379 # FIXME: potential to flood the bus 380 # We should make sure mainloops all have idle handlers 381 # and do one message per idle 382 for (proxy_method, args, keywords) in self._pending_introspect_queue: 383 proxy_method(*args, **keywords) 384 self._pending_introspect_queue = []
385
386 - def _introspect_reply_handler(self, data):
387 self._introspect_lock.acquire() 388 try: 389 try: 390 self._introspect_method_map = process_introspection_data(data) 391 except IntrospectionParserException, e: 392 self._introspect_error_handler(e) 393 return 394 395 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE 396 self._pending_introspect = None 397 self._introspect_execute_queue() 398 finally: 399 self._introspect_lock.release()
400
401 - def _introspect_error_handler(self, error):
402 logging.basicConfig() 403 _logger.error("Introspect error on %s:%s: %s.%s: %s", 404 self._named_service, self.__dbus_object_path__, 405 error.__class__.__module__, error.__class__.__name__, 406 error) 407 self._introspect_lock.acquire() 408 try: 409 _logger.debug('Executing introspect queue due to error') 410 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT 411 self._pending_introspect = None 412 self._introspect_execute_queue() 413 finally: 414 self._introspect_lock.release()
415
416 - def _introspect_block(self):
417 self._introspect_lock.acquire() 418 try: 419 if self._pending_introspect is not None: 420 self._pending_introspect.block() 421 # else someone still has a _DeferredMethod from before we 422 # finished introspection: no need to do anything special any more 423 finally: 424 self._introspect_lock.release()
425
426 - def _introspect_add_to_queue(self, callback, args, kwargs):
427 self._introspect_lock.acquire() 428 try: 429 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS: 430 self._pending_introspect_queue.append((callback, args, kwargs)) 431 else: 432 # someone still has a _DeferredMethod from before we 433 # finished introspection 434 callback(*args, **kwargs) 435 finally: 436 self._introspect_lock.release()
437
438 - def __getattr__(self, member):
439 if member.startswith('__') and member.endswith('__'): 440 raise AttributeError(member) 441 else: 442 return self.get_dbus_method(member)
443
444 - def get_dbus_method(self, member, dbus_interface=None):
445 """Return a proxy method representing the given D-Bus method. The 446 returned proxy method can be called in the usual way. For instance, :: 447 448 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123) 449 450 is equivalent to:: 451 452 proxy.Foo(123, dbus_interface='com.example.Bar') 453 454 or even:: 455 456 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar') 457 458 However, using `get_dbus_method` is the only way to call D-Bus 459 methods with certain awkward names - if the author of a service 460 implements a method called ``connect_to_signal`` or even 461 ``__getattr__``, you'll need to use `get_dbus_method` to call them. 462 463 For services which follow the D-Bus convention of CamelCaseMethodNames 464 this won't be a problem. 465 """ 466 467 ret = self.ProxyMethodClass(self, self._bus, 468 self._named_service, 469 self.__dbus_object_path__, member, 470 dbus_interface) 471 472 # this can be done without taking the lock - the worst that can 473 # happen is that we accidentally return a _DeferredMethod just after 474 # finishing introspection, in which case _introspect_add_to_queue and 475 # _introspect_block will do the right thing anyway 476 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS: 477 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue, 478 self._introspect_block) 479 480 return ret
481
482 - def __repr__(self):
483 return '<ProxyObject wrapping %s %s %s at %#x>'%( 484 self._bus, self._named_service, self.__dbus_object_path__, id(self))
485 __str__ = __repr__
486 487
488 -class Interface(object):
489 """An interface into a remote object. 490 491 An Interface can be used to wrap ProxyObjects 492 so that calls can be routed to their correct 493 D-Bus interface. 494 """ 495
496 - def __init__(self, object, dbus_interface):
497 """Construct a proxy for the given interface on the given object. 498 499 :Parameters: 500 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface` 501 The remote object or another of its interfaces 502 `dbus_interface` : str 503 An interface the `object` implements 504 """ 505 if isinstance(object, Interface): 506 self._obj = object.proxy_object 507 else: 508 self._obj = object 509 self._dbus_interface = dbus_interface
510 511 object_path = property (lambda self: self._obj.object_path, None, None, 512 "The D-Bus object path of the underlying object") 513 __dbus_object_path__ = object_path 514 bus_name = property (lambda self: self._obj.bus_name, None, None, 515 "The bus name to which the underlying proxy object " 516 "is bound") 517 requested_bus_name = property (lambda self: self._obj.requested_bus_name, 518 None, None, 519 "The bus name which was requested when the " 520 "underlying object was created") 521 proxy_object = property (lambda self: self._obj, None, None, 522 """The underlying proxy object""") 523 dbus_interface = property (lambda self: self._dbus_interface, None, None, 524 """The D-Bus interface represented""") 525
526 - def connect_to_signal(self, signal_name, handler_function, 527 dbus_interface=None, **keywords):
528 """Arrange for a function to be called when the given signal is 529 emitted. 530 531 The parameters and keyword arguments are the same as for 532 `dbus.proxies.ProxyObject.connect_to_signal`, except that if 533 `dbus_interface` is None (the default), the D-Bus interface that 534 was passed to the `Interface` constructor is used. 535 """ 536 if not dbus_interface: 537 dbus_interface = self._dbus_interface 538 539 return self._obj.connect_to_signal(signal_name, handler_function, 540 dbus_interface, **keywords)
541
542 - def __getattr__(self, member):
543 if member.startswith('__') and member.endswith('__'): 544 raise AttributeError(member) 545 else: 546 return self._obj.get_dbus_method(member, self._dbus_interface)
547
548 - def get_dbus_method(self, member, dbus_interface=None):
549 """Return a proxy method representing the given D-Bus method. 550 551 This is the same as `dbus.proxies.ProxyObject.get_dbus_method` 552 except that if `dbus_interface` is None (the default), 553 the D-Bus interface that was passed to the `Interface` constructor 554 is used. 555 """ 556 if dbus_interface is None: 557 dbus_interface = self._dbus_interface 558 return self._obj.get_dbus_method(member, dbus_interface)
559
560 - def __repr__(self):
561 return '<Interface %r implementing %r at %#x>'%( 562 self._obj, self._dbus_interface, id(self))
563 __str__ = __repr__
564