where to buy misoprostol online how to buy valtrex
A simple event-driven plugin system in Python | Evan Fosmark

A simple event-driven plugin system in Python

So I just wanted to share my solution for integrating a plugin system into Python applications. Overall it’s separated into four parts: the plugin manager, the plugin(s), a config file listing the plugins, and the application that triggers the events. In this system, plugins are just functions that get registered through the use of a decorator.

Let’s start with the code for the plugin manager:

# pluginmanager.py
from collections import defaultdict
 
 
plugins = defaultdict(list)
def register(*events):
    """ This decorator is to be used for registering a function as a plugin for
        a specific event or list of events. 
    """
    def registered_plugin(funct):
        for event in events:
            plugins[event].append(funct)
        return funct
    return registered_plugin
 
 
def trigger_event(event, *args, **kwargs):
    """ Call this function to trigger an event. It will run any plugins that
        have registered themselves to the event. Any additional arguments or
        keyword arguments you pass in will be passed to the plugins.
    """
    for plugin in plugins[event]:
        plugin(*args, **kwargs)
 
 
def load_plugins(config_file):
    """ This reads a config file of a list of plugins to load. It ignores
        empty lines or lines beginning with a hash mark (#). It is so plugin
        imports are more dynamic and you don't need to continue appending
        import statements to the top of a file.
    """
    with open(config_file, "r") as fh:
        for line in fh:
            line = line.strip()
            if line.startswith("#") or line == "":
                continue
            __import__(line,  globals(), locals(), [], -1)

The plugin system is separated into three functions. The first is register() which is the decorator for registering plugins. The function trigger_event() is used by your application notify the plugin system that an event has occured. It goes out and runs any plugins registered with the event. Lastly, load_plugins() accepts a file location that contains the plugins to be loaded. We’ll investigate these more closely later on in the article.

An example plugin

Let’s build a package called "plugins" and create a module inside that called "example.py" with the following data:

import pluginmanager
 
@pluginmanager.register("FOO_ACTIVE")
def print_data(*args, **kwargs):
    if "data" in kwargs:
        print "Received the following: %s" % kwargs["data"]
    else:
        print "Didn't receive any data."

Here, the print_data plugin is registered for the FOO_ACTIVE event, and will be run if that event is triggered.

How to trigger the plugin

First, the plugin needs to be loaded. Right now, the source file is just sitting out there and unless we import it we’ll never be able to do anything with it. So, you can either do a manual import or you can call the load_plugins() function and have it parse a list of plugin locations. We’ll use the latter method.

The config file (plugins.list)

Let’s create a config file with data resembling the following:

# Blank lines and lines starting with a hash (#) are ignored.
# So be as verbose as you want.
plugins.example

As you can see from above, it specifies to import the plugins.example module (since plugins is a package and example is a module inside of it). I’m doing it this way so adding or removing additional plugins is simple.

Application use

Finally, let’s look at how an application would utilize this system:

import pluginmanager
pluginmanager.load_plugins("plugins.list") # load any plugins in the list
 
# ... code ...
 
# The FOO_ACTIVE event occurs somewhere
pluginmanager.trigger_event("FOO_ACTIVE", 
                            data="this data is sent to the plugin", 
                            foo="so is this")
 
# ... code ...

What happenes here is pretty straightforward. The manger loads the plugins, the application eventually triggers the FOO_ACTIVE event and sends data along with it. Any arguments or keyword arguments after the event in trigger_event() are sent directly to the plugin. When the event is triggered, it finds any plugins that have registered with it and activates it.

So yeah, that’s what I came up with. It does have the downside of not being able to easily extend a plugin like you can with class-based systems since you can’t inherit. Still, it’s pretty nice for smaller apps that want a taste of pluggability.

If you have your own system for dealing with this type of situation, I’d love to hear from you.

 

 

4 Comments

  1. Ville wrote,

    You may also want to check out pydispatcher:

    http://pydispatcher.sourceforge.net/

  2. Eli wrote,

    Nice system, Evan.
    How about the plugin functions responding to events by returning values? It doesn’t seem to be implemented here.

  3. Evan wrote,

    Yeah Eli that’d be useful, but I’m not sure how well it’d work with potentially numerous plugins being run from one trigger_event() call. Well, I guess it could return a tuple.

  4. Vasudev Ram wrote,

    Interesting plugin system.

    I think the answer to Eli’s question would partly, at least, depend on how this plugin system is used in actual apps. In an app where there are multiple plugins registered for a single event like FOO_ACTIVE, but where the effects of running each separate plugin are independent from each other, i.e. mutually exclusive, returning a tuple of their return values would work and make sense. But if the user wants to write a UNIX pipe-like app where, let’s say, the multiple plugins (for a single event) are written to get a cumulative or chain effect, i.e., where each succeeding plugin uses some results or return value from the previous one, then there _may_ be an issue – not sure. Changing the system to use some sort of “main class” in an app (the “main class” being the class of the object to be operated on by the plugins), and then passing (a reference to) an instance of that class to each plugin somehow, may solve this case, since each plugin could just modify the state (member variables) of the instance, and those changes would be visible to the next plugin, and so on.

    - Vasudev

Leave a comment