When user sets reply_handler but not error_handler raise MissingReplyHandlerException...
[dbus-python-phuang.git] / dbus / proxies.py
blobcbb5d53dcc0722a38b15b88eb05cd943701222ef
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/>
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:
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
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.
26 import sys
27 import logging
29 try:
30 from threading import RLock
31 except ImportError:
32 from dummy_threading import RLock
34 import _dbus_bindings
35 from dbus._expat_introspect_parser import process_introspection_data
36 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
38 __docformat__ = 'restructuredtext'
41 _logger = logging.getLogger('dbus.proxies')
43 from _dbus_bindings import LOCAL_PATH, \
44 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
45 INTROSPECTABLE_IFACE
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
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)
70 def call_async(self, *args, **keywords):
71 self._append(self._proxy_method, args, keywords)
74 class _ProxyMethod:
75 """A proxy method.
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)
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
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)
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')
115 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
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)
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)
142 def call_async(self, *args, **keywords):
143 reply_handler = keywords.pop('reply_handler', None)
144 error_handler = keywords.pop('error_handler', None)
146 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
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)
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)
165 class ProxyObject(object):
166 """A proxy to the remote Object.
168 A ProxyObject is provided by the Bus. ProxyObjects
169 have member functions, and can be called like normal Python objects.
171 ProxyMethodClass = _ProxyMethod
172 DeferredMethodClass = _DeferredMethod
174 INTROSPECT_STATE_DONT_INTROSPECT = 0
175 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
176 INTROSPECT_STATE_INTROSPECT_DONE = 2
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.
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
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()))
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()
229 self._bus = conn
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
237 _dbus_bindings.validate_object_path(object_path)
238 self.__dbus_object_path__ = object_path
240 if not follow_name_owner_changes:
241 self._named_service = conn.activate_name_owner(bus_name)
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 = {}
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()
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
259 self._pending_introspect = self._Introspect()
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.)
265 If the proxy was instantiated using a unique name, this property
266 is that unique name.
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.
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 """)
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 """)
285 object_path = property(lambda self: self.__dbus_object_path__,
286 None, None,
287 """The object-path of this proxy.""")
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 # """)
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.
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).
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)
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)
373 def _introspect_execute_queue(self):
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)
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
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()
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()
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()
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()
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)
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, ::
442 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
444 is equivalent to::
446 proxy.Foo(123, dbus_interface='com.example.Bar')
448 or even::
450 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
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.
457 For services which follow the D-Bus convention of CamelCaseMethodNames
458 this won't be a problem.
461 ret = self.ProxyMethodClass(self, self._bus,
462 self._named_service,
463 self.__dbus_object_path__, member,
464 dbus_interface)
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)
474 return ret
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__
482 class Interface(object):
483 """An interface into a remote object.
485 An Interface can be used to wrap ProxyObjects
486 so that calls can be routed to their correct
487 D-Bus interface.
490 def __init__(self, object, dbus_interface):
491 """Construct a proxy for the given interface on the given object.
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
499 if isinstance(object, Interface):
500 self._obj = object.proxy_object
501 else:
502 self._obj = object
503 self._dbus_interface = dbus_interface
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""")
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.
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.
530 if not dbus_interface:
531 dbus_interface = self._dbus_interface
533 return self._obj.connect_to_signal(signal_name, handler_function,
534 dbus_interface, **keywords)
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)
542 def get_dbus_method(self, member, dbus_interface=None):
543 """Return a proxy method representing the given D-Bus method.
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.
550 if dbus_interface is None:
551 dbus_interface = self._dbus_interface
552 return self._obj.get_dbus_method(member, dbus_interface)
554 def __repr__(self):
555 return '<Interface %r implementing %r at %#x>'%(
556 self._obj, self._dbus_interface, id(self))
557 __str__ = __repr__