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
This commit is contained in:
Goffi 2015-09-29 17:54:23 +02:00
commit 2e70b01b28
2 changed files with 43 additions and 14 deletions

View file

@ -54,6 +54,12 @@ class UnknownGroupError(Exception):
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

View file

@ -151,8 +151,17 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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: