Glen Pitt-Pladdy :: Blog
Simple Python Plugins
Very often I end up needing an extensible framework that I can slot in new functionality regularly - basically a Plugin framework. I've done this kind of stuff for ages in one form or another for everything from automated infrastructure testing to home automation. This is a very common requirement for a lot more applications.
For Python there are quite a few different modules floating around for doing Plugins, but already baked in there are all the parts you need to do simple Plugins.
I'm going to be sticking to Python 2.7 for this as things have changed in 3.x, and most code kicking around is still 2.7 code.
Basic nuts 'n bolts
The basics of Plugins in everything from C++ to Perl is fairly similar: dynamically load a library/module containing fixed entry points that the loader can call to use the plugin. Often there is some registration process where the Plugin attaches it's self to the appropriate data and event structures in the loading process, or provides metadata for the loader to do that for it. Potentially you also want the ability to unload the Plugin, but that can get more complex.
A typical flow of events in the Loader will be something like:
Now, what should immediately become obvious here every Plugin needs to have a common structure for interfacing with it, and hence in OO programming inheriting from a template class will likely make a lot of sense and save loads of hassle keeping things manageable.
That's not to say it's absolutely necessary - I have some code which only needs a bit of metadata (set some variables) and maybe a single function for each of a small number of Plugins and creating templates risks introducing complications that could make things less reliable and maintainable for such simple code.
In Python we will be using the imp module which has two basic functions that are relevant to us. The first step is to locate and gather the data needed to load the module (Plugin). This is done with something like:
info = imp.find_module ( name )
This searches various paths to locate a module matching the name given, and returns the data needed to load it.
Then we need to actually load the module:
plugin = imp.load_module ( name, *info )
Now plugin is a window into the contents of the module.
If there's a variable foo in the module then plugin.foo references that variable. If there's a function bar in the module then plugin.bar() executes that function. If there's a class baz then plugin.baz() returns an object of that class.
Provided the entry points (variables, functions, classes) in the module are consistent then there's nothing to stop you loading all the Plugins into an array or dict to be used within the program.
I've stuck some example Python Plugin code on GitHub for you to experiment with.
This includes additional code to print out the number of objects at different stages of the process and force garbage collection to ensure we get up-to-date counts. This is interesting since Python doesn't behave entirely like I would expect with this, but maybe someone who understands more about what's going on under the hood can explain.
There is an OO (Class/Object) example loader_classtest.py with Plugin plugin_classtest.py, and a super-simple non-OO example loader_functiontest.py with Plugin plugin_functiontest.py
Copyright Glen Pitt-Pladdy 2008-2017