From faca9953c0d412944a23dfbc62e5634a509c51e8 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Tue, 27 Nov 2012 15:41:57 +0100 Subject: [PATCH] Add a module for dynamically loading plugins Change-Id: I662b5989941b467c78a392098db0cd19ff86201c Signed-off-by: Zane Bitter --- heat/common/plugin_loader.py | 64 +++++++++++++++++++++++++++++ heat/tests/test_plugin_loader.py | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 heat/common/plugin_loader.py create mode 100644 heat/tests/test_plugin_loader.py diff --git a/heat/common/plugin_loader.py b/heat/common/plugin_loader.py new file mode 100644 index 0000000000..325d560492 --- /dev/null +++ b/heat/common/plugin_loader.py @@ -0,0 +1,64 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pkgutil +import sys +import types + +from heat.openstack.common import log as logging + +logger = logging.getLogger(__name__) + + +def _module_name(*components): + return '.'.join(components) + + +def create_subpackage(path, parent_package_name, subpackage_name="plugins"): + package_name = _module_name(parent_package_name, subpackage_name) + + package = types.ModuleType(package_name) + package.__path__ = [path] if isinstance(path, basestring) else list(path) + sys.modules[package_name] = package + + return package + + +def _import_module(importer, module_name, package): + fullname = _module_name(package.__name__, module_name) + if fullname in sys.modules: + return sys.modules[fullname] + + loader = importer.find_module(fullname) + if loader is None: + return None + + module = loader.load_module(fullname) + setattr(package, module_name, module) + return module + + +def load_modules(package, ignore_error=False): + path = package.__path__ + for importer, module_name, is_package in pkgutil.walk_packages(path): + try: + module = _import_module(importer, module_name, package) + except ImportError as ex: + logger.error(_('Failed to import module %s') % module_name) + if not ignore_error: + raise + else: + if module is not None: + yield module diff --git a/heat/tests/test_plugin_loader.py b/heat/tests/test_plugin_loader.py new file mode 100644 index 0000000000..d458d32493 --- /dev/null +++ b/heat/tests/test_plugin_loader.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import nose +import pkgutil +import sys +import unittest +from nose.plugins.attrib import attr + +import heat.engine +from heat.common import plugin_loader + + +@attr(tag=['unit', 'plugin_loader']) +@attr(speed='fast') +class PluginLoaderTest(unittest.TestCase): + def test_module_name(self): + self.assertEqual(plugin_loader._module_name('foo.bar', 'blarg.wibble'), + 'foo.bar.blarg.wibble') + + def test_create_subpackage_single_path(self): + pkg_name = 'heat.engine.test_single_path' + self.assertFalse(pkg_name in sys.modules) + pkg = plugin_loader.create_subpackage('/tmp', + 'heat.engine', + 'test_single_path') + self.assertTrue(pkg_name in sys.modules) + self.assertEqual(sys.modules[pkg_name], pkg) + self.assertEqual(pkg.__path__, ['/tmp']) + self.assertEqual(pkg.__name__, pkg_name) + + def test_create_subpackage_path_list(self): + path_list = ['/tmp'] + pkg_name = 'heat.engine.test_path_list' + self.assertFalse(pkg_name in sys.modules) + pkg = plugin_loader.create_subpackage('/tmp', + 'heat.engine', + 'test_path_list') + self.assertTrue(pkg_name in sys.modules) + self.assertEqual(sys.modules[pkg_name], pkg) + self.assertEqual(pkg.__path__, path_list) + self.assertFalse(pkg.__path__ is path_list) + self.assertEqual(pkg.__name__, pkg_name) + + def test_import_module_existing(self): + import heat.engine.service + importer = pkgutil.ImpImporter(heat.engine.__path__[0]) + loaded = plugin_loader._import_module(importer, + 'service', + heat.engine) + self.assertTrue(loaded is heat.engine.service) + + def test_import_module_garbage(self): + importer = pkgutil.ImpImporter(heat.engine.__path__[0]) + self.assertEqual(plugin_loader._import_module(importer, + 'wibble', + heat.engine), + None)