Source code for workbench.server.plugin_manager
"""A simple plugin manager. Rolling my own for three reasons:
1) Environmental scan did not give me quite what I wanted.
2) The super simple examples didn't support automatic/dynamic loading.
3) I kinda wanted to understand the process :)
"""
import os, sys
from datetime import datetime
import dir_watcher
import inspect
from IPython.utils.coloransi import TermColors as color
#pylint: disable=no-member
[docs]class PluginManager(object):
"""Plugin Manager for Workbench."""
def __init__(self, plugin_callback, plugin_dir = 'workers'):
"""Initialize the Plugin Manager for Workbench.
Args:
plugin_callback: The callback for plugin. This is called when plugin is added.
plugin_dir: The dir where plugin resides.
"""
# Set the callback, the plugin directory and load the plugins
self.plugin_callback = plugin_callback
self.plugin_dir = plugin_dir
self.load_all_plugins()
# Now setup dynamic monitoring of the plugins directory
self.watcher = dir_watcher.DirWatcher(self.plugin_path)
self.watcher.register_callbacks(self.on_created, self.on_modified, self.on_deleted)
self.watcher.start_monitoring()
[docs] def load_all_plugins(self):
"""Load all the plugins in the plugin directory"""
# Go through the existing python files in the plugin directory
self.plugin_path = os.path.realpath(self.plugin_dir)
sys.path.append(self.plugin_dir)
print '<<< Plugin Manager >>>'
for f in [os.path.join(self.plugin_dir, child) for child in os.listdir(self.plugin_dir)]:
# Skip certain files
if '.DS_Store' in f or '__init__.py' in f:
continue
# Add the plugin
self.add_plugin(f)
[docs] def on_created(self, file_list):
"""Watcher callback
Args:
event: The creation event.
"""
for plugin in file_list:
self.add_plugin(plugin)
[docs] def on_modified(self, file_list):
"""Watcher callback.
Args:
event: The modification event.
"""
for plugin in file_list:
self.add_plugin(plugin)
[docs] def on_deleted(self, file_list):
"""Watcher callback.
Args:
event: The modification event.
"""
for plugin in file_list:
self.remove_plugin(plugin)
[docs] def remove_plugin(self, f):
"""Remvoing a deleted plugin.
Args:
f: the filepath for the plugin.
"""
if f.endswith('.py'):
plugin_name = os.path.splitext(os.path.basename(f))[0]
print '- %s %sREMOVED' % (plugin_name, color.Red)
print '\t%sNote: still in memory, restart Workbench to remove...%s' % \
(color.Yellow, color.Normal)
[docs] def add_plugin(self, f):
"""Adding and verifying plugin.
Args:
f: the filepath for the plugin.
"""
if f.endswith('.py'):
# Just the basename without extension
plugin_name = os.path.splitext(os.path.basename(f))[0]
# It's possible the plugin has been modified and needs to be reloaded
if plugin_name in sys.modules:
try:
handler = reload(sys.modules[plugin_name])
print'\t- %s %sRELOAD%s' % (plugin_name, color.Yellow, color.Normal)
except ImportError, error:
print 'Failed to import plugin: %s (%s)' % (plugin_name, error)
return
else:
# Not already loaded so try to import it
try:
handler = __import__(plugin_name, globals(), locals(), [], -1)
except ImportError, error:
print 'Failed to import plugin: %s (%s)' % (plugin_name, error)
return
# Run the handler through plugin validation
plugin = self.validate(handler)
print '\t- %s %sOK%s' % (plugin_name, color.Green, color.Normal)
if plugin:
# Okay must be successfully loaded so capture the plugin meta-data,
# modification time and register the plugin through the callback
plugin['name'] = plugin_name
plugin['dependencies'] = plugin['class'].dependencies
plugin['docstring'] = plugin['class'].__doc__
plugin['mod_time'] = datetime.utcfromtimestamp(os.path.getmtime(f))
# Plugin may accept sample_sets as input
try:
plugin['sample_set_input'] = getattr(plugin['class'], 'sample_set_input')
except AttributeError:
plugin['sample_set_input'] = False
# Now pass the plugin back to workbench
self.plugin_callback(plugin)
[docs] def validate(self, handler):
"""Validate the plugin, each plugin must have the following:
1) The worker class must have an execute method: execute(self, input_data).
2) The worker class must have a dependencies list (even if it's empty).
3) The file must have a top level test() method.
Args:
handler: the loaded plugin.
"""
# Check for the test method first
test_method = self.plugin_test_validation(handler)
if not test_method:
return None
# Here we iterate through the classes found in the module and pick
# the first one that satisfies the validation
for name, plugin_class in inspect.getmembers(handler, inspect.isclass):
if self.plugin_class_validation(plugin_class):
return {'class':plugin_class, 'test':test_method}
# If we're here the plugin didn't pass validation
print 'Failure for plugin: %s' % (handler.__name__)
print 'Validation Error: Worker class is required to have a dependencies list and an execute method'
return None
[docs] def plugin_test_validation(self, handler):
"""Plugin validation.
Every workbench plugin must have top level test method.
Args:
handler: The loaded plugin.
Returns:
None if the test fails or the test function.
"""
methods = {name:func for name, func in inspect.getmembers(handler, callable)}
if 'test' not in methods.keys():
print 'Failure for plugin: %s' % (handler.__name__)
print 'Validation Error: The file must have a top level test() method'
return None
else:
return methods['test']
[docs] def plugin_class_validation(self, plugin_class):
"""Plugin validation
Every workbench plugin must have a dependencies list (even if it's empty).
Every workbench plugin must have an execute method.
Args:
plugin_class: The loaded plugun class.
Returns:
True if dependencies and execute are present, else False.
"""
try:
getattr(plugin_class, 'dependencies')
getattr(plugin_class, 'execute')
except AttributeError:
return False
return True
# Just create the class and run it for a test
[docs]def test():
"""Executes plugin_manager.py test."""
# This test actually does more than it appears. The workers directory
# will get scanned and stuff will get loaded into workbench.
def new_plugin(plugin):
"""new plugin callback """
print '%s' % (plugin['name'])
# Create Plugin Manager
plugin_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),'../workers')
PluginManager(new_plugin, plugin_dir=plugin_dir)
if __name__ == "__main__":
test()