Back to index

python-cliapp  1.20120630
pluginmgr.py
Go to the documentation of this file.
00001 # Copyright (C) 2009-2012  Lars Wirzenius
00002 # 
00003 # This program is free software; you can redistribute it and/or modify
00004 # it under the terms of the GNU General Public License as published by
00005 # the Free Software Foundation; either version 2 of the License, or
00006 # (at your option) any later version.
00007 # 
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 # 
00013 # You should have received a copy of the GNU General Public License along
00014 # with this program; if not, write to the Free Software Foundation, Inc.,
00015 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00016 
00017 
00018 '''A generic plugin manager.
00019 
00020 The plugin manager finds files with plugins and loads them. It looks
00021 for plugins in a number of locations specified by the caller. To add
00022 a plugin to be loaded, it is enough to put it in one of the locations,
00023 and name it *_plugin.py. (The naming convention is to allow having
00024 other modules as well, such as unit tests, in the same locations.)
00025 
00026 '''
00027 
00028 
00029 import imp
00030 import inspect
00031 import os
00032 
00033 
00034 from cliapp import Plugin
00035 
00036 
00037 class PluginManager(object):
00038 
00039     '''Manage plugins.
00040     
00041     This class finds and loads plugins, and keeps a list of them that
00042     can be accessed in various ways.
00043     
00044     The locations are set via the locations attribute, which is a list.
00045     
00046     When a plugin is loaded, an instance of its class is created. This
00047     instance is initialized using normal and keyword arguments specified
00048     in the plugin manager attributes plugin_arguments and 
00049     plugin_keyword_arguments.
00050     
00051     The version of the application using the plugin manager is set via
00052     the application_version attribute. This defaults to '0.0.0'.
00053     
00054     '''
00055     
00056     suffix = '_plugin.py'
00057 
00058     def __init__(self):
00059         self.locations = []
00060         self._plugins = None
00061         self._plugin_files = None
00062         self.plugin_arguments = []
00063         self.plugin_keyword_arguments = {}
00064         self.application_version = '0.0.0'
00065 
00066     @property
00067     def plugin_files(self):
00068         if self._plugin_files is None:
00069             self._plugin_files = self.find_plugin_files()
00070         return self._plugin_files
00071 
00072     @property
00073     def plugins(self):
00074         if self._plugins is None:
00075             self._plugins = self.load_plugins()
00076         return self._plugins
00077 
00078     def __getitem__(self, name):
00079         for plugin in self.plugins:
00080             if plugin.name == name:
00081                 return plugin
00082         raise KeyError('Plugin %s is not known' % name)
00083 
00084     def find_plugin_files(self):
00085         '''Find files that may contain plugins.
00086         
00087         This finds all files named *_plugin.py in all locations.
00088         The returned list is sorted.
00089         
00090         '''
00091         
00092         pathnames = []
00093         
00094         for location in self.locations:
00095             try:
00096                 basenames = os.listdir(location)
00097             except os.error:
00098                 continue
00099             for basename in basenames:
00100                 s = os.path.join(location, basename)
00101                 if s.endswith(self.suffix) and os.path.exists(s):
00102                     pathnames.append(s)
00103         
00104         return sorted(pathnames)
00105 
00106     def load_plugins(self):
00107         '''Load plugins from all plugin files.'''
00108         
00109         plugins = dict()
00110         
00111         for pathname in self.plugin_files:
00112             for plugin in self.load_plugin_file(pathname):
00113                 if plugin.name in plugins:
00114                     p = plugins[plugin.name]
00115                     if self.is_older(p.version, plugin.version):
00116                         plugins[plugin.name] = plugin
00117                 else:
00118                     plugins[plugin.name] = plugin
00119 
00120         return plugins.values()
00121 
00122     def is_older(self, version1, version2):
00123         '''Is version1 older than version2?'''
00124         return self.parse_version(version1) < self.parse_version(version2)
00125 
00126     def load_plugin_file(self, pathname):
00127         '''Return plugin classes in a plugin file.'''
00128 
00129         name, ext = os.path.splitext(os.path.basename(pathname))
00130         f = file(pathname, 'r')
00131         module = imp.load_module(name, f, pathname, 
00132                                  ('.py', 'r', imp.PY_SOURCE))
00133         f.close()
00134         
00135         plugins = []
00136         for dummy, member in inspect.getmembers(module, inspect.isclass):
00137             if issubclass(member, Plugin):
00138                 p = member(*self.plugin_arguments,
00139                            **self.plugin_keyword_arguments)
00140                 if self.compatible_version(p.required_application_version):
00141                     plugins.append(p)
00142         
00143         return plugins
00144 
00145     def compatible_version(self, required_application_version):
00146         '''Check that the plugin is version-compatible with the application.
00147         
00148         This checks the plugin's required_application_version against
00149         the declared application version and returns True if they are
00150         compatible, and False if not.
00151         
00152         '''
00153 
00154         req = self.parse_version(required_application_version)
00155         app = self.parse_version(self.application_version)
00156         
00157         return app[0] == req[0] and app >= req
00158 
00159     def parse_version(self, version):
00160         '''Parse a string represenation of a version into list of ints.'''
00161         
00162         return [int(s) for s in version.split('.')]
00163 
00164     def enable_plugins(self, plugins=None):
00165         '''Enable all or selected plugins.'''
00166         
00167         for plugin in plugins or self.plugins:
00168             plugin.enable_wrapper()
00169 
00170     def disable_plugins(self, plugins=None):
00171         '''Disable all or selected plugins.'''
00172         
00173         for plugin in plugins or self.plugins:
00174             plugin.disable_wrapper()
00175