Improve API documentation markup, particularly adding :Since: annotations
[dbus-python-phuang.git] / dbus / service.py
blobd7b3a74a3cf7bfeb33fd0ded3899e6e496635d00
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
36 from dbus.exceptions import DBusException, \
37 NameExistsException, \
38 UnknownMethodException
39 from dbus.decorators import method
40 from dbus.decorators import signal
41 from dbus.proxies import LOCAL_PATH
44 _logger = logging.getLogger('dbus.service')
47 class _VariantSignature(object):
48 """A fake method signature which, when iterated, yields an endless stream
49 of 'v' characters representing variants (handy with zip()).
51 It has no string representation.
52 """
53 def __iter__(self):
54 """Return self."""
55 return self
57 def next(self):
58 """Return 'v' whenever called."""
59 return 'v'
61 class BusName(object):
62 """A base class for exporting your own Named Services across the Bus.
64 When instantiated, objects of this class attempt to claim the given
65 well-known name on the given bus for the current process. The name is
66 released when the BusName object becomes unreferenced.
68 If a well-known name is requested multiple times, multiple references
69 to the same BusName object will be returned.
71 Caveats
72 -------
73 - Assumes that named services are only ever requested using this class -
74 if you request names from the bus directly, confusion may occur.
75 - Does not handle queueing.
76 """
77 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
78 """Constructor, which may either return an existing cached object
79 or a new object.
81 :Parameters:
82 `name` : str
83 The well-known name to be advertised
84 `bus` : dbus.Bus
85 A Bus on which this service will be advertised; if None
86 (default) the session bus will be used
87 `allow_replacement` : bool
88 If True, other processes trying to claim the same well-known
89 name will take precedence over this one.
90 `replace_existing` : bool
91 If True, this process can take over the well-known name
92 from other processes already holding it.
93 `do_not_queue` : bool
94 If True, this service will not be placed in the queue of
95 services waiting for the requested name if another service
96 already holds it.
97 """
98 _dbus_bindings.validate_bus_name(name, allow_well_known=True,
99 allow_unique=False)
101 # get default bus
102 if bus == None:
103 bus = SessionBus()
105 # see if this name is already defined, return it if so
106 # FIXME: accessing internals of Bus
107 if name in bus._bus_names:
108 return bus._bus_names[name]
110 # otherwise register the name
111 name_flags = (
112 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
113 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
114 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
116 retval = bus.request_name(name, name_flags)
118 # TODO: more intelligent tracking of bus name states?
119 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
120 pass
121 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
122 # queueing can happen by default, maybe we should
123 # track this better or let the user know if they're
124 # queued or not?
125 pass
126 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
127 raise NameExistsException(name)
128 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
129 # if this is a shared bus which is being used by someone
130 # else in this process, this can happen legitimately
131 pass
132 else:
133 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
135 # and create the object
136 bus_name = object.__new__(cls)
137 bus_name._bus = bus
138 bus_name._name = name
140 # cache instance (weak ref only)
141 # FIXME: accessing Bus internals again
142 bus._bus_names[name] = bus_name
144 return bus_name
146 # do nothing because this is called whether or not the bus name
147 # object was retrieved from the cache or created new
148 def __init__(self, *args, **keywords):
149 pass
151 # we can delete the low-level name here because these objects
152 # are guaranteed to exist only once for each bus name
153 def __del__(self):
154 self._bus.release_name(self._name)
155 pass
157 def get_bus(self):
158 """Get the Bus this Service is on"""
159 return self._bus
161 def get_name(self):
162 """Get the name of this service"""
163 return self._name
165 def __repr__(self):
166 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
167 __str__ = __repr__
170 def _method_lookup(self, method_name, dbus_interface):
171 """Walks the Python MRO of the given class to find the method to invoke.
173 Returns two methods, the one to call, and the one it inherits from which
174 defines its D-Bus interface name, signature, and attributes.
176 parent_method = None
177 candidate_class = None
178 successful = False
180 # split up the cases when we do and don't have an interface because the
181 # latter is much simpler
182 if dbus_interface:
183 # search through the class hierarchy in python MRO order
184 for cls in self.__class__.__mro__:
185 # if we haven't got a candidate class yet, and we find a class with a
186 # suitably named member, save this as a candidate class
187 if (not candidate_class and method_name in cls.__dict__):
188 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
189 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
190 # however if it is annotated for a different interface
191 # than we are looking for, it cannot be a candidate
192 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
193 candidate_class = cls
194 parent_method = cls.__dict__[method_name]
195 successful = True
196 break
197 else:
198 pass
199 else:
200 candidate_class = cls
202 # if we have a candidate class, carry on checking this and all
203 # superclasses for a method annoated as a dbus method
204 # on the correct interface
205 if (candidate_class and method_name in cls.__dict__
206 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
207 and "_dbus_interface" in cls.__dict__[method_name].__dict__
208 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
209 # the candidate class has a dbus method on the correct interface,
210 # or overrides a method that is, success!
211 parent_method = cls.__dict__[method_name]
212 successful = True
213 break
215 else:
216 # simpler version of above
217 for cls in self.__class__.__mro__:
218 if (not candidate_class and method_name in cls.__dict__):
219 candidate_class = cls
221 if (candidate_class and method_name in cls.__dict__
222 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
223 parent_method = cls.__dict__[method_name]
224 successful = True
225 break
227 if successful:
228 return (candidate_class.__dict__[method_name], parent_method)
229 else:
230 if dbus_interface:
231 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
232 else:
233 raise UnknownMethodException('%s is not a valid method' % method_name)
236 def _method_reply_return(connection, message, method_name, signature, *retval):
237 reply = _dbus_bindings.MethodReturnMessage(message)
238 try:
239 reply.append(signature=signature, *retval)
240 except Exception, e:
241 logging.basicConfig()
242 if signature is None:
243 try:
244 signature = reply.guess_signature(retval) + ' (guessed)'
245 except Exception, e:
246 _logger.error('Unable to guess signature for arguments %r: '
247 '%s: %s', retval, e.__class__, e)
248 raise
249 _logger.error('Unable to append %r to message with signature %s: '
250 '%s: %s', retval, signature, e.__class__, e)
251 raise
253 connection.send_message(reply)
256 def _method_reply_error(connection, message, exception):
257 name = getattr(exception, '_dbus_error_name', None)
259 if name is not None:
260 pass
261 elif getattr(exception, '__module__', '') in ('', '__main__'):
262 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
263 else:
264 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
266 contents = traceback.format_exc()
267 reply = _dbus_bindings.ErrorMessage(message, name, contents)
269 connection.send_message(reply)
272 class InterfaceType(type):
273 def __init__(cls, name, bases, dct):
274 # these attributes are shared between all instances of the Interface
275 # object, so this has to be a dictionary that maps class names to
276 # the per-class introspection/interface data
277 class_table = getattr(cls, '_dbus_class_table', {})
278 cls._dbus_class_table = class_table
279 interface_table = class_table[cls.__module__ + '.' + name] = {}
281 # merge all the name -> method tables for all the interfaces
282 # implemented by our base classes into our own
283 for b in bases:
284 base_name = b.__module__ + '.' + b.__name__
285 if getattr(b, '_dbus_class_table', False):
286 for (interface, method_table) in class_table[base_name].iteritems():
287 our_method_table = interface_table.setdefault(interface, {})
288 our_method_table.update(method_table)
290 # add in all the name -> method entries for our own methods/signals
291 for func in dct.values():
292 if getattr(func, '_dbus_interface', False):
293 method_table = interface_table.setdefault(func._dbus_interface, {})
294 method_table[func.__name__] = func
296 super(InterfaceType, cls).__init__(name, bases, dct)
298 # methods are different to signals, so we have two functions... :)
299 def _reflect_on_method(cls, func):
300 args = func._dbus_args
302 if func._dbus_in_signature:
303 # convert signature into a tuple so length refers to number of
304 # types, not number of characters. the length is checked by
305 # the decorator to make sure it matches the length of args.
306 in_sig = tuple(_dbus_bindings.Signature(func._dbus_in_signature))
307 else:
308 # magic iterator which returns as many v's as we need
309 in_sig = _VariantSignature()
311 if func._dbus_out_signature:
312 out_sig = _dbus_bindings.Signature(func._dbus_out_signature)
313 else:
314 # its tempting to default to _dbus_bindings.Signature('v'), but
315 # for methods that return nothing, providing incorrect
316 # introspection data is worse than providing none at all
317 out_sig = []
319 reflection_data = ' <method name="%s">\n' % (func.__name__)
320 for pair in zip(in_sig, args):
321 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
322 for type in out_sig:
323 reflection_data += ' <arg direction="out" type="%s" />\n' % type
324 reflection_data += ' </method>\n'
326 return reflection_data
328 def _reflect_on_signal(cls, func):
329 args = func._dbus_args
331 if func._dbus_signature:
332 # convert signature into a tuple so length refers to number of
333 # types, not number of characters
334 sig = tuple(_dbus_bindings.Signature(func._dbus_signature))
335 else:
336 # magic iterator which returns as many v's as we need
337 sig = _VariantSignature()
339 reflection_data = ' <signal name="%s">\n' % (func.__name__)
340 for pair in zip(sig, args):
341 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
342 reflection_data = reflection_data + ' </signal>\n'
344 return reflection_data
346 class Interface(object):
347 __metaclass__ = InterfaceType
349 #: A unique object used as the value of Object._object_path and
350 #: Object._connection if it's actually in more than one place
351 _MANY = object()
353 class Object(Interface):
354 r"""A base class for exporting your own Objects across the Bus.
356 Just inherit from Object and mark exported methods with the
357 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
359 Example::
361 class Example(dbus.service.object):
362 def __init__(self, object_path):
363 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
364 self._last_input = None
366 @dbus.service.method(interface='com.example.Sample',
367 in_signature='v', out_signature='s')
368 def StringifyVariant(self, var):
369 self.LastInputChanged(var) # emits the signal
370 return str(var)
372 @dbus.service.signal(interface='com.example.Sample',
373 signature='v')
374 def LastInputChanged(self, var):
375 # run just before the signal is actually emitted
376 # just put "pass" if nothing should happen
377 self._last_input = var
379 @dbus.service.method(interface='com.example.Sample',
380 in_signature='', out_signature='v')
381 def GetLastInput(self):
382 return self._last_input
385 #: If True, this object can be made available at more than one object path.
386 #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
387 #: handle more than one object path, but they must all be on the same
388 #: connection.
389 SUPPORTS_MULTIPLE_OBJECT_PATHS = False
391 #: If True, this object can be made available on more than one connection.
392 #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
393 #: have the same object path on all its connections.
394 SUPPORTS_MULTIPLE_CONNECTIONS = False
396 def __init__(self, conn=None, object_path=None, bus_name=None):
397 """Constructor. Either conn or bus_name is required; object_path
398 is also required.
400 :Parameters:
401 `conn` : dbus.connection.Connection or None
402 The connection on which to export this object.
404 If None, use the Bus associated with the given ``bus_name``.
405 If there is no ``bus_name`` either, the object is not
406 initially available on any Connection.
408 For backwards compatibility, if an instance of
409 dbus.service.BusName is passed as the first parameter,
410 this is equivalent to passing its associated Bus as
411 ``conn``, and passing the BusName itself as ``bus_name``.
413 `object_path` : str or None
414 A D-Bus object path at which to make this Object available
415 immediately. If this is not None, a `conn` or `bus_name` must
416 also be provided.
418 `bus_name` : dbus.service.BusName or None
419 Represents a well-known name claimed by this process. A
420 reference to the BusName object will be held by this
421 Object, preventing the name from being released during this
422 Object's lifetime (unless it's released manually).
424 if object_path is not None:
425 _dbus_bindings.validate_object_path(object_path)
427 if isinstance(conn, BusName):
428 # someone's using the old API; don't gratuitously break them
429 bus_name = conn
430 conn = bus_name.get_bus()
431 elif conn is None:
432 if bus_name is not None:
433 # someone's using the old API but naming arguments, probably
434 conn = bus_name.get_bus()
436 #: Either an object path, None or _MANY
437 self._object_path = None
438 #: Either a dbus.connection.Connection, None or _MANY
439 self._connection = None
440 #: A list of tuples (Connection, object path, False) where the False
441 #: is for future expansion (to support fallback paths)
442 self._locations = []
443 #: Lock protecting `_locations`, `_connection` and `_object_path`
444 self._locations_lock = thread.allocate_lock()
446 #: True if this is a fallback object handling a whole subtree.
447 self._fallback = False
449 self._name = bus_name
451 if conn is None and object_path is not None:
452 raise TypeError('If object_path is given, either conn or bus_name '
453 'is required')
454 if conn is not None and object_path is not None:
455 self.add_to_connection(conn, object_path)
457 @property
458 def __dbus_object_path__(self):
459 """The object-path at which this object is available.
460 Access raises AttributeError if there is no object path, or more than
461 one object path.
463 Changed in 0.82.0: AttributeError can be raised.
465 if self._object_path is _MANY:
466 raise AttributeError('Object %r has more than one object path: '
467 'use Object.locations instead' % self)
468 elif self._object_path is None:
469 raise AttributeError('Object %r has no object path yet' % self)
470 else:
471 return self._object_path
473 @property
474 def connection(self):
475 """The Connection on which this object is available.
476 Access raises AttributeError if there is no Connection, or more than
477 one Connection.
479 Changed in 0.82.0: AttributeError can be raised.
481 if self._connection is _MANY:
482 raise AttributeError('Object %r is on more than one Connection: '
483 'use Object.locations instead' % self)
484 elif self._connection is None:
485 raise AttributeError('Object %r has no Connection yet' % self)
486 else:
487 return self._connection
489 @property
490 def locations(self):
491 """An iterable over tuples representing locations at which this
492 object is available.
494 Each tuple has at least two items, but may have more in future
495 versions of dbus-python, so do not rely on their exact length.
496 The first two items are the dbus.connection.Connection and the object
497 path.
499 :Since: 0.82.0
501 return iter(self._locations)
503 def add_to_connection(self, connection, path):
504 """Make this object accessible via the given D-Bus connection and
505 object path.
507 :Parameters:
508 `connection` : dbus.connection.Connection
509 Export the object on this connection. If the class attribute
510 SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
511 can only be made available on one connection; if the class
512 attribute is set True by a subclass, the object can be made
513 available on more than one connection.
515 `path` : dbus.ObjectPath or other str
516 Place the object at this object path. If the class attribute
517 SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
518 can only be made available at one object path; if the class
519 attribute is set True by a subclass, the object can be made
520 available with more than one object path.
522 :Raises ValueError: if the object's class attributes do not allow the
523 object to be exported in the desired way.
524 :Since: 0.82.0
526 if path == LOCAL_PATH:
527 raise ValueError('Objects may not be exported on the reserved '
528 'path %s' % LOCAL_PATH)
530 self._locations_lock.acquire()
531 try:
532 if (self._connection is not None and
533 self._connection is not connection and
534 not self.SUPPORTS_MULTIPLE_CONNECTIONS):
535 raise ValueError('%r is already exported on '
536 'connection %r' % (self, self._connection))
538 if (self._object_path is not None and
539 not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
540 self._object_path != path):
541 raise ValueError('%r is already exported at object '
542 'path %s' % (self, self._object_path))
544 connection._register_object_path(path, self._message_cb,
545 self._unregister_cb,
546 self._fallback)
548 if self._connection is None:
549 self._connection = connection
550 elif self._connection is not connection:
551 self._connection = _MANY
553 if self._object_path is None:
554 self._object_path = path
555 elif self._object_path != path:
556 self._object_path = _MANY
558 self._locations.append((connection, path, self._fallback))
559 finally:
560 self._locations_lock.release()
562 def remove_from_connection(self, connection=None, path=None):
563 """Make this object inaccessible via the given D-Bus connection
564 and object path. If no connection or path is specified,
565 the object ceases to be accessible via any connection or path.
567 It's not currently possible to export an object on more than one
568 connection or with more than one object-path, but this will be
569 supported in future.
571 :Parameters:
572 `connection` : dbus.connection.Connection or None
573 Only remove the object from this Connection. If None,
574 remove from all Connections on which it's exported.
575 `path` : dbus.ObjectPath or other str, or None
576 Only remove the object from this object path. If None,
577 remove from all object paths.
578 :Raises LookupError:
579 if the object was not exported on the requested connection
580 or path, or (if both are None) was not exported at all.
581 :Since: 0.81.1
583 self._locations_lock.acquire()
584 try:
585 if self._object_path is None or self._connection is None:
586 raise LookupError('%r is not exported' % self)
588 if connection is not None or path is not None:
589 dropped = []
590 for location in self._locations:
591 if ((connection is None or location[0] is connection) and
592 (path is None or location[1] == path)):
593 dropped.append(location)
594 else:
595 dropped = self._locations
596 self._locations = []
598 if not dropped:
599 raise LookupError('%r is not exported at a location matching '
600 '(%r,%r)' % (self, connection, path))
602 for location in dropped:
603 try:
604 location[0]._unregister_object_path(location[1])
605 except LookupError:
606 pass
607 if self._locations:
608 try:
609 self._locations.remove(location)
610 except ValueError:
611 pass
612 finally:
613 self._locations_lock.release()
615 def _unregister_cb(self, connection):
616 # there's not really enough information to do anything useful here
617 _logger.info('Unregistering exported object %r from some path '
618 'on %r', self, connection)
620 def _message_cb(self, connection, message):
621 try:
622 # lookup candidate method and parent method
623 method_name = message.get_member()
624 interface_name = message.get_interface()
625 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
627 # set up method call parameters
628 args = message.get_args_list(**parent_method._dbus_get_args_options)
629 keywords = {}
631 if parent_method._dbus_out_signature is not None:
632 signature = _dbus_bindings.Signature(parent_method._dbus_out_signature)
633 else:
634 signature = None
636 # set up async callback functions
637 if parent_method._dbus_async_callbacks:
638 (return_callback, error_callback) = parent_method._dbus_async_callbacks
639 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
640 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
642 # include the sender etc. if desired
643 if parent_method._dbus_sender_keyword:
644 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
645 if parent_method._dbus_path_keyword:
646 keywords[parent_method._dbus_path_keyword] = message.get_path()
647 if parent_method._dbus_destination_keyword:
648 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
649 if parent_method._dbus_message_keyword:
650 keywords[parent_method._dbus_message_keyword] = message
651 if parent_method._dbus_connection_keyword:
652 keywords[parent_method._dbus_connection_keyword] = connection
654 # call method
655 retval = candidate_method(self, *args, **keywords)
657 # we're done - the method has got callback functions to reply with
658 if parent_method._dbus_async_callbacks:
659 return
661 # otherwise we send the return values in a reply. if we have a
662 # signature, use it to turn the return value into a tuple as
663 # appropriate
664 if signature is not None:
665 signature_tuple = tuple(signature)
666 # if we have zero or one return values we want make a tuple
667 # for the _method_reply_return function, otherwise we need
668 # to check we're passing it a sequence
669 if len(signature_tuple) == 0:
670 if retval == None:
671 retval = ()
672 else:
673 raise TypeError('%s has an empty output signature but did not return None' %
674 method_name)
675 elif len(signature_tuple) == 1:
676 retval = (retval,)
677 else:
678 if operator.isSequenceType(retval):
679 # multi-value signature, multi-value return... proceed unchanged
680 pass
681 else:
682 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
683 (method_name, signature))
685 # no signature, so just turn the return into a tuple and send it as normal
686 else:
687 if retval is None:
688 retval = ()
689 elif (isinstance(retval, tuple)
690 and not isinstance(retval, _dbus_bindings.Struct)):
691 # If the return is a tuple that is not a Struct, we use it
692 # as-is on the assumption that there are multiple return
693 # values - this is the usual Python idiom. (fd.o #10174)
694 pass
695 else:
696 retval = (retval,)
698 _method_reply_return(connection, message, method_name, signature, *retval)
699 except Exception, exception:
700 # send error reply
701 _method_reply_error(connection, message, exception)
703 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
704 def Introspect(self):
705 """Return a string of XML encoding this object's supported interfaces,
706 methods and signals.
708 reflection_data = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n'
709 reflection_data += '<node name="%s">\n' % (self._object_path)
711 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
712 for (name, funcs) in interfaces.iteritems():
713 reflection_data += ' <interface name="%s">\n' % (name)
715 for func in funcs.values():
716 if getattr(func, '_dbus_is_method', False):
717 reflection_data += self.__class__._reflect_on_method(func)
718 elif getattr(func, '_dbus_is_signal', False):
719 reflection_data += self.__class__._reflect_on_signal(func)
721 reflection_data += ' </interface>\n'
723 for name in self._connection.list_exported_child_objects(
724 self._object_path):
725 reflection_data += ' <node name="%s"/>\n' % name
727 reflection_data += '</node>\n'
729 return reflection_data
731 def __repr__(self):
732 where = ''
733 if (self._object_path is not _MANY
734 and self._object_path is not None):
735 where = ' at %s' % self._object_path
736 return '<%s.%s%s at %#x>' % (self.__class__.__module__,
737 self.__class__.__name__, where,
738 id(self))
739 __str__ = __repr__
741 class FallbackObject(Object):
742 """An object that implements an entire subtree of the object-path
743 tree.
745 :Since: 0.82.0
748 SUPPORTS_MULTIPLE_OBJECT_PATHS = True
750 def __init__(self, conn=None, object_path=None):
751 """Constructor.
753 Note that the superclass' ``bus_name`` __init__ argument is not
754 supported here.
756 :Parameters:
757 `conn` : dbus.connection.Connection or None
758 The connection on which to export this object. If this is not
759 None, an `object_path` must also be provided.
761 If None, the object is not initially available on any
762 Connection.
764 `object_path` : str or None
765 A D-Bus object path at which to make this Object available
766 immediately. If this is not None, a `conn` must also be
767 provided.
769 This object will implements all object-paths in the subtree
770 starting at this object-path, except where a more specific
771 object has been added.
773 super(FallbackObject, self).__init__()
774 self._fallback = True
776 if conn is None:
777 if object_path is not None:
778 raise TypeError('If object_path is given, conn is required')
779 elif object_path is None:
780 raise TypeError('If conn is given, object_path is required')
781 else:
782 self.add_to_connection(conn, object_path)