diff --git a/docs/source/history.rst b/docs/source/history.rst index f8bd375..f2481f3 100644 --- a/docs/source/history.rst +++ b/docs/source/history.rst @@ -12,6 +12,9 @@ dev - Change the :class:`~stevedore.dispatch.NamedDispatchExtensionManager` to ignore missing extensions (:issue:`14`). +- Add ``__getitem__`` to + :class:`~stevedore.extension.ExtensionManager` for looking up + individual plugins by name (:issue:`15`). 0.8 diff --git a/stevedore/extension.py b/stevedore/extension.py index 4f9bee8..67ba46a 100644 --- a/stevedore/extension.py +++ b/stevedore/extension.py @@ -55,6 +55,7 @@ class ExtensionManager(object): self.extensions = self._load_plugins(invoke_on_load, invoke_args, invoke_kwds) + self._extensions_by_name = None ENTRY_POINT_CACHE = {} @@ -93,6 +94,9 @@ class ExtensionManager(object): def names(self): "Returns the names of the discovered extensions" + # We want to return the names of the extensions in the order + # they would be used by map(), since some subclasses change + # that order. return [e.name for e in self.extensions] def map(self, func, *args, **kwds): @@ -133,3 +137,17 @@ class ExtensionManager(object): def __iter__(self): return iter(self.extensions) + + def __getitem__(self, name): + """Return the named extension. + + Accessing an ExtensionManager as a dictionary (``em['name']``) + produces the :class:`Extension` instance with the + specified name. + """ + if self._extensions_by_name is None: + d = {} + for e in self.extensions: + d[e.name] = e + self._extensions_by_name = d + return self._extensions_by_name[name] diff --git a/stevedore/hook.py b/stevedore/hook.py index 8d2a79b..c1cec7f 100644 --- a/stevedore/hook.py +++ b/stevedore/hook.py @@ -23,6 +23,7 @@ class HookManager(NamedExtensionManager): def __init__(self, namespace, name, invoke_on_load=False, invoke_args=(), invoke_kwds={}): + self._name = name super(HookManager, self).__init__( namespace, [name], @@ -30,3 +31,14 @@ class HookManager(NamedExtensionManager): invoke_args=invoke_args, invoke_kwds=invoke_kwds, ) + + def __getitem__(self, name): + """Return the named extensions. + + Accessing a HookManager as a dictionary (``em['name']``) + produces a list of the :class:`Extension` instance(s) with the + specified name, in the order they would be invoked by map(). + """ + if name != self._name: + raise KeyError(name) + return self.extensions diff --git a/stevedore/tests/test_extension.py b/stevedore/tests/test_extension.py index ecb0750..bc76bbd 100644 --- a/stevedore/tests/test_extension.py +++ b/stevedore/tests/test_extension.py @@ -18,6 +18,22 @@ def test_detect_plugins(): assert names == ['t1', 't2'] +def test_get_by_name(): + em = extension.ExtensionManager('stevedore.test.extension') + e = em['t1'] + assert e.name == 't1' + + +def test_get_by_name_missing(): + em = extension.ExtensionManager('stevedore.test.extension') + try: + em['t3'] + except KeyError: + pass + else: + assert False, 'Failed to raise KeyError' + + def test_load_multiple_times_entry_points(): # We expect to get the same EntryPoint object because we save them # in the cache. diff --git a/stevedore/tests/test_hook.py b/stevedore/tests/test_hook.py index 1c5ea81..a698346 100644 --- a/stevedore/tests/test_hook.py +++ b/stevedore/tests/test_hook.py @@ -11,3 +11,33 @@ def test_hook(): ) assert len(em.extensions) == 1 assert em.names() == ['t1'] + + +def test_get_by_name(): + em = hook.HookManager( + 'stevedore.test.extension', + 't1', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + e_list = em['t1'] + assert len(e_list) == 1 + e = e_list[0] + assert e.name == 't1' + + +def test_get_by_name_missing(): + em = hook.HookManager( + 'stevedore.test.extension', + 't1', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + try: + em['t2'] + except KeyError: + pass + else: + assert False, 'Failed to raise KeyError'