commit-gnue
[Top][All Lists]
Advanced

[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,





reply via email to

[Prev in Thread] Current Thread [Next in Thread]