decorator factory for dbus-python methods

This is a crazy idea I had; that I want to share with people.

When you're implementing an object in dbus-python, you decorate your published method calls like this:

class ExampleObserver(dbus.service.Object):
    ...

    @dbus.service.method(dbus_interface=telepathy.interfaces.CLIENT_OBSERVER,
                         in_signature='ooa(oa{sv})oaoa{sv}',
                         out_signature='')
    def ObserveChannels(self, account, connection, channels, dispatch_operation,
                        requests_satisfied, observer_info):
        ...

The input and output signatures are incredibly easy to get wrong. The thing is, most D-Bus APIs (e.g. Telepathy) have a specification that contains these arguments. Some APIs (e.g. Telepathy-Python) provide generated code including interface names and constants. So why can't we do something more like?

class ExampleObserver(dbus.service.Object):
    ...

    @telepathy.decorators.Telepathy.Client.Observer.ObserveChannels
    def ObserveChannels(self, account, connection, channels, dispatch_operation,
                        requests_satisfied, observer_info):
        ...

With a decorator factory that looks up the parameters and then wraps the dbus.service.method factory.

Well, I just wrote a proof-of-concept. It looks something like this:

class Decorators(object):
    methods = {
        'org.freedesktop.DBus.Properties.GetAll': [ 's', 'a{sv}' ],
        'org.freedesktop.DBus.Properties.Get': [ 'ss', 'v' ],
        'org.freedesktop.Telepathy.Client.Observer.ObserveChannels': [ 'ooa(oa{sv})oaoa{sv}', '' ],
    }

    def __init__(self, namespace):
        self._namespace = namespace

    def __getattr__(self, key):
        return Decorators('%s.%s' % (self._namespace, key))

    def __call__(self, func):
        iface = self._namespace.rsplit('.', 1)[0]
        in_sig, out_sig = self.methods[self._namespace]
        return dbus.service.method(dbus_interface=iface,
                                   in_signature=in_sig,
                                   out_signature=out_sig)(func)

    def __str__(self):
        return self._namespace

decorators = Decorators('org.freedesktop')

Obviously in the real version, it would have a generated map of functions, or map of interfaces each with a map of functions, and a way to handle signals, but neat huh?

Leave a Reply

Your email address will not be published. Required fields are marked *

Creative Commons Attribution-ShareAlike 2.5 Australia
This work by Danielle Madeley is licensed under a Creative Commons Attribution-ShareAlike 2.5 Australia.