Rename dbus_bindings (sometimes a.k.a. dbus.dbus_bindings) to _dbus_bindings.
[dbus-python-phuang.git] / dbus / service.py
blobe0b784dae57fb83e85c43e4f6c3ae4abac16eb18
1 __all__ = ('BusName', 'Object', 'method', 'signal')
2 __docformat__ = 'restructuredtext'
4 import _dbus_bindings
5 import _dbus
6 import operator
7 import traceback
9 from exceptions import NameExistsException
10 from exceptions import UnknownMethodException
11 from decorators import method
12 from decorators import signal
14 class BusName(object):
15 """A base class for exporting your own Named Services across the Bus.
17 When instantiated, objects of this class attempt to claim the given
18 well-known name on the given bus for the current process. The name is
19 released when the BusName object becomes unreferenced.
21 If a well-known name is requested multiple times, multiple references
22 to the same BusName object will be returned.
24 Caveats
25 -------
26 - Assumes that named services are only ever requested using this class -
27 if you request names from the bus directly, confusion may occur.
28 - Does not handle queueing.
29 """
30 def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
31 """Constructor, which may either return an existing cached object
32 or a new object.
34 :Parameters:
35 `name` : str
36 The well-known name to be advertised
37 `bus` : dbus.Bus
38 A Bus on which this service will be advertised; if None
39 (default) a default bus will be used
40 `allow_replacement` : bool
41 If True, other processes trying to claim the same well-known
42 name will take precedence over this one.
43 `replace_existing` : bool
44 If True, this process can take over the well-known name
45 from other processes already holding it.
46 `do_not_queue` : bool
47 If True, this service will not be placed in the queue of
48 services waiting for the requested name if another service
49 already holds it.
50 """
51 # get default bus
52 if bus == None:
53 bus = _dbus.Bus()
55 # see if this name is already defined, return it if so
56 # FIXME: accessing internals of Bus
57 if name in bus._bus_names:
58 return bus._bus_names[name]
60 # otherwise register the name
61 name_flags = \
62 _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT * allow_replacement + \
63 _dbus_bindings.NAME_FLAG_REPLACE_EXISTING * replace_existing + \
64 _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE * do_not_queue
66 retval = _dbus_bindings.bus_request_name(bus.get_connection(), name, name_flags)
68 # TODO: more intelligent tracking of bus name states?
69 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
70 pass
71 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
72 # queueing can happen by default, maybe we should
73 # track this better or let the user know if they're
74 # queued or not?
75 pass
76 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
77 raise NameExistsException(name)
78 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
79 # if this is a shared bus which is being used by someone
80 # else in this process, this can happen legitimately
81 pass
82 else:
83 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
85 # and create the object
86 bus_name = object.__new__(cls)
87 bus_name._bus = bus
88 bus_name._name = name
90 # cache instance (weak ref only)
91 # FIXME: accessing Bus internals again
92 bus._bus_names[name] = bus_name
94 return bus_name
96 # do nothing because this is called whether or not the bus name
97 # object was retrieved from the cache or created new
98 def __init__(self, *args, **keywords):
99 pass
101 # we can delete the low-level name here because these objects
102 # are guaranteed to exist only once for each bus name
103 def __del__(self):
104 _dbus_bindings.bus_release_name(self._bus.get_connection(), self._name)
105 pass
107 def get_bus(self):
108 """Get the Bus this Service is on"""
109 return self._bus
111 def get_name(self):
112 """Get the name of this service"""
113 return self._name
115 def __repr__(self):
116 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
117 __str__ = __repr__
120 def _method_lookup(self, method_name, dbus_interface):
121 """Walks the Python MRO of the given class to find the method to invoke.
123 Returns two methods, the one to call, and the one it inherits from which
124 defines its D-Bus interface name, signature, and attributes.
126 parent_method = None
127 candidate_class = None
128 successful = False
130 # split up the cases when we do and don't have an interface because the
131 # latter is much simpler
132 if dbus_interface:
133 # search through the class hierarchy in python MRO order
134 for cls in self.__class__.__mro__:
135 # if we haven't got a candidate class yet, and we find a class with a
136 # suitably named member, save this as a candidate class
137 if (not candidate_class and method_name in cls.__dict__):
138 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
139 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
140 # however if it is annotated for a different interface
141 # than we are looking for, it cannot be a candidate
142 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
143 candidate_class = cls
144 parent_method = cls.__dict__[method_name]
145 successful = True
146 break
147 else:
148 pass
149 else:
150 candidate_class = cls
152 # if we have a candidate class, carry on checking this and all
153 # superclasses for a method annoated as a dbus method
154 # on the correct interface
155 if (candidate_class and method_name in cls.__dict__
156 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
157 and "_dbus_interface" in cls.__dict__[method_name].__dict__
158 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
159 # the candidate class has a dbus method on the correct interface,
160 # or overrides a method that is, success!
161 parent_method = cls.__dict__[method_name]
162 successful = True
163 break
165 else:
166 # simpler version of above
167 for cls in self.__class__.__mro__:
168 if (not candidate_class and method_name in cls.__dict__):
169 candidate_class = cls
171 if (candidate_class and method_name in cls.__dict__
172 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
173 parent_method = cls.__dict__[method_name]
174 successful = True
175 break
177 if successful:
178 return (candidate_class.__dict__[method_name], parent_method)
179 else:
180 if dbus_interface:
181 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
182 else:
183 raise UnknownMethodException('%s is not a valid method' % method_name)
186 def _method_reply_return(connection, message, method_name, signature, *retval):
187 reply = _dbus_bindings.MethodReturn(message)
188 iter = reply.get_iter(append=True)
190 # do strict adding if an output signature was provided
191 if signature:
192 if len(signature) > len(retval):
193 raise TypeError('output signature %s is longer than the number of values returned by %s' %
194 (signature, method_name))
195 elif len(retval) > len(signature):
196 raise TypeError('output signature %s is shorter than the number of values returned by %s' %
197 (signature, method_name))
198 else:
199 for (value, sig) in zip(retval, signature):
200 iter.append_strict(value, sig)
202 # no signature, try and guess the return type by inspection
203 else:
204 for value in retval:
205 iter.append(value)
207 connection.send(reply)
210 def _method_reply_error(connection, message, exception):
211 if '_dbus_error_name' in exception.__dict__:
212 name = exception._dbus_error_name
213 elif exception.__module__ == '__main__':
214 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
215 else:
216 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
218 contents = traceback.format_exc()
219 reply = _dbus_bindings.Error(message, name, contents)
221 connection.send(reply)
224 class InterfaceType(type):
225 def __init__(cls, name, bases, dct):
226 # these attributes are shared between all instances of the Interface
227 # object, so this has to be a dictionary that maps class names to
228 # the per-class introspection/interface data
229 class_table = getattr(cls, '_dbus_class_table', {})
230 cls._dbus_class_table = class_table
231 interface_table = class_table[cls.__module__ + '.' + name] = {}
233 # merge all the name -> method tables for all the interfaces
234 # implemented by our base classes into our own
235 for b in bases:
236 base_name = b.__module__ + '.' + b.__name__
237 if getattr(b, '_dbus_class_table', False):
238 for (interface, method_table) in class_table[base_name].iteritems():
239 our_method_table = interface_table.setdefault(interface, {})
240 our_method_table.update(method_table)
242 # add in all the name -> method entries for our own methods/signals
243 for func in dct.values():
244 if getattr(func, '_dbus_interface', False):
245 method_table = interface_table.setdefault(func._dbus_interface, {})
246 method_table[func.__name__] = func
248 super(InterfaceType, cls).__init__(name, bases, dct)
250 # methods are different to signals, so we have two functions... :)
251 def _reflect_on_method(cls, func):
252 args = func._dbus_args
254 if func._dbus_in_signature:
255 # convert signature into a tuple so length refers to number of
256 # types, not number of characters. the length is checked by
257 # the decorator to make sure it matches the length of args.
258 in_sig = tuple(_dbus_bindings.Signature(func._dbus_in_signature))
259 else:
260 # magic iterator which returns as many v's as we need
261 in_sig = _dbus_bindings.VariantSignature()
263 if func._dbus_out_signature:
264 out_sig = _dbus_bindings.Signature(func._dbus_out_signature)
265 else:
266 # its tempting to default to _dbus_bindings.Signature('v'), but
267 # for methods that return nothing, providing incorrect
268 # introspection data is worse than providing none at all
269 out_sig = []
271 reflection_data = ' <method name="%s">\n' % (func.__name__)
272 for pair in zip(in_sig, args):
273 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
274 for type in out_sig:
275 reflection_data += ' <arg direction="out" type="%s" />\n' % type
276 reflection_data += ' </method>\n'
278 return reflection_data
280 def _reflect_on_signal(cls, func):
281 args = func._dbus_args
283 if func._dbus_signature:
284 # convert signature into a tuple so length refers to number of
285 # types, not number of characters
286 sig = tuple(_dbus_bindings.Signature(func._dbus_signature))
287 else:
288 # magic iterator which returns as many v's as we need
289 sig = _dbus_bindings.VariantSignature()
291 reflection_data = ' <signal name="%s">\n' % (func.__name__)
292 for pair in zip(sig, args):
293 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
294 reflection_data = reflection_data + ' </signal>\n'
296 return reflection_data
298 class Interface(object):
299 __metaclass__ = InterfaceType
301 class Object(Interface):
302 """A base class for exporting your own Objects across the Bus.
304 Just inherit from Object and provide a list of methods to share
305 across the Bus.
307 Issues
308 ------
309 - The constructor takes a well-known name: this is wrong. There should be
310 no requirement to register a well-known name in order to export bus
311 objects.
313 def __init__(self, bus_name, object_path):
314 """Constructor.
316 :Parameters:
317 `bus_name` : BusName
318 Represents a well-known name claimed by this process. A
319 reference to the BusName object will be held by this
320 Object, preventing the name from being released during this
321 Object's lifetime (subject to status).
322 `object_path` : str
323 The D-Bus object path at which to export this Object.
325 self._object_path = object_path
326 self._name = bus_name
327 self._bus = bus_name.get_bus()
329 self._connection = self._bus.get_connection()
331 self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
333 def _unregister_cb(self, connection):
334 print ("Unregister")
336 def _message_cb(self, connection, message):
337 try:
338 # lookup candidate method and parent method
339 method_name = message.get_member()
340 interface_name = message.get_interface()
341 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
343 # set up method call parameters
344 args = message.get_args_list()
345 keywords = {}
347 # iterate signature into list of complete types
348 if parent_method._dbus_out_signature:
349 signature = tuple(_dbus_bindings.Signature(parent_method._dbus_out_signature))
350 else:
351 signature = None
353 # set up async callback functions
354 if parent_method._dbus_async_callbacks:
355 (return_callback, error_callback) = parent_method._dbus_async_callbacks
356 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
357 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
359 # include the sender if desired
360 if parent_method._dbus_sender_keyword:
361 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
363 # call method
364 retval = candidate_method(self, *args, **keywords)
366 # we're done - the method has got callback functions to reply with
367 if parent_method._dbus_async_callbacks:
368 return
370 # otherwise we send the return values in a reply. if we have a
371 # signature, use it to turn the return value into a tuple as
372 # appropriate
373 if parent_method._dbus_out_signature:
374 # if we have zero or one return values we want make a tuple
375 # for the _method_reply_return function, otherwise we need
376 # to check we're passing it a sequence
377 if len(signature) == 0:
378 if retval == None:
379 retval = ()
380 else:
381 raise TypeError('%s has an empty output signature but did not return None' %
382 method_name)
383 elif len(signature) == 1:
384 retval = (retval,)
385 else:
386 if operator.isSequenceType(retval):
387 # multi-value signature, multi-value return... proceed unchanged
388 pass
389 else:
390 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
391 (method_name, signature))
393 # no signature, so just turn the return into a tuple and send it as normal
394 else:
395 signature = None
396 if retval == None:
397 retval = ()
398 else:
399 retval = (retval,)
401 _method_reply_return(connection, message, method_name, signature, *retval)
402 except Exception, exception:
403 # send error reply
404 _method_reply_error(connection, message, exception)
406 @method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
407 def Introspect(self):
408 """Return a string of XML encoding this object's supported interfaces,
409 methods and signals.
411 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'
412 reflection_data += '<node name="%s">\n' % (self._object_path)
414 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
415 for (name, funcs) in interfaces.iteritems():
416 reflection_data += ' <interface name="%s">\n' % (name)
418 for func in funcs.values():
419 if getattr(func, '_dbus_is_method', False):
420 reflection_data += self.__class__._reflect_on_method(func)
421 elif getattr(func, '_dbus_is_signal', False):
422 reflection_data += self.__class__._reflect_on_signal(func)
424 reflection_data += ' </interface>\n'
426 reflection_data += '</node>\n'
428 return reflection_data
430 def __repr__(self):
431 return '<dbus.service.Object %s on %r at %#x>' % (self._object_path, self._name, id(self))
432 __str__ = __repr__