6 from exceptions import NameExistsException
7 from exceptions import UnknownMethodException
8 from decorators import method
9 from decorators import signal
11 class BusName(object):
12 """A base class for exporting your own Named Services across the Bus
14 def __new__(cls, name, bus=None):
19 # see if this name is already defined, return it if so
20 if name in bus._bus_names:
21 return bus._bus_names[name]
23 # otherwise register the name
24 retval = dbus_bindings.bus_request_name(bus.get_connection(), name)
26 # TODO: more intelligent tracking of bus name states?
27 if retval == dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
29 elif retval == dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
30 # you can't arrive at this state via the high-level bindings
31 # because you can't put flags in, but... who knows?
33 elif retval == dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
34 raise NameExistsException(name)
35 elif retval == dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
36 # if this is a shared bus which is being used by someone
37 # else in this process, this can happen legitimately
40 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
42 # and create the object
43 bus_name = object.__new__(cls)
48 bus._bus_names[name] = bus_name
52 # do nothing because this is called whether or not the bus name
53 # object was retrieved from the cache or created new
54 def __init__(self, *args, **keywords):
57 # we can delete the low-level name here because these objects
58 # are guaranteed to exist only once for each bus name
60 dbus_bindings.bus_release_name(self._bus.get_connection(), self._name)
64 """Get the Bus this Service is on"""
68 """Get the name of this service"""
72 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
76 def _method_lookup(self, method_name, dbus_interface):
77 """Walks the Python MRO of the given class to find the method to invoke.
79 Returns two methods, the one to call, and the one it inherits from which
80 defines its D-Bus interface name, signature, and attributes.
83 candidate_class = None
86 # split up the cases when we do and don't have an interface because the
87 # latter is much simpler
89 # search through the class hierarchy in python MRO order
90 for cls in self.__class__.__mro__:
91 # if we haven't got a candidate class yet, and we find a class with a
92 # suitably named member, save this as a candidate class
93 if (not candidate_class and method_name in cls.__dict__):
94 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
95 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
96 # however if it is annotated for a different interface
97 # than we are looking for, it cannot be a candidate
98 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
100 parent_method = cls.__dict__[method_name]
106 candidate_class = cls
108 # if we have a candidate class, carry on checking this and all
109 # superclasses for a method annoated as a dbus method
110 # on the correct interface
111 if (candidate_class and method_name in cls.__dict__
112 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
113 and "_dbus_interface" in cls.__dict__[method_name].__dict__
114 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
115 # the candidate class has a dbus method on the correct interface,
116 # or overrides a method that is, success!
117 parent_method = cls.__dict__[method_name]
122 # simpler version of above
123 for cls in self.__class__.__mro__:
124 if (not candidate_class and method_name in cls.__dict__):
125 candidate_class = cls
127 if (candidate_class and method_name in cls.__dict__
128 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
129 parent_method = cls.__dict__[method_name]
134 return (candidate_class.__dict__[method_name], parent_method)
137 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
139 raise UnknownMethodException('%s is not a valid method' % method_name)
142 def _method_reply_return(connection, message, method_name, signature, *retval):
143 reply = dbus_bindings.MethodReturn(message)
144 iter = reply.get_iter(append=True)
146 # do strict adding if an output signature was provided
148 if len(signature) > len(retval):
149 raise TypeError('output signature %s is longer than the number of values returned by %s' %
150 (signature, method_name))
151 elif len(retval) > len(signature):
152 raise TypeError('output signature %s is shorter than the number of values returned by %s' %
153 (signature, method_name))
155 for (value, sig) in zip(retval, signature):
156 iter.append_strict(value, sig)
158 # no signature, try and guess the return type by inspection
163 connection.send(reply)
166 def _method_reply_error(connection, message, exception):
167 if '_dbus_error_name' in exception.__dict__:
168 name = exception._dbus_error_name
169 elif exception.__module__ == '__main__':
170 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
172 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
174 contents = traceback.format_exc()
175 reply = dbus_bindings.Error(message, name, contents)
177 connection.send(reply)
180 class InterfaceType(type):
181 def __init__(cls, name, bases, dct):
182 # these attributes are shared between all instances of the Interface
183 # object, so this has to be a dictionary that maps class names to
184 # the per-class introspection/interface data
185 class_table = getattr(cls, '_dbus_class_table', {})
186 cls._dbus_class_table = class_table
187 interface_table = class_table[cls.__module__ + '.' + name] = {}
189 # merge all the name -> method tables for all the interfaces
190 # implemented by our base classes into our own
192 base_name = b.__module__ + '.' + b.__name__
193 if getattr(b, '_dbus_class_table', False):
194 for (interface, method_table) in class_table[base_name].iteritems():
195 our_method_table = interface_table.setdefault(interface, {})
196 our_method_table.update(method_table)
198 # add in all the name -> method entries for our own methods/signals
199 for func in dct.values():
200 if getattr(func, '_dbus_interface', False):
201 method_table = interface_table.setdefault(func._dbus_interface, {})
202 method_table[func.__name__] = func
204 super(InterfaceType, cls).__init__(name, bases, dct)
206 # methods are different to signals, so we have two functions... :)
207 def _reflect_on_method(cls, func):
208 args = func._dbus_args
210 if func._dbus_in_signature:
211 # convert signature into a tuple so length refers to number of
212 # types, not number of characters. the length is checked by
213 # the decorator to make sure it matches the length of args.
214 in_sig = tuple(dbus_bindings.Signature(func._dbus_in_signature))
216 # magic iterator which returns as many v's as we need
217 in_sig = dbus_bindings.VariantSignature()
219 if func._dbus_out_signature:
220 out_sig = dbus_bindings.Signature(func._dbus_out_signature)
222 # its tempting to default to dbus_bindings.Signature('v'), but
223 # for methods that return nothing, providing incorrect
224 # introspection data is worse than providing none at all
227 reflection_data = ' <method name="%s">\n' % (func.__name__)
228 for pair in zip(in_sig, args):
229 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
231 reflection_data += ' <arg direction="out" type="%s" />\n' % type
232 reflection_data += ' </method>\n'
234 return reflection_data
236 def _reflect_on_signal(cls, func):
237 args = func._dbus_args
239 if func._dbus_signature:
240 # convert signature into a tuple so length refers to number of
241 # types, not number of characters
242 sig = tuple(dbus_bindings.Signature(func._dbus_signature))
244 # magic iterator which returns as many v's as we need
245 sig = dbus_bindings.VariantSignature()
247 reflection_data = ' <signal name="%s">\n' % (func.__name__)
248 for pair in zip(sig, args):
249 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
250 reflection_data = reflection_data + ' </signal>\n'
252 return reflection_data
254 class Interface(object):
255 __metaclass__ = InterfaceType
257 class Object(Interface):
258 """A base class for exporting your own Objects across the Bus.
260 Just inherit from Object and provide a list of methods to share
263 def __init__(self, bus_name, object_path):
264 self._object_path = object_path
265 self._name = bus_name
266 self._bus = bus_name.get_bus()
268 self._connection = self._bus.get_connection()
270 self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
272 def _unregister_cb(self, connection):
275 def _message_cb(self, connection, message):
277 # lookup candidate method and parent method
278 method_name = message.get_member()
279 interface_name = message.get_interface()
280 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
282 # set up method call parameters
283 args = message.get_args_list()
286 # iterate signature into list of complete types
287 if parent_method._dbus_out_signature:
288 signature = tuple(dbus_bindings.Signature(parent_method._dbus_out_signature))
292 # set up async callback functions
293 if parent_method._dbus_async_callbacks:
294 (return_callback, error_callback) = parent_method._dbus_async_callbacks
295 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
296 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
298 # include the sender if desired
299 if parent_method._dbus_sender_keyword:
300 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
303 retval = candidate_method(self, *args, **keywords)
305 # we're done - the method has got callback functions to reply with
306 if parent_method._dbus_async_callbacks:
309 # otherwise we send the return values in a reply. if we have a
310 # signature, use it to turn the return value into a tuple as
312 if parent_method._dbus_out_signature:
313 # if we have zero or one return values we want make a tuple
314 # for the _method_reply_return function, otherwise we need
315 # to check we're passing it a sequence
316 if len(signature) == 0:
320 raise TypeError('%s has an empty output signature but did not return None' %
322 elif len(signature) == 1:
325 if operator.isSequenceType(retval):
326 # multi-value signature, multi-value return... proceed unchanged
329 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
330 (method_name, signature))
332 # no signature, so just turn the return into a tuple and send it as normal
340 _method_reply_return(connection, message, method_name, signature, *retval)
341 except Exception, exception:
343 _method_reply_error(connection, message, exception)
345 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
346 def Introspect(self):
347 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'
348 reflection_data += '<node name="%s">\n' % (self._object_path)
350 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
351 for (name, funcs) in interfaces.iteritems():
352 reflection_data += ' <interface name="%s">\n' % (name)
354 for func in funcs.values():
355 if getattr(func, '_dbus_is_method', False):
356 reflection_data += self.__class__._reflect_on_method(func)
357 elif getattr(func, '_dbus_is_signal', False):
358 reflection_data += self.__class__._reflect_on_signal(func)
360 reflection_data += ' </interface>\n'
362 reflection_data += '</node>\n'
364 return reflection_data
367 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))