Author: johannes Date: 2007-11-15 03:39:57 -0600 (Thu, 15 Nov 2007) New Revision: 9812
Added: trunk/gnue-common/src/utils/plugin.py Modified: trunk/gnue-common/src/apps/plugin.py Log: Moved plugin.py from apps into utils Modified: trunk/gnue-common/src/apps/plugin.py =================================================================== --- trunk/gnue-common/src/apps/plugin.py 2007-11-15 09:32:30 UTC (rev 9811) +++ trunk/gnue-common/src/apps/plugin.py 2007-11-15 09:39:57 UTC (rev 9812) @@ -22,425 +22,8 @@ # $Id$ """ -Functions to list and load avaliable plugins dynamically. - -Plugins are Python modules that implement a defined API and are loaded on -demand. Usually, there are several plugins of the same type (that means they -implement the same API) that can be used interchangeable. The user can select -which plugin he wants to use. - -All plugins of a specific type must be Python modules in a defined package. -For example, all database drivers must be modules in the -gnue.common.datasources.drivers package. - -Any plugin must define a specific symbol (usually a class definition like -LanguageAdapter, ClientAdapter or Connection) to qualify itself as valid. - -Any plugin must immediately check whether it is functional (especially whether -all dependencies are installed), and import must fail with a meaningful -exception otherwise. Optionally, a module can also define a function with the -name __initplugin__ that initializes the module and raises an exception if the -plugin cannot be initialized. - -Plugins can be organized in a tree structure. To load a plugin, any point in -the tree may be specified. For example, consider there are three plugins named -base.group.foo, base.group.bar and base.group.baz. "foo" will specify the foo -plugin, as well as "group.foo". Loading the plugin "group" means to load the -first available functional plugin in the group. - -Modules and packages located within the plugin tree but not being valid plugins -can define a symbol with the name __noplugin__ to indicate that they are no -valid plugin. Note that the module defining the __noplugin__ symbol is still -imported, but no submodules of it. This is useful to exclude, for example, -abstract base drivers. +This module is *DEPRECATED*. Please use gnue.common.utils.plugin instead """ -import os -import sys -import traceback +from gnue.common.utils.plugin import * -from types import ModuleType -from gnue.common.base import errors, i18n - -__all__ = ['LoadError', 'list_plugins', 'find'] - -# ============================================================================= -# Exceptions -# ============================================================================= - -# ----------------------------------------------------------------------------- -# Module loading error -# ----------------------------------------------------------------------------- - -class LoadError(errors.AdminError): - """ - Indicates a failure to load a given module. Raised by L{find}. - - If e is an Exception of this class, e.exceptions gives a dictionary with - the keys being the modules that were trying to be imported and the values - being the exception info tuples for the exception that happened trying, and - e.detail is a string containing basically the same info. - """ - def __init__(self, name, exceptions): - - self.name = name - self.exceptions = exceptions - - if self.exceptions: - message = u_("Cannot load plugin '%s'") % self.name - detail = u_("The following plugins failed:\n") - for (name, exc) in self.exceptions.items(): - tlist = traceback.format_exception_only(exc[0], exc[1]) - del tlist[1:2] - msg = unicode(''.join(tlist), i18n.get_encoding()) - detail += "* %s: %s" % (name, msg) - else: - message = u_("Cannot find plugin '%s'") % self.name - detail = None - - errors.AdminError.__init__(self, message) - - if detail: - self.detail = detail - - -# ----------------------------------------------------------------------------- -# List all available plugins -# ----------------------------------------------------------------------------- - -def list_plugins(base, identifier, try_to_init=True): - """ - List all available plugins. - - @param base: Name of the package that contains the plugins. - @param identifier: Identifier that a plugin must define to qualify as - module. - @param try_to_init: If set to False, __initplugin__ is not called. - @return: A dictionary with the available plugin module names as keys and - either the loaded module or the exception info tuple of the exception - raised when trying to import the module as values. - """ - checktype(base, [basestring]) - checktype(identifier, [basestring]) - - # Make sure everything is a string. Non-ASCII characters are not allowed in - # Python module names anyway. - _base = base.encode() - _identifier = identifier.encode() - - # Now recursively list the plugins - return __list(_base, _identifier, try_to_init, True) - - -# ----------------------------------------------------------------------------- -# Find a plugin -# ----------------------------------------------------------------------------- - -def find(name, base, identifier): - """ - Find a plugin by name. If no plugin is functional, a LoadError is raised. - - @param name: Name of the plugin to find. If the plugin is foo.bar, name can - be bar, or foo.bar, or foo, where the last one returns the first - functional plugin in the foo group. - @param base: Name of the package that contains the plugins. - @param identifier: Identifier that a plugin must define to qualify as - module. - @return: The loaded module of the plugin. - """ - checktype(name, [basestring]) - checktype(base, [basestring]) - checktype(identifier, [basestring]) - - # Make sure everything is a string. Non-ASCII characters are not allowed in - # Python module names anyway. - _name = name.encode() - _base = base.encode() - _identifier = identifier.encode() - - # First, see if we've already found this module previously - global __findCache - try: - result = __findCache[(_base, _name)] - except KeyError: - # If not, search for the plugin - result = __findCache[(_base, _name)] = __find(_base, _name, _identifier) - - if isinstance(result, ModuleType): - return result - else: - raise LoadError, (name, result) - - -# ----------------------------------------------------------------------------- -# A list of all previously failed modules -# ----------------------------------------------------------------------------- - -# This dictionary remembers all previous failure of module imports/inits. That -# is necessary because module initialization code is only run at first import -# attempt. -__failed = {} - - -# ----------------------------------------------------------------------------- -# Mapping of previously found plugins -# ----------------------------------------------------------------------------- - -__findCache = {} - - -# ----------------------------------------------------------------------------- -# Find all modules and subpackages in a package -# ----------------------------------------------------------------------------- - -def __modules(package, want_packages): - - # package.__file__ is a directory if GImportLogger is in use. This makes it - # necessary to 'simulate' a package, otherwise stepping down subpackages - # won't work. - if os.path.isdir(package.__file__): - (basedir, basefile) = (package.__file__, '__init__.py') - else: - (basedir, basefile) = os.path.split(package.__file__) - - (basename, baseext) = os.path.splitext(basefile) - - if basename != '__init__': - # This is not a package, so no need to go deeper - return [] - - # Find all submodules - result = {} - for subfile in os.listdir(basedir): - (subname, subext) = os.path.splitext(subfile) - subpath = os.path.join(basedir, subfile) - # We are only interested in Python modules or packages - if (not want_packages and subext in ['.py', '.pyc', '.pyo'] and \ - subname != '__init__') or \ - (os.path.isdir(subpath) and \ - os.path.isfile(os.path.join(subpath, '__init__.py')) or \ - os.path.isfile(os.path.join(subpath, '__init__.pyc')) or \ - os.path.isfile(os.path.join(subpath, '__init__.pyo'))): - result[subname] = True - - return result.keys() - - -# ----------------------------------------------------------------------------- -# Recursively list all plugins -# ----------------------------------------------------------------------------- - -def __list(base, identifier, try_to_init, top): - - global __failed - - if __failed.has_key(base): - # This has already failed in previous attempt - return {base: __failed[base]} - - try: - mod = __import__(base, None, None, '*') - except: - __failed[base] = sys.exc_info() - return {base: __failed[base]} - - if hasattr(mod, '__noplugin__'): - # This is not a plugin, ignore it - return {} - - if not top: - if hasattr(mod, identifier): - # This is already a plugin, no need to go deeper - if try_to_init and hasattr(mod, '__initplugin__'): - try: - mod.__initplugin__() - except: - __failed[base] = sys.exc_info() - return {base: __failed[base]} - return {base: mod} - - # List all submodules - result = {} - for sub in __modules(mod, False): - result.update(__list(base + '.' + sub, identifier, try_to_init, False)) - return result - - -# ----------------------------------------------------------------------------- -# Recursively find first available plugin and return the module or a dictionary -# with the exceptions that occured -# ----------------------------------------------------------------------------- - -def __first(base, identifier): - - global __failed - - if __failed.has_key(base): - # This has already failed in previous attempt - return {base: __failed[base]} - - try: - mod = __import__(base, None, None, '*') - except: - __failed[base] = sys.exc_info() - return {base: __failed[base]} - - if hasattr(mod, '__noplugin__'): - # This is not a plugin, ignore it - return {} - - if hasattr(mod, identifier): - # This is already a plugin, no need to go deeper - if hasattr(mod, '__initplugin__'): - try: - mod.__initplugin__() - except: - __failed[base] = sys.exc_info() - return {base: __failed[base]} - return mod - - # Search all submodules - exceptions = {} - for sub in __modules(mod, False): - result = __first(base + '.' + sub, identifier) - if isinstance(result, ModuleType): - return result - exceptions.update(result) - return exceptions - - -# ----------------------------------------------------------------------------- -# Recursively search for a plugin and return the module or the exceptions that -# occured -# ----------------------------------------------------------------------------- - -def __find(base, name, identifier): - - if __failed.has_key(base): - # This has already failed in previous attempt - return {base: __failed[base]} - - try: - mod = __import__(base, None, None, '*') - except: - __failed[base] = sys.exc_info() - return {base: __failed[base]} - - if hasattr(mod, '__noplugin__'): - # This is not a plugin, ignore it - return {} - - # Is the searched driver an alias of this module? - if hasattr(mod, '__pluginalias__'): - if name in mod.__pluginalias__: - return __first(base, identifier) - - if __failed.has_key(base + '.' + name): - # This has already failed in previous attempt - return {base + '.' + name: __failed[base + '.' + name]} - - try: - mod = __import__(base + '.' + name, None, None, '*') - except ImportError: - pass - except: - __failed[base + '.' + name] = sys.exc_info() - return {base + '.' + name: __failed[base + '.' + name]} - else: - return __first(base + '.' + name, identifier) - - # Search all submodules - exceptions = {} - for sub in __modules(mod, False): - result = __find(base + '.' + sub, name, identifier) - if isinstance(result, ModuleType): - return result - exceptions.update(result) - - return exceptions - - -# ============================================================================= -# Self test code -# ============================================================================= - -if __name__ == '__main__': - - basep = 'gnue.common.datasources.drivers' - - if len(sys.argv) == 1: - - # List all modules - for (iname, iresult) in(list_plugins(basep, 'Connection')).items(): - print iname[len(basep)+1:] + ":", - if isinstance(iresult, ModuleType): - print "ok" - else: - data = traceback.format_exception_only(iresult[0], iresult[1]) - print ''.join(data), - - elif sys.argv[1] == 'test': - - try: - print 'find "postgresql.popy":', - fmod = find('postgresql.popy', basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), - - print - - try: - print 'find "pygresql":', - fmod = find('pygresql', basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), - - print - - try: - print 'find "mysql":', - fmod = find('mysql', basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), - - print - - try: - print 'find "oracle":', - fmod = find('oracle', basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), - - print - - try: - print 'find "nonexistent":', - fmod = find('nonexistent', basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), - - else: - - try: - print 'find %s:' % sys.argv[1], - fmod = find(sys.argv[1], basep, 'Connection') - print fmod.__name__ - except LoadError, e: - print e - print "Detail:" - print o(e.detail), Added: trunk/gnue-common/src/utils/plugin.py =================================================================== --- trunk/gnue-common/src/utils/plugin.py 2007-11-15 09:32:30 UTC (rev 9811) +++ trunk/gnue-common/src/utils/plugin.py 2007-11-15 09:39:57 UTC (rev 9812) @@ -0,0 +1,446 @@ +# GNU Enterprise Common Library - Plugin support +# +# This file is part of GNU Enterprise. +# +# GNU Enterprise is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2, or (at your option) any later version. +# +# GNU Enterprise is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with program; see the file COPYING. If not, +# write to the Free Software Foundation, Inc., 59 Temple Place +# - Suite 330, Boston, MA 02111-1307, USA. +# +# Copyright 2001-2007 Free Software Foundation +# +# $Id$ + +""" +Functions to list and load avaliable plugins dynamically. + +Plugins are Python modules that implement a defined API and are loaded on +demand. Usually, there are several plugins of the same type (that means they +implement the same API) that can be used interchangeable. The user can select +which plugin he wants to use. + +All plugins of a specific type must be Python modules in a defined package. +For example, all database drivers must be modules in the +gnue.common.datasources.drivers package. + +Any plugin must define a specific symbol (usually a class definition like +LanguageAdapter, ClientAdapter or Connection) to qualify itself as valid. + +Any plugin must immediately check whether it is functional (especially whether +all dependencies are installed), and import must fail with a meaningful +exception otherwise. Optionally, a module can also define a function with the +name __initplugin__ that initializes the module and raises an exception if the +plugin cannot be initialized. + +Plugins can be organized in a tree structure. To load a plugin, any point in +the tree may be specified. For example, consider there are three plugins named +base.group.foo, base.group.bar and base.group.baz. "foo" will specify the foo +plugin, as well as "group.foo". Loading the plugin "group" means to load the +first available functional plugin in the group. + +Modules and packages located within the plugin tree but not being valid plugins +can define a symbol with the name __noplugin__ to indicate that they are no +valid plugin. Note that the module defining the __noplugin__ symbol is still +imported, but no submodules of it. This is useful to exclude, for example, +abstract base drivers. +""" + +import os +import sys +import traceback + +from types import ModuleType +from gnue.common.base import errors, i18n + +__all__ = ['LoadError', 'list_plugins', 'find'] + +# ============================================================================= +# Exceptions +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Module loading error +# ----------------------------------------------------------------------------- + +class LoadError(errors.AdminError): + """ + Indicates a failure to load a given module. Raised by L{find}. + + If e is an Exception of this class, e.exceptions gives a dictionary with + the keys being the modules that were trying to be imported and the values + being the exception info tuples for the exception that happened trying, and + e.detail is a string containing basically the same info. + """ + def __init__(self, name, exceptions): + + self.name = name + self.exceptions = exceptions + + if self.exceptions: + message = u_("Cannot load plugin '%s'") % self.name + detail = u_("The following plugins failed:\n") + for (name, exc) in self.exceptions.items(): + tlist = traceback.format_exception_only(exc[0], exc[1]) + del tlist[1:2] + msg = unicode(''.join(tlist), i18n.get_encoding()) + detail += "* %s: %s" % (name, msg) + else: + message = u_("Cannot find plugin '%s'") % self.name + detail = None + + errors.AdminError.__init__(self, message) + + if detail: + self.detail = detail + + +# ----------------------------------------------------------------------------- +# List all available plugins +# ----------------------------------------------------------------------------- + +def list_plugins(base, identifier, try_to_init=True): + """ + List all available plugins. + + @param base: Name of the package that contains the plugins. + @param identifier: Identifier that a plugin must define to qualify as + module. + @param try_to_init: If set to False, __initplugin__ is not called. + @return: A dictionary with the available plugin module names as keys and + either the loaded module or the exception info tuple of the exception + raised when trying to import the module as values. + """ + checktype(base, [basestring]) + checktype(identifier, [basestring]) + + # Make sure everything is a string. Non-ASCII characters are not allowed in + # Python module names anyway. + _base = base.encode() + _identifier = identifier.encode() + + # Now recursively list the plugins + return __list(_base, _identifier, try_to_init, True) + + +# ----------------------------------------------------------------------------- +# Find a plugin +# ----------------------------------------------------------------------------- + +def find(name, base, identifier): + """ + Find a plugin by name. If no plugin is functional, a LoadError is raised. + + @param name: Name of the plugin to find. If the plugin is foo.bar, name can + be bar, or foo.bar, or foo, where the last one returns the first + functional plugin in the foo group. + @param base: Name of the package that contains the plugins. + @param identifier: Identifier that a plugin must define to qualify as + module. + @return: The loaded module of the plugin. + """ + checktype(name, [basestring]) + checktype(base, [basestring]) + checktype(identifier, [basestring]) + + # Make sure everything is a string. Non-ASCII characters are not allowed in + # Python module names anyway. + _name = name.encode() + _base = base.encode() + _identifier = identifier.encode() + + # First, see if we've already found this module previously + global __findCache + try: + result = __findCache[(_base, _name)] + except KeyError: + # If not, search for the plugin + result = __findCache[(_base, _name)] = __find(_base, _name, _identifier) + + if isinstance(result, ModuleType): + return result + else: + raise LoadError, (name, result) + + +# ----------------------------------------------------------------------------- +# A list of all previously failed modules +# ----------------------------------------------------------------------------- + +# This dictionary remembers all previous failure of module imports/inits. That +# is necessary because module initialization code is only run at first import +# attempt. +__failed = {} + + +# ----------------------------------------------------------------------------- +# Mapping of previously found plugins +# ----------------------------------------------------------------------------- + +__findCache = {} + + +# ----------------------------------------------------------------------------- +# Find all modules and subpackages in a package +# ----------------------------------------------------------------------------- + +def __modules(package, want_packages): + + # package.__file__ is a directory if GImportLogger is in use. This makes it + # necessary to 'simulate' a package, otherwise stepping down subpackages + # won't work. + if os.path.isdir(package.__file__): + (basedir, basefile) = (package.__file__, '__init__.py') + else: + (basedir, basefile) = os.path.split(package.__file__) + + (basename, baseext) = os.path.splitext(basefile) + + if basename != '__init__': + # This is not a package, so no need to go deeper + return [] + + # Find all submodules + result = {} + for subfile in os.listdir(basedir): + (subname, subext) = os.path.splitext(subfile) + subpath = os.path.join(basedir, subfile) + # We are only interested in Python modules or packages + if (not want_packages and subext in ['.py', '.pyc', '.pyo'] and \ + subname != '__init__') or \ + (os.path.isdir(subpath) and \ + os.path.isfile(os.path.join(subpath, '__init__.py')) or \ + os.path.isfile(os.path.join(subpath, '__init__.pyc')) or \ + os.path.isfile(os.path.join(subpath, '__init__.pyo'))): + result[subname] = True + + return result.keys() + + +# ----------------------------------------------------------------------------- +# Recursively list all plugins +# ----------------------------------------------------------------------------- + +def __list(base, identifier, try_to_init, top): + + global __failed + + if __failed.has_key(base): + # This has already failed in previous attempt + return {base: __failed[base]} + + try: + mod = __import__(base, None, None, '*') + except: + __failed[base] = sys.exc_info() + return {base: __failed[base]} + + if hasattr(mod, '__noplugin__'): + # This is not a plugin, ignore it + return {} + + if not top: + if hasattr(mod, identifier): + # This is already a plugin, no need to go deeper + if try_to_init and hasattr(mod, '__initplugin__'): + try: + mod.__initplugin__() + except: + __failed[base] = sys.exc_info() + return {base: __failed[base]} + return {base: mod} + + # List all submodules + result = {} + for sub in __modules(mod, False): + result.update(__list(base + '.' + sub, identifier, try_to_init, False)) + return result + + +# ----------------------------------------------------------------------------- +# Recursively find first available plugin and return the module or a dictionary +# with the exceptions that occured +# ----------------------------------------------------------------------------- + +def __first(base, identifier): + + global __failed + + if __failed.has_key(base): + # This has already failed in previous attempt + return {base: __failed[base]} + + try: + mod = __import__(base, None, None, '*') + except: + __failed[base] = sys.exc_info() + return {base: __failed[base]} + + if hasattr(mod, '__noplugin__'): + # This is not a plugin, ignore it + return {} + + if hasattr(mod, identifier): + # This is already a plugin, no need to go deeper + if hasattr(mod, '__initplugin__'): + try: + mod.__initplugin__() + except: + __failed[base] = sys.exc_info() + return {base: __failed[base]} + return mod + + # Search all submodules + exceptions = {} + for sub in __modules(mod, False): + result = __first(base + '.' + sub, identifier) + if isinstance(result, ModuleType): + return result + exceptions.update(result) + return exceptions + + +# ----------------------------------------------------------------------------- +# Recursively search for a plugin and return the module or the exceptions that +# occured +# ----------------------------------------------------------------------------- + +def __find(base, name, identifier): + + if __failed.has_key(base): + # This has already failed in previous attempt + return {base: __failed[base]} + + try: + mod = __import__(base, None, None, '*') + except: + __failed[base] = sys.exc_info() + return {base: __failed[base]} + + if hasattr(mod, '__noplugin__'): + # This is not a plugin, ignore it + return {} + + # Is the searched driver an alias of this module? + if hasattr(mod, '__pluginalias__'): + if name in mod.__pluginalias__: + return __first(base, identifier) + + if __failed.has_key(base + '.' + name): + # This has already failed in previous attempt + return {base + '.' + name: __failed[base + '.' + name]} + + try: + mod = __import__(base + '.' + name, None, None, '*') + except ImportError: + pass + except: + __failed[base + '.' + name] = sys.exc_info() + return {base + '.' + name: __failed[base + '.' + name]} + else: + return __first(base + '.' + name, identifier) + + # Search all submodules + exceptions = {} + for sub in __modules(mod, False): + result = __find(base + '.' + sub, name, identifier) + if isinstance(result, ModuleType): + return result + exceptions.update(result) + + return exceptions + + +# ============================================================================= +# Self test code +# ============================================================================= + +if __name__ == '__main__': + + basep = 'gnue.common.datasources.drivers' + + if len(sys.argv) == 1: + + # List all modules + for (iname, iresult) in(list_plugins(basep, 'Connection')).items(): + print iname[len(basep)+1:] + ":", + if isinstance(iresult, ModuleType): + print "ok" + else: + data = traceback.format_exception_only(iresult[0], iresult[1]) + print ''.join(data), + + elif sys.argv[1] == 'test': + + try: + print 'find "postgresql.popy":', + fmod = find('postgresql.popy', basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), + + print + + try: + print 'find "pygresql":', + fmod = find('pygresql', basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), + + print + + try: + print 'find "mysql":', + fmod = find('mysql', basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), + + print + + try: + print 'find "oracle":', + fmod = find('oracle', basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), + + print + + try: + print 'find "nonexistent":', + fmod = find('nonexistent', basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), + + else: + + try: + print 'find %s:' % sys.argv[1], + fmod = find(sys.argv[1], basep, 'Connection') + print fmod.__name__ + except LoadError, e: + print e + print "Detail:" + print o(e.detail), Property changes on: trunk/gnue-common/src/utils/plugin.py ___________________________________________________________________ Name: svn:keywords + Id _______________________________________________ commit-gnue mailing list commit-gnue@gnu.org http://lists.gnu.org/mailman/listinfo/commit-gnue