Creating (rather) generic plugin framework?

Edvard Majakari edvard at
Wed Nov 16 16:14:27 CET 2005


My idea is to create a system working as follows: each module knows
path to plugin directory, and that directory contains modules which
may add hooks to some points in the code.

Inspired by

I would create a class like this:

class Printer:

    def printit(self, msg):
        stuff = self.beforePrintHook(msg)
        if stuff:
            msg = stuff
        print msg

    def beforePrintHook(self, msg): pass
    def afterPrintHook(self, msg): pass

Now, in the spirit of py.test, I'd like API to be practically no API at all :)
moreover, deploying a plugin must consist simply of adding appropriate file to
plugins directory, nothing more, and removing it would uninstall it. The
plugin should be able to somehow result in all future invocations to
Printer.printit() call hooks specified in the plugin. Now, the plugin module
for class above /might/ be along the following lines (I'm thinking of stuff
here, so I don't know yet what would be the most appropriate way):

### a very simple plugin which uppercases all data fed to it.

extensions = {'Printer': 'PrinterHook'}

class PrinterHook:

    def beforePrintHook(self, msg):
        return msg.upper()
    def afterPrintHook(self, msg):
        print "Called afterPrintHook with msg %s" % msg

Now, I have a very rude (I think) implementation which has two methods, first
the one that loads plugin modules:

def find_plugins():
    mods = [mod for mod in os.listdir(PLUGIN_DIR) if mod.endswith('.py')]

    # for each module in plugin dir, import the module and setup hooks.  Hooks
    # must have equal method names in plugin module as in the original class.
    for mod in mods:
        name = os.path.splitext(mod)[0]
        fobj, fpath, desc = imp.find_module(os.path.join(PLUGIN_DIR, name))
        module = imp.load_module(name, fobj, fpath, desc)

...then the other that is responsible for setting up hooks

def set_hooks(mod):
    # mod.extensions has "base" class names as keys, (hook, priority) as
    # values
    for k, hook in mod.extensions.items():
        # get class object
        hook_cls = mod.__dict__[hook]

            base = globals()[k]
        except KeyError:
            print "No such class to insert hooks to:", k
            for item in base.__dict__:
                if item.endswith('Hook'):
                    # Override original (no-op) hook method
                    # uhh.. kludgety kludge
                    base.__dict__[item] = hook_cls.__dict__[item]

now, my just does as follows:

p = Printer()
p.printit('Hello, world!')

which prints

$ python2.4
Called afterPrintHook with msg HELLO, WORLD!

But hey, this has many downsides. First off, mechanism doesn't support
arbitrary namespaces. Here, class identifier in the plugin must be as it is
seen from the module which calls the plugin (not a problem for me, but could
be more elegant; probably a mapping between logical class identifiers and
actual class names, hmm?). Second, if one wants to use many hooks (with
priority for conflicts), it is not possible now; set_hooks always overrides
potentially existing hooks. And probably many other problems that are not
obvious to me, but for the simple purpose I have in mind, it seems to work.

This is the first plugin system in Python I'm writing, so I can be a way off
the correct path..

More information about the Python-list mailing list