diff options
| author | Mark McLoughlin <markmc@redhat.com> | 2012-11-23 15:50:04 +0000 |
|---|---|---|
| committer | Mark McLoughlin <markmc@redhat.com> | 2012-11-29 17:40:44 +0000 |
| commit | d194fd9edc2f94ffb3a922a853a7c4421fe44109 (patch) | |
| tree | 66e0157a83a1697a8f934bed11ee320fea9b6e30 /openstack | |
| parent | 21e1cd3c01675c2df38f2833ca1ca8edb6d399d1 (diff) | |
| download | oslo-d194fd9edc2f94ffb3a922a853a7c4421fe44109.tar.gz oslo-d194fd9edc2f94ffb3a922a853a7c4421fe44109.tar.xz oslo-d194fd9edc2f94ffb3a922a853a7c4421fe44109.zip | |
Improve cfg's argparse sub-parsers support
In order for sub-parsers to be useful, you need some way of knowing
which sub-parser was chosen during argument parsing. It's pretty obvious
from the current sub-parsers test case that we don't have a convenient
interface for this.
One way of doing it is to use the 'dest' argument when adding
sub-parsers:
>>> subparsers = parser.add_subparsers(dest='cmd')
>>> subparsers.add_parser('a')
>>> subparsers.add_parser('b')
>>> parser.parse_args(['a'])
Namespace(cmd='a')
The most sensible way to map this into cfg concepts is to register
sub-parsers as an Opt. This way, we can make name and argument values
of the sub-parser as an attribute on the ConfigOpts object:
>>> def add_parsers(subparsers):
... a = subparsers.add_parser('a')
... a.add_argument('id')
... b = subparsers.add_parser('b')
...
>>> CONF.register_cli_opt(SubCommandOpt('cmd', handler=add_parsers))
True
>>> CONF(['a', '10'])
>>> CONF.cmd.name, CONF.cmd.id
('a', '10')
The handler method is a bit awkward, but each time cfg is to parse
command line args it takes all the registered opts and creates a new
argparse parser. So we need to be able to re-add the sub-parsers each
time.
Change-Id: I01bfd01bf8853cf57a9248b1663eb3da142366a4
Diffstat (limited to 'openstack')
| -rw-r--r-- | openstack/common/cfg.py | 143 |
1 files changed, 117 insertions, 26 deletions
diff --git a/openstack/common/cfg.py b/openstack/common/cfg.py index 673675b..28baa04 100644 --- a/openstack/common/cfg.py +++ b/openstack/common/cfg.py @@ -205,8 +205,6 @@ Option values may reference other values using PEP 292 string substitution:: Note that interpolation can be avoided by using '$$'. -FIXME(markmc): document add_cli_subparsers() - Options may be declared as required so that an error is raised if the user does not supply a value for the option. @@ -235,6 +233,28 @@ in order to support a common usage pattern in OpenStack:: def start(server, app): server.start(app, CONF.bind_port, CONF.bind_host) +Positional command line arguments are supported via a 'positional' Opt +constructor argument:: + + >>> CONF.register_cli_opt(MultiStrOpt('bar', positional=True)) + True + >>> CONF(['a', 'b']) + >>> CONF.bar + ['a', 'b'] + +It is also possible to use argparse "sub-parsers" to parse additional +command line arguments using the SubCommandOpt class: + + >>> def add_parsers(subparsers): + ... list_action = subparsers.add_parser('list') + ... list_action.add_argument('id') + ... + >>> CONF.register_cli_opt(SubCommandOpt('action', handler=add_parsers)) + True + >>> CONF(['list', '10']) + >>> CONF.action.name, CONF.action.id + ('list', '10') + """ import argparse @@ -786,6 +806,57 @@ class MultiStrOpt(Opt): return cparser.get(section, [self.dest], multi=True) +class SubCommandOpt(Opt): + + """ + Sub-command options allow argparse sub-parsers to be used to parse + additional command line arguments. + + The handler argument to the SubCommandOpt contructor is a callable + which is supplied an argparse subparsers object. Use this handler + callable to add sub-parsers. + + The opt value is SubCommandAttr object with the name of the chosen + sub-parser stored in the 'name' attribute and the values of other + sub-parser arguments available as additional attributes. + """ + + def __init__(self, name, dest=None, handler=None, + title=None, description=None, help=None): + """Construct an sub-command parsing option. + + This behaves similarly to other Opt sub-classes but adds a + 'handler' argument. The handler is a callable which is supplied + an subparsers object when invoked. The add_parser() method on + this subparsers object can be used to register parsers for + sub-commands. + + :param name: the option's name + :param dest: the name of the corresponding ConfigOpts property + :param title: title of the sub-commands group in help output + :param description: description of the group in help output + :param help: a help string giving an overview of available sub-commands + """ + super(SubCommandOpt, self).__init__(name, dest=dest, help=help) + self.handler = handler + self.title = title + self.description = description + + def _add_to_cli(self, parser, group=None): + """Add argparse sub-parsers and invoke the handler method.""" + dest = self.dest + if group is not None: + dest = group.name + '_' + dest + + subparsers = parser.add_subparsers(dest=dest, + title=self.title, + description=self.description, + help=self.help) + + if not self.handler is None: + self.handler(subparsers) + + class OptGroup(object): """ @@ -951,9 +1022,6 @@ class ConfigOpts(collections.Mapping): 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 = {} @@ -969,18 +1037,7 @@ class ConfigOpts(collections.Mapping): if default_config_files is None: default_config_files = find_config_files(project, prog) - # if _pre_init_parser does not exist, create one - if self._pre_init_parser is None: - self._oparser = argparse.ArgumentParser(prog=prog, 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 - + self._oparser = argparse.ArgumentParser(prog=prog, usage=usage) self._oparser.add_argument('--version', action='version', version=version) @@ -1101,13 +1158,6 @@ 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 = argparse.ArgumentParser() - 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() @@ -1115,10 +1165,14 @@ class ConfigOpts(collections.Mapping): @__clear_cache def clear(self): - """Clear the state of the object to before it was called.""" + """Clear the state of the object to before it was called. + + Any subparsers added using the add_cli_subparsers() will also be + removed as a side-effect of this method. + """ self._args = None self._cli_values.clear() - self._oparser = None + self._oparser = argparse.ArgumentParser() self._cparser = None self.unregister_opts(self._config_opts) for group in self._groups.values(): @@ -1408,6 +1462,9 @@ class ConfigOpts(collections.Mapping): info = self._get_opt_info(name, group) opt = info['opt'] + if isinstance(opt, SubCommandOpt): + return self.SubCommandAttr(self, group, opt.dest) + if 'override' in info: return info['override'] @@ -1600,6 +1657,40 @@ class ConfigOpts(collections.Mapping): """Return the number of options and option groups.""" return len(self.group._opts) + class SubCommandAttr(object): + + """ + A helper class representing the name and arguments of an argparse + sub-parser. + """ + + def __init__(self, conf, group, dest): + """Construct a SubCommandAttr object. + + :param conf: a ConfigOpts object + :param group: an OptGroup object + :param dest: the name of the sub-parser + """ + self._conf = conf + self._group = group + self._dest = dest + + def __getattr__(self, name): + """Look up a sub-parser name or argument value.""" + if name == 'name': + name = self._dest + if self._group is not None: + name = self._group.name + '_' + name + return self._conf._cli_values[name] + + if name in self._conf: + raise DuplicateOptError(name) + + try: + return self._conf._cli_values[name] + except KeyError: + raise NoSuchOptError(name) + class StrSubWrapper(object): """ |
