Use MIT/X11 license as per permission given on the dbus mailing list.
[dbus-python-phuang.git] / dbus / service.py
blobdf9412acfce86bbff1d39608543308a69597ed9e
1 # Copyright (C) 2003-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 # 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 __all__ = ('BusName', 'Object', 'method', 'signal')
27 __docformat__ = 'restructuredtext'
29 import sys
30 import logging
31 import operator
32 import traceback
33 try:
34 import thread
35 except ImportError:
36 import dummy_thread as thread
38 import _dbus_bindings
39 from dbus import SessionBus, Signature, Struct, validate_bus_name, \
40 validate_object_path, INTROSPECTABLE_IFACE, ObjectPath
41 from dbus.decorators import method, signal
42 from dbus.exceptions import DBusException, \
43 NameExistsException, \
44 UnknownMethodException
45 from dbus.lowlevel import ErrorMessage, MethodReturnMessage
46 from dbus.proxies import LOCAL_PATH
49 _logger = logging.getLogger('dbus.service')
52 class _VariantSignature(object):
53 """A fake method signature which, when iterated, yields an endless stream
54 of 'v' characters representing variants (handy with zip()).
56 It has no string representation.
57 """
58 def __iter__(self):
59 """Return self."""
60 return self
62 def next(self):
63 """Return 'v' whenever called."""
64 return 'v'
66 class BusName(object):
67 """A base class for exporting your own Named Services across the Bus.
69 When instantiated, objects of this class attempt to claim the given
70 well-known name on the given bus for the current process. The name is
71 released when the BusName object becomes unreferenced.
73 If a well-known name is requested multiple times, multiple references
74 to the same BusName object will be returned.
76 Caveats
77 -------
78 - Assumes that named services are only ever requested using this class -
79 if you request names from the bus directly, confusion may occur.
80 - Does not handle queueing.
81 """
82 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
83 """Constructor, which may either return an existing cached object
84 or a new object.
86 :Parameters:
87 `name` : str
88 The well-known name to be advertised
89 `bus` : dbus.Bus
90 A Bus on which this service will be advertised.
92 Omitting this parameter or setting it to None has been
93 deprecated since version 0.82.1. For backwards compatibility,
94 if this is done, the global shared connection to the session
95 bus will be used.
97 `allow_replacement` : bool
98 If True, other processes trying to claim the same well-known
99 name will take precedence over this one.
100 `replace_existing` : bool
101 If True, this process can take over the well-known name
102 from other processes already holding it.
103 `do_not_queue` : bool
104 If True, this service will not be placed in the queue of
105 services waiting for the requested name if another service
106 already holds it.
108 validate_bus_name(name, allow_well_known=True, allow_unique=False)
110 # if necessary, get default bus (deprecated)
111 if bus is None:
112 import warnings
113 warnings.warn('Omitting the "bus" parameter to '
114 'dbus.service.BusName.__init__ is deprecated',
115 DeprecationWarning, stacklevel=2)
116 bus = SessionBus()
118 # see if this name is already defined, return it if so
119 # FIXME: accessing internals of Bus
120 if name in bus._bus_names:
121 return bus._bus_names[name]
123 # otherwise register the name
124 name_flags = (
125 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
126 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
127 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
129 retval = bus.request_name(name, name_flags)
131 # TODO: more intelligent tracking of bus name states?
132 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
133 pass
134 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
135 # queueing can happen by default, maybe we should
136 # track this better or let the user know if they're
137 # queued or not?
138 pass
139 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
140 raise NameExistsException(name)
141 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
142 # if this is a shared bus which is being used by someone
143 # else in this process, this can happen legitimately
144 pass
145 else:
146 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
148 # and create the object
149 bus_name = object.__new__(cls)
150 bus_name._bus = bus
151 bus_name._name = name
153 # cache instance (weak ref only)
154 # FIXME: accessing Bus internals again
155 bus._bus_names[name] = bus_name
157 return bus_name
159 # do nothing because this is called whether or not the bus name
160 # object was retrieved from the cache or created new
161 def __init__(self, *args, **keywords):
162 pass
164 # we can delete the low-level name here because these objects
165 # are guaranteed to exist only once for each bus name
166 def __del__(self):
167 self._bus.release_name(self._name)
168 pass
170 def get_bus(self):
171 """Get the Bus this Service is on"""
172 return self._bus
174 def get_name(self):
175 """Get the name of this service"""
176 return self._name
178 def __repr__(self):
179 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
180 __str__ = __repr__
183 def _method_lookup(self, method_name, dbus_interface):
184 """Walks the Python MRO of the given class to find the method to invoke.
186 Returns two methods, the one to call, and the one it inherits from which
187 defines its D-Bus interface name, signature, and attributes.
189 parent_method = None
190 candidate_class = None
191 successful = False
193 # split up the cases when we do and don't have an interface because the
194 # latter is much simpler
195 if dbus_interface:
196 # search through the class hierarchy in python MRO order
197 for cls in self.__class__.__mro__:
198 # if we haven't got a candidate class yet, and we find a class with a
199 # suitably named member, save this as a candidate class
200 if (not candidate_class and method_name in cls.__dict__):
201 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
202 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
203 # however if it is annotated for a different interface
204 # than we are looking for, it cannot be a candidate
205 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
206 candidate_class = cls
207 parent_method = cls.__dict__[method_name]
208 successful = True
209 break
210 else:
211 pass
212 else:
213 candidate_class = cls
215 # if we have a candidate class, carry on checking this and all
216 # superclasses for a method annoated as a dbus method
217 # on the correct interface
218 if (candidate_class and method_name in cls.__dict__
219 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
220 and "_dbus_interface" in cls.__dict__[method_name].__dict__
221 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
222 # the candidate class has a dbus method on the correct interface,
223 # or overrides a method that is, success!
224 parent_method = cls.__dict__[method_name]
225 successful = True
226 break
228 else:
229 # simpler version of above
230 for cls in self.__class__.__mro__:
231 if (not candidate_class and method_name in cls.__dict__):
232 candidate_class = cls
234 if (candidate_class and method_name in cls.__dict__
235 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
236 parent_method = cls.__dict__[method_name]
237 successful = True
238 break
240 if successful:
241 return (candidate_class.__dict__[method_name], parent_method)
242 else:
243 if dbus_interface:
244 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
245 else:
246 raise UnknownMethodException('%s is not a valid method' % method_name)
249 def _method_reply_return(connection, message, method_name, signature, *retval):
250 reply = MethodReturnMessage(message)
251 try:
252 reply.append(signature=signature, *retval)
253 except Exception, e:
254 logging.basicConfig()
255 if signature is None:
256 try:
257 signature = reply.guess_signature(retval) + ' (guessed)'
258 except Exception, e:
259 _logger.error('Unable to guess signature for arguments %r: '
260 '%s: %s', retval, e.__class__, e)
261 raise
262 _logger.error('Unable to append %r to message with signature %s: '
263 '%s: %s', retval, signature, e.__class__, e)
264 raise
266 connection.send_message(reply)
269 def _method_reply_error(connection, message, exception):
270 name = getattr(exception, '_dbus_error_name', None)
272 if name is not None:
273 pass
274 elif getattr(exception, '__module__', '') in ('', '__main__'):
275 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
276 else:
277 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
279 et, ev, etb = sys.exc_info()
280 if ev is exception:
281 # The exception was actually thrown, so we can get a traceback
282 contents = ''.join(traceback.format_exception(et, ev, etb))
283 else:
284 # We don't have any traceback for it, e.g.
285 # async_err_cb(MyException('Failed to badger the mushroom'))
286 # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
287 contents = ''.join(traceback.format_exception_only(exception.__class__,
288 exception))
289 reply = ErrorMessage(message, name, contents)
291 connection.send_message(reply)
294 class InterfaceType(type):
295 def __init__(cls, name, bases, dct):
296 # these attributes are shared between all instances of the Interface
297 # object, so this has to be a dictionary that maps class names to
298 # the per-class introspection/interface data
299 class_table = getattr(cls, '_dbus_class_table', {})
300 cls._dbus_class_table = class_table
301 interface_table = class_table[cls.__module__ + '.' + name] = {}
303 # merge all the name -> method tables for all the interfaces
304 # implemented by our base classes into our own
305 for b in bases:
306 base_name = b.__module__ + '.' + b.__name__
307 if getattr(b, '_dbus_class_table', False):
308 for (interface, method_table) in class_table[base_name].iteritems():
309 our_method_table = interface_table.setdefault(interface, {})
310 our_method_table.update(method_table)
312 # add in all the name -> method entries for our own methods/signals
313 for func in dct.values():
314 if getattr(func, '_dbus_interface', False):
315 method_table = interface_table.setdefault(func._dbus_interface, {})
316 method_table[func.__name__] = func
318 super(InterfaceType, cls).__init__(name, bases, dct)
320 # methods are different to signals, so we have two functions... :)
321 def _reflect_on_method(cls, func):
322 args = func._dbus_args
324 if func._dbus_in_signature:
325 # convert signature into a tuple so length refers to number of
326 # types, not number of characters. the length is checked by
327 # the decorator to make sure it matches the length of args.
328 in_sig = tuple(Signature(func._dbus_in_signature))
329 else:
330 # magic iterator which returns as many v's as we need
331 in_sig = _VariantSignature()
333 if func._dbus_out_signature:
334 out_sig = Signature(func._dbus_out_signature)
335 else:
336 # its tempting to default to Signature('v'), but
337 # for methods that return nothing, providing incorrect
338 # introspection data is worse than providing none at all
339 out_sig = []
341 reflection_data = ' <method name="%s">\n' % (func.__name__)
342 for pair in zip(in_sig, args):
343 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
344 for type in out_sig:
345 reflection_data += ' <arg direction="out" type="%s" />\n' % type
346 reflection_data += ' </method>\n'
348 return reflection_data
350 def _reflect_on_signal(cls, func):
351 args = func._dbus_args
353 if func._dbus_signature:
354 # convert signature into a tuple so length refers to number of
355 # types, not number of characters
356 sig = tuple(Signature(func._dbus_signature))
357 else:
358 # magic iterator which returns as many v's as we need
359 sig = _VariantSignature()
361 reflection_data = ' <signal name="%s">\n' % (func.__name__)
362 for pair in zip(sig, args):
363 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
364 reflection_data = reflection_data + ' </signal>\n'
366 return reflection_data
368 class Interface(object):
369 __metaclass__ = InterfaceType
371 #: A unique object used as the value of Object._object_path and
372 #: Object._connection if it's actually in more than one place
373 _MANY = object()
375 class Object(Interface):
376 r"""A base class for exporting your own Objects across the Bus.
378 Just inherit from Object and mark exported methods with the
379 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
381 Example::
383 class Example(dbus.service.object):
384 def __init__(self, object_path):
385 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
386 self._last_input = None
388 @dbus.service.method(interface='com.example.Sample',
389 in_signature='v', out_signature='s')
390 def StringifyVariant(self, var):
391 self.LastInputChanged(var) # emits the signal
392 return str(var)
394 @dbus.service.signal(interface='com.example.Sample',
395 signature='v')
396 def LastInputChanged(self, var):
397 # run just before the signal is actually emitted
398 # just put "pass" if nothing should happen
399 self._last_input = var
401 @dbus.service.method(interface='com.example.Sample',
402 in_signature='', out_signature='v')
403 def GetLastInput(self):
404 return self._last_input
407 #: If True, this object can be made available at more than one object path.
408 #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
409 #: handle more than one object path, but they must all be on the same
410 #: connection.
411 SUPPORTS_MULTIPLE_OBJECT_PATHS = False
413 #: If True, this object can be made available on more than one connection.
414 #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
415 #: have the same object path on all its connections.
416 SUPPORTS_MULTIPLE_CONNECTIONS = False
418 def __init__(self, conn=None, object_path=None, bus_name=None):
419 """Constructor. Either conn or bus_name is required; object_path
420 is also required.
422 :Parameters:
423 `conn` : dbus.connection.Connection or None
424 The connection on which to export this object.
426 If None, use the Bus associated with the given ``bus_name``.
427 If there is no ``bus_name`` either, the object is not
428 initially available on any Connection.
430 For backwards compatibility, if an instance of
431 dbus.service.BusName is passed as the first parameter,
432 this is equivalent to passing its associated Bus as
433 ``conn``, and passing the BusName itself as ``bus_name``.
435 `object_path` : str or None
436 A D-Bus object path at which to make this Object available
437 immediately. If this is not None, a `conn` or `bus_name` must
438 also be provided.
440 `bus_name` : dbus.service.BusName or None
441 Represents a well-known name claimed by this process. A
442 reference to the BusName object will be held by this
443 Object, preventing the name from being released during this
444 Object's lifetime (unless it's released manually).
446 if object_path is not None:
447 validate_object_path(object_path)
449 if isinstance(conn, BusName):
450 # someone's using the old API; don't gratuitously break them
451 bus_name = conn
452 conn = bus_name.get_bus()
453 elif conn is None:
454 if bus_name is not None:
455 # someone's using the old API but naming arguments, probably
456 conn = bus_name.get_bus()
458 #: Either an object path, None or _MANY
459 self._object_path = None
460 #: Either a dbus.connection.Connection, None or _MANY
461 self._connection = None
462 #: A list of tuples (Connection, object path, False) where the False
463 #: is for future expansion (to support fallback paths)
464 self._locations = []
465 #: Lock protecting `_locations`, `_connection` and `_object_path`
466 self._locations_lock = thread.allocate_lock()
468 #: True if this is a fallback object handling a whole subtree.
469 self._fallback = False
471 self._name = bus_name
473 if conn is None and object_path is not None:
474 raise TypeError('If object_path is given, either conn or bus_name '
475 'is required')
476 if conn is not None and object_path is not None:
477 self.add_to_connection(conn, object_path)
479 @property
480 def __dbus_object_path__(self):
481 """The object-path at which this object is available.
482 Access raises AttributeError if there is no object path, or more than
483 one object path.
485 Changed in 0.82.0: AttributeError can be raised.
487 if self._object_path is _MANY:
488 raise AttributeError('Object %r has more than one object path: '
489 'use Object.locations instead' % self)
490 elif self._object_path is None:
491 raise AttributeError('Object %r has no object path yet' % self)
492 else:
493 return self._object_path
495 @property
496 def connection(self):
497 """The Connection on which this object is available.
498 Access raises AttributeError if there is no Connection, or more than
499 one Connection.
501 Changed in 0.82.0: AttributeError can be raised.
503 if self._connection is _MANY:
504 raise AttributeError('Object %r is on more than one Connection: '
505 'use Object.locations instead' % self)
506 elif self._connection is None:
507 raise AttributeError('Object %r has no Connection yet' % self)
508 else:
509 return self._connection
511 @property
512 def locations(self):
513 """An iterable over tuples representing locations at which this
514 object is available.
516 Each tuple has at least two items, but may have more in future
517 versions of dbus-python, so do not rely on their exact length.
518 The first two items are the dbus.connection.Connection and the object
519 path.
521 :Since: 0.82.0
523 return iter(self._locations)
525 def add_to_connection(self, connection, path):
526 """Make this object accessible via the given D-Bus connection and
527 object path.
529 :Parameters:
530 `connection` : dbus.connection.Connection
531 Export the object on this connection. If the class attribute
532 SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
533 can only be made available on one connection; if the class
534 attribute is set True by a subclass, the object can be made
535 available on more than one connection.
537 `path` : dbus.ObjectPath or other str
538 Place the object at this object path. If the class attribute
539 SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
540 can only be made available at one object path; if the class
541 attribute is set True by a subclass, the object can be made
542 available with more than one object path.
544 :Raises ValueError: if the object's class attributes do not allow the
545 object to be exported in the desired way.
546 :Since: 0.82.0
548 if path == LOCAL_PATH:
549 raise ValueError('Objects may not be exported on the reserved '
550 'path %s' % LOCAL_PATH)
552 self._locations_lock.acquire()
553 try:
554 if (self._connection is not None and
555 self._connection is not connection and
556 not self.SUPPORTS_MULTIPLE_CONNECTIONS):
557 raise ValueError('%r is already exported on '
558 'connection %r' % (self, self._connection))
560 if (self._object_path is not None and
561 not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
562 self._object_path != path):
563 raise ValueError('%r is already exported at object '
564 'path %s' % (self, self._object_path))
566 connection._register_object_path(path, self._message_cb,
567 self._unregister_cb,
568 self._fallback)
570 if self._connection is None:
571 self._connection = connection
572 elif self._connection is not connection:
573 self._connection = _MANY
575 if self._object_path is None:
576 self._object_path = path
577 elif self._object_path != path:
578 self._object_path = _MANY
580 self._locations.append((connection, path, self._fallback))
581 finally:
582 self._locations_lock.release()
584 def remove_from_connection(self, connection=None, path=None):
585 """Make this object inaccessible via the given D-Bus connection
586 and object path. If no connection or path is specified,
587 the object ceases to be accessible via any connection or path.
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)