While working on the initial version of DjangoSVN, I realised that one of the coolest things I could do with the DjangoSVN command interpreter was to make it pluggable.
The idea was simple: You can set up a DjangoSVN server anywhere, then write your own custom plug-ins that you can just drop into the plug-in folder AND make use of the Django ORM should you want to give it database backing and an admin screen.
So I had to figure out how to make that work, and it was surprisingly easy, so this will be a short post. Credit where it is due, I found a lot of help over at StackOverflow – so never underestimate the true power of Google
So, how do you write a plug-in framework?
The first step is planning – in order to use a plug-in, it should have a set, fixed format that can be written in. In the case of DjangoSVN, I wrote a ‘master’ plug-in class that could be inherited from any new plug-in to add functionality – this would ensure that the interface between your app and any new plug-in will always work the same way. It also gives you a chance to think about how you’ll pass values to it and return them in a standardised way.
Here’s the interface for DjangoSVN plug-ins:
Base SVN Hook class, svncommand will load a hook class from the
target command and pass it the args and dsvncommand() object.
def __init__(self, args=None, dsvnobj=None):
self.args = args # The command arguments that are after the command
self.dsvnobj = dsvnobj # the dsvncommand() object that is called by the parser
# Can be ignored, but if set in init() will have an effect on how repository
# paths are managed.
self.django = False
This is what gets called by the command parser - override this function to
execute complex scripts
It’s pretty straightforward – essentially you have a class, with an init() function you can override. You can put whatever you like in here that’s specific to your app.
It get’s interesting when you look at how a plug-in gets called in the app itself – here’s the command loader from DjangoSVN that loads up a plug-in:
This will take the command and args variables, check a module list
in the COMMAND_PATH directory for a new hook
if it finds one it will kick off the execute_command() function after
importing the module
Module loading is dynamic, so more commands can be added as needed!
commands_dir = os.listdir(COMMAND_PATH)
found = False
if command.lower() in commands_dir:
hook_module = "from command_list." + command.lower() + ".hook import hook"
logme('Importing hook classn')
found = True
except Exception, e:
found = False
logme('Import failed:' + str(e))
logme("FOUND HOOK: " + str(command))
thishook = hook(args=args, dsvnobj=dsvnobj)
except Exception, e:
logme('Execute failed... n')
What is happening here is this:
- Get a list of the plug-ins that you have created in a directory – the plug-in name is the name of the directory (they are basically python modules)
- If the plug-in we’re looking for is in the commands dictionary, set up a load statement as a string. It’s important to get the parent module here – it might be worth it dynamically generating this if you don;t have your app in you PYTHONPATH
- Run exec hook_module – this will actually run the string as if it were typed in the interpreter
- Try / Catch that thing to ensure that you can shoot out an error if it doesn’t work
- Run the hooks main function – in the case of our interface, we use execute_hook()
That’s it – essentially this is all that is required to make your app plugable.
To make it more Django-specific, we include the following lines in the actual hook to load up the ORM machinery:
from dsvn.svnwrapper.dsvn_hook import dsvn_hook
#Put what you like in here...
#Initialise Django settings
from django.core.management import setup_environ
from dsvn import settings
Here we load up the django machinery in the init function and can essentially bolt-on a django app as a manager to your plug-in using your normal settings.py file!
How would you improve this model? Do you have ideas on making a plug-in framework that can be re-used in python easily?