Python Style Plugins Made Easy


Sometimes you need to write code that loads python at runtime. Plugin architectures are a good example of this. Plugins allow extensibility but more importantly (for me at least) they enforce a strict API. Anyway, I've written this code a few times so I thought I'd modularize it.

The specific bit of code I am going to post is the python code to look for and load a series of python plugins. Plugins (in this case) are just classes that are subclasses of the Plugin base class. This plugin base class dictates the API that all plugins must implement. Here is an example plugin abstract base class

class Plugin(object):
    def setup(self):
        """called before the plugin is asked to do anything"""
        raise NotImplementedError
 
    def teardown(self):
        """called to allow the plugin to free anything"""
        raise NotImplementedError
 
    def domagic(self):
        """do whatever it is the plugin does"""
        raise NotImplementedError
 

Here the is code to look for and return a list of classes that can be instantiated:

def find_subclasses(path, cls):
    """
    Find all subclass of cls in py files located below path
    (does look in sub directories)
 
    @param path: the path to the top level folder to walk
    @type path: str
    @param cls: the base class that all subclasses should inherit from
    @type cls: class
    @rtype: list
    @return: a list if classes that are subclasses of cls
    """
 
    subclasses=[]
 
    def look_for_subclass(modulename):
        log.debug("searching %s" % (modulename))
        module=__import__(modulename)
 
        #walk the dictionaries to get to the last one
        d=module.__dict__
        for m in modulename.split('.')[1:]:
            d=d[m].__dict__
 
        #look through this dictionary for things
        #that are subclass of Job
        #but are not Job itself
        for key, entry in d.items():
            if key == cls.__name__:
                continue
 
            try:
                if issubclass(entry, cls):
                    log.debug("Found subclass: "+key)
                    subclasses.append(entry)
            except TypeError:
                #this happens when a non-type is passed in to issubclass. We
                #don't care as it can't be a subclass of Job if it isn't a
                #type
                continue
 
    for root, dirs, files in os.walk(path):
        for name in files:
            if name.endswith(".py") and not name.startswith("__"):
                path = os.path.join(root, name)
                modulename = path.rsplit('.', 1)[0].replace('/', '.')
                look_for_subclass(modulename)
 
    return subclasses
 

and here is how you would call it:

classes = find_subclasses("./pluginsfolder/", Plugin)
 
#lets create an instance of the first class
inst = classes[0]()
 

So there you go, create folder of python files with classes in them that subclass the Plugin class and you are away.

7 Responses to “Python Style Plugins Made Easy”

  1. Petro Says:

    Good example. However, it there is a slight problem with the directory name that is passed to the “find_subclasses” function. The code works better without “./” from the path to the plugins directory.

    klasses = find_classes(”pluginsfolder”, Plugin)

  2. stargaming Says:

    Is there any advantage of the module __dict__ hassle over just using Plugin.__subclasses__()?

  3. dazza Says:

    @stargaming: The only advantage for me is that I didn’t know __subclasses__() existed! Thanks, I’ll look in to that :)

  4. kib2 Says:

    Hi dazza,

    Armin R. has written an article with the __subclasses__() methods here :

    http://lucumr.pocoo.org/blogarchive/python-plugin-system

    See you.

  5. dazza Says:

    @kib2: Cheers for the link, it looks a lot easier then playing with __dict__s. Thanks.

  6. bminews.com Says:

    Python Style Plugins Made Easy…

    How to guide for writing and loading plugins for a Python application….

  7. Blaine Says:

    I’ve been looking for something like this, exactly. Thanks!

    I had managed to hack up something using isinstance() and looping through dir(filename)

Leave a Reply