Allow a on_load_failure_callback to be provided

When entrypoints are loaded it is sometimes useful
to be able to do more than LOG the failure that could
be emitted from the plugin failing to be constructed,
allowing the manager classes to be provided a callback
that can be called when such extension fails to load
is useful to track these types of failures.

Change-Id: Idc7e55ade6b3f80c348123fbceea64425d7d3508
This commit is contained in:
Joshua Harlow 2013-12-16 14:02:02 -08:00
parent 1ace033015
commit 3eccd4baad
9 changed files with 85 additions and 24 deletions

View File

@ -34,6 +34,7 @@ stevedore.example.formatter =
stevedore.test.extension =
t1 = stevedore.tests.test_extension:FauxExtension
t2 = stevedore.tests.test_extension:FauxExtension
e1 = stevedore.tests.test_extension:BrokenExtension
[build_sphinx]

View File

@ -133,7 +133,8 @@ class NameDispatchExtensionManager(DispatchExtensionManager):
def __init__(self, namespace, check_func, invoke_on_load=False,
invoke_args=(), invoke_kwds={},
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
super(NameDispatchExtensionManager, self).__init__(
namespace=namespace,
check_func=check_func,
@ -141,6 +142,7 @@ class NameDispatchExtensionManager(DispatchExtensionManager):
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback
)
def _init_plugins(self, extensions):

View File

@ -22,18 +22,21 @@ class DriverManager(NamedExtensionManager):
"""
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={}):
invoke_on_load=False, invoke_args=(), invoke_kwds={},
on_load_failure_callback=None):
super(DriverManager, self).__init__(
namespace=namespace,
names=[name],
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
on_load_failure_callback=on_load_failure_callback
)
@classmethod
def make_test_instance(cls, extension, namespace='TESTING',
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
"""Construct a test DriverManager
Test instances are passed a list of extensions to work from rather
@ -54,7 +57,8 @@ class DriverManager(NamedExtensionManager):
o = super(DriverManager, cls).make_test_instance(
[extension], namespace=namespace,
propagate_map_exceptions=propagate_map_exceptions)
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
return o
def _init_plugins(self, extensions, propagate_map_exceptions=False):

View File

@ -37,7 +37,8 @@ class EnabledExtensionManager(ExtensionManager):
def __init__(self, namespace, check_func, invoke_on_load=False,
invoke_args=(), invoke_kwds={},
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
self.check_func = check_func
super(EnabledExtensionManager, self).__init__(
namespace,
@ -45,6 +46,7 @@ class EnabledExtensionManager(ExtensionManager):
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback
)
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds):

View File

@ -65,16 +65,23 @@ class ExtensionManager(object):
are propagated up through the map call or whether they are logged and
then ignored
:type propagate_map_exceptions: bool
:param on_load_failure_callback: Callback function that will be called when
a entrypoint can not be loaded. The arguments that will be provided
when this is called (when an entrypoint fails to load) are
(manager, entrypoint, exception)
:type on_load_failure_callback: function
"""
def __init__(self, namespace,
invoke_on_load=False,
invoke_args=(),
invoke_kwds={},
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
self._init_attributes(
namespace, propagate_map_exceptions=propagate_map_exceptions)
namespace,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds)
@ -82,7 +89,8 @@ class ExtensionManager(object):
@classmethod
def make_test_instance(cls, extensions, namespace='TESTING',
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
"""Construct a test ExtensionManager
Test instances are passed a list of extensions to work from rather
@ -103,13 +111,16 @@ class ExtensionManager(object):
o = cls.__new__(cls)
o._init_attributes(namespace,
propagate_map_exceptions=propagate_map_exceptions)
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
o._init_plugins(extensions)
return o
def _init_attributes(self, namespace, propagate_map_exceptions=False):
def _init_attributes(self, namespace, propagate_map_exceptions=False,
on_load_failure_callback=None):
self.namespace = namespace
self.propagate_map_exceptions = propagate_map_exceptions
self._on_load_failure_callback = on_load_failure_callback
def _init_plugins(self, extensions):
self.extensions = extensions
@ -140,6 +151,8 @@ class ExtensionManager(object):
except Exception as err:
LOG.error('Could not load %r: %s', ep.name, err)
LOG.exception(err)
if self._on_load_failure_callback is not None:
self._on_load_failure_callback(self, ep, err)
return extensions
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds):

View File

@ -22,20 +22,24 @@ class HookManager(NamedExtensionManager):
"""
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={}):
invoke_on_load=False, invoke_args=(), invoke_kwds={},
on_load_failure_callback=None):
super(HookManager, self).__init__(
namespace,
[name],
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
on_load_failure_callback=on_load_failure_callback
)
def _init_attributes(self, namespace, names, name_order=False,
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
super(HookManager, self)._init_attributes(
namespace, names,
propagate_map_exceptions=propagate_map_exceptions)
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
self._name = names[0]
def __getitem__(self, name):

View File

@ -34,10 +34,12 @@ class NamedExtensionManager(ExtensionManager):
def __init__(self, namespace, names,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
name_order=False, propagate_map_exceptions=False):
name_order=False, propagate_map_exceptions=False,
on_load_failure_callback=None):
self._init_attributes(
namespace, names, name_order=name_order,
propagate_map_exceptions=propagate_map_exceptions)
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds)
@ -45,7 +47,8 @@ class NamedExtensionManager(ExtensionManager):
@classmethod
def make_test_instance(cls, extensions, namespace='TESTING',
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
"""Construct a test NamedExtensionManager
Test instances are passed a list of extensions to use rather than
@ -67,14 +70,17 @@ class NamedExtensionManager(ExtensionManager):
o = cls.__new__(cls)
names = [e.name for e in extensions]
o._init_attributes(namespace, names,
propagate_map_exceptions=propagate_map_exceptions)
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
o._init_plugins(extensions)
return o
def _init_attributes(self, namespace, names, name_order=False,
propagate_map_exceptions=False):
propagate_map_exceptions=False,
on_load_failure_callback=None):
super(NamedExtensionManager, self)._init_attributes(
namespace, propagate_map_exceptions=propagate_map_exceptions)
namespace, propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
self._names = names
self._name_order = name_order

View File

@ -0,0 +1,21 @@
"""Tests for failure loading callback
"""
from stevedore import extension
def test_extension_failure_custom_callback():
errors = []
def failure_callback(manager, entrypoint, error):
errors.append((manager, entrypoint, error))
em = extension.ExtensionManager('stevedore.test.extension',
invoke_on_load=True,
on_load_failure_callback=failure_callback)
extensions = list(em.extensions)
assert len(extensions) > 0
assert len(errors) == 1
(manager, entrypoint, error) = errors[0]
assert manager is em
assert isinstance(error, IOError)

View File

@ -5,6 +5,9 @@ import mock
from stevedore import extension
ALL_NAMES = ['e1', 't1', 't2']
WORKING_NAMES = ['t1', 't2']
class FauxExtension(object):
def __init__(self, *args, **kwds):
@ -15,10 +18,15 @@ class FauxExtension(object):
return self.args, self.kwds, data
class BrokenExtension(object):
def __init__(self, *args, **kwds):
raise IOError("Did not create")
def test_detect_plugins():
em = extension.ExtensionManager('stevedore.test.extension')
names = sorted(em.names())
assert names == ['t1', 't2']
assert names == ALL_NAMES
def test_get_by_name():
@ -73,7 +81,7 @@ def test_use_cache():
def test_iterable():
em = extension.ExtensionManager('stevedore.test.extension')
names = sorted(e.name for e in em)
assert names == ['t1', 't2']
assert names == ALL_NAMES
def test_invoke_on_load():
@ -96,7 +104,7 @@ def test_map_return_values():
invoke_on_load=True,
)
results = em.map(mapped)
assert sorted(results) == ['t1', 't2']
assert sorted(results) == WORKING_NAMES
def test_map_arguments():
@ -111,7 +119,7 @@ def test_map_arguments():
em.map(mapped, 1, 2, a='A', b='B')
assert len(objs) == 2
names = sorted([o[0].name for o in objs])
assert names == ['t1', 't2']
assert names == WORKING_NAMES
for o in objs:
assert o[1] == (1, 2)
assert o[2] == {'a': 'A', 'b': 'B'}