Add Makefile target maintainer-update-website to update d.fd.o/doc/dbus-python
[dbus-python-phuang.git] / dbus / service.py
blobda64a1e3e75ff4f13a643035e8a4013229d72d9e
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 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 __all__ = ('BusName', 'Object', 'method', 'signal')
25 __docformat__ = 'restructuredtext'
27 import sys
28 import logging
29 import operator
30 import traceback
32 import _dbus_bindings
33 import dbus._dbus as _dbus
34 from dbus.exceptions import NameExistsException
35 from dbus.exceptions import UnknownMethodException
36 from dbus.decorators import method
37 from dbus.decorators import signal
40 _logger = logging.getLogger('dbus.service')
43 class _VariantSignature(object):
44 """A fake method signature which, when iterated, yields an endless stream
45 of 'v' characters representing variants (handy with zip()).
47 It has no string representation.
48 """
49 def __iter__(self):
50 """Return self."""
51 return self
53 def next(self):
54 """Return 'v' whenever called."""
55 return 'v'
57 class BusName(object):
58 """A base class for exporting your own Named Services across the Bus.
60 When instantiated, objects of this class attempt to claim the given
61 well-known name on the given bus for the current process. The name is
62 released when the BusName object becomes unreferenced.
64 If a well-known name is requested multiple times, multiple references
65 to the same BusName object will be returned.
67 Caveats
68 -------
69 - Assumes that named services are only ever requested using this class -
70 if you request names from the bus directly, confusion may occur.
71 - Does not handle queueing.
72 """
73 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
74 """Constructor, which may either return an existing cached object
75 or a new object.
77 :Parameters:
78 `name` : str
79 The well-known name to be advertised
80 `bus` : dbus.Bus
81 A Bus on which this service will be advertised; if None
82 (default) the session bus will be used
83 `allow_replacement` : bool
84 If True, other processes trying to claim the same well-known
85 name will take precedence over this one.
86 `replace_existing` : bool
87 If True, this process can take over the well-known name
88 from other processes already holding it.
89 `do_not_queue` : bool
90 If True, this service will not be placed in the queue of
91 services waiting for the requested name if another service
92 already holds it.
93 """
94 # get default bus
95 if bus == None:
96 bus = _dbus.Bus()
98 # see if this name is already defined, return it if so
99 # FIXME: accessing internals of Bus
100 if name in bus._bus_names:
101 return bus._bus_names[name]
103 # otherwise register the name
104 name_flags = (
105 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
106 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
107 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
109 retval = bus.request_name(name, name_flags)
111 # TODO: more intelligent tracking of bus name states?
112 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
113 pass
114 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
115 # queueing can happen by default, maybe we should
116 # track this better or let the user know if they're
117 # queued or not?
118 pass
119 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
120 raise NameExistsException(name)
121 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
122 # if this is a shared bus which is being used by someone
123 # else in this process, this can happen legitimately
124 pass
125 else:
126 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
128 # and create the object
129 bus_name = object.__new__(cls)
130 bus_name._bus = bus
131 bus_name._name = name
133 # cache instance (weak ref only)
134 # FIXME: accessing Bus internals again
135 bus._bus_names[name] = bus_name
137 return bus_name
139 # do nothing because this is called whether or not the bus name
140 # object was retrieved from the cache or created new
141 def __init__(self, *args, **keywords):
142 pass
144 # we can delete the low-level name here because these objects
145 # are guaranteed to exist only once for each bus name
146 def __del__(self):
147 self._bus.release_name(self._name)
148 pass
150 def get_bus(self):
151 """Get the Bus this Service is on"""
152 return self._bus
154 def get_name(self):
155 """Get the name of this service"""
156 return self._name
158 def __repr__(self):
159 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
160 __str__ = __repr__
163 def _method_lookup(self, method_name, dbus_interface):
164 """Walks the Python MRO of the given class to find the method to invoke.
166 Returns two methods, the one to call, and the one it inherits from which
167 defines its D-Bus interface name, signature, and attributes.
169 parent_method = None
170 candidate_class = None
171 successful = False
173 # split up the cases when we do and don't have an interface because the
174 # latter is much simpler
175 if dbus_interface:
176 # search through the class hierarchy in python MRO order
177 for cls in self.__class__.__mro__:
178 # if we haven't got a candidate class yet, and we find a class with a
179 # suitably named member, save this as a candidate class
180 if (not candidate_class and method_name in cls.__dict__):
181 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
182 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
183 # however if it is annotated for a different interface
184 # than we are looking for, it cannot be a candidate
185 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
186 candidate_class = cls
187 parent_method = cls.__dict__[method_name]
188 successful = True
189 break
190 else:
191 pass
192 else:
193 candidate_class = cls
195 # if we have a candidate class, carry on checking this and all
196 # superclasses for a method annoated as a dbus method
197 # on the correct interface
198 if (candidate_class and method_name in cls.__dict__
199 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
200 and "_dbus_interface" in cls.__dict__[method_name].__dict__
201 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
202 # the candidate class has a dbus method on the correct interface,
203 # or overrides a method that is, success!
204 parent_method = cls.__dict__[method_name]
205 successful = True
206 break
208 else:
209 # simpler version of above
210 for cls in self.__class__.__mro__:
211 if (not candidate_class and method_name in cls.__dict__):
212 candidate_class = cls
214 if (candidate_class and method_name in cls.__dict__
215 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
216 parent_method = cls.__dict__[method_name]
217 successful = True
218 break
220 if successful:
221 return (candidate_class.__dict__[method_name], parent_method)
222 else:
223 if dbus_interface:
224 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
225 else:
226 raise UnknownMethodException('%s is not a valid method' % method_name)
229 # FIXME: if signature is '', we used to accept anything, but now insist on
230 # zero args. Which was desired? -smcv
231 def _method_reply_return(connection, message, method_name, signature, *retval):
232 reply = _dbus_bindings.MethodReturnMessage(message)
233 try:
234 reply.append(signature=signature, *retval)
235 except Exception, e:
236 if signature is None:
237 try:
238 signature = reply.guess_signature(retval) + ' (guessed)'
239 except Exception, e:
240 _logger.error('Unable to guess signature for arguments %r: '
241 '%s: %s', retval, e.__class__, e)
242 raise
243 _logger.error('Unable to append %r to message with signature %s: '
244 '%s: %s', retval, signature, e.__class__, e)
245 raise
247 connection.send_message(reply)
250 def _method_reply_error(connection, message, exception):
251 if hasattr(exception, '_dbus_error_name'):
252 name = exception._dbus_error_name
253 elif getattr(exception, '__module__', '') in ('', '__main__'):
254 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
255 else:
256 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
258 contents = traceback.format_exc()
259 reply = _dbus_bindings.ErrorMessage(message, name, contents)
261 connection.send_message(reply)
264 class InterfaceType(type):
265 def __init__(cls, name, bases, dct):
266 # these attributes are shared between all instances of the Interface
267 # object, so this has to be a dictionary that maps class names to
268 # the per-class introspection/interface data
269 class_table = getattr(cls, '_dbus_class_table', {})
270 cls._dbus_class_table = class_table
271 interface_table = class_table[cls.__module__ + '.' + name] = {}
273 # merge all the name -> method tables for all the interfaces
274 # implemented by our base classes into our own
275 for b in bases:
276 base_name = b.__module__ + '.' + b.__name__
277 if getattr(b, '_dbus_class_table', False):
278 for (interface, method_table) in class_table[base_name].iteritems():
279 our_method_table = interface_table.setdefault(interface, {})
280 our_method_table.update(method_table)
282 # add in all the name -> method entries for our own methods/signals
283 for func in dct.values():
284 if getattr(func, '_dbus_interface', False):
285 method_table = interface_table.setdefault(func._dbus_interface, {})
286 method_table[func.__name__] = func
288 super(InterfaceType, cls).__init__(name, bases, dct)
290 # methods are different to signals, so we have two functions... :)
291 def _reflect_on_method(cls, func):
292 args = func._dbus_args
294 if func._dbus_in_signature:
295 # convert signature into a tuple so length refers to number of
296 # types, not number of characters. the length is checked by
297 # the decorator to make sure it matches the length of args.
298 in_sig = tuple(_dbus_bindings.Signature(func._dbus_in_signature))
299 else:
300 # magic iterator which returns as many v's as we need
301 in_sig = _VariantSignature()
303 if func._dbus_out_signature:
304 out_sig = _dbus_bindings.Signature(func._dbus_out_signature)
305 else:
306 # its tempting to default to _dbus_bindings.Signature('v'), but
307 # for methods that return nothing, providing incorrect
308 # introspection data is worse than providing none at all
309 out_sig = []
311 reflection_data = ' <method name="%s">\n' % (func.__name__)
312 for pair in zip(in_sig, args):
313 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
314 for type in out_sig:
315 reflection_data += ' <arg direction="out" type="%s" />\n' % type
316 reflection_data += ' </method>\n'
318 return reflection_data
320 def _reflect_on_signal(cls, func):
321 args = func._dbus_args
323 if func._dbus_signature:
324 # convert signature into a tuple so length refers to number of
325 # types, not number of characters
326 sig = tuple(_dbus_bindings.Signature(func._dbus_signature))
327 else:
328 # magic iterator which returns as many v's as we need
329 sig = _VariantSignature()
331 reflection_data = ' <signal name="%s">\n' % (func.__name__)
332 for pair in zip(sig, args):
333 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
334 reflection_data = reflection_data + ' </signal>\n'
336 return reflection_data
338 class Interface(object):
339 __metaclass__ = InterfaceType
341 class Object(Interface):
342 r"""A base class for exporting your own Objects across the Bus.
344 Just inherit from Object and mark exported methods with the
345 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
347 Example::
349 class Example(dbus.service.object):
350 def __init__(self, object_path):
351 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
352 self._last_input = None
354 @dbus.service.method(interface='com.example.Sample',
355 in_signature='v', out_signature='s')
356 def StringifyVariant(self, var):
357 self.LastInputChanged(var) # emits the signal
358 return str(var)
360 @dbus.service.signal(interface='com.example.Sample',
361 signature='v')
362 def LastInputChanged(self, var):
363 # run just before the signal is actually emitted
364 # just put "pass" if nothing should happen
365 self._last_input = var
367 @dbus.service.method(interface='com.example.Sample',
368 in_signature='', out_signature='v')
369 def GetLastInput(self):
370 return self._last_input
373 # the signature of __init__ is a bit mad, for backwards compatibility
374 def __init__(self, conn=None, object_path=None, bus_name=None):
375 """Constructor. Either conn or bus_name is required; object_path
376 is also required.
378 :Parameters:
379 `conn` : dbus.Connection
380 The connection on which to export this object.
382 If None, use the Bus associated with the given ``bus_name``,
383 or raise TypeError if there is no ``bus_name`` either.
385 For backwards compatibility, if an instance of
386 dbus.service.BusName is passed as the first parameter,
387 this is equivalent to passing its associated Bus as
388 ``conn``, and passing the BusName itself as ``bus_name``.
390 `object_path` : str
391 The D-Bus object path at which to export this Object.
393 `bus_name` : dbus.service.BusName
394 Represents a well-known name claimed by this process. A
395 reference to the BusName object will be held by this
396 Object, preventing the name from being released during this
397 Object's lifetime (unless it's released manually).
399 if object_path is None:
400 raise TypeError('The object_path argument is required')
402 if isinstance(conn, BusName):
403 # someone's using the old API; don't gratuitously break them
404 bus_name = conn
405 conn = bus_name.get_bus()
406 elif conn is None:
407 # someone's using the old API but naming arguments, probably
408 if bus_name is None:
409 raise TypeError('Either conn or bus_name is required')
410 conn = bus_name.get_bus()
412 self._object_path = object_path
413 self._name = bus_name
414 self._bus = conn
416 self._connection = self._bus.get_connection()
418 self._connection._register_object_path(object_path, self._message_cb, self._unregister_cb)
420 __dbus_object_path__ = property(lambda self: self._object_path, None, None,
421 "The D-Bus object path of this object")
423 def _unregister_cb(self, connection):
424 _logger.info('Unregistering exported object %r', self)
426 def _message_cb(self, connection, message):
427 try:
428 # lookup candidate method and parent method
429 method_name = message.get_member()
430 interface_name = message.get_interface()
431 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
433 # set up method call parameters
434 args = message.get_args_list(**parent_method._dbus_get_args_options)
435 keywords = {}
437 if parent_method._dbus_out_signature is not None:
438 signature = _dbus_bindings.Signature(parent_method._dbus_out_signature)
439 else:
440 signature = None
442 # set up async callback functions
443 if parent_method._dbus_async_callbacks:
444 (return_callback, error_callback) = parent_method._dbus_async_callbacks
445 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
446 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
448 # include the sender if desired
449 if parent_method._dbus_sender_keyword:
450 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
452 # call method
453 retval = candidate_method(self, *args, **keywords)
455 # we're done - the method has got callback functions to reply with
456 if parent_method._dbus_async_callbacks:
457 return
459 # otherwise we send the return values in a reply. if we have a
460 # signature, use it to turn the return value into a tuple as
461 # appropriate
462 if signature is not None:
463 signature_tuple = tuple(signature)
464 # if we have zero or one return values we want make a tuple
465 # for the _method_reply_return function, otherwise we need
466 # to check we're passing it a sequence
467 if len(signature_tuple) == 0:
468 if retval == None:
469 retval = ()
470 else:
471 raise TypeError('%s has an empty output signature but did not return None' %
472 method_name)
473 elif len(signature_tuple) == 1:
474 retval = (retval,)
475 else:
476 if operator.isSequenceType(retval):
477 # multi-value signature, multi-value return... proceed unchanged
478 pass
479 else:
480 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
481 (method_name, signature))
483 # no signature, so just turn the return into a tuple and send it as normal
484 else:
485 if retval == None:
486 retval = ()
487 else:
488 retval = (retval,)
490 _method_reply_return(connection, message, method_name, signature, *retval)
491 except Exception, exception:
492 # send error reply
493 _method_reply_error(connection, message, exception)
495 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
496 def Introspect(self):
497 """Return a string of XML encoding this object's supported interfaces,
498 methods and signals.
500 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'
501 reflection_data += '<node name="%s">\n' % (self._object_path)
503 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
504 for (name, funcs) in interfaces.iteritems():
505 reflection_data += ' <interface name="%s">\n' % (name)
507 for func in funcs.values():
508 if getattr(func, '_dbus_is_method', False):
509 reflection_data += self.__class__._reflect_on_method(func)
510 elif getattr(func, '_dbus_is_signal', False):
511 reflection_data += self.__class__._reflect_on_signal(func)
513 reflection_data += ' </interface>\n'
515 reflection_data += '</node>\n'
517 return reflection_data
519 def __repr__(self):
520 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
521 __str__ = __repr__