dbus.service: Don't assume that exceptions passed to asynchronous callbacks are the...
[dbus-python-phuang.git] / dbus / service.py
blob4f600fc767bbf267d6f49154a2d4da5cb995a298
1 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006 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 __all__ = ('BusName', 'Object', 'method', 'signal')
23 __docformat__ = 'restructuredtext'
25 import sys
26 import logging
27 import operator
28 import traceback
29 try:
30 import thread
31 except ImportError:
32 import dummy_thread as thread
34 import _dbus_bindings
35 from dbus import SessionBus, Signature, Struct, validate_bus_name, \
36 validate_object_path, INTROSPECTABLE_IFACE, ObjectPath
37 from dbus.decorators import method, signal
38 from dbus.exceptions import DBusException, \
39 NameExistsException, \
40 UnknownMethodException
41 from dbus.lowlevel import ErrorMessage, MethodReturnMessage
42 from dbus.proxies import LOCAL_PATH
45 _logger = logging.getLogger('dbus.service')
48 class _VariantSignature(object):
49 """A fake method signature which, when iterated, yields an endless stream
50 of 'v' characters representing variants (handy with zip()).
52 It has no string representation.
53 """
54 def __iter__(self):
55 """Return self."""
56 return self
58 def next(self):
59 """Return 'v' whenever called."""
60 return 'v'
62 class BusName(object):
63 """A base class for exporting your own Named Services across the Bus.
65 When instantiated, objects of this class attempt to claim the given
66 well-known name on the given bus for the current process. The name is
67 released when the BusName object becomes unreferenced.
69 If a well-known name is requested multiple times, multiple references
70 to the same BusName object will be returned.
72 Caveats
73 -------
74 - Assumes that named services are only ever requested using this class -
75 if you request names from the bus directly, confusion may occur.
76 - Does not handle queueing.
77 """
78 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
79 """Constructor, which may either return an existing cached object
80 or a new object.
82 :Parameters:
83 `name` : str
84 The well-known name to be advertised
85 `bus` : dbus.Bus
86 A Bus on which this service will be advertised.
88 Omitting this parameter or setting it to None has been
89 deprecated since version 0.82.1. For backwards compatibility,
90 if this is done, the global shared connection to the session
91 bus will be used.
93 `allow_replacement` : bool
94 If True, other processes trying to claim the same well-known
95 name will take precedence over this one.
96 `replace_existing` : bool
97 If True, this process can take over the well-known name
98 from other processes already holding it.
99 `do_not_queue` : bool
100 If True, this service will not be placed in the queue of
101 services waiting for the requested name if another service
102 already holds it.
104 validate_bus_name(name, allow_well_known=True, allow_unique=False)
106 # if necessary, get default bus (deprecated)
107 if bus is None:
108 import warnings
109 warnings.warn('Omitting the "bus" parameter to '
110 'dbus.service.BusName.__init__ is deprecated',
111 DeprecationWarning, stacklevel=2)
112 bus = SessionBus()
114 # see if this name is already defined, return it if so
115 # FIXME: accessing internals of Bus
116 if name in bus._bus_names:
117 return bus._bus_names[name]
119 # otherwise register the name
120 name_flags = (
121 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
122 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
123 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
125 retval = bus.request_name(name, name_flags)
127 # TODO: more intelligent tracking of bus name states?
128 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
129 pass
130 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
131 # queueing can happen by default, maybe we should
132 # track this better or let the user know if they're
133 # queued or not?
134 pass
135 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
136 raise NameExistsException(name)
137 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
138 # if this is a shared bus which is being used by someone
139 # else in this process, this can happen legitimately
140 pass
141 else:
142 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
144 # and create the object
145 bus_name = object.__new__(cls)
146 bus_name._bus = bus
147 bus_name._name = name
149 # cache instance (weak ref only)
150 # FIXME: accessing Bus internals again
151 bus._bus_names[name] = bus_name
153 return bus_name
155 # do nothing because this is called whether or not the bus name
156 # object was retrieved from the cache or created new
157 def __init__(self, *args, **keywords):
158 pass
160 # we can delete the low-level name here because these objects
161 # are guaranteed to exist only once for each bus name
162 def __del__(self):
163 self._bus.release_name(self._name)
164 pass
166 def get_bus(self):
167 """Get the Bus this Service is on"""
168 return self._bus
170 def get_name(self):
171 """Get the name of this service"""
172 return self._name
174 def __repr__(self):
175 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
176 __str__ = __repr__
179 def _method_lookup(self, method_name, dbus_interface):
180 """Walks the Python MRO of the given class to find the method to invoke.
182 Returns two methods, the one to call, and the one it inherits from which
183 defines its D-Bus interface name, signature, and attributes.
185 parent_method = None
186 candidate_class = None
187 successful = False
189 # split up the cases when we do and don't have an interface because the
190 # latter is much simpler
191 if dbus_interface:
192 # search through the class hierarchy in python MRO order
193 for cls in self.__class__.__mro__:
194 # if we haven't got a candidate class yet, and we find a class with a
195 # suitably named member, save this as a candidate class
196 if (not candidate_class and method_name in cls.__dict__):
197 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
198 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
199 # however if it is annotated for a different interface
200 # than we are looking for, it cannot be a candidate
201 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
202 candidate_class = cls
203 parent_method = cls.__dict__[method_name]
204 successful = True
205 break
206 else:
207 pass
208 else:
209 candidate_class = cls
211 # if we have a candidate class, carry on checking this and all
212 # superclasses for a method annoated as a dbus method
213 # on the correct interface
214 if (candidate_class and method_name in cls.__dict__
215 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
216 and "_dbus_interface" in cls.__dict__[method_name].__dict__
217 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
218 # the candidate class has a dbus method on the correct interface,
219 # or overrides a method that is, success!
220 parent_method = cls.__dict__[method_name]
221 successful = True
222 break
224 else:
225 # simpler version of above
226 for cls in self.__class__.__mro__:
227 if (not candidate_class and method_name in cls.__dict__):
228 candidate_class = cls
230 if (candidate_class and method_name in cls.__dict__
231 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
232 parent_method = cls.__dict__[method_name]
233 successful = True
234 break
236 if successful:
237 return (candidate_class.__dict__[method_name], parent_method)
238 else:
239 if dbus_interface:
240 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
241 else:
242 raise UnknownMethodException('%s is not a valid method' % method_name)
245 def _method_reply_return(connection, message, method_name, signature, *retval):
246 reply = MethodReturnMessage(message)
247 try:
248 reply.append(signature=signature, *retval)
249 except Exception, e:
250 logging.basicConfig()
251 if signature is None:
252 try:
253 signature = reply.guess_signature(retval) + ' (guessed)'
254 except Exception, e:
255 _logger.error('Unable to guess signature for arguments %r: '
256 '%s: %s', retval, e.__class__, e)
257 raise
258 _logger.error('Unable to append %r to message with signature %s: '
259 '%s: %s', retval, signature, e.__class__, e)
260 raise
262 connection.send_message(reply)
265 def _method_reply_error(connection, message, exception):
266 name = getattr(exception, '_dbus_error_name', None)
268 if name is not None:
269 pass
270 elif getattr(exception, '__module__', '') in ('', '__main__'):
271 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
272 else:
273 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
275 et, ev, etb = sys.exc_info()
276 if ev is exception:
277 # The exception was actually thrown, so we can get a traceback
278 contents = ''.join(traceback.format_exception(et, ev, etb))
279 else:
280 # We don't have any traceback for it, e.g.
281 # async_err_cb(MyException('Failed to badger the mushroom'))
282 # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
283 contents = ''.join(traceback.format_exception_only(exception.__class__,
284 exception))
285 reply = ErrorMessage(message, name, contents)
287 connection.send_message(reply)
290 class InterfaceType(type):
291 def __init__(cls, name, bases, dct):
292 # these attributes are shared between all instances of the Interface
293 # object, so this has to be a dictionary that maps class names to
294 # the per-class introspection/interface data
295 class_table = getattr(cls, '_dbus_class_table', {})
296 cls._dbus_class_table = class_table
297 interface_table = class_table[cls.__module__ + '.' + name] = {}
299 # merge all the name -> method tables for all the interfaces
300 # implemented by our base classes into our own
301 for b in bases:
302 base_name = b.__module__ + '.' + b.__name__
303 if getattr(b, '_dbus_class_table', False):
304 for (interface, method_table) in class_table[base_name].iteritems():
305 our_method_table = interface_table.setdefault(interface, {})
306 our_method_table.update(method_table)
308 # add in all the name -> method entries for our own methods/signals
309 for func in dct.values():
310 if getattr(func, '_dbus_interface', False):
311 method_table = interface_table.setdefault(func._dbus_interface, {})
312 method_table[func.__name__] = func
314 super(InterfaceType, cls).__init__(name, bases, dct)
316 # methods are different to signals, so we have two functions... :)
317 def _reflect_on_method(cls, func):
318 args = func._dbus_args
320 if func._dbus_in_signature:
321 # convert signature into a tuple so length refers to number of
322 # types, not number of characters. the length is checked by
323 # the decorator to make sure it matches the length of args.
324 in_sig = tuple(Signature(func._dbus_in_signature))
325 else:
326 # magic iterator which returns as many v's as we need
327 in_sig = _VariantSignature()
329 if func._dbus_out_signature:
330 out_sig = Signature(func._dbus_out_signature)
331 else:
332 # its tempting to default to Signature('v'), but
333 # for methods that return nothing, providing incorrect
334 # introspection data is worse than providing none at all
335 out_sig = []
337 reflection_data = ' <method name="%s">\n' % (func.__name__)
338 for pair in zip(in_sig, args):
339 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
340 for type in out_sig:
341 reflection_data += ' <arg direction="out" type="%s" />\n' % type
342 reflection_data += ' </method>\n'
344 return reflection_data
346 def _reflect_on_signal(cls, func):
347 args = func._dbus_args
349 if func._dbus_signature:
350 # convert signature into a tuple so length refers to number of
351 # types, not number of characters
352 sig = tuple(Signature(func._dbus_signature))
353 else:
354 # magic iterator which returns as many v's as we need
355 sig = _VariantSignature()
357 reflection_data = ' <signal name="%s">\n' % (func.__name__)
358 for pair in zip(sig, args):
359 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
360 reflection_data = reflection_data + ' </signal>\n'
362 return reflection_data
364 class Interface(object):
365 __metaclass__ = InterfaceType
367 #: A unique object used as the value of Object._object_path and
368 #: Object._connection if it's actually in more than one place
369 _MANY = object()
371 class Object(Interface):
372 r"""A base class for exporting your own Objects across the Bus.
374 Just inherit from Object and mark exported methods with the
375 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
377 Example::
379 class Example(dbus.service.object):
380 def __init__(self, object_path):
381 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
382 self._last_input = None
384 @dbus.service.method(interface='com.example.Sample',
385 in_signature='v', out_signature='s')
386 def StringifyVariant(self, var):
387 self.LastInputChanged(var) # emits the signal
388 return str(var)
390 @dbus.service.signal(interface='com.example.Sample',
391 signature='v')
392 def LastInputChanged(self, var):
393 # run just before the signal is actually emitted
394 # just put "pass" if nothing should happen
395 self._last_input = var
397 @dbus.service.method(interface='com.example.Sample',
398 in_signature='', out_signature='v')
399 def GetLastInput(self):
400 return self._last_input
403 #: If True, this object can be made available at more than one object path.
404 #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
405 #: handle more than one object path, but they must all be on the same
406 #: connection.
407 SUPPORTS_MULTIPLE_OBJECT_PATHS = False
409 #: If True, this object can be made available on more than one connection.
410 #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
411 #: have the same object path on all its connections.
412 SUPPORTS_MULTIPLE_CONNECTIONS = False
414 def __init__(self, conn=None, object_path=None, bus_name=None):
415 """Constructor. Either conn or bus_name is required; object_path
416 is also required.
418 :Parameters:
419 `conn` : dbus.connection.Connection or None
420 The connection on which to export this object.
422 If None, use the Bus associated with the given ``bus_name``.
423 If there is no ``bus_name`` either, the object is not
424 initially available on any Connection.
426 For backwards compatibility, if an instance of
427 dbus.service.BusName is passed as the first parameter,
428 this is equivalent to passing its associated Bus as
429 ``conn``, and passing the BusName itself as ``bus_name``.
431 `object_path` : str or None
432 A D-Bus object path at which to make this Object available
433 immediately. If this is not None, a `conn` or `bus_name` must
434 also be provided.
436 `bus_name` : dbus.service.BusName or None
437 Represents a well-known name claimed by this process. A
438 reference to the BusName object will be held by this
439 Object, preventing the name from being released during this
440 Object's lifetime (unless it's released manually).
442 if object_path is not None:
443 validate_object_path(object_path)
445 if isinstance(conn, BusName):
446 # someone's using the old API; don't gratuitously break them
447 bus_name = conn
448 conn = bus_name.get_bus()
449 elif conn is None:
450 if bus_name is not None:
451 # someone's using the old API but naming arguments, probably
452 conn = bus_name.get_bus()
454 #: Either an object path, None or _MANY
455 self._object_path = None
456 #: Either a dbus.connection.Connection, None or _MANY
457 self._connection = None
458 #: A list of tuples (Connection, object path, False) where the False
459 #: is for future expansion (to support fallback paths)
460 self._locations = []
461 #: Lock protecting `_locations`, `_connection` and `_object_path`
462 self._locations_lock = thread.allocate_lock()
464 #: True if this is a fallback object handling a whole subtree.
465 self._fallback = False
467 self._name = bus_name
469 if conn is None and object_path is not None:
470 raise TypeError('If object_path is given, either conn or bus_name '
471 'is required')
472 if conn is not None and object_path is not None:
473 self.add_to_connection(conn, object_path)
475 @property
476 def __dbus_object_path__(self):
477 """The object-path at which this object is available.
478 Access raises AttributeError if there is no object path, or more than
479 one object path.
481 Changed in 0.82.0: AttributeError can be raised.
483 if self._object_path is _MANY:
484 raise AttributeError('Object %r has more than one object path: '
485 'use Object.locations instead' % self)
486 elif self._object_path is None:
487 raise AttributeError('Object %r has no object path yet' % self)
488 else:
489 return self._object_path
491 @property
492 def connection(self):
493 """The Connection on which this object is available.
494 Access raises AttributeError if there is no Connection, or more than
495 one Connection.
497 Changed in 0.82.0: AttributeError can be raised.
499 if self._connection is _MANY:
500 raise AttributeError('Object %r is on more than one Connection: '
501 'use Object.locations instead' % self)
502 elif self._connection is None:
503 raise AttributeError('Object %r has no Connection yet' % self)
504 else:
505 return self._connection
507 @property
508 def locations(self):
509 """An iterable over tuples representing locations at which this
510 object is available.
512 Each tuple has at least two items, but may have more in future
513 versions of dbus-python, so do not rely on their exact length.
514 The first two items are the dbus.connection.Connection and the object
515 path.
517 :Since: 0.82.0
519 return iter(self._locations)
521 def add_to_connection(self, connection, path):
522 """Make this object accessible via the given D-Bus connection and
523 object path.
525 :Parameters:
526 `connection` : dbus.connection.Connection
527 Export the object on this connection. If the class attribute
528 SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
529 can only be made available on one connection; if the class
530 attribute is set True by a subclass, the object can be made
531 available on more than one connection.
533 `path` : dbus.ObjectPath or other str
534 Place the object at this object path. If the class attribute
535 SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
536 can only be made available at one object path; if the class
537 attribute is set True by a subclass, the object can be made
538 available with more than one object path.
540 :Raises ValueError: if the object's class attributes do not allow the
541 object to be exported in the desired way.
542 :Since: 0.82.0
544 if path == LOCAL_PATH:
545 raise ValueError('Objects may not be exported on the reserved '
546 'path %s' % LOCAL_PATH)
548 self._locations_lock.acquire()
549 try:
550 if (self._connection is not None and
551 self._connection is not connection and
552 not self.SUPPORTS_MULTIPLE_CONNECTIONS):
553 raise ValueError('%r is already exported on '
554 'connection %r' % (self, self._connection))
556 if (self._object_path is not None and
557 not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
558 self._object_path != path):
559 raise ValueError('%r is already exported at object '
560 'path %s' % (self, self._object_path))
562 connection._register_object_path(path, self._message_cb,
563 self._unregister_cb,
564 self._fallback)
566 if self._connection is None:
567 self._connection = connection
568 elif self._connection is not connection:
569 self._connection = _MANY
571 if self._object_path is None:
572 self._object_path = path
573 elif self._object_path != path:
574 self._object_path = _MANY
576 self._locations.append((connection, path, self._fallback))
577 finally:
578 self._locations_lock.release()
580 def remove_from_connection(self, connection=None, path=None):
581 """Make this object inaccessible via the given D-Bus connection
582 and object path. If no connection or path is specified,
583 the object ceases to be accessible via any connection or path.
585 It's not currently possible to export an object on more than one
586 connection or with more than one object-path, but this will be
587 supported in future.
589 :Parameters:
590 `connection` : dbus.connection.Connection or None
591 Only remove the object from this Connection. If None,
592 remove from all Connections on which it's exported.
593 `path` : dbus.ObjectPath or other str, or None
594 Only remove the object from this object path. If None,
595 remove from all object paths.
596 :Raises LookupError:
597 if the object was not exported on the requested connection
598 or path, or (if both are None) was not exported at all.
599 :Since: 0.81.1
601 self._locations_lock.acquire()
602 try:
603 if self._object_path is None or self._connection is None:
604 raise LookupError('%r is not exported' % self)
606 if connection is not None or path is not None:
607 dropped = []
608 for location in self._locations:
609 if ((connection is None or location[0] is connection) and
610 (path is None or location[1] == path)):
611 dropped.append(location)
612 else:
613 dropped = self._locations
614 self._locations = []
616 if not dropped:
617 raise LookupError('%r is not exported at a location matching '
618 '(%r,%r)' % (self, connection, path))
620 for location in dropped:
621 try:
622 location[0]._unregister_object_path(location[1])
623 except LookupError:
624 pass
625 if self._locations:
626 try:
627 self._locations.remove(location)
628 except ValueError:
629 pass
630 finally:
631 self._locations_lock.release()
633 def _unregister_cb(self, connection):
634 # there's not really enough information to do anything useful here
635 _logger.info('Unregistering exported object %r from some path '
636 'on %r', self, connection)
638 def _message_cb(self, connection, message):
639 try:
640 # lookup candidate method and parent method
641 method_name = message.get_member()
642 interface_name = message.get_interface()
643 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
645 # set up method call parameters
646 args = message.get_args_list(**parent_method._dbus_get_args_options)
647 keywords = {}
649 if parent_method._dbus_out_signature is not None:
650 signature = Signature(parent_method._dbus_out_signature)
651 else:
652 signature = None
654 # set up async callback functions
655 if parent_method._dbus_async_callbacks:
656 (return_callback, error_callback) = parent_method._dbus_async_callbacks
657 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
658 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
660 # include the sender etc. if desired
661 if parent_method._dbus_sender_keyword:
662 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
663 if parent_method._dbus_path_keyword:
664 keywords[parent_method._dbus_path_keyword] = message.get_path()
665 if parent_method._dbus_rel_path_keyword:
666 path = message.get_path()
667 rel_path = path
668 for exp in self._locations:
669 # pathological case: if we're exported in two places,
670 # one of which is a subtree of the other, then pick the
671 # subtree by preference (i.e. minimize the length of
672 # rel_path)
673 if exp[0] is connection:
674 if path == exp[1]:
675 rel_path = '/'
676 break
677 if exp[1] == '/':
678 # we already have rel_path == path at the beginning
679 continue
680 if path.startswith(exp[1] + '/'):
681 # yes we're in this exported subtree
682 suffix = path[len(exp[1]):]
683 if len(suffix) < len(rel_path):
684 rel_path = suffix
685 rel_path = ObjectPath(rel_path)
686 keywords[parent_method._dbus_rel_path_keyword] = rel_path
688 if parent_method._dbus_destination_keyword:
689 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
690 if parent_method._dbus_message_keyword:
691 keywords[parent_method._dbus_message_keyword] = message
692 if parent_method._dbus_connection_keyword:
693 keywords[parent_method._dbus_connection_keyword] = connection
695 # call method
696 retval = candidate_method(self, *args, **keywords)
698 # we're done - the method has got callback functions to reply with
699 if parent_method._dbus_async_callbacks:
700 return
702 # otherwise we send the return values in a reply. if we have a
703 # signature, use it to turn the return value into a tuple as
704 # appropriate
705 if signature is not None:
706 signature_tuple = tuple(signature)
707 # if we have zero or one return values we want make a tuple
708 # for the _method_reply_return function, otherwise we need
709 # to check we're passing it a sequence
710 if len(signature_tuple) == 0:
711 if retval == None:
712 retval = ()
713 else:
714 raise TypeError('%s has an empty output signature but did not return None' %
715 method_name)
716 elif len(signature_tuple) == 1:
717 retval = (retval,)
718 else:
719 if operator.isSequenceType(retval):
720 # multi-value signature, multi-value return... proceed unchanged
721 pass
722 else:
723 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
724 (method_name, signature))
726 # no signature, so just turn the return into a tuple and send it as normal
727 else:
728 if retval is None:
729 retval = ()
730 elif (isinstance(retval, tuple)
731 and not isinstance(retval, Struct)):
732 # If the return is a tuple that is not a Struct, we use it
733 # as-is on the assumption that there are multiple return
734 # values - this is the usual Python idiom. (fd.o #10174)
735 pass
736 else:
737 retval = (retval,)
739 _method_reply_return(connection, message, method_name, signature, *retval)
740 except Exception, exception:
741 # send error reply
742 _method_reply_error(connection, message, exception)
744 @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
745 path_keyword='object_path', connection_keyword='connection')
746 def Introspect(self, object_path, connection):
747 """Return a string of XML encoding this object's supported interfaces,
748 methods and signals.
750 reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
751 reflection_data += '<node name="%s">\n' % object_path
753 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
754 for (name, funcs) in interfaces.iteritems():
755 reflection_data += ' <interface name="%s">\n' % (name)
757 for func in funcs.values():
758 if getattr(func, '_dbus_is_method', False):
759 reflection_data += self.__class__._reflect_on_method(func)
760 elif getattr(func, '_dbus_is_signal', False):
761 reflection_data += self.__class__._reflect_on_signal(func)
763 reflection_data += ' </interface>\n'
765 for name in connection.list_exported_child_objects(object_path):
766 reflection_data += ' <node name="%s"/>\n' % name
768 reflection_data += '</node>\n'
770 return reflection_data
772 def __repr__(self):
773 where = ''
774 if (self._object_path is not _MANY
775 and self._object_path is not None):
776 where = ' at %s' % self._object_path
777 return '<%s.%s%s at %#x>' % (self.__class__.__module__,
778 self.__class__.__name__, where,
779 id(self))
780 __str__ = __repr__
782 class FallbackObject(Object):
783 """An object that implements an entire subtree of the object-path
784 tree.
786 :Since: 0.82.0
789 SUPPORTS_MULTIPLE_OBJECT_PATHS = True
791 def __init__(self, conn=None, object_path=None):
792 """Constructor.
794 Note that the superclass' ``bus_name`` __init__ argument is not
795 supported here.
797 :Parameters:
798 `conn` : dbus.connection.Connection or None
799 The connection on which to export this object. If this is not
800 None, an `object_path` must also be provided.
802 If None, the object is not initially available on any
803 Connection.
805 `object_path` : str or None
806 A D-Bus object path at which to make this Object available
807 immediately. If this is not None, a `conn` must also be
808 provided.
810 This object will implements all object-paths in the subtree
811 starting at this object-path, except where a more specific
812 object has been added.
814 super(FallbackObject, self).__init__()
815 self._fallback = True
817 if conn is None:
818 if object_path is not None:
819 raise TypeError('If object_path is given, conn is required')
820 elif object_path is None:
821 raise TypeError('If conn is given, object_path is required')
822 else:
823 self.add_to_connection(conn, object_path)