Note: In order for this example to work, you need place __init__.py
into
plugins/example/
where plugins.py
, example.py
and the plugins
directory
are in the same directory.
Note: This implementation requires Python 2.7 or higher.
$ python plugins.py
INFO:root:plugin "test" loaded
hello bob
{'name': 'bob johnson'}
INFO:root:plugin "test" unloaded
Using the PluginManager
class is easy:
import plugins
# create a plugin manager
plugin_manager = plugins.PluginManager('./plugins')
In the above example, we're saying that plugin modules are stored in the
./plugins
directory.
Now, assume that you've placed the example __init__.py
file in
./plugins/example/__init__.py
. Then, in order to load the plugin, you use the
plugin manager's load_plugin
function:
plugin_manager.load_plugin('example')
Similarly, you can use the unload_plugin
to unload the plugin if you so
desire.
In the example __init__.py
file, we're expecting to be able to extend the
main application at two different hooks: action_hello
and filter_hello
(more on hooks below). This means that somewhere in your application, you
need to use the plugin manager's execute_action_hook
and execute_filter_hook
functions:
# executes action_hello(hook_params) in ./plugins/*/__init__.py (if loaded)
plugin_manager.execute_action_hook('hello', {'name': 'bob'})
# executes filter_hello(hook_params) in ./plugins/*/__init__.py (if loaded)
result = plugin_manager.execute_filter_hook('hello', {'name': 'bob'})
print result
Hooks are basically places in your application where you allow plugins to extend your application. This plugin manager supports two different kinds of hooks, action hooks and filter hooks. These hooks should be documented for your plugin developers.
Action hooks can be thought of as places in your code where you would like
plugins to be able to execute arbitrary code (perform actions), possibly using
a dict
that your application provides to them. In the example __init__.py
file, an action hook called hello
is registered by defining an action_hello
function. The example application passed the following dictionary to all
plugins that register the hook: {'name': 'bob'}
.
Note: In order to ensure that actions occur in the order you need them to, the loaded plugins are executed in the same order in which they are loaded.
Filter hooks are places in you code where you would llike plugins to be able to
take a dict
, modifiy it, and return it. An interesting use case for this
might be a plugin that replaces bbcode or smilies with HTML image tags. In the
example __init__.py
file, a filter hook called hello
is registered by
defining a filter_hello
function.
Note, the PluginManager
class checks that the dictionary returned by the
plugins contains the same set of keys as when the execute_filter_hook
function was called. For example, since the dictionary passed to the
execute_filter_hook
function in the example application contains a single key
called name
, then an exception would be raised if the filter_hello
function
in the example __init__.py
file did not return a dictionary that key in it.
Note: In order to ensure that filtering of the dictionary occurs in the order you need it to, the loaded plugins are executed in the same order in which they are loaded.
By default, this implementation expects that the main module for each plugin is
__init__
. You can change this by using the optional main_module
parameter
in the PluginManager
constructor. For example, if you wanted plugin.py
in
each plugin folder to be a plugin's main module, then you could do the
following:
plugin_manager = plugins.PluginManager('./plugins', 'plugin')
By defauly, this implementation uses it's own logger from logging
. If you
would like for the PluginManager
class to you use your own logger, you can
specify one using the optional log
parameter in the PluginManager
constructor.
There's a bug in this that causes plugin hooks to be run multiple times. :/ If two plugins are loaded and only one of them defines action_foo, action_foo will execute twice.