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 dbus_bindings.DBusException('requested name %s already exists' % 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 dbus_bindings.DBusException('requesting 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 # FIXME: we don't have this function yet :)
61 #dbus_bindings.bus_release_name(self._bus.get_connection(), self._named_service)
65 """Get the Bus this Service is on"""
69 """Get the name of this service"""
73 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
77 def _method_lookup(self, method_name, dbus_interface):
78 """Walks the Python MRO of the given class to find the method to invoke.
80 Returns two methods, the one to call, and the one it inherits from which
81 defines its D-Bus interface name, signature, and attributes.
84 candidate_class = None
87 # split up the cases when we do and don't have an interface because the
88 # latter is much simpler
90 # search through the class hierarchy in python MRO order
91 for cls in self.__class__.__mro__:
92 # if we haven't got a candidate class yet, and we find a class with a
93 # suitably named member, save this as a candidate class
94 if (not candidate_class and method_name in cls.__dict__):
95 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
96 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
97 # however if it is annotated for a different interface
98 # than we are looking for, it cannot be a candidate
99 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
100 candidate_class = cls
101 parent_method = cls.__dict__[method_name]
107 candidate_class = cls
109 # if we have a candidate class, carry on checking this and all
110 # superclasses for a method annoated as a dbus method
111 # on the correct interface
112 if (candidate_class and method_name in cls.__dict__
113 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
114 and "_dbus_interface" in cls.__dict__[method_name].__dict__
115 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
116 # the candidate class has a dbus method on the correct interface,
117 # or overrides a method that is, success!
118 parent_method = cls.__dict__[method_name]
123 # simpler version of above
124 for cls in self.__class__.__mro__:
125 if (not candidate_class and method_name in cls.__dict__):
126 candidate_class = cls
128 if (candidate_class and method_name in cls.__dict__
129 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
130 parent_method = cls.__dict__[method_name]
135 return (candidate_class.__dict__[method_name], parent_method)
138 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
140 raise UnknownMethodException('%s is not a valid method' % method_name)
143 def _method_reply_return(connection, message, method_name, signature, *retval):
144 reply = dbus_bindings.MethodReturn(message)
145 iter = reply.get_iter(append=True)
147 # do strict adding if an output signature was provided
149 if len(signature) > len(retval):
150 raise TypeError('output signature %s is longer than the number of values returned by %s' %
151 (signature, method_name))
152 elif len(retval) > len(signature):
153 raise TypeError('output signature %s is shorter than the number of values returned by %s' %
154 (signature, method_name))
156 for (value, sig) in zip(retval, signature):
157 iter.append_strict(value, sig)
159 # no signature, try and guess the return type by inspection
164 connection.send(reply)
167 def _method_reply_error(connection, message, exception):
168 if '_dbus_error_name' in exception.__dict__:
169 name = exception._dbus_error_name
170 elif exception.__module__ == '__main__':
171 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
173 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
175 contents = traceback.format_exc()
176 reply = dbus_bindings.Error(message, name, contents)
178 connection.send(reply)
181 class InterfaceType(type):
182 def __init__(cls, name, bases, dct):
183 # these attributes are shared between all instances of the Interface
184 # object, so this has to be a dictionary that maps class names to
185 # the per-class introspection/interface data
186 class_table = getattr(cls, '_dbus_class_table', {})
187 cls._dbus_class_table = class_table
188 interface_table = class_table[cls.__module__ + '.' + name] = {}
190 # merge all the name -> method tables for all the interfaces
191 # implemented by our base classes into our own
193 base_name = b.__module__ + '.' + b.__name__
194 if getattr(b, '_dbus_class_table', False):
195 for (interface, method_table) in class_table[base_name].iteritems():
196 our_method_table = interface_table.setdefault(interface, {})
197 our_method_table.update(method_table)
199 # add in all the name -> method entries for our own methods/signals
200 for func in dct.values():
201 if getattr(func, '_dbus_interface', False):
202 method_table = interface_table.setdefault(func._dbus_interface, {})
203 method_table[func.__name__] = func
205 super(InterfaceType, cls).__init__(name, bases, dct)
207 # methods are different to signals, so we have two functions... :)
208 def _reflect_on_method(cls, func):
209 args = func._dbus_args
211 if func._dbus_in_signature:
212 # convert signature into a tuple so length refers to number of
213 # types, not number of characters. the length is checked by
214 # the decorator to make sure it matches the length of args.
215 in_sig = tuple(dbus_bindings.Signature(func._dbus_in_signature))
217 # magic iterator which returns as many v's as we need
218 in_sig = dbus_bindings.VariantSignature()
220 if func._dbus_out_signature:
221 out_sig = dbus_bindings.Signature(func._dbus_out_signature)
223 # its tempting to default to dbus_bindings.Signature('v'), but
224 # for methods that return nothing, providing incorrect
225 # introspection data is worse than providing none at all
228 reflection_data = ' <method name="%s">\n' % (func.__name__)
229 for pair in zip(in_sig, args):
230 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
232 reflection_data += ' <arg direction="out" type="%s" />\n' % type
233 reflection_data += ' </method>\n'
235 return reflection_data
237 def _reflect_on_signal(cls, func):
238 args = func._dbus_args
240 if func._dbus_signature:
241 # convert signature into a tuple so length refers to number of
242 # types, not number of characters
243 sig = tuple(dbus_bindings.Signature(func._dbus_signature))
245 # magic iterator which returns as many v's as we need
246 sig = dbus_bindings.VariantSignature()
248 reflection_data = ' <signal name="%s">\n' % (func.__name__)
249 for pair in zip(sig, args):
250 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
251 reflection_data = reflection_data + ' </signal>\n'
253 return reflection_data
255 class Interface(object):
256 __metaclass__ = InterfaceType
258 class Object(Interface):
259 """A base class for exporting your own Objects across the Bus.
261 Just inherit from Object and provide a list of methods to share
264 def __init__(self, bus_name, object_path):
265 self._object_path = object_path
266 self._name = bus_name
267 self._bus = bus_name.get_bus()
269 self._connection = self._bus.get_connection()
271 self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
273 def _unregister_cb(self, connection):
276 def _message_cb(self, connection, message):
278 # lookup candidate method and parent method
279 method_name = message.get_member()
280 interface_name = message.get_interface()
281 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
283 # set up method call parameters
284 args = message.get_args_list()
287 # iterate signature into list of complete types
288 if parent_method._dbus_out_signature:
289 signature = tuple(dbus_bindings.Signature(parent_method._dbus_out_signature))
293 # set up async callback functions
294 if parent_method._dbus_async_callbacks:
295 (return_callback, error_callback) = parent_method._dbus_async_callbacks
296 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
297 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
299 # include the sender if desired
300 if parent_method._dbus_sender_keyword:
301 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
304 retval = candidate_method(self, *args, **keywords)
306 # we're done - the method has got callback functions to reply with
307 if parent_method._dbus_async_callbacks:
310 # otherwise we send the return values in a reply. if we have a
311 # signature, use it to turn the return value into a tuple as
313 if parent_method._dbus_out_signature:
314 # if we have zero or one return values we want make a tuple
315 # for the _method_reply_return function, otherwise we need
316 # to check we're passing it a sequence
317 if len(signature) == 0:
321 raise TypeError('%s has an empty output signature but did not return None' %
323 elif len(signature) == 1:
326 if operator.isSequenceType(retval):
327 # multi-value signature, multi-value return... proceed unchanged
330 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
331 (method_name, signature))
333 # no signature, so just turn the return into a tuple and send it as normal
341 _method_reply_return(connection, message, method_name, signature, *retval)
342 except Exception, exception:
344 _method_reply_error(connection, message, exception)
346 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
347 def Introspect(self):
348 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'
349 reflection_data += '<node name="%s">\n' % (self._object_path)
351 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
352 for (name, funcs) in interfaces.iteritems():
353 reflection_data += ' <interface name="%s">\n' % (name)
355 for func in funcs.values():
356 if getattr(func, '_dbus_is_method', False):
357 reflection_data += self.__class__._reflect_on_method(func)
358 elif getattr(func, '_dbus_is_signal', False):
359 reflection_data += self.__class__._reflect_on_signal(func)
361 reflection_data += ' </interface>\n'
363 reflection_data += '</node>\n'
365 return reflection_data
368 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))