Add wrapper for DBusServer.
[dbus-python-phuang.git] / dbus / service.py
blob55bb04e8cec4cd758f12a20b3a4b527585a39018
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, MethodCallMessage
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 if not isinstance(message, MethodCallMessage):
640 return
642 try:
643 # lookup candidate method and parent method
644 method_name = message.get_member()
645 interface_name = message.get_interface()
646 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
648 # set up method call parameters
649 args = message.get_args_list(**parent_method._dbus_get_args_options)
650 keywords = {}
652 if parent_method._dbus_out_signature is not None:
653 signature = Signature(parent_method._dbus_out_signature)
654 else:
655 signature = None
657 # set up async callback functions
658 if parent_method._dbus_async_callbacks:
659 (return_callback, error_callback) = parent_method._dbus_async_callbacks
660 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
661 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
663 # include the sender etc. if desired
664 if parent_method._dbus_sender_keyword:
665 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
666 if parent_method._dbus_path_keyword:
667 keywords[parent_method._dbus_path_keyword] = message.get_path()
668 if parent_method._dbus_rel_path_keyword:
669 path = message.get_path()
670 rel_path = path
671 for exp in self._locations:
672 # pathological case: if we're exported in two places,
673 # one of which is a subtree of the other, then pick the
674 # subtree by preference (i.e. minimize the length of
675 # rel_path)
676 if exp[0] is connection:
677 if path == exp[1]:
678 rel_path = '/'
679 break
680 if exp[1] == '/':
681 # we already have rel_path == path at the beginning
682 continue
683 if path.startswith(exp[1] + '/'):
684 # yes we're in this exported subtree
685 suffix = path[len(exp[1]):]
686 if len(suffix) < len(rel_path):
687 rel_path = suffix
688 rel_path = ObjectPath(rel_path)
689 keywords[parent_method._dbus_rel_path_keyword] = rel_path
691 if parent_method._dbus_destination_keyword:
692 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
693 if parent_method._dbus_message_keyword:
694 keywords[parent_method._dbus_message_keyword] = message
695 if parent_method._dbus_connection_keyword:
696 keywords[parent_method._dbus_connection_keyword] = connection
698 # call method
699 retval = candidate_method(self, *args, **keywords)
701 # we're done - the method has got callback functions to reply with
702 if parent_method._dbus_async_callbacks:
703 return
705 # otherwise we send the return values in a reply. if we have a
706 # signature, use it to turn the return value into a tuple as
707 # appropriate
708 if signature is not None:
709 signature_tuple = tuple(signature)
710 # if we have zero or one return values we want make a tuple
711 # for the _method_reply_return function, otherwise we need
712 # to check we're passing it a sequence
713 if len(signature_tuple) == 0:
714 if retval == None:
715 retval = ()
716 else:
717 raise TypeError('%s has an empty output signature but did not return None' %
718 method_name)
719 elif len(signature_tuple) == 1:
720 retval = (retval,)
721 else:
722 if operator.isSequenceType(retval):
723 # multi-value signature, multi-value return... proceed unchanged
724 pass
725 else:
726 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
727 (method_name, signature))
729 # no signature, so just turn the return into a tuple and send it as normal
730 else:
731 if retval is None:
732 retval = ()
733 elif (isinstance(retval, tuple)
734 and not isinstance(retval, Struct)):
735 # If the return is a tuple that is not a Struct, we use it
736 # as-is on the assumption that there are multiple return
737 # values - this is the usual Python idiom. (fd.o #10174)
738 pass
739 else:
740 retval = (retval,)
742 _method_reply_return(connection, message, method_name, signature, *retval)
743 except Exception, exception:
744 # send error reply
745 _method_reply_error(connection, message, exception)
747 @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
748 path_keyword='object_path', connection_keyword='connection')
749 def Introspect(self, object_path, connection):
750 """Return a string of XML encoding this object's supported interfaces,
751 methods and signals.
753 reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
754 reflection_data += '<node name="%s">\n' % object_path
756 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
757 for (name, funcs) in interfaces.iteritems():
758 reflection_data += ' <interface name="%s">\n' % (name)
760 for func in funcs.values():
761 if getattr(func, '_dbus_is_method', False):
762 reflection_data += self.__class__._reflect_on_method(func)
763 elif getattr(func, '_dbus_is_signal', False):
764 reflection_data += self.__class__._reflect_on_signal(func)
766 reflection_data += ' </interface>\n'
768 for name in connection.list_exported_child_objects(object_path):
769 reflection_data += ' <node name="%s"/>\n' % name
771 reflection_data += '</node>\n'
773 return reflection_data
775 def __repr__(self):
776 where = ''
777 if (self._object_path is not _MANY
778 and self._object_path is not None):
779 where = ' at %s' % self._object_path
780 return '<%s.%s%s at %#x>' % (self.__class__.__module__,
781 self.__class__.__name__, where,
782 id(self))
783 __str__ = __repr__
785 class FallbackObject(Object):
786 """An object that implements an entire subtree of the object-path
787 tree.
789 :Since: 0.82.0
792 SUPPORTS_MULTIPLE_OBJECT_PATHS = True
794 def __init__(self, conn=None, object_path=None):
795 """Constructor.
797 Note that the superclass' ``bus_name`` __init__ argument is not
798 supported here.
800 :Parameters:
801 `conn` : dbus.connection.Connection or None
802 The connection on which to export this object. If this is not
803 None, an `object_path` must also be provided.
805 If None, the object is not initially available on any
806 Connection.
808 `object_path` : str or None
809 A D-Bus object path at which to make this Object available
810 immediately. If this is not None, a `conn` must also be
811 provided.
813 This object will implements all object-paths in the subtree
814 starting at this object-path, except where a more specific
815 object has been added.
817 super(FallbackObject, self).__init__()
818 self._fallback = True
820 if conn is None:
821 if object_path is not None:
822 raise TypeError('If object_path is given, conn is required')
823 elif object_path is None:
824 raise TypeError('If conn is given, object_path is required')
825 else:
826 self.add_to_connection(conn, object_path)