From 97b898b6c7820da9df424b2cef27539139b9514f Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 3 Dec 2012 17:31:40 -0500 Subject: [PATCH] Update openstack-common Now at oslo-incubator version e2fdbd38da576edfd40aeac3956eb48ae1d0e08a Change-Id: Ie0360fc666718c809cddeb9904af35c929dededd Signed-off-by: Jeff Peeler --- heat/openstack/common/cfg.py | 146 ++++++++++++++++++++----- heat/openstack/common/rpc/impl_fake.py | 9 +- 2 files changed, 126 insertions(+), 29 deletions(-) diff --git a/heat/openstack/common/cfg.py b/heat/openstack/common/cfg.py index f792c977..ac34a954 100644 --- a/heat/openstack/common/cfg.py +++ b/heat/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 @@ -612,7 +632,8 @@ class Opt(object): kwargs['dest'] = dest else: kwargs['nargs'] = '?' - kwargs.update({'metavar': self.metavar, + kwargs.update({'default': None, + 'metavar': self.metavar, 'help': self.help, }) return kwargs @@ -786,6 +807,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 +1023,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 +1038,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 +1159,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 +1166,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 +1463,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 +1658,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): """ diff --git a/heat/openstack/common/rpc/impl_fake.py b/heat/openstack/common/rpc/impl_fake.py index c2935960..d56665b4 100644 --- a/heat/openstack/common/rpc/impl_fake.py +++ b/heat/openstack/common/rpc/impl_fake.py @@ -18,11 +18,15 @@ queues. Casts will block, but this is very useful for tests. """ import inspect +# NOTE(russellb): We specifically want to use json, not our own jsonutils. +# jsonutils has some extra logic to automatically convert objects to primitive +# types so that they can be serialized. We want to catch all cases where +# non-primitive types make it into this code and treat it as an error. +import json import time import eventlet -from heat.openstack.common import jsonutils from heat.openstack.common.rpc import common as rpc_common CONSUMERS = {} @@ -121,7 +125,7 @@ def create_connection(conf, new=True): def check_serialize(msg): """Make sure a message intended for rpc can be serialized.""" - jsonutils.dumps(msg) + json.dumps(msg) def multicall(conf, context, topic, msg, timeout=None): @@ -154,6 +158,7 @@ def call(conf, context, topic, msg, timeout=None): def cast(conf, context, topic, msg): + check_serialize(msg) try: call(conf, context, topic, msg) except Exception: -- 2.45.2