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