changeset 1535:c9ef16de3f13

core: more robust plugins loading: - avoid stopping loading remaining plugins when a plugin raise an error - new error exceptions.MissingModule allows to detect a missing third party module, and to give instructions to get it - error during instanciation are catched too
author Goffi <goffi@goffi.org>
date Tue, 29 Sep 2015 17:54:23 +0200 (2015-09-29)
parents a5e0393a06cd
children 04a13d9ae265
files src/core/exceptions.py src/core/sat_main.py
diffstat 2 files changed, 43 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/exceptions.py	Tue Sep 29 17:54:22 2015 +0200
+++ b/src/core/exceptions.py	Tue Sep 29 17:54:23 2015 +0200
@@ -54,6 +54,12 @@
     pass
 
 
+class MissingModule(Exception):
+    # Used to indicate when a plugin dependence is not found
+    # it's nice to indicate when to find the dependence in argument string
+    pass
+
+
 class NotFound(Exception):
     pass
 
--- a/src/core/sat_main.py	Tue Sep 29 17:54:22 2015 +0200
+++ b/src/core/sat_main.py	Tue Sep 29 17:54:23 2015 +0200
@@ -151,8 +151,17 @@
             plugin_path = 'sat.plugins.' + plug
             try:
                 __import__(plugin_path)
-            except ImportError as e:
-                log.error(_(u"Can't import plugin [%(path)s]: %(error)s") % {'path': plugin_path, 'error':e})
+            except exceptions.MissingModule as e:
+                try:
+                    del sys.modules[plugin_path]
+                except KeyError:
+                    pass
+                log.warning(u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format(
+                    path=plugin_path, msg=e))
+                continue
+            except Exception as e:
+                import traceback
+                log.error(_(u"Can't import plugin [{path}]:\n{error}").format(path=plugin_path, error=traceback.format_exc()))
                 continue
             mod = sys.modules[plugin_path]
             plugin_info = mod.PLUGIN_INFO
@@ -162,29 +171,33 @@
                 continue
             plugins_to_import[import_name] = (plugin_path, mod, plugin_info)
         while True:
-            self._import_plugins_from_dict(plugins_to_import)
+            try:
+                self._import_plugins_from_dict(plugins_to_import)
+            except ImportError:
+                pass
             if not plugins_to_import:
                 break
 
     def _import_plugins_from_dict(self, plugins_to_import, import_name=None, optional=False):
         """Recursively import and their dependencies in the right order
-        @param plugins_to_import: dict where key=import_name and values= (plugin_path, module, plugin_info)
-        @param import_name: name of the plugin to import as found in PLUGIN_INFO['import_name']
-        @param optional: if False and plugin is not found, an ImportError exception is raised
 
+        @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info)
+        @param import_name(unicode, None): name of the plugin to import as found in PLUGIN_INFO['import_name']
+        @param optional(bool): if False and plugin is not found, an ImportError exception is raised
         """
         if import_name in self.plugins:
-            log.debug(u'Plugin [%s] already imported, passing' % import_name)
+            log.debug(u'Plugin {} already imported, passing'.format(import_name))
             return
         if not import_name:
             import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem()
         else:
             if not import_name in plugins_to_import:
                 if optional:
-                    log.warning(_(u"Recommended plugin not found: %s") % import_name)
+                    log.warning(_(u"Recommended plugin not found: {}").format(import_name))
                     return
-                log.error(_(u"Dependency not found: %s") % import_name)
-                raise ImportError(_('Dependency plugin not found: [%s]') % import_name)
+                msg = u"Dependency not found: {}".format(import_name)
+                log.error(msg)
+                raise ImportError(msg)
             plugin_path, mod, plugin_info = plugins_to_import.pop(import_name)
         dependencies = plugin_info.setdefault("dependencies", [])
         recommendations = plugin_info.setdefault("recommendations", [])
@@ -194,10 +207,20 @@
                 try:
                     self._import_plugins_from_dict(plugins_to_import, to_import, to_import not in dependencies)
                 except ImportError as e:
-                    log.error(_(u"Can't import plugin %(name)s: %(error)s") % {'name':plugin_info['name'], 'error':e})
-                    return
-        log.info(_("importing plugin: %s") % plugin_info['name'])
-        self.plugins[import_name] = getattr(mod, plugin_info['main'])(self)
+                    log.warning(_(u"Can't import plugin {name}: {error}").format(name=plugin_info['name'], error=e))
+                    if optional:
+                        return
+                    raise e
+        log.info("importing plugin: {}".format(plugin_info['name']))
+        # we instanciate the plugin here
+        try:
+            self.plugins[import_name] = getattr(mod, plugin_info['main'])(self)
+        except Exception as e:
+            log.warning(u'Error while loading plugin "{name}", ignoring it: {error}'
+                .format(name=plugin_info['name'], error=e))
+            if optional:
+                return
+            raise ImportError(u"Error during initiation")
         if 'handler' in plugin_info and plugin_info['handler'] == 'yes':
             self.plugins[import_name].is_handler = True
         else: