I'm trying to dynamically add signals to a D-Bus service using dbus-python. It provides a decorator for this that works fine if the signal names are known at module load time; however, I don't know what name to export to D-Bus until runtime.
To illustrate the problem, what I'd like to do is the moral equivalent of:
import dbus
import dbus.service
import gobject
from dbus.mainloop.glib import DBusGMainLoop
class Event(dbus.service.Object):
def __init__(self, name):
self.name = name
self.busName = dbus.service.BusName('com.acme.EventManager',
bus=dbus.SessionBus())
dbus.service.Object.__init__(self,
self.busName,
'/com/acme/EventManager/' +
self.name)
self.signame = 'com.acme.EventManager.' + self.name
# THIS DOES NOT WORK: this decorator is parsed before the Event
# class, and 'self' wouldn't exist here, anyway...
@dbus.service.signal(dbus_interface=self.signame, signature='v')
def emit(self, data):
print "In %s event, got: %s " % (self.name, data)
if __name__ == "__main__":
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
loop = gobject.MainLoop()
connect = Event('Connect')
disconnect = Event('Disconnect')
loop.run()
Not surprisingly, this generates:
@dbus.service.signal(dbus_interface=self.signame, signature='v')
NameError: name 'self' is not defined
I thought I could just dispense with the syntactic sugar provided by the @
decoration operator and patch Event manually after the class has been defined, something like this:
import dbus
import dbus.service
import gobject
from dbus.mainloop.glib import DBusGMainLoop
class Event(dbus.service.Object):
def __init__(self, name):
self.name = name
self.busName = dbus.service.BusName('com.acme.EventManager',
bus=dbus.SessionBus())
dbus.service.Object.__init__(self,
self.busName,
'/com/acme/EventManager/' +
self.name)
self.signame = 'com.acme.EventManager.' + self.name
def emit(self, data):
print "In %s event, got: %s " % (self.name, data)
if __name__ == "__main__":
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
loop = gobject.MainLoop()
e1 = Event('Connect')
e1.emit = dbus.service.signal(dbus_interface=e1.signame,
signature='v')(e1.emit)
loop.run()
This runs without errors, but it fails to export the signals to D-Bus. When I run D-Feet, I see the object path /com/acme/EventManager/Connect
but it has no interface methods aside from Introspect()
.
To see if I could learn something about what was going on, I examined the value of the function passed to the dbus.service.signal
decorator in a debugger. For this typical use case:
@dbus.service.signal(dbus_interface='com.acme.foo', signature='v')
def emit(self, data):
pass
the function passed in to the decorator (in the variable func
) looks like this:
>>> func
>>> <function emit at 0x99fbed4>
But when I manually invoke the decorator function (as in the the e1.emit =
assignment in the second example above), I see:
>>> func
>>> <bound method Event.emit of <__main__.Event at /com/acme/EventManager/Connect at 0x9b3348c>>
So... it seems that, for the normal use case, dbus.service.signal
expects to receive a free function---not an unbound function, but a function that, for all intents and purposes, looks like it was defined using:
def emit():
pass
This behavior is utterly mystifying to me. I've read lots of tutorials on decorators, and thought I understood them pretty well, but I have been stumped on this for hours. If this decorator expects to be called with 'raw' function, how can I convert an object method such that I can invoke it manually?
From this question, I see that types.MethodType()
can be used to convert a free function to a bound method. But it seems I need to do the opposite(?)