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