From 7bdc14c62059a50fedb6c385ed8e7c57fc4bb135 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 26 Nov 2012 12:13:37 -0500 Subject: [PATCH] Cache the entry points discovered within a namespace Scanning the entry point registry is relatively expensive and causes performance issues with unit tests of code depending on stevedore. This change addresses the performance issues by caching the entry points as they are loaded from pkg_resources in a class attribute in the base class of the extension managesr so they can be reused by other instances. Change-Id: Iba7bee6790cdedc94cb537e2ed6e12219c85f26a Signed-off-by: Doug Hellmann --- stevedore/extension.py | 10 ++++++++- stevedore/tests/test_extension.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/stevedore/extension.py b/stevedore/extension.py index b083220..e4794a4 100644 --- a/stevedore/extension.py +++ b/stevedore/extension.py @@ -56,9 +56,17 @@ class ExtensionManager(object): invoke_args, invoke_kwds) + ENTRY_POINT_CACHE = {} + + def _find_entry_points(self, namespace): + if namespace not in self.ENTRY_POINT_CACHE: + eps = list(pkg_resources.iter_entry_points(namespace)) + self.ENTRY_POINT_CACHE[namespace] = eps + return self.ENTRY_POINT_CACHE[namespace] + def _load_plugins(self, invoke_on_load, invoke_args, invoke_kwds): extensions = [] - for ep in pkg_resources.iter_entry_points(self.namespace): + for ep in self._find_entry_points(self.namespace): LOG.debug('found extension %r', ep) try: ext = self._load_one_plugin(ep, diff --git a/stevedore/tests/test_extension.py b/stevedore/tests/test_extension.py index dcd555a..ecb0750 100644 --- a/stevedore/tests/test_extension.py +++ b/stevedore/tests/test_extension.py @@ -1,6 +1,8 @@ """Tests for stevedore.extension """ +import mock + from stevedore import extension @@ -16,6 +18,39 @@ def test_detect_plugins(): assert names == ['t1', 't2'] +def test_load_multiple_times_entry_points(): + # We expect to get the same EntryPoint object because we save them + # in the cache. + em1 = extension.ExtensionManager('stevedore.test.extension') + eps1 = [ext.entry_point for ext in em1] + em2 = extension.ExtensionManager('stevedore.test.extension') + eps2 = [ext.entry_point for ext in em2] + assert eps1[0] is eps2[0] + + +def test_load_multiple_times_plugins(): + # We expect to get the same plugin object (module or class) + # because the underlying import machinery will cache the values. + em1 = extension.ExtensionManager('stevedore.test.extension') + plugins1 = [ext.plugin for ext in em1] + em2 = extension.ExtensionManager('stevedore.test.extension') + plugins2 = [ext.plugin for ext in em2] + assert plugins1[0] is plugins2[0] + + +def test_use_cache(): + # If we insert something into the cache of entry points, + # the manager should not have to call into pkg_resources + # to find the plugins. + cache = extension.ExtensionManager.ENTRY_POINT_CACHE + cache['stevedore.test.faux'] = [] + with mock.patch('pkg_resources.iter_entry_points', + side_effect=AssertionError('called iter_entry_points')): + em = extension.ExtensionManager('stevedore.test.faux') + names = em.names() + assert names == [] + + def test_iterable(): em = extension.ExtensionManager('stevedore.test.extension') names = sorted(e.name for e in em)