[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
r5531 - trunk/gnue-common/src/apps
From: |
reinhard |
Subject: |
r5531 - trunk/gnue-common/src/apps |
Date: |
Fri, 26 Mar 2004 08:51:45 -0600 (CST) |
Author: reinhard
Date: 2004-03-26 08:51:44 -0600 (Fri, 26 Mar 2004)
New Revision: 5531
Modified:
trunk/gnue-common/src/apps/plugin.py
Log:
Finished (?) plugin loader.
Modified: trunk/gnue-common/src/apps/plugin.py
===================================================================
--- trunk/gnue-common/src/apps/plugin.py 2004-03-26 14:45:15 UTC (rev
5530)
+++ trunk/gnue-common/src/apps/plugin.py 2004-03-26 14:51:44 UTC (rev
5531)
@@ -23,24 +23,97 @@
"""
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.
+
+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.
"""
from types import *
import os
+import string
+import sys
+import traceback
from gnue.common.apps import GDebug
+# =============================================================================
+# Exceptions
+# =============================================================================
+
# -----------------------------------------------------------------------------
+# Module loading error
+# -----------------------------------------------------------------------------
+
+class LoadError (gException):
+ """
+ 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\n"
+ "The following plugins failed:\n") % self.name
+ self.detail = message
+ for (name, exc) in exceptions.items ():
+ shortlist = traceback.format_exception_only (exc [0], exc [1])
+ longlist = traceback.format_exception (*exc)
+ message += "* %s: %s" % (name, string.join (shortlist, ''))
+ self.detail += "* %s: %s" % (name, string.join (longlist, ''))
+ else:
+ message = u_("Cannot find plugin %s") % self.name
+ self.detail = message
+
+ gException.__init__ (self, message)
+
+# -----------------------------------------------------------------------------
# List all available plugins
# -----------------------------------------------------------------------------
-def list (base, identifier, test):
+def list (base, identifier):
"""
- List all available plugins
+ 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.
+ @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, [StringType, UnicodeType])
checktype (identifier, [StringType, UnicodeType])
- checktype (test, FunctionType)
# Make sure everything is a string. Non-ASCII characters are not allowed in
# Python module names anyway.
@@ -48,20 +121,26 @@
_identifier = identifier.encode ()
# Now recursively list the plugins
- return __list (_base, _identifier, test)
+ return __list (_base, _identifier)
# -----------------------------------------------------------------------------
# Find a plugin
# -----------------------------------------------------------------------------
-def find (name, base, identifier, test):
+def find (name, base, identifier):
"""
- Find a plugin by name.
+ 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, [StringType, UnicodeType])
checktype (base, [StringType, UnicodeType])
checktype (identifier, [StringType, UnicodeType])
- checktype (test, FunctionType)
# Make sure everything is a string. Non-ASCII characters are not allowed in
# Python module names anyway.
@@ -70,7 +149,11 @@
_identifier = identifier.encode ()
# Now search the plugin
- return __find (_base, name, _identifier, test)
+ result = __find (_base, _name, _identifier)
+ if isinstance (result, ModuleType):
+ return result
+ else:
+ raise LoadError, (name, result)
# -----------------------------------------------------------------------------
# Find all modules and subpackages in a package
@@ -91,7 +174,7 @@
(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 == '.py' and subfile != '__init__') or \
+ if (not want_packages and subext == '.py' and subname != '__init__') or \
(os.path.isdir (subpath) and \
os.path.isfile (os.path.join (subpath, '__init__.py'))):
result = result + [subname]
@@ -101,80 +184,89 @@
# Recursively list all plugins
# -----------------------------------------------------------------------------
-def __list (base, identifier, test):
+def __list (base, identifier):
try:
m = __import__ (base, None, None, '*')
except:
- return []
+ return {base: sys.exc_info ()}
+ if hasattr (m, '__noplugin__'):
+ # This is not a plugin, ignore it
+ return {}
+
if hasattr (m, identifier):
- # This is already a plugin, now test if it is loadable
- try:
- test (m)
- return [m]
- except Exception, message:
- gDebug (1, _('Can\'t use module %(module)s: %(message)s') % \
- {'module': m.__name__, 'message': message})
- return []
+ # This is already a plugin, no need to go deeper
+ return {base: m}
# List all submodules
- result = []
+ result = {}
for sub in __modules (m, False):
- result = result + __list (base + '.' + sub, identifier, test)
+ result.update (__list (base + '.' + sub, identifier))
return result
# -----------------------------------------------------------------------------
-# Recursively find first available plugin
+# Recursively find first available plugin and return the module or a dictionary
+# with the exceptions that occured
# -----------------------------------------------------------------------------
-def __first (base, identifier, test):
+def __first (base, identifier):
try:
m = __import__ (base, None, None, '*')
except:
- return None
+ return {base: sys.exc_info ()}
+ if hasattr (m, '__noplugin__'):
+ # This is not a plugin, ignore it
+ return {}
+
if hasattr (m, identifier):
- # This is already a plugin, now test if it is loadable
- try:
- test (m)
- return m
- except Exception, message:
- gDebug (1, _('Can\'t use module %(module)s: %(message)s') % \
- {'module': m.__name__, 'message': message})
- return None
+ # This is already a plugin, no need to go deeper
+ return m
# Search all submodules
+ exceptions = {}
for sub in __modules (m, False):
- result = __first (base + '.' + sub, identifier, test)
- if result:
+ result = __first (base + '.' + sub, identifier)
+ if isinstance (result, ModuleType):
return result
- return None
+ exceptions.update (result)
+ return exceptions
# -----------------------------------------------------------------------------
-# Recursively search for a plugin
+# Recursively search for a plugin and return the module or the exceptions that
+# occured
# -----------------------------------------------------------------------------
-def __find (base, name, identifier, test):
+def __find (base, name, identifier):
try:
m = __import__ (base, None, None, '*')
except:
- return None
+ return {base: sys.exc_info ()}
+ if hasattr (m, '__noplugin__'):
+ # This is not a plugin, ignore it
+ return {}
+
try:
m = __import__ (base + '.' + name, None, None, '*')
- return __first (base + '.' + name, identifier, test)
+ except ImportError:
+ pass
except:
- pass
+ return {base: sys.exc_info ()}
+ else:
+ return __first (base + '.' + name, identifier)
# Search all submodules
+ exceptions = {}
for sub in __modules (m, True):
- result = __find (base + '.' + sub, name, identifier, test)
- if result:
+ result = __find (base + '.' + sub, name, identifier)
+ if isinstance (result, ModuleType):
return result
- return None
+ exceptions.update (result)
+ return exceptions
# =============================================================================
# Self test code
@@ -182,25 +274,81 @@
if __name__ == '__main__':
- def test (module):
- module.Connection (None, None, {})
-
base = 'gnue.common.datasources.drivers'
- for m in list (base, 'Connection', test):
- print m.__name__
+ if len (sys.argv) == 1:
- m = find ('postgresql.popy', base, 'Connection', test)
- print 'find "postgresql.popy":', (m and m.__name__)
+ # List all modules
+ for (name, result) in (list (base, 'Connection')).items ():
+ print name + ":",
+ if isinstance (result, ModuleType):
+ print "ok"
+ else:
+ list = traceback.format_exception_only (result [0], result [1])
+ print string.join (list, '')
- m = find ('pygresql', base, 'Connection', test)
- print 'find "pygresql":', (m and m.__name__)
+ elif sys.argv [1] == 'test':
- m = find ('mysql', base, 'Connection', test)
- print 'find "mysql":', (m and m.__name__)
+ try:
+ print 'find "postgresql.popy":',
+ m = find ('postgresql.popy', base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
- m = find ('oracle', base, 'Connection', test)
- print 'find "oracle":', (m and m.__name__)
+ print
- m = find ('nonexistent', base, 'Connection', test)
- print 'find "nonexistent":', m
+ try:
+ print 'find "pygresql":',
+ m = find ('pygresql', base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
+
+ print
+
+ try:
+ print 'find "mysql":',
+ m = find ('mysql', base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
+
+ print
+
+ try:
+ print 'find "oracle":',
+ m = find ('oracle', base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
+
+ print
+
+ try:
+ print 'find "nonexistent":',
+ m = find ('nonexistent', base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
+
+ else:
+
+ try:
+ print 'find %s:' % sys.argv [1],
+ m = find (sys.argv [1], base, 'Connection')
+ print m.__name__
+ except LoadError, e:
+ print e
+ print "Detail:"
+ print e.detail,
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- r5531 - trunk/gnue-common/src/apps,
reinhard <=