Submitted by Paul Kishimoto on

DBus is great, and so is Python—each for their own reasons. One mismatch is that everything passing over the bus must be a message or signal. You wind up with Python code that looks like

  proxyobject.SetThis('foo')
  bar = proxyobject.GetThat()
  proxyobject.DoSomeOtherStuff()

But then you realize the dbus-python binding is very Pythonic, so you can extend it in a Pythonic way. Here's something I banged together in about half an hour:

import dbus

class FancyInterface(dbus.Interface):
    """A fancier DBus Interface.
    
    Derived classes should define a list of strings __dbus_properties__ and a
    _dbus_interface. For every listed property ('foo') the named interface
    should support two methods:
      * GetFoo() -- accepts nothing, returns a single value.
      * SetFoo(..) -- returns nothing, accepts a single value of the same type
        as GetFoo.
    
    Then DerivedClass.foo may be accessed as if it were a normal variable.
    
    """
    def __init__(self, object):
        # next four lines from dbus.Interface.__init__()
        if isinstance(object, dbus.Interface):
            self._obj = object.proxy_object
        else:
            self._obj = object
        # set up properties
        for a in self.__dbus_properties__:
            fget = lambda self: self._obj.get_dbus_method('Get' +
              a.capitalize())()
            fset = lambda self, value: self._obj.get_dbus_method('Set' +
              a.capitalize())(value)
            setattr(self.__class__, a, property(fget, fset))


class Sample(FancyInterface):
    _dbus_interface = 'com.example.SampleInterface'
    __dbus_properties__ = ('foo', 'bar',)
    def __init__(self, object): FancyInterface.__init__(self, object)
    # this class is 'empty' otherwise...

Using FancyInterface, it becomes even easier to forget you're talking to a proxy of a remote object:

    bus = dbus.SessionBus() 
    o = Sample(bus.get_object('com.example.SampleService',
      '/SomeObject'))
    o.foo = 'Hello, '
    o.bar = 'world!'
    print o.foo, o.bar

Look for this stuff being used in GTG in the near future! (Also, fixes to my blog CSS...)

Stories: 

Comments

Some comments: - Looks like

Some comments:

- Looks like you're setting the class attributes on every instance creation of a Sample object (or any other type deriving from FancyInterface). I guess that's not the desired behaviour?

- Why defining __init__ in Sample if it only calls the super constructor?

- I'd rather implement this using a metaclass mixin... OTOH (not on a Linux system right now...), with a slight API change ('__dbus_interface__' instead of a protected '_dbus_interface' name):

class _FancyInterfaceMeta(type):
    def __new__(cls, name, bases, attrs):
        try:
            FancyInterface
        except NameError:
            return type.__new__(name, bases, attrs)

        interface = attrs.pop('__dbus_interface__')
        properties = attrs.pop('__dbus_properties__')

        assert '_dbus_interface' not in attrs
        attrs['_dbus_interface'] = interface

        for name in properties:
            assert name not in attrs

            name_ = name.capitalize()

            fget = lambda self: self._obj.get_dbus_method('Get%s' % name_)
            fset = lambda self, value: self._obj.get_dbus_method('Set%s' % name_)(value)
            prop = property(fget, fset, doc='Accessor to DBus property %s' % name)

            attrs[name] = prop

        return type.__new__(name, bases, attrs)


class FancyInterface(object, dbus.Interface):
    __metaclass__ = _FancyInterfaceMeta

    def __init__(self, object_):
        if isinstance(object_, dbus.Interface):
            self._obj = object_.proxy_object
        else:
            self._obj = object_


class Sample(FancyInterface):
    __dbus_interface__ = 'com.example.SampleInterface'
    __dbus_properties__ = 'foo', 'bar',

@Nicolas: No, it's not,

@Nicolas:

  • No, it's not, you're correct.
  • At one point I had another line of code in there.
  • The mixin concept is new to me, but that's really neat. Also, _dbus_interface is used elsewhere in the parent classes, so I just reused it.

@Staz: That's handy! So GetAll() can obviate storing __dbus_properties__, and Get() and Set() will simplify defining the lambda functions for fget and fset.

Thanks both for your comments!

One problem with this

One problem with this proposal is that people expect attribute access to be fast, but will tolerate slow method calls. People usually code accordingly, which can lead to quite slow programs (e.g. repeatedly getting the property when it could do with a single call).

A D-Bus method call (which the standard property access interface entails) certainly doesn't count as fast, since it will block waiting for the response.