From aa5622147faa0de137f67c6be45dbdb3e11320f6 Mon Sep 17 00:00:00 2001 From: melwitt Date: Wed, 7 Nov 2012 22:58:32 +0000 Subject: [PATCH] discover extensions via entry points Currently, nova client can only discover extensions in two ways: 1. Installing the extension in the novaclient/v1_1/contrib/ directory. 2. Installing the extension in the top-level python path or modifying the path to be picked up by pkgutils.iter_modules() This patch allows a third, more flexible option of discovering extensions via entry points. This means the extension can be installed anywhere and entry points can be registered with python to be picked up by pkg_resources.iter_entry_points(). To register an entry point, simply add the extension module to the setup() call in setup.py like this: setuptools.setup( name='mydistribution', packages=setuptools.find_packages(), entry_points={ 'novaclient.extension' : [ 'foo = mydistribution.mynovaclientexts.foo' ] }, ) Change-Id: Ic1e223a9173546131e742506897f585f4ac65767 --- novaclient/shell.py | 11 +++++- tests/test_discover.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/test_discover.py diff --git a/novaclient/shell.py b/novaclient/shell.py index dd2a5c7de..c15893f0e 100644 --- a/novaclient/shell.py +++ b/novaclient/shell.py @@ -24,6 +24,7 @@ import httplib2 import imp import itertools import os +import pkg_resources import pkgutil import sys import logging @@ -257,7 +258,8 @@ class OpenStackComputeShell(object): extensions = [] for name, module in itertools.chain( self._discover_via_python_path(), - self._discover_via_contrib_path(version)): + self._discover_via_contrib_path(version), + self._discover_via_entry_points()): extension = novaclient.extension.Extension(name, module) extensions.append(extension) @@ -289,6 +291,13 @@ class OpenStackComputeShell(object): module = imp.load_source(name, ext_path) yield name, module + def _discover_via_entry_points(self): + for ep in pkg_resources.iter_entry_points('novaclient.extension'): + name = ep.name + module = ep.load() + + yield name, module + def _add_bash_completion_subparser(self, subparsers): subparser = subparsers.add_parser('bash_completion', add_help=False, diff --git a/tests/test_discover.py b/tests/test_discover.py new file mode 100644 index 000000000..dbbddc4e3 --- /dev/null +++ b/tests/test_discover.py @@ -0,0 +1,79 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# 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 mock +import imp +import inspect +import pkg_resources + +import novaclient.shell +from tests import utils + + +class DiscoverTest(utils.TestCase): + + def test_discover_via_entry_points(self): + + def mock_iter_entry_points(group): + if group == 'novaclient.extension': + fake_ep = mock.Mock() + fake_ep.name = 'foo' + fake_ep.module = imp.new_module('foo') + fake_ep.load.return_value = fake_ep.module + return [fake_ep] + + @mock.patch.object(pkg_resources, 'iter_entry_points', + mock_iter_entry_points) + def test(): + shell = novaclient.shell.OpenStackComputeShell() + for name, module in shell._discover_via_entry_points(): + self.assertEqual(name, 'foo') + self.assertTrue(inspect.ismodule(module)) + + test() + + def test_discover_extensions(self): + + def mock_discover_via_python_path(self): + yield 'foo', imp.new_module('foo') + + def mock_discover_via_contrib_path(self, version): + yield 'bar', imp.new_module('bar') + + def mock_discover_via_entry_points(self): + yield 'baz', imp.new_module('baz') + + @mock.patch.object(novaclient.shell.OpenStackComputeShell, + '_discover_via_python_path', + mock_discover_via_python_path) + @mock.patch.object(novaclient.shell.OpenStackComputeShell, + '_discover_via_contrib_path', + mock_discover_via_contrib_path) + @mock.patch.object(novaclient.shell.OpenStackComputeShell, + '_discover_via_entry_points', + mock_discover_via_entry_points) + def test(): + shell = novaclient.shell.OpenStackComputeShell() + extensions = shell._discover_extensions('1.1') + self.assertEqual(len(extensions), 3) + names = sorted(['foo', 'bar', 'baz']) + sorted_extensions = sorted(extensions, key=lambda ext: ext.name) + for i in range(len(names)): + ext = sorted_extensions[i] + name = names[i] + self.assertEqual(ext.name, name) + self.assertTrue(inspect.ismodule(ext.module)) + + test()