Mention relicensing in NEWS
[dbus-python-phuang.git] / dbus / service.py
blob9f56fe5752d21580cdc297e28dcfe774f402193a
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 :Parameters:
586 `connection` : dbus.connection.Connection or None
587 Only remove the object from this Connection. If None,
588 remove from all Connections on which it's exported.
589 `path` : dbus.ObjectPath or other str, or None
590 Only remove the object from this object path. If None,
591 remove from all object paths.
592 :Raises LookupError:
593 if the object was not exported on the requested connection
594 or path, or (if both are None) was not exported at all.
595 :Since: 0.81.1
597 self._locations_lock.acquire()
598 try:
599 if self._object_path is None or self._connection is None:
600 raise LookupError('%r is not exported' % self)
602 if connection is not None or path is not None:
603 dropped = []
604 for location in self._locations:
605 if ((connection is None or location[0] is connection) and
606 (path is None or location[1] == path)):
607 dropped.append(location)
608 else:
609 dropped = self._locations
610 self._locations = []
612 if not dropped:
613 raise LookupError('%r is not exported at a location matching '
614 '(%r,%r)' % (self, connection, path))
616 for location in dropped:
617 try:
618 location[0]._unregister_object_path(location[1])
619 except LookupError:
620 pass
621 if self._locations:
622 try:
623 self._locations.remove(location)
624 except ValueError:
625 pass
626 finally:
627 self._locations_lock.release()
629 def _unregister_cb(self, connection):
630 # there's not really enough information to do anything useful here
631 _logger.info('Unregistering exported object %r from some path '
632 'on %r', self, connection)
634 def _message_cb(self, connection, message):
635 try:
636 # lookup candidate method and parent method
637 method_name = message.get_member()
638 interface_name = message.get_interface()
639 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
641 # set up method call parameters
642 args = message.get_args_list(**parent_method._dbus_get_args_options)
643 keywords = {}
645 if parent_method._dbus_out_signature is not None:
646 signature = Signature(parent_method._dbus_out_signature)
647 else:
648 signature = None
650 # set up async callback functions
651 if parent_method._dbus_async_callbacks:
652 (return_callback, error_callback) = parent_method._dbus_async_callbacks
653 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
654 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
656 # include the sender etc. if desired
657 if parent_method._dbus_sender_keyword:
658 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
659 if parent_method._dbus_path_keyword:
660 keywords[parent_method._dbus_path_keyword] = message.get_path()
661 if parent_method._dbus_rel_path_keyword:
662 path = message.get_path()
663 rel_path = path
664 for exp in self._locations:
665 # pathological case: if we're exported in two places,
666 # one of which is a subtree of the other, then pick the
667 # subtree by preference (i.e. minimize the length of
668 # rel_path)
669 if exp[0] is connection:
670 if path == exp[1]:
671 rel_path = '/'
672 break
673 if exp[1] == '/':
674 # we already have rel_path == path at the beginning
675 continue
676 if path.startswith(exp[1] + '/'):
677 # yes we're in this exported subtree
678 suffix = path[len(exp[1]):]
679 if len(suffix) < len(rel_path):
680 rel_path = suffix
681 rel_path = ObjectPath(rel_path)
682 keywords[parent_method._dbus_rel_path_keyword] = rel_path
684 if parent_method._dbus_destination_keyword:
685 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
686 if parent_method._dbus_message_keyword:
687 keywords[parent_method._dbus_message_keyword] = message
688 if parent_method._dbus_connection_keyword:
689 keywords[parent_method._dbus_connection_keyword] = connection
691 # call method
692 retval = candidate_method(self, *args, **keywords)
694 # we're done - the method has got callback functions to reply with
695 if parent_method._dbus_async_callbacks:
696 return
698 # otherwise we send the return values in a reply. if we have a
699 # signature, use it to turn the return value into a tuple as
700 # appropriate
701 if signature is not None:
702 signature_tuple = tuple(signature)
703 # if we have zero or one return values we want make a tuple
704 # for the _method_reply_return function, otherwise we need
705 # to check we're passing it a sequence
706 if len(signature_tuple) == 0:
707 if retval == None:
708 retval = ()
709 else:
710 raise TypeError('%s has an empty output signature but did not return None' %
711 method_name)
712 elif len(signature_tuple) == 1:
713 retval = (retval,)
714 else:
715 if operator.isSequenceType(retval):
716 # multi-value signature, multi-value return... proceed unchanged
717 pass
718 else:
719 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
720 (method_name, signature))
722 # no signature, so just turn the return into a tuple and send it as normal
723 else:
724 if retval is None:
725 retval = ()
726 elif (isinstance(retval, tuple)
727 and not isinstance(retval, Struct)):
728 # If the return is a tuple that is not a Struct, we use it
729 # as-is on the assumption that there are multiple return
730 # values - this is the usual Python idiom. (fd.o #10174)
731 pass
732 else:
733 retval = (retval,)
735 _method_reply_return(connection, message, method_name, signature, *retval)
736 except Exception, exception:
737 # send error reply
738 _method_reply_error(connection, message, exception)
740 @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
741 path_keyword='object_path', connection_keyword='connection')
742 def Introspect(self, object_path, connection):
743 """Return a string of XML encoding this object's supported interfaces,
744 methods and signals.
746 reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
747 reflection_data += '<node name="%s">\n' % object_path
749 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
750 for (name, funcs) in interfaces.iteritems():
751 reflection_data += ' <interface name="%s">\n' % (name)
753 for func in funcs.values():
754 if getattr(func, '_dbus_is_method', False):
755 reflection_data += self.__class__._reflect_on_method(func)
756 elif getattr(func, '_dbus_is_signal', False):
757 reflection_data += self.__class__._reflect_on_signal(func)
759 reflection_data += ' </interface>\n'
761 for name in connection.list_exported_child_objects(object_path):
762 reflection_data += ' <node name="%s"/>\n' % name
764 reflection_data += '</node>\n'
766 return reflection_data
768 def __repr__(self):
769 where = ''
770 if (self._object_path is not _MANY
771 and self._object_path is not None):
772 where = ' at %s' % self._object_path
773 return '<%s.%s%s at %#x>' % (self.__class__.__module__,
774 self.__class__.__name__, where,
775 id(self))
776 __str__ = __repr__
778 class FallbackObject(Object):
779 """An object that implements an entire subtree of the object-path
780 tree.
782 :Since: 0.82.0
785 SUPPORTS_MULTIPLE_OBJECT_PATHS = True
787 def __init__(self, conn=None, object_path=None):
788 """Constructor.
790 Note that the superclass' ``bus_name`` __init__ argument is not
791 supported here.
793 :Parameters:
794 `conn` : dbus.connection.Connection or None
795 The connection on which to export this object. If this is not
796 None, an `object_path` must also be provided.
798 If None, the object is not initially available on any
799 Connection.
801 `object_path` : str or None
802 A D-Bus object path at which to make this Object available
803 immediately. If this is not None, a `conn` must also be
804 provided.
806 This object will implements all object-paths in the subtree
807 starting at this object-path, except where a more specific
808 object has been added.
810 super(FallbackObject, self).__init__()
811 self._fallback = True
813 if conn is None:
814 if object_path is not None:
815 raise TypeError('If object_path is given, conn is required')
816 elif object_path is None:
817 raise TypeError('If conn is given, object_path is required')
818 else:
819 self.add_to_connection(conn, object_path)