mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2025-12-28 03:50:18 +01:00
* python/service.py: Major changes to allow multiple inheritance
from classes that define D-Bus interfaces:
1. Create a new Interface class which is the parent class of
Object, and make the ObjectType metaclass into InterfaceType.
2. Patch written with Rob Taylor to replace use of method_vtable
with code that walks the class's __MRO__ (method resolution order)
to behave like Python does when invoking methods and allow
overriding as you'd expect. Code is quite tricky because
we have to find two methods, the one to invoke which has the
right name and isn't decorated with the /wrong/ interface,
and the one to pick up the signatures from which is decorated
with the right interface.
The same caveats apply as to normal multiple inheritance -
this has undefined behaviour if you try and inherit from two
classes that define a method with the same name but are
decorated with different interfaces. You should decorate
your overriding method with the interface you want.
3. Replace grungy introspection XML generation code in the metaclass
with dictionaries that cope correctly with multiple inheritance
and the overriding of methods. This also uses the signature
decorations to provide correct introspection data, including
the debut appearance of the types of your return values. :D
* test/python/test-client.py, test/python/test-service.py: Add a test
case to try invoking an method that overrides one inherited from a
D-Bus interface class.
282 lines
12 KiB
Python
282 lines
12 KiB
Python
|
|
import dbus_bindings
|
|
import _dbus
|
|
import operator
|
|
from exceptions import UnknownMethodException
|
|
from decorators import method
|
|
from decorators import signal
|
|
|
|
class BusName:
|
|
"""A base class for exporting your own Named Services across the Bus
|
|
"""
|
|
def __init__(self, named_service, bus=None):
|
|
self._named_service = named_service
|
|
|
|
if bus == None:
|
|
# Get the default bus
|
|
self._bus = _dbus.Bus()
|
|
else:
|
|
self._bus = bus
|
|
|
|
dbus_bindings.bus_request_name(self._bus.get_connection(), named_service)
|
|
|
|
def get_bus(self):
|
|
"""Get the Bus this Service is on"""
|
|
return self._bus
|
|
|
|
def get_name(self):
|
|
"""Get the name of this service"""
|
|
return self._named_service
|
|
|
|
def _dispatch_dbus_method_call(self, argument_list, message):
|
|
"""Calls method_to_call using argument_list, but handles
|
|
exceptions, etc, and generates a reply to the DBus Message message
|
|
"""
|
|
try:
|
|
method_name = message.get_member()
|
|
dbus_interface = message.get_interface()
|
|
candidate = None
|
|
successful = False
|
|
|
|
# split up the cases when we do and don't have an interface because the
|
|
# latter is much simpler
|
|
if dbus_interface:
|
|
# search through the class hierarchy in python MRO order
|
|
for cls in self.__class__.__mro__:
|
|
# if we haven't got a candidate yet, and we find a class with a
|
|
# suitably named member, save this as a candidate
|
|
if (not candidate and method_name in cls.__dict__):
|
|
if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
|
|
and "_dbus_interface" in cls.__dict__[method_name].__dict__):
|
|
# however if it is annotated for a different interface
|
|
# than we are looking for, it cannot be a candidate
|
|
if cls.__dict__[method_name]._dbus_interface == dbus_interface:
|
|
candidate = cls
|
|
sucessful = True
|
|
target_parent = cls.__dict__[method_name]
|
|
break
|
|
else:
|
|
pass
|
|
else:
|
|
candidate = cls
|
|
|
|
# if we have a candidate, carry on checking this and all
|
|
# superclasses for a method annoated as a dbus method
|
|
# on the correct interface
|
|
if (candidate and method_name in cls.__dict__
|
|
and "_dbus_is_method" in cls.__dict__[method_name].__dict__
|
|
and "_dbus_interface" in cls.__dict__[method_name].__dict__
|
|
and cls.__dict__[method_name]._dbus_interface == dbus_interface):
|
|
# the candidate is a dbus method on the correct interface,
|
|
# or overrides a method that is, success!
|
|
target_parent = cls.__dict__[method_name]
|
|
sucessful = True
|
|
break
|
|
|
|
else:
|
|
# simpler version of above
|
|
for cls in self.__class__.__mro__:
|
|
if (not candidate and method_name in cls.__dict__):
|
|
candidate = cls
|
|
|
|
if (candidate and method_name in cls.__dict__
|
|
and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
|
|
target_parent = cls.__dict__[method_name]
|
|
sucessful = True
|
|
break
|
|
|
|
retval = candidate.__dict__[method_name](self, *argument_list)
|
|
target_name = str(candidate.__module__) + '.' + candidate.__name__ + '.' + method_name
|
|
|
|
if not sucessful:
|
|
if not dbus_interface:
|
|
raise UnknownMethodException('%s is not a valid method'%(message.get_member()))
|
|
else:
|
|
raise UnknownMethodException('%s is not a valid method of interface %s'%(message.get_member(), dbus_interface))
|
|
|
|
except Exception, e:
|
|
if e.__module__ == '__main__':
|
|
# FIXME: is it right to use .__name__ here?
|
|
error_name = e.__class__.__name__
|
|
else:
|
|
error_name = e.__module__ + '.' + str(e.__class__.__name__)
|
|
|
|
error_contents = str(e)
|
|
reply = dbus_bindings.Error(message, error_name, error_contents)
|
|
else:
|
|
reply = dbus_bindings.MethodReturn(message)
|
|
|
|
# do strict adding if an output signature was provided
|
|
if target_parent._dbus_out_signature != None:
|
|
# iterate signature into list of complete types
|
|
signature = tuple(dbus_bindings.Signature(target_parent._dbus_out_signature))
|
|
|
|
if retval == None:
|
|
if len(signature) != 0:
|
|
raise TypeError('%s returned nothing but output signature is %s' %
|
|
(target_name, target_parent._dbus_out_signature))
|
|
elif len(signature) == 1:
|
|
iter = reply.get_iter(append=True)
|
|
iter.append_strict(retval, signature[0])
|
|
elif len(signature) > 1:
|
|
if operator.isSequenceType(retval):
|
|
if len(signature) > len(retval):
|
|
raise TypeError('output signature %s is longer than the number of values returned by %s' %
|
|
(target_parent._dbus_out_signature, target_name))
|
|
elif len(retval) > len(signature):
|
|
raise TypeError('output signature %s is shorter than the number of values returned by %s' %
|
|
(target_parent._dbus_out_signature, target_name))
|
|
else:
|
|
iter = reply.get_iter(append=True)
|
|
for (value, sig) in zip(retval, signature):
|
|
iter.append_strict(value, sig)
|
|
else:
|
|
raise TypeError('output signature %s has multiple values but %s didn\'t return a sequence type' %
|
|
(target_parent._dbus_out_signature, target_name))
|
|
|
|
# try and guess the return type
|
|
elif retval != None:
|
|
iter = reply.get_iter(append=True)
|
|
iter.append(retval)
|
|
|
|
return reply
|
|
|
|
class InterfaceType(type):
|
|
def __init__(cls, name, bases, dct):
|
|
# these attributes are shared between all instances of the Interface
|
|
# object, so this has to be a dictionary that maps class names to
|
|
# the per-class introspection/interface data
|
|
class_table = getattr(cls, '_dbus_class_table', {})
|
|
cls._dbus_class_table = class_table
|
|
interface_table = class_table[cls.__module__ + '.' + name] = {}
|
|
|
|
# merge all the name -> method tables for all the interfaces
|
|
# implemented by our base classes into our own
|
|
for b in bases:
|
|
base_name = b.__module__ + '.' + b.__name__
|
|
if getattr(b, '_dbus_class_table', False):
|
|
for (interface, method_table) in class_table[base_name].iteritems():
|
|
our_method_table = interface_table.setdefault(interface, {})
|
|
our_method_table.update(method_table)
|
|
|
|
# add in all the name -> method entries for our own methods/signals
|
|
for func in dct.values():
|
|
if getattr(func, '_dbus_interface', False):
|
|
method_table = interface_table.setdefault(func._dbus_interface, {})
|
|
method_table[func.__name__] = func
|
|
|
|
super(InterfaceType, cls).__init__(name, bases, dct)
|
|
|
|
# methods are different to signals, so we have two functions... :)
|
|
def _reflect_on_method(cls, func):
|
|
args = func._dbus_args
|
|
|
|
if func._dbus_in_signature:
|
|
# convert signature into a tuple so length refers to number of
|
|
# types, not number of characters
|
|
in_sig = tuple(dbus_bindings.Signature(func._dbus_in_signature))
|
|
|
|
if len(in_sig) > len(args):
|
|
raise ValueError, 'input signature is longer than the number of arguments taken'
|
|
elif len(in_sig) < len(args):
|
|
raise ValueError, 'input signature is shorter than the number of arguments taken'
|
|
else:
|
|
# magic iterator which returns as many v's as we need
|
|
in_sig = dbus_bindings.VariantSignature()
|
|
|
|
if func._dbus_out_signature:
|
|
out_sig = dbus_bindings.Signature(func._dbus_out_signature)
|
|
else:
|
|
# its tempting to default to dbus_bindings.Signature('v'), but
|
|
# for methods that return nothing, providing incorrect
|
|
# introspection data is worse than providing none at all
|
|
out_sig = []
|
|
|
|
reflection_data = ' <method name="%s">\n' % (func.__name__)
|
|
for pair in zip(in_sig, args):
|
|
reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
|
|
for type in out_sig:
|
|
reflection_data += ' <arg direction="out" type="%s" />\n' % type
|
|
reflection_data += ' </method>\n'
|
|
|
|
return reflection_data
|
|
|
|
def _reflect_on_signal(cls, func):
|
|
args = func._dbus_args
|
|
|
|
if func._dbus_signature:
|
|
# convert signature into a tuple so length refers to number of
|
|
# types, not number of characters
|
|
sig = tuple(dbus_bindings.Signature(func._dbus_signature))
|
|
|
|
if len(sig) > len(args):
|
|
raise ValueError, 'signal signature is longer than the number of arguments provided'
|
|
elif len(sig) < len(args):
|
|
raise ValueError, 'signal signature is shorter than the number of arguments provided'
|
|
else:
|
|
# magic iterator which returns as many v's as we need
|
|
sig = dbus_bindings.VariantSignature()
|
|
|
|
reflection_data = ' <signal name="%s">\n' % (func.__name__)
|
|
for pair in zip(sig, args):
|
|
reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
|
|
reflection_data = reflection_data + ' </signal>\n'
|
|
|
|
return reflection_data
|
|
|
|
class Interface(object):
|
|
__metaclass__ = InterfaceType
|
|
|
|
class Object(Interface):
|
|
"""A base class for exporting your own Objects across the Bus.
|
|
|
|
Just inherit from Object and provide a list of methods to share
|
|
across the Bus
|
|
"""
|
|
def __init__(self, bus_name, object_path):
|
|
self._object_path = object_path
|
|
self._name = bus_name
|
|
self._bus = bus_name.get_bus()
|
|
|
|
self._connection = self._bus.get_connection()
|
|
|
|
self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb)
|
|
|
|
def _unregister_cb(self, connection):
|
|
print ("Unregister")
|
|
|
|
def _message_cb(self, connection, message):
|
|
try:
|
|
target_method_name = message.get_member()
|
|
args = message.get_args_list()
|
|
|
|
reply = _dispatch_dbus_method_call(self, args, message)
|
|
|
|
self._connection.send(reply)
|
|
except Exception, e:
|
|
error_reply = dbus_bindings.Error(message,
|
|
"org.freedesktop.DBus.Python.%s" % e.__class__.__name__,
|
|
str(e))
|
|
self._connection.send(error_reply)
|
|
|
|
@method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s')
|
|
def Introspect(self):
|
|
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'
|
|
reflection_data += '<node name="%s">\n' % (self._object_path)
|
|
|
|
interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
|
|
for (name, funcs) in interfaces.iteritems():
|
|
reflection_data += ' <interface name="%s">\n' % (name)
|
|
|
|
for func in funcs.values():
|
|
if getattr(func, '_dbus_is_method', False):
|
|
reflection_data += self.__class__._reflect_on_method(func)
|
|
elif getattr(func, '_dbus_is_signal', False):
|
|
reflection_data += self.__class__._reflect_on_signal(func)
|
|
|
|
reflection_data += ' </interface>\n'
|
|
|
|
reflection_data += '</node>\n'
|
|
|
|
return reflection_data
|
|
|