Throughout dbus-python: Use the C implementation.
[dbus-python-phuang.git] / dbus / service.py
blob75c0c1a3ef62f2035f0552d545e795c717f3ea16
1 __all__ = ('BusName', 'Object', 'method', 'signal')
2 __docformat__ = 'restructuredtext'
4 import sys
5 import logging
6 import operator
7 import traceback
9 import _dbus_bindings
10 import dbus._dbus as _dbus
11 from dbus.exceptions import NameExistsException
12 from dbus.exceptions import UnknownMethodException
13 from dbus.decorators import method
14 from dbus.decorators import signal
17 _logger = logging.getLogger('dbus.service')
20 class _VariantSignature(object):
21 """A fake method signature which, when iterated, yields an endless stream
22 of 'v' characters representing variants (handy with zip()).
24 It has no string representation.
25 """
26 def __iter__(self):
27 """Return self."""
28 return self
30 def next(self):
31 """Return 'v' whenever called."""
32 return 'v'
34 class BusName(object):
35 """A base class for exporting your own Named Services across the Bus.
37 When instantiated, objects of this class attempt to claim the given
38 well-known name on the given bus for the current process. The name is
39 released when the BusName object becomes unreferenced.
41 If a well-known name is requested multiple times, multiple references
42 to the same BusName object will be returned.
44 Caveats
45 -------
46 - Assumes that named services are only ever requested using this class -
47 if you request names from the bus directly, confusion may occur.
48 - Does not handle queueing.
49 """
50 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
51 """Constructor, which may either return an existing cached object
52 or a new object.
54 :Parameters:
55 `name` : str
56 The well-known name to be advertised
57 `bus` : dbus.Bus
58 A Bus on which this service will be advertised; if None
59 (default) a default bus will be used
60 `allow_replacement` : bool
61 If True, other processes trying to claim the same well-known
62 name will take precedence over this one.
63 `replace_existing` : bool
64 If True, this process can take over the well-known name
65 from other processes already holding it.
66 `do_not_queue` : bool
67 If True, this service will not be placed in the queue of
68 services waiting for the requested name if another service
69 already holds it.
70 """
71 # get default bus
72 if bus == None:
73 bus = _dbus.Bus()
75 # see if this name is already defined, return it if so
76 # FIXME: accessing internals of Bus
77 if name in bus._bus_names:
78 return bus._bus_names[name]
80 # otherwise register the name
81 name_flags = \
82 _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT * allow_replacement + \
83 _dbus_bindings.NAME_FLAG_REPLACE_EXISTING * replace_existing + \
84 _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE * do_not_queue
86 retval = bus.request_name(name, name_flags)
88 # TODO: more intelligent tracking of bus name states?
89 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
90 pass
91 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
92 # queueing can happen by default, maybe we should
93 # track this better or let the user know if they're
94 # queued or not?
95 pass
96 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
97 raise NameExistsException(name)
98 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
99 # if this is a shared bus which is being used by someone
100 # else in this process, this can happen legitimately
101 pass
102 else:
103 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
105 # and create the object
106 bus_name = object.__new__(cls)
107 bus_name._bus = bus
108 bus_name._name = name
110 # cache instance (weak ref only)
111 # FIXME: accessing Bus internals again
112 bus._bus_names[name] = bus_name
114 return bus_name
116 # do nothing because this is called whether or not the bus name
117 # object was retrieved from the cache or created new
118 def __init__(self, *args, **keywords):
119 pass
121 # we can delete the low-level name here because these objects
122 # are guaranteed to exist only once for each bus name
123 def __del__(self):
124 self._bus.release_name(self._name)
125 pass
127 def get_bus(self):
128 """Get the Bus this Service is on"""
129 return self._bus
131 def get_name(self):
132 """Get the name of this service"""
133 return self._name
135 def __repr__(self):
136 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
137 __str__ = __repr__
140 def _method_lookup(self, method_name, dbus_interface):
141 """Walks the Python MRO of the given class to find the method to invoke.
143 Returns two methods, the one to call, and the one it inherits from which
144 defines its D-Bus interface name, signature, and attributes.
146 parent_method = None
147 candidate_class = None
148 successful = False
150 # split up the cases when we do and don't have an interface because the
151 # latter is much simpler
152 if dbus_interface:
153 # search through the class hierarchy in python MRO order
154 for cls in self.__class__.__mro__:
155 # if we haven't got a candidate class yet, and we find a class with a
156 # suitably named member, save this as a candidate class
157 if (not candidate_class and method_name in cls.__dict__):
158 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
159 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
160 # however if it is annotated for a different interface
161 # than we are looking for, it cannot be a candidate
162 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
163 candidate_class = cls
164 parent_method = cls.__dict__[method_name]
165 successful = True
166 break
167 else:
168 pass
169 else:
170 candidate_class = cls
172 # if we have a candidate class, carry on checking this and all
173 # superclasses for a method annoated as a dbus method
174 # on the correct interface
175 if (candidate_class and method_name in cls.__dict__
176 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
177 and "_dbus_interface" in cls.__dict__[method_name].__dict__
178 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
179 # the candidate class has a dbus method on the correct interface,
180 # or overrides a method that is, success!
181 parent_method = cls.__dict__[method_name]
182 successful = True
183 break
185 else:
186 # simpler version of above
187 for cls in self.__class__.__mro__:
188 if (not candidate_class and method_name in cls.__dict__):
189 candidate_class = cls
191 if (candidate_class and method_name in cls.__dict__
192 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
193 parent_method = cls.__dict__[method_name]
194 successful = True
195 break
197 if successful:
198 return (candidate_class.__dict__[method_name], parent_method)
199 else:
200 if dbus_interface:
201 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
202 else:
203 raise UnknownMethodException('%s is not a valid method' % method_name)
206 # FIXME: if signature is '', we used to accept anything, but now insist on
207 # zero args. Which was desired? -smcv
208 def _method_reply_return(connection, message, method_name, signature, *retval):
209 reply = _dbus_bindings.MethodReturnMessage(message)
210 try:
211 reply.append(signature=signature, *retval)
212 except Exception, e:
213 if signature is None:
214 try:
215 signature = reply.guess_signature(retval) + ' (guessed)'
216 except Exception, e:
217 _logger.error('Unable to guess signature for arguments %r: '
218 '%s: %s', retval, e.__class__, e)
219 raise
220 _logger.error('Unable to append %r to message with signature %s: '
221 '%s: %s', retval, signature, e.__class__, e)
222 raise
224 connection._send(reply)
227 def _method_reply_error(connection, message, exception):
228 if '_dbus_error_name' in exception.__dict__:
229 name = exception._dbus_error_name
230 elif exception.__module__ == '__main__':
231 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
232 else:
233 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
235 contents = traceback.format_exc()
236 reply = _dbus_bindings.ErrorMessage(message, name, contents)
238 connection._send(reply)
241 class InterfaceType(type):
242 def __init__(cls, name, bases, dct):
243 # these attributes are shared between all instances of the Interface
244 # object, so this has to be a dictionary that maps class names to
245 # the per-class introspection/interface data
246 class_table = getattr(cls, '_dbus_class_table', {})
247 cls._dbus_class_table = class_table
248 interface_table = class_table[cls.__module__ + '.' + name] = {}
250 # merge all the name -> method tables for all the interfaces
251 # implemented by our base classes into our own
252 for b in bases:
253 base_name = b.__module__ + '.' + b.__name__
254 if getattr(b, '_dbus_class_table', False):
255 for (interface, method_table) in class_table[base_name].iteritems():
256 our_method_table = interface_table.setdefault(interface, {})
257 our_method_table.update(method_table)
259 # add in all the name -> method entries for our own methods/signals
260 for func in dct.values():
261 if getattr(func, '_dbus_interface', False):
262 method_table = interface_table.setdefault(func._dbus_interface, {})
263 method_table[func.__name__] = func
265 super(InterfaceType, cls).__init__(name, bases, dct)
267 # methods are different to signals, so we have two functions... :)
268 def _reflect_on_method(cls, func):
269 args = func._dbus_args
271 if func._dbus_in_signature:
272 # convert signature into a tuple so length refers to number of
273 # types, not number of characters. the length is checked by
274 # the decorator to make sure it matches the length of args.
275 in_sig = tuple(_dbus_bindings.Signature(func._dbus_in_signature))
276 else:
277 # magic iterator which returns as many v's as we need
278 in_sig = _VariantSignature()
280 if func._dbus_out_signature:
281 out_sig = _dbus_bindings.Signature(func._dbus_out_signature)
282 else:
283 # its tempting to default to _dbus_bindings.Signature('v'), but
284 # for methods that return nothing, providing incorrect
285 # introspection data is worse than providing none at all
286 out_sig = []
288 reflection_data = ' <method name="%s">\n' % (func.__name__)
289 for pair in zip(in_sig, args):
290 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
291 for type in out_sig:
292 reflection_data += ' <arg direction="out" type="%s" />\n' % type
293 reflection_data += ' </method>\n'
295 return reflection_data
297 def _reflect_on_signal(cls, func):
298 args = func._dbus_args
300 if func._dbus_signature:
301 # convert signature into a tuple so length refers to number of
302 # types, not number of characters
303 sig = tuple(_dbus_bindings.Signature(func._dbus_signature))
304 else:
305 # magic iterator which returns as many v's as we need
306 sig = _VariantSignature()
308 reflection_data = ' <signal name="%s">\n' % (func.__name__)
309 for pair in zip(sig, args):
310 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
311 reflection_data = reflection_data + ' </signal>\n'
313 return reflection_data
315 class Interface(object):
316 __metaclass__ = InterfaceType
318 class Object(Interface):
319 """A base class for exporting your own Objects across the Bus.
321 Just inherit from Object and provide a list of methods to share
322 across the Bus.
324 Issues
325 ------
326 - The constructor takes a well-known name: this is wrong. There should be
327 no requirement to register a well-known name in order to export bus
328 objects.
330 def __init__(self, bus_name, object_path):
331 """Constructor.
333 :Parameters:
334 `bus_name` : BusName
335 Represents a well-known name claimed by this process. A
336 reference to the BusName object will be held by this
337 Object, preventing the name from being released during this
338 Object's lifetime (subject to status).
339 `object_path` : str
340 The D-Bus object path at which to export this Object.
342 self._object_path = object_path
343 self._name = bus_name
344 self._bus = bus_name.get_bus()
346 self._connection = self._bus.get_connection()
348 self._connection._register_object_path(object_path, self._message_cb, self._unregister_cb)
350 def _unregister_cb(self, connection):
351 _logger.info('Unregistering exported object %r', self)
353 def _message_cb(self, connection, message):
354 try:
355 # lookup candidate method and parent method
356 method_name = message.get_member()
357 interface_name = message.get_interface()
358 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
360 # set up method call parameters
361 args = message.get_args_list()
362 keywords = {}
364 if parent_method._dbus_out_signature is not None:
365 signature = _dbus_bindings.Signature(parent_method._dbus_out_signature)
366 else:
367 signature = None
369 # set up async callback functions
370 if parent_method._dbus_async_callbacks:
371 (return_callback, error_callback) = parent_method._dbus_async_callbacks
372 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
373 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
375 # include the sender if desired
376 if parent_method._dbus_sender_keyword:
377 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
379 # call method
380 retval = candidate_method(self, *args, **keywords)
382 # we're done - the method has got callback functions to reply with
383 if parent_method._dbus_async_callbacks:
384 return
386 # otherwise we send the return values in a reply. if we have a
387 # signature, use it to turn the return value into a tuple as
388 # appropriate
389 if signature is not None:
390 signature_tuple = tuple(signature)
391 # if we have zero or one return values we want make a tuple
392 # for the _method_reply_return function, otherwise we need
393 # to check we're passing it a sequence
394 if len(signature_tuple) == 0:
395 if retval == None:
396 retval = ()
397 else:
398 raise TypeError('%s has an empty output signature but did not return None' %
399 method_name)
400 elif len(signature_tuple) == 1:
401 retval = (retval,)
402 else:
403 if operator.isSequenceType(retval):
404 # multi-value signature, multi-value return... proceed unchanged
405 pass
406 else:
407 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
408 (method_name, signature))
410 # no signature, so just turn the return into a tuple and send it as normal
411 else:
412 if retval == None:
413 retval = ()
414 else:
415 retval = (retval,)
417 _method_reply_return(connection, message, method_name, signature, *retval)
418 except Exception, exception:
419 # send error reply
420 _method_reply_error(connection, message, exception)
422 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
423 def Introspect(self):
424 """Return a string of XML encoding this object's supported interfaces,
425 methods and signals.
427 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'
428 reflection_data += '<node name="%s">\n' % (self._object_path)
430 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
431 for (name, funcs) in interfaces.iteritems():
432 reflection_data += ' <interface name="%s">\n' % (name)
434 for func in funcs.values():
435 if getattr(func, '_dbus_is_method', False):
436 reflection_data += self.__class__._reflect_on_method(func)
437 elif getattr(func, '_dbus_is_signal', False):
438 reflection_data += self.__class__._reflect_on_signal(func)
440 reflection_data += ' </interface>\n'
442 reflection_data += '</node>\n'
444 return reflection_data
446 def __repr__(self):
447 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
448 __str__ = __repr__