From 1482cf264f598e30127088bb011be5d31e4fa514 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 17 Sep 2013 13:52:03 +0200 Subject: [PATCH] Add map_method function to managers This allows to use map() directly over some extension method in a more convenient way. It's a pattern we often need, so let's built it directly in Stevedore. --- stevedore/dispatch.py | 46 +++++++++++++++++++++++++++++++ stevedore/extension.py | 24 ++++++++++++++++ stevedore/tests/test_dispatch.py | 34 +++++++++++++++++++++-- stevedore/tests/test_extension.py | 12 ++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/stevedore/dispatch.py b/stevedore/dispatch.py index 53b11e7..66c84cf 100644 --- a/stevedore/dispatch.py +++ b/stevedore/dispatch.py @@ -73,6 +73,29 @@ class DispatchExtensionManager(EnabledExtensionManager): self._invoke_one_plugin(response.append, func, e, args, kwds) return response + def map_method(self, filter_func, method_name, *args, **kwds): + """Iterate over the extensions invoking each one's object method called + `method_name` for any where filter_func() returns True. + + This is equivalent of using :meth:`map` with func set to + `lambda x: x.obj.method_name()` + while being more convenient. + + Exceptions raised from within the called method are propagated up + and processing stopped if self.propagate_map_exceptions is True, + otherwise they are logged and ignored. + + .. versionadded:: 0.12 + + :param filter_func: Callable to test each extension. + :param method_name: The extension method name to call for each extension. + :param args: Variable arguments to pass to method + :param kwds: Keyword arguments to pass to method + :returns: List of values returned from methods + """ + return self.map(filter_func, self._call_extension_method, + method_name, *args, **kwds) + class NameDispatchExtensionManager(DispatchExtensionManager): """Loads all plugins and filters on execution. @@ -151,3 +174,26 @@ class NameDispatchExtensionManager(DispatchExtensionManager): else: self._invoke_one_plugin(response.append, func, e, args, kwds) return response + + def map_method(self, names, method_name, *args, **kwds): + """Iterate over the extensions invoking each one's object method called + `method_name` for any where the name is in the given list of names. + + This is equivalent of using :meth:`map` with func set to + `lambda x: x.obj.method_name()` + while being more convenient. + + Exceptions raised from within the called method are propagated up + and processing stopped if self.propagate_map_exceptions is True, + otherwise they are logged and ignored. + + .. versionadded:: 0.12 + + :param names: List or set of name(s) of extension(s) to invoke. + :param method_name: The extension method name to call for each extension. + :param args: Variable arguments to pass to method + :param kwds: Keyword arguments to pass to method + :returns: List of values returned from methods + """ + return self.map(names, self._call_extension_method, + method_name, *args, **kwds) diff --git a/stevedore/extension.py b/stevedore/extension.py index 8e7801e..66eb3f1 100644 --- a/stevedore/extension.py +++ b/stevedore/extension.py @@ -140,6 +140,30 @@ class ExtensionManager(object): self._invoke_one_plugin(response.append, func, e, args, kwds) return response + @staticmethod + def _call_extension_method(extension, method_name, *args, **kwds): + return getattr(extension.obj, method_name)(*args, **kwds) + + def map_method(self, method_name, *args, **kwds): + """Iterate over the extensions invoking each one's object method called `method_name`. + + This is equivalent of using :meth:`map` with func set to + `lambda x: x.obj.method_name()` + while being more convenient. + + Exceptions raised from within the called method are propagated up + and processing stopped if self.propagate_map_exceptions is True, + otherwise they are logged and ignored. + + .. versionadded:: 0.12 + + :param method_name: The extension method name to call for each extension. + :param args: Variable arguments to pass to method + :param kwds: Keyword arguments to pass to method + :returns: List of values returned from methods + """ + return self.map(self._call_extension_method, method_name, *args, **kwds) + def _invoke_one_plugin(self, response_callback, func, e, args, kwds): try: response_callback(func(e, *args, **kwds)) diff --git a/stevedore/tests/test_dispatch.py b/stevedore/tests/test_dispatch.py index 01a1731..76a66e2 100644 --- a/stevedore/tests/test_dispatch.py +++ b/stevedore/tests/test_dispatch.py @@ -1,10 +1,11 @@ from stevedore import dispatch -def test_dispatch(): +def check_dispatch(ep, *args, **kwds): + return ep.name == 't2' - def check_dispatch(ep, *args, **kwds): - return ep.name == 't2' + +def test_dispatch(): def invoke(ep, *args, **kwds): return (ep.name, args, kwds) @@ -28,6 +29,20 @@ def test_dispatch(): assert results == expected +def test_dispatch_map_method(): + em = dispatch.DispatchExtensionManager( + 'stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + + results = em.map_method(check_dispatch, 'get_args_and_data', + 'first') + assert results == [(('a',), {'b': 'B'}, 'first')] + + def test_name_dispatch(): def invoke(ep, *args, **kwds): @@ -72,3 +87,16 @@ def test_name_dispatch_ignore_missing(): ) expected = [('t1', ('first',), {'named': 'named value'})] assert results == expected + +def test_name_dispatch_map_method(): + em = dispatch.NameDispatchExtensionManager( + 'stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + + results = em.map_method(['t3', 't1'], 'get_args_and_data', + 'first') + assert results == [(('a',), {'b': 'B'}, 'first')] diff --git a/stevedore/tests/test_extension.py b/stevedore/tests/test_extension.py index c2f8d6a..7a0349f 100644 --- a/stevedore/tests/test_extension.py +++ b/stevedore/tests/test_extension.py @@ -11,6 +11,9 @@ class FauxExtension(object): self.args = args self.kwds = kwds + def get_args_and_data(self, data): + return self.args, self.kwds, data + def test_detect_plugins(): em = extension.ExtensionManager('stevedore.test.extension') @@ -155,3 +158,12 @@ def test_map_errors_when_no_plugins(): em.map(mapped, 1, 2, a='A', b='B') except RuntimeError as err: assert 'No stevedore.test.extension.none extensions found' == str(err) + + +def test_map_method(): + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + ) + + result = em.map_method('get_args_and_data', 42) + assert set(r[2] for r in result) == set([42])