From f43c1f76559ae8b5b738b7ae8b69b15c379f9145 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 13 Apr 2015 16:47:49 -0500 Subject: [PATCH] Defer client imports So we really weren't deferring the loading of client libs dadgummit, do that for real where possible. This shaves a couple of tenths off the static import times. Also defer as much import-time procesing as possible. This is a little ugly in api.auth but this also eliminates import of the auth plugins until they are needed. Change-Id: Ia11d4b9cf98231d37449103fc29101dc17afb009 --- openstackclient/api/auth.py | 64 ++++++++++++------- openstackclient/compute/client.py | 17 ++--- .../tests/common/test_clientmanager.py | 4 ++ .../tests/volume/test_find_resource.py | 7 ++ openstackclient/volume/client.py | 20 +++--- 5 files changed, 73 insertions(+), 39 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index ba51bee1ff..ffbcb6fce0 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -27,29 +27,49 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) - # Initialize the list of Authentication plugins early in order # to get the command-line options -PLUGIN_LIST = stevedore.ExtensionManager( - base.PLUGIN_NAMESPACE, - invoke_on_load=False, - propagate_map_exceptions=True, -) +PLUGIN_LIST = None -# Get the command line options so the help action has them available +# List of plugin command line options OPTIONS_LIST = {} -for plugin in PLUGIN_LIST: - for o in plugin.plugin.get_options(): - os_name = o.dest.lower().replace('_', '-') - os_env_name = 'OS_' + os_name.upper().replace('-', '_') - OPTIONS_LIST.setdefault(os_name, {'env': os_env_name, 'help': ''}) - # TODO(mhu) simplistic approach, would be better to only add - # help texts if they vary from one auth plugin to another - # also the text rendering is ugly in the CLI ... - OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin.name, - o.help, + + +def get_plugin_list(): + """Gather plugin list and cache it""" + + global PLUGIN_LIST + + if PLUGIN_LIST is None: + PLUGIN_LIST = stevedore.ExtensionManager( + base.PLUGIN_NAMESPACE, + invoke_on_load=False, + propagate_map_exceptions=True, ) + return PLUGIN_LIST + + +def get_options_list(): + """Gather plugin options so the help action has them available""" + + global OPTIONS_LIST + + if not OPTIONS_LIST: + for plugin in get_plugin_list(): + for o in plugin.plugin.get_options(): + os_name = o.dest.lower().replace('_', '-') + os_env_name = 'OS_' + os_name.upper().replace('-', '_') + OPTIONS_LIST.setdefault( + os_name, {'env': os_env_name, 'help': ''}, + ) + # TODO(mhu) simplistic approach, would be better to only add + # help texts if they vary from one auth plugin to another + # also the text rendering is ugly in the CLI ... + OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( + plugin.name, + o.help, + ) + return OPTIONS_LIST def select_auth_plugin(options): @@ -57,7 +77,7 @@ def select_auth_plugin(options): auth_plugin_name = None - if options.os_auth_type in [plugin.name for plugin in PLUGIN_LIST]: + if options.os_auth_type in [plugin.name for plugin in get_plugin_list()]: # A direct plugin name was given, use it return options.os_auth_type @@ -113,7 +133,7 @@ def build_auth_params(auth_plugin_name, cmd_options): else: LOG.debug('no auth_type') # delay the plugin choice, grab every option - plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST]) + plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: option_name = 'os_' + option LOG.debug('fetching option %s' % option_name) @@ -147,7 +167,7 @@ def build_auth_plugins_option_parser(parser): authentication plugin. """ - available_plugins = [plugin.name for plugin in PLUGIN_LIST] + available_plugins = [plugin.name for plugin in get_plugin_list()] parser.add_argument( '--os-auth-type', metavar='', @@ -169,7 +189,7 @@ def build_auth_plugins_option_parser(parser): default=utils.env('OS_TENANT_ID') ), } - for o in OPTIONS_LIST: + for o in get_options_list(): # remove allusion to tenants from v2.0 API if 'tenant' not in o: parser.add_argument( diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 7ca08a4f23..461c39267d 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,14 +15,6 @@ import logging -from novaclient import client as nova_client -from novaclient import extension - -try: - from novaclient.v2.contrib import list_extensions -except ImportError: - from novaclient.v1_1.contrib import list_extensions - from openstackclient.common import utils LOG = logging.getLogger(__name__) @@ -34,6 +26,15 @@ API_NAME = 'compute' def make_client(instance): """Returns a compute service client.""" + + # Defer client imports until we actually need them + from novaclient import client as nova_client + from novaclient import extension + try: + from novaclient.v2.contrib import list_extensions + except ImportError: + from novaclient.v1_1.contrib import list_extensions + compute_client = nova_client.get_client_class( instance._api_version[API_NAME], ) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 3648bf5756..7a2f57bef6 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -34,6 +34,10 @@ AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) +# This is deferred in api.auth but we need it here... +auth.get_options_list() + + class Container(object): attr = clientmanager.ClientCache(lambda x: object()) diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 56081966b7..00cc46a6ed 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -24,6 +24,13 @@ from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa +# Monkey patch for v1 cinderclient +# NOTE(dtroyer): Do here because openstackclient.volume.client +# doesn't do it until the client object is created now. +volumes.Volume.NAME_ATTR = 'display_name' +volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + + ID = '1after909' NAME = 'PhilSpector' diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 21072aeb01..a7b64def68 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -15,17 +15,8 @@ import logging -from cinderclient import extension -from cinderclient.v1.contrib import list_extensions -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes - from openstackclient.common import utils -# Monkey patch for v1 cinderclient -volumes.Volume.NAME_ATTR = 'display_name' -volume_snapshots.Snapshot.NAME_ATTR = 'display_name' - LOG = logging.getLogger(__name__) DEFAULT_VOLUME_API_VERSION = '1' @@ -38,6 +29,17 @@ API_VERSIONS = { def make_client(instance): """Returns a volume service client.""" + + # Defer client imports until we actually need them + from cinderclient import extension + from cinderclient.v1.contrib import list_extensions + from cinderclient.v1 import volume_snapshots + from cinderclient.v1 import volumes + + # Monkey patch for v1 cinderclient + volumes.Volume.NAME_ATTR = 'display_name' + volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + volume_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME],