summaryrefslogtreecommitdiffstats
path: root/openstack
diff options
context:
space:
mode:
authorLaurence Miao <laurence.miao@gmail.com>2012-10-06 21:08:14 +0800
committerLaurence Miao <laurence.miao@gmail.com>2012-11-15 14:18:50 +0800
commit5f564b24be4592c5212c492e6d67d9f4616229d2 (patch)
treee5af1ff3d589701593e809fc8fcc90c1a94a1b72 /openstack
parentd6fa3847cc64f6815641cdb549dd100e1aab6043 (diff)
downloadoslo-5f564b24be4592c5212c492e6d67d9f4616229d2.tar.gz
oslo-5f564b24be4592c5212c492e6d67d9f4616229d2.tar.xz
oslo-5f564b24be4592c5212c492e6d67d9f4616229d2.zip
argparse support for cfg
* openstack/common/cfg.py Optparse is fading out since python 2.7, this patch will help openstack/common work on more advanded version of python(argparse). Now, disable_interspersed_args() has no effect. Added new method add_cli_subparsers, return argparse subparser, for usages such as subcommand. * tests/unit/test_cfg.py SubcommandTestCase added. Disabled test_disable_interspersed_args test entry for happiness of tox, temporarily. Modified test_help for port of argparse. * tools/pip-requires include argparse module for python 2.6 Change-Id: Ie5cbde1a92a92786d64dea0ddfcfbf288c29f960
Diffstat (limited to 'openstack')
-rw-r--r--openstack/common/cfg.py288
1 files changed, 220 insertions, 68 deletions
diff --git a/openstack/common/cfg.py b/openstack/common/cfg.py
index 1d67514..c256ca7 100644
--- a/openstack/common/cfg.py
+++ b/openstack/common/cfg.py
@@ -251,11 +251,11 @@ in order to support a common usage pattern in OpenStack:
"""
+import argparse
import collections
import copy
import functools
import glob
-import optparse
import os
import string
import sys
@@ -561,17 +561,17 @@ class Opt(object):
:param parser: the CLI option parser
:param group: an optional OptGroup object
"""
- container = self._get_optparse_container(parser, group)
- kwargs = self._get_optparse_kwargs(group)
- prefix = self._get_optparse_prefix('', group)
- self._add_to_optparse(container, self.name, self.short, kwargs, prefix,
+ container = self._get_argparse_container(parser, group)
+ kwargs = self._get_argparse_kwargs(group)
+ prefix = self._get_argparse_prefix('', group)
+ self._add_to_argparse(container, self.name, self.short, kwargs, prefix,
self.deprecated_name)
- def _add_to_optparse(self, container, name, short, kwargs, prefix='',
+ def _add_to_argparse(self, container, name, short, kwargs, prefix='',
deprecated_name=None):
- """Add an option to an optparse parser or group.
+ """Add an option to an argparse parser or group.
- :param container: an optparse.OptionContainer object
+ :param container: an argparse._ArgumentGroup object
:param name: the opt name
:param short: the short opt name
:param kwargs: the keyword arguments for add_option()
@@ -581,30 +581,32 @@ class Opt(object):
args = ['--' + prefix + name]
if short:
args += ['-' + short]
+
if deprecated_name:
args += ['--' + prefix + deprecated_name]
- for a in args:
- if container.has_option(a):
- raise DuplicateOptError(a)
- container.add_option(*args, **kwargs)
- def _get_optparse_container(self, parser, group):
- """Returns an optparse.OptionContainer.
+ try:
+ container.add_argument(*args, **kwargs)
+ except(argparse.ArgumentError) as e:
+ raise DuplicateOptError(e)
+
+ def _get_argparse_container(self, parser, group):
+ """Returns an argparse._ArgumentGroup.
- :param parser: an optparse.OptionParser
+ :param parser: an argparse.ArgumentParser
:param group: an (optional) OptGroup object
- :returns: an optparse.OptionGroup if a group is given, else the parser
+ :returns: an argparse._ArgumentGroup if group is given, else parser
"""
if group is not None:
- return group._get_optparse_group(parser)
+ return group._get_argparse_group(parser)
else:
return parser
- def _get_optparse_kwargs(self, group, **kwargs):
- """Build a dict of keyword arguments for optparse's add_option().
+ def _get_argparse_kwargs(self, group, **kwargs):
+ """Build a dict of keyword arguments for argparse's add_argument().
Most opt types extend this method to customize the behaviour of the
- options added to optparse.
+ options added to argparse.
:param group: an optional group
:param kwargs: optional keyword arguments to add to
@@ -618,7 +620,7 @@ class Opt(object):
'help': self.help, })
return kwargs
- def _get_optparse_prefix(self, prefix, group):
+ def _get_argparse_prefix(self, prefix, group):
"""Build a prefix for the CLI option name, if required.
CLI options in a group are prefixed with the group's name in order
@@ -671,21 +673,32 @@ class BoolOpt(Opt):
def _add_to_cli(self, parser, group=None):
"""Extends the base class method to add the --nooptname option."""
super(BoolOpt, self)._add_to_cli(parser, group)
- self._add_inverse_to_optparse(parser, group)
+ self._add_inverse_to_argparse(parser, group)
- def _add_inverse_to_optparse(self, parser, group):
+ def _add_inverse_to_argparse(self, parser, group):
"""Add the --nooptname option to the option parser."""
- container = self._get_optparse_container(parser, group)
- kwargs = self._get_optparse_kwargs(group, action='store_false')
- prefix = self._get_optparse_prefix('no', group)
+ container = self._get_argparse_container(parser, group)
+ kwargs = self._get_argparse_kwargs(group, action='store_false')
+ prefix = self._get_argparse_prefix('no', group)
kwargs["help"] = "The inverse of --" + self.name
- self._add_to_optparse(container, self.name, None, kwargs, prefix,
+ self._add_to_argparse(container, self.name, None, kwargs, prefix,
self.deprecated_name)
- def _get_optparse_kwargs(self, group, action='store_true', **kwargs):
- """Extends the base optparse keyword dict for boolean options."""
- return super(BoolOpt,
- self)._get_optparse_kwargs(group, action=action, **kwargs)
+ def _get_argparse_kwargs(self, group, action='store_true', **kwargs):
+ """Extends the base argparse keyword dict for boolean options."""
+
+ kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs)
+
+ # metavar has no effect for BoolOpt
+ if 'metavar' in kwargs:
+ del kwargs['metavar']
+
+ if action != 'store_true':
+ action = 'store_false'
+
+ kwargs['action'] = action
+
+ return kwargs
class IntOpt(Opt):
@@ -697,10 +710,10 @@ class IntOpt(Opt):
return [int(v) for v in self._cparser_get_with_deprecated(cparser,
section)]
- def _get_optparse_kwargs(self, group, **kwargs):
- """Extends the base optparse keyword dict for integer options."""
+ def _get_argparse_kwargs(self, group, **kwargs):
+ """Extends the base argparse keyword dict for integer options."""
return super(IntOpt,
- self)._get_optparse_kwargs(group, type='int', **kwargs)
+ self)._get_argparse_kwargs(group, type=int, **kwargs)
class FloatOpt(Opt):
@@ -712,10 +725,10 @@ class FloatOpt(Opt):
return [float(v) for v in
self._cparser_get_with_deprecated(cparser, section)]
- def _get_optparse_kwargs(self, group, **kwargs):
- """Extends the base optparse keyword dict for float options."""
- return super(FloatOpt,
- self)._get_optparse_kwargs(group, type='float', **kwargs)
+ def _get_argparse_kwargs(self, group, **kwargs):
+ """Extends the base argparse keyword dict for float options."""
+ return super(FloatOpt, self)._get_argparse_kwargs(group,
+ type=float, **kwargs)
class ListOpt(Opt):
@@ -724,24 +737,38 @@ class ListOpt(Opt):
List opt values are simple string values separated by commas. The opt value
is a list containing these strings.
"""
+ class _StoreListAction(argparse._StoreAction):
+ """
+ An argparse action for parsing an option value into a list.
+ """
+ def __init__(self,
+ option_strings,
+ dest,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ argparse._StoreAction.__init__(self,
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, values.split(','))
def _get_from_config_parser(self, cparser, section):
"""Retrieve the opt value as a list from ConfigParser."""
return [v.split(',') for v in
self._cparser_get_with_deprecated(cparser, section)]
- def _get_optparse_kwargs(self, group, **kwargs):
- """Extends the base optparse keyword dict for list options."""
- return super(ListOpt,
- self)._get_optparse_kwargs(group,
- type='string',
- action='callback',
- callback=self._parse_list,
- **kwargs)
-
- def _parse_list(self, option, opt, value, parser):
- """An optparse callback for parsing an option value into a list."""
- setattr(parser.values, self.dest, value.split(','))
+ def _get_argparse_kwargs(self, group, **kwargs):
+ """Extends the base argparse keyword dict for list options."""
+ return Opt._get_argparse_kwargs(self,
+ group,
+ action=ListOpt._StoreListAction,
+ **kwargs)
class MultiStrOpt(Opt):
@@ -752,10 +779,10 @@ class MultiStrOpt(Opt):
"""
multi = True
- def _get_optparse_kwargs(self, group, **kwargs):
- """Extends the base optparse keyword dict for multi str options."""
+ def _get_argparse_kwargs(self, group, **kwargs):
+ """Extends the base argparse keyword dict for multi str options."""
return super(MultiStrOpt,
- self)._get_optparse_kwargs(group, action='append')
+ self)._get_argparse_kwargs(group, action='append')
def _cparser_get_with_deprecated(self, cparser, section):
"""If cannot find option as dest try deprecated_name alias."""
@@ -800,7 +827,7 @@ class OptGroup(object):
self.help = help
self._opts = {} # dict of dicts of (opt:, override:, default:)
- self._optparse_group = None
+ self._argparse_group = None
def _register_opt(self, opt):
"""Add an opt to this group.
@@ -824,16 +851,16 @@ class OptGroup(object):
if opt.dest in self._opts:
del self._opts[opt.dest]
- def _get_optparse_group(self, parser):
- """Build an optparse.OptionGroup for this group."""
- if self._optparse_group is None:
- self._optparse_group = optparse.OptionGroup(parser, self.title,
- self.help)
- return self._optparse_group
+ def _get_argparse_group(self, parser):
+ if self._argparse_group is None:
+ """Build an argparse._ArgumentGroup for this group."""
+ self._argparse_group = parser.add_argument_group(self.title,
+ self.help)
+ return self._argparse_group
def _clear(self):
"""Clear this group's option parsing state."""
- self._optparse_group = None
+ self._argparse_group = None
class ParseError(iniparser.ParseError):
@@ -912,6 +939,97 @@ class MultiConfigParser(object):
raise KeyError
+class _VersionAction(argparse.Action):
+
+ def __init__(self,
+ option_strings,
+ version=None,
+ dest=argparse.SUPPRESS,
+ default=argparse.SUPPRESS,
+ help="show program's version number and exit"):
+ super(_VersionAction, self).__init__(option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+ self.version = version
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ version = self.version
+ if version is None:
+ version = parser.version
+ formatter = parser._get_formatter()
+ formatter.add_text(version)
+ message = formatter.format_help()
+ if message:
+ parser._print_message(message, sys.stdout)
+ sys.exit(0)
+
+
+class ConfigCliParser(argparse.ArgumentParser):
+
+ def __init__(self, prog=None, usage=None, version=None, *args, **kwargs):
+ super(ConfigCliParser, self).__init__(prog=prog, usage=usage,
+ *args, **kwargs)
+ # to eliminate error:
+ # ValueError: unsupported format character 'p' (0x70) at index 1
+ if usage is not None:
+ self.usage = usage.replace("%prog", self.prog)
+
+ self.add_option('--version',
+ action=_VersionAction,
+ version=version)
+ self._optionals.title = 'Options'
+
+ def add_option(self, *args, **kwargs):
+ self.add_argument(*args, **kwargs)
+
+ def add_argument(self, *args, **kwargs):
+ try:
+ super(ConfigCliParser, self).add_argument(*args, **kwargs)
+ except(argparse.ArgumentError) as e:
+ raise DuplicateOptError(e)
+
+ def add_subparsers(self, **kwargs):
+ return super(ConfigCliParser, self).add_subparsers(**kwargs)
+
+ def parse_args(self, *args, **kwargs):
+ opts, args = super(ConfigCliParser, self).parse_known_args(*args,
+ **kwargs)
+ unknown_args = []
+ for arg in args:
+ if 0 == arg.find("-"):
+ unknown_args.append(arg)
+
+ if unknown_args:
+ msg = 'unrecognized arguments: %s'
+ self.error(msg % ' '.join(unknown_args))
+
+ return opts, args
+
+ def print_usage(self, file=None):
+ '''
+ overriding original print_usage method, only for
+ compatibility with optparse and happiness of unittest
+ '''
+ print >>file, "Usage: " + self.usage
+
+ def print_help(self, file=None):
+ '''
+ overriding original print_usage method, only for
+ compatibility with optparse and happiness of unittest
+ unittest: HelpTestCase:test_print_help
+ '''
+ msg = self.format_help()
+ if 0 == msg.find("usage:"):
+ msg = msg.replace("usage:", "Usage:")
+ print >>file, msg
+
+ def print_version(self, file=None):
+ super(ConfigCliParser, self)._print_message(self.format_version(),
+ file)
+
+
class ConfigOpts(collections.Mapping):
"""
@@ -928,6 +1046,10 @@ class ConfigOpts(collections.Mapping):
self._groups = {}
self._args = None
+
+ # for subparser support, a root parser should be initialized earlier
+ # and conserved for later use
+ self._pre_init_parser = None
self._oparser = None
self._cparser = None
self._cli_values = {}
@@ -935,19 +1057,33 @@ class ConfigOpts(collections.Mapping):
self._config_opts = []
self._disable_interspersed_args = False
- def _setup(self, project, prog, version, usage, default_config_files):
- """Initialize a ConfigOpts object for option parsing."""
+ def _pre_setup(self, project, prog, version, usage, default_config_files):
+ """Initialize a ConfigCliParser object for option parsing."""
+
if prog is None:
prog = os.path.basename(sys.argv[0])
if default_config_files is None:
default_config_files = find_config_files(project, prog)
- self._oparser = optparse.OptionParser(prog=prog,
- version=version,
- usage=usage)
- if self._disable_interspersed_args:
- self._oparser.disable_interspersed_args()
+ # if _pre_init_parser does not exist, create one
+ if self._pre_init_parser is None:
+ self._oparser = ConfigCliParser(prog=prog,
+ version=version,
+ usage=usage)
+ # otherwise, use the pre-initialized parser with subparsers
+ # and re-initialize parser
+ else:
+ self._oparser = self._pre_init_parser
+ self._oparser.prog = prog
+ self._oparser.version = version
+ self._oparser.usage = usage
+ self._pre_init_parser = None
+
+ return prog, default_config_files
+
+ def _setup(self, project, prog, version, usage, default_config_files):
+ """Initialize a ConfigOpts object for option parsing."""
self._config_opts = [
MultiStrOpt('config-file',
@@ -1017,8 +1153,15 @@ class ConfigOpts(collections.Mapping):
:raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
RequiredOptError, DuplicateOptError
"""
+
self.clear()
+ prog, default_config_files = self._pre_setup(project,
+ prog,
+ version,
+ usage,
+ default_config_files)
+
self._setup(project, prog, version, usage, default_config_files)
self._cli_values, leftovers = self._parse_cli_opts(args)
@@ -1055,6 +1198,13 @@ class ConfigOpts(collections.Mapping):
"""Return the number of options and option groups."""
return len(self._opts) + len(self._groups)
+ def add_cli_subparsers(self, **kwargs):
+ # only add subparsers to pre-initialized root parser
+ # to avoid cleared by self.clear()
+ if self._pre_init_parser is None:
+ self._pre_init_parser = ConfigCliParser()
+ return self._pre_init_parser.add_subparsers(**kwargs)
+
def reset(self):
"""Clear the object state and unset overrides and defaults."""
self._unset_defaults_and_overrides()
@@ -1271,12 +1421,14 @@ class ConfigOpts(collections.Mapping):
i.e. argument parsing is stopped at the first non-option argument.
"""
+ # do nothing
self._disable_interspersed_args = True
def enable_interspersed_args(self):
"""Set parsing to not stop on the first non-option.
This it the default behaviour."""
+ # do nothing
self._disable_interspersed_args = False
def find_file(self, name):