Port to argparse based cfg
Import latest cfg from oslo-incubator with these changes: Add deprecated --logdir common opt Add deprecated --logfile common opt. Allow nova and others to override some logging defaults Fixing the trim for ListOp when reading from config file Fix set_default() with boolean CLI options Improve cfg's argparse sub-parsers support Hide the GroupAttr conf and group attributes Fix regression with cfg CLI arguments Fix broken --help with CommonConfigOpts Fix ListOpt to trim whitespace updating sphinx documentation Don't reference argparse._StoreAction Fix minor coding style issue Remove ConfigCliParser class Add support for positional arguments Use stock argparse behaviour for optional args Use stock argparse --usage behaviour Use stock argparse --version behaviour Remove add_option() method Completely remove cfg's disable_interspersed_args() argparse support for cfg The main cfg API change is that CONF() no longer returns the un-parsed CLI arguments. To handle these args, you need to use the support for positional arguments or sub-parsers. Switching nova-manage to use sub-parser based CLI arguments means the following changes in behaviour: - no more lazy matching of commands - e.g. 'nova-manage proj q' will no longer work. If we find out about common abbreviations used in peoples' scripts, we can easily add those. - the help output displayed if you run nova-manage without any args (or just a category) has changed - 'nova-manage version list' is no longer equivalent to 'nova-manage version' Change-Id: I19ef3a1c00e97af64d199e27cb1cdc5c63b46a82
This commit is contained in:
parent
f354276930
commit
5e09b2cab7
|
@ -48,12 +48,18 @@ from nova.openstack.common import log as logging
|
||||||
from nova.openstack.common import rpc
|
from nova.openstack.common import rpc
|
||||||
|
|
||||||
|
|
||||||
delete_exchange_opt = cfg.BoolOpt('delete_exchange',
|
opts = [
|
||||||
|
cfg.MultiStrOpt('queues',
|
||||||
|
default=[],
|
||||||
|
positional=True,
|
||||||
|
help='Queues to delete'),
|
||||||
|
cfg.BoolOpt('delete_exchange',
|
||||||
default=False,
|
default=False,
|
||||||
help='delete nova exchange too.')
|
help='delete nova exchange too.'),
|
||||||
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_cli_opt(delete_exchange_opt)
|
CONF.register_cli_opts(opts)
|
||||||
|
|
||||||
|
|
||||||
def delete_exchange(exch):
|
def delete_exchange(exch):
|
||||||
|
@ -69,8 +75,8 @@ def delete_queues(queues):
|
||||||
x.queue_delete(q)
|
x.queue_delete(q)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
args = config.parse_args(sys.argv)
|
config.parse_args(sys.argv)
|
||||||
logging.setup("nova")
|
logging.setup("nova")
|
||||||
delete_queues(args[1:])
|
delete_queues(CONF.queues)
|
||||||
if CONF.delete_exchange:
|
if CONF.delete_exchange:
|
||||||
delete_exchange(CONF.control_exchange)
|
delete_exchange(CONF.control_exchange)
|
||||||
|
|
|
@ -93,28 +93,44 @@ def init_leases(network_id):
|
||||||
return network_manager.get_dhcp_leases(ctxt, network_ref)
|
return network_manager.get_dhcp_leases(ctxt, network_ref)
|
||||||
|
|
||||||
|
|
||||||
|
def add_action_parsers(subparsers):
|
||||||
|
parser = subparsers.add_parser('init')
|
||||||
|
|
||||||
|
for action in ['add', 'del', 'old']:
|
||||||
|
parser = subparsers.add_parser(action)
|
||||||
|
parser.add_argument('mac')
|
||||||
|
parser.add_argument('ip')
|
||||||
|
parser.set_defaults(func=globals()[action + '_lease'])
|
||||||
|
|
||||||
|
|
||||||
|
CONF.register_cli_opt(
|
||||||
|
cfg.SubCommandOpt('action',
|
||||||
|
title='Action options',
|
||||||
|
help='Available dhcpbridge options',
|
||||||
|
handler=add_action_parsers))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Parse environment and arguments and call the approproate action."""
|
"""Parse environment and arguments and call the approproate action."""
|
||||||
try:
|
try:
|
||||||
config_file = os.environ['CONFIG_FILE']
|
config_file = os.environ['CONFIG_FILE']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
config_file = os.environ['FLAGFILE']
|
config_file = os.environ['FLAGFILE']
|
||||||
argv = config.parse_args(sys.argv, default_config_files=[config_file])
|
|
||||||
|
config.parse_args(sys.argv, default_config_files=[config_file])
|
||||||
|
|
||||||
logging.setup("nova")
|
logging.setup("nova")
|
||||||
|
|
||||||
if int(os.environ.get('TESTING', '0')):
|
if int(os.environ.get('TESTING', '0')):
|
||||||
from nova.tests import fake_flags
|
from nova.tests import fake_flags
|
||||||
|
|
||||||
action = argv[1]
|
if CONF.action.name in ['add', 'del', 'old']:
|
||||||
if action in ['add', 'del', 'old']:
|
|
||||||
mac = argv[2]
|
|
||||||
ip = argv[3]
|
|
||||||
msg = (_("Called '%(action)s' for mac '%(mac)s' with ip '%(ip)s'") %
|
msg = (_("Called '%(action)s' for mac '%(mac)s' with ip '%(ip)s'") %
|
||||||
{"action": action,
|
{"action": CONF.action.name,
|
||||||
"mac": mac,
|
"mac": CONF.action.mac,
|
||||||
"ip": ip})
|
"ip": CONF.action.ip})
|
||||||
LOG.debug(msg)
|
LOG.debug(msg)
|
||||||
globals()[action + '_lease'](mac, ip)
|
CONF.action.func(CONF.action.mac, CONF.action.ip)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
network_id = int(os.environ.get('NETWORK_ID'))
|
network_id = int(os.environ.get('NETWORK_ID'))
|
||||||
|
|
189
bin/nova-manage
189
bin/nova-manage
|
@ -56,7 +56,6 @@
|
||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
import netaddr
|
import netaddr
|
||||||
import optparse
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ QUOTAS = quota.QUOTAS
|
||||||
# Decorators for actions
|
# Decorators for actions
|
||||||
def args(*args, **kwargs):
|
def args(*args, **kwargs):
|
||||||
def _decorator(func):
|
def _decorator(func):
|
||||||
func.__dict__.setdefault('options', []).insert(0, (args, kwargs))
|
func.__dict__.setdefault('args', []).insert(0, (args, kwargs))
|
||||||
return func
|
return func
|
||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
|
@ -764,21 +763,6 @@ class DbCommands(object):
|
||||||
print migration.db_version()
|
print migration.db_version()
|
||||||
|
|
||||||
|
|
||||||
class VersionCommands(object):
|
|
||||||
"""Class for exposing the codebase version."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def list(self):
|
|
||||||
print (_("%(version)s (%(vcs)s)") %
|
|
||||||
{'version': version.version_string(),
|
|
||||||
'vcs': version.version_string_with_vcs()})
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
self.list()
|
|
||||||
|
|
||||||
|
|
||||||
class InstanceTypeCommands(object):
|
class InstanceTypeCommands(object):
|
||||||
"""Class for managing instance types / flavors."""
|
"""Class for managing instance types / flavors."""
|
||||||
|
|
||||||
|
@ -982,10 +966,10 @@ class GetLogCommands(object):
|
||||||
def errors(self):
|
def errors(self):
|
||||||
"""Get all of the errors from the log files"""
|
"""Get all of the errors from the log files"""
|
||||||
error_found = 0
|
error_found = 0
|
||||||
if CONF.logdir:
|
if CONF.log_dir:
|
||||||
logs = [x for x in os.listdir(CONF.logdir) if x.endswith('.log')]
|
logs = [x for x in os.listdir(CONF.log_dir) if x.endswith('.log')]
|
||||||
for file in logs:
|
for file in logs:
|
||||||
log_file = os.path.join(CONF.logdir, file)
|
log_file = os.path.join(CONF.log_dir, file)
|
||||||
lines = [line.strip() for line in open(log_file, "r")]
|
lines = [line.strip() for line in open(log_file, "r")]
|
||||||
lines.reverse()
|
lines.reverse()
|
||||||
print_name = 0
|
print_name = 0
|
||||||
|
@ -1026,45 +1010,23 @@ class GetLogCommands(object):
|
||||||
print _('No nova entries in syslog!')
|
print _('No nova entries in syslog!')
|
||||||
|
|
||||||
|
|
||||||
CATEGORIES = [
|
CATEGORIES = {
|
||||||
('account', AccountCommands),
|
'account': AccountCommands,
|
||||||
('agent', AgentBuildCommands),
|
'agent': AgentBuildCommands,
|
||||||
('db', DbCommands),
|
'db': DbCommands,
|
||||||
('fixed', FixedIpCommands),
|
'fixed': FixedIpCommands,
|
||||||
('flavor', InstanceTypeCommands),
|
'flavor': InstanceTypeCommands,
|
||||||
('floating', FloatingIpCommands),
|
'floating': FloatingIpCommands,
|
||||||
('host', HostCommands),
|
'host': HostCommands,
|
||||||
('instance_type', InstanceTypeCommands),
|
'instance_type': InstanceTypeCommands,
|
||||||
('logs', GetLogCommands),
|
'logs': GetLogCommands,
|
||||||
('network', NetworkCommands),
|
'network': NetworkCommands,
|
||||||
('project', ProjectCommands),
|
'project': ProjectCommands,
|
||||||
('service', ServiceCommands),
|
'service': ServiceCommands,
|
||||||
('shell', ShellCommands),
|
'shell': ShellCommands,
|
||||||
('version', VersionCommands),
|
'vm': VmCommands,
|
||||||
('vm', VmCommands),
|
'vpn': VpnCommands,
|
||||||
('vpn', VpnCommands),
|
}
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def lazy_match(name, key_value_tuples):
|
|
||||||
"""Finds all objects that have a key that case insensitively contains
|
|
||||||
[name] key_value_tuples is a list of tuples of the form (key, value)
|
|
||||||
returns a list of tuples of the form (key, value)"""
|
|
||||||
result = []
|
|
||||||
for (k, v) in key_value_tuples:
|
|
||||||
if k.lower().find(name.lower()) == 0:
|
|
||||||
result.append((k, v))
|
|
||||||
if len(result) == 0:
|
|
||||||
print _('%s does not match any options:') % name
|
|
||||||
for k, _v in key_value_tuples:
|
|
||||||
print "\t%s" % k
|
|
||||||
sys.exit(2)
|
|
||||||
if len(result) > 1:
|
|
||||||
print _('%s matched multiple options:') % name
|
|
||||||
for k, _v in result:
|
|
||||||
print "\t%s" % k
|
|
||||||
sys.exit(2)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def methods_of(obj):
|
def methods_of(obj):
|
||||||
|
@ -1077,11 +1039,46 @@ def methods_of(obj):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def add_command_parsers(subparsers):
|
||||||
|
parser = subparsers.add_parser('version')
|
||||||
|
|
||||||
|
parser = subparsers.add_parser('bash-completion')
|
||||||
|
parser.add_argument('query_category', nargs='?')
|
||||||
|
|
||||||
|
for category in CATEGORIES:
|
||||||
|
command_object = CATEGORIES[category]()
|
||||||
|
|
||||||
|
parser = subparsers.add_parser(category)
|
||||||
|
parser.set_defaults(command_object=command_object)
|
||||||
|
|
||||||
|
category_subparsers = parser.add_subparsers(dest='action')
|
||||||
|
|
||||||
|
for (action, action_fn) in methods_of(command_object):
|
||||||
|
parser = category_subparsers.add_parser(action)
|
||||||
|
|
||||||
|
action_kwargs = []
|
||||||
|
for args, kwargs in getattr(action_fn, 'args', []):
|
||||||
|
action_kwargs.append(kwargs['dest'])
|
||||||
|
kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
|
||||||
|
parser.add_argument(*args, **kwargs)
|
||||||
|
|
||||||
|
parser.set_defaults(action_fn=action_fn)
|
||||||
|
parser.set_defaults(action_kwargs=action_kwargs)
|
||||||
|
|
||||||
|
parser.add_argument('action_args', nargs='*')
|
||||||
|
|
||||||
|
|
||||||
|
category_opt = cfg.SubCommandOpt('category',
|
||||||
|
title='Command categories',
|
||||||
|
help='Available categories',
|
||||||
|
handler=add_command_parsers)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Parse options and call the appropriate class/method."""
|
"""Parse options and call the appropriate class/method."""
|
||||||
|
CONF.register_cli_opt(category_opt)
|
||||||
try:
|
try:
|
||||||
argv = config.parse_args(sys.argv)
|
config.parse_args(sys.argv)
|
||||||
logging.setup("nova")
|
logging.setup("nova")
|
||||||
except cfg.ConfigFilesNotFoundError:
|
except cfg.ConfigFilesNotFoundError:
|
||||||
cfgfile = CONF.config_file[-1] if CONF.config_file else None
|
cfgfile = CONF.config_file[-1] if CONF.config_file else None
|
||||||
|
@ -1096,69 +1093,33 @@ def main():
|
||||||
print _('Please re-run nova-manage as root.')
|
print _('Please re-run nova-manage as root.')
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
script_name = argv.pop(0)
|
if CONF.category.name == "version":
|
||||||
if len(argv) < 1:
|
print (_("%(version)s (%(vcs)s)") %
|
||||||
print (_("\nOpenStack Nova version: %(version)s (%(vcs)s)\n") %
|
|
||||||
{'version': version.version_string(),
|
{'version': version.version_string(),
|
||||||
'vcs': version.version_string_with_vcs()})
|
'vcs': version.version_string_with_vcs()})
|
||||||
print script_name + " category action [<args>]"
|
sys.exit(0)
|
||||||
print _("Available categories:")
|
|
||||||
for k, _v in CATEGORIES:
|
if CONF.category.name == "bash-completion":
|
||||||
print "\t%s" % k
|
if not CONF.category.query_category:
|
||||||
sys.exit(2)
|
print " ".join(CATEGORIES.keys())
|
||||||
category = argv.pop(0)
|
elif CONF.category.query_category in CATEGORIES:
|
||||||
if category == "bash-completion":
|
fn = CATEGORIES[CONF.category.query_category]
|
||||||
if len(argv) < 1:
|
|
||||||
print " ".join([k for (k, v) in CATEGORIES])
|
|
||||||
else:
|
|
||||||
query_category = argv.pop(0)
|
|
||||||
matches = lazy_match(query_category, CATEGORIES)
|
|
||||||
# instantiate the command group object
|
|
||||||
category, fn = matches[0]
|
|
||||||
command_object = fn()
|
command_object = fn()
|
||||||
actions = methods_of(command_object)
|
actions = methods_of(command_object)
|
||||||
print " ".join([k for (k, v) in actions])
|
print " ".join([k for (k, v) in actions])
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
matches = lazy_match(category, CATEGORIES)
|
|
||||||
# instantiate the command group object
|
|
||||||
category, fn = matches[0]
|
|
||||||
command_object = fn()
|
|
||||||
actions = methods_of(command_object)
|
|
||||||
if len(argv) < 1:
|
|
||||||
if hasattr(command_object, '__call__'):
|
|
||||||
action = ''
|
|
||||||
fn = command_object.__call__
|
|
||||||
else:
|
|
||||||
print script_name + " category action [<args>]"
|
|
||||||
print _("Available actions for %s category:") % category
|
|
||||||
for k, _v in actions:
|
|
||||||
print "\t%s" % k
|
|
||||||
sys.exit(2)
|
|
||||||
else:
|
|
||||||
action = argv.pop(0)
|
|
||||||
matches = lazy_match(action, actions)
|
|
||||||
action, fn = matches[0]
|
|
||||||
|
|
||||||
# For not decorated methods
|
fn = CONF.category.action_fn
|
||||||
options = getattr(fn, 'options', [])
|
fn_args = [arg.decode('utf-8') for arg in CONF.category.action_args]
|
||||||
|
fn_kwargs = {}
|
||||||
usage = "%%prog %s %s <args> [options]" % (category, action)
|
for k in CONF.category.action_kwargs:
|
||||||
parser = optparse.OptionParser(usage=usage)
|
v = getattr(CONF.category, 'action_kwarg_' + k)
|
||||||
for ar, kw in options:
|
|
||||||
parser.add_option(*ar, **kw)
|
|
||||||
(opts, fn_args) = parser.parse_args(argv)
|
|
||||||
fn_kwargs = vars(opts)
|
|
||||||
|
|
||||||
for k, v in fn_kwargs.items():
|
|
||||||
if v is None:
|
if v is None:
|
||||||
del fn_kwargs[k]
|
continue
|
||||||
elif isinstance(v, basestring):
|
if isinstance(v, basestring):
|
||||||
fn_kwargs[k] = v.decode('utf-8')
|
v = v.decode('utf-8')
|
||||||
else:
|
|
||||||
fn_kwargs[k] = v
|
fn_kwargs[k] = v
|
||||||
|
|
||||||
fn_args = [arg.decode('utf-8') for arg in fn_args]
|
|
||||||
|
|
||||||
# call the action with the remaining arguments
|
# call the action with the remaining arguments
|
||||||
try:
|
try:
|
||||||
fn(*fn_args, **fn_kwargs)
|
fn(*fn_args, **fn_kwargs)
|
||||||
|
|
|
@ -285,7 +285,6 @@ cfg.CONF.register_opts(global_opts)
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv, default_config_files=None):
|
def parse_args(argv, default_config_files=None):
|
||||||
cfg.CONF.disable_interspersed_args()
|
cfg.CONF(argv[1:],
|
||||||
return argv[:1] + cfg.CONF(argv[1:],
|
|
||||||
project='nova',
|
project='nova',
|
||||||
default_config_files=default_config_files)
|
default_config_files=default_config_files)
|
||||||
|
|
|
@ -35,8 +35,8 @@ class MiniDNS(object):
|
||||||
A proper implementation will need some manner of locking."""
|
A proper implementation will need some manner of locking."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if CONF.logdir:
|
if CONF.log_dir:
|
||||||
self.filename = os.path.join(CONF.logdir, "dnstest.txt")
|
self.filename = os.path.join(CONF.log_dir, "dnstest.txt")
|
||||||
self.tempdir = None
|
self.tempdir = None
|
||||||
else:
|
else:
|
||||||
self.tempdir = tempfile.mkdtemp()
|
self.tempdir = tempfile.mkdtemp()
|
||||||
|
|
|
@ -205,27 +205,11 @@ Option values may reference other values using PEP 292 string substitution::
|
||||||
|
|
||||||
Note that interpolation can be avoided by using '$$'.
|
Note that interpolation can be avoided by using '$$'.
|
||||||
|
|
||||||
For command line utilities that dispatch to other command line utilities, the
|
|
||||||
disable_interspersed_args() method is available. If this this method is called,
|
|
||||||
then parsing e.g.::
|
|
||||||
|
|
||||||
script --verbose cmd --debug /tmp/mything
|
|
||||||
|
|
||||||
will no longer return::
|
|
||||||
|
|
||||||
['cmd', '/tmp/mything']
|
|
||||||
|
|
||||||
as the leftover arguments, but will instead return::
|
|
||||||
|
|
||||||
['cmd', '--debug', '/tmp/mything']
|
|
||||||
|
|
||||||
i.e. argument parsing is stopped at the first non-option argument.
|
|
||||||
|
|
||||||
Options may be declared as required so that an error is raised if the user
|
Options may be declared as required so that an error is raised if the user
|
||||||
does not supply a value for the option.
|
does not supply a value for the option.
|
||||||
|
|
||||||
Options may be declared as secret so that their values are not leaked into
|
Options may be declared as secret so that their values are not leaked into
|
||||||
log files:
|
log files::
|
||||||
|
|
||||||
opts = [
|
opts = [
|
||||||
cfg.StrOpt('s3_store_access_key', secret=True),
|
cfg.StrOpt('s3_store_access_key', secret=True),
|
||||||
|
@ -234,7 +218,7 @@ log files:
|
||||||
]
|
]
|
||||||
|
|
||||||
This module also contains a global instance of the CommonConfigOpts class
|
This module also contains a global instance of the CommonConfigOpts class
|
||||||
in order to support a common usage pattern in OpenStack:
|
in order to support a common usage pattern in OpenStack::
|
||||||
|
|
||||||
from nova.openstack.common import cfg
|
from nova.openstack.common import cfg
|
||||||
|
|
||||||
|
@ -249,13 +233,35 @@ in order to support a common usage pattern in OpenStack:
|
||||||
def start(server, app):
|
def start(server, app):
|
||||||
server.start(app, CONF.bind_port, CONF.bind_host)
|
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
|
||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
import optparse
|
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
@ -474,6 +480,13 @@ def _is_opt_registered(opts, opt):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def set_defaults(opts, **kwargs):
|
||||||
|
for opt in opts:
|
||||||
|
if opt.dest in kwargs:
|
||||||
|
opt.default = kwargs[opt.dest]
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class Opt(object):
|
class Opt(object):
|
||||||
|
|
||||||
"""Base class for all configuration options.
|
"""Base class for all configuration options.
|
||||||
|
@ -489,6 +502,8 @@ class Opt(object):
|
||||||
a single character CLI option name
|
a single character CLI option name
|
||||||
default:
|
default:
|
||||||
the default value of the option
|
the default value of the option
|
||||||
|
positional:
|
||||||
|
True if the option is a positional CLI argument
|
||||||
metavar:
|
metavar:
|
||||||
the name shown as the argument to a CLI option in --help output
|
the name shown as the argument to a CLI option in --help output
|
||||||
help:
|
help:
|
||||||
|
@ -497,8 +512,8 @@ class Opt(object):
|
||||||
multi = False
|
multi = False
|
||||||
|
|
||||||
def __init__(self, name, dest=None, short=None, default=None,
|
def __init__(self, name, dest=None, short=None, default=None,
|
||||||
metavar=None, help=None, secret=False, required=False,
|
positional=False, metavar=None, help=None,
|
||||||
deprecated_name=None):
|
secret=False, required=False, deprecated_name=None):
|
||||||
"""Construct an Opt object.
|
"""Construct an Opt object.
|
||||||
|
|
||||||
The only required parameter is the option's name. However, it is
|
The only required parameter is the option's name. However, it is
|
||||||
|
@ -508,6 +523,7 @@ class Opt(object):
|
||||||
:param dest: the name of the corresponding ConfigOpts property
|
:param dest: the name of the corresponding ConfigOpts property
|
||||||
:param short: a single character CLI option name
|
:param short: a single character CLI option name
|
||||||
:param default: the default value of the option
|
:param default: the default value of the option
|
||||||
|
:param positional: True if the option is a positional CLI argument
|
||||||
:param metavar: the option argument to show in --help
|
:param metavar: the option argument to show in --help
|
||||||
:param help: an explanation of how the option is used
|
:param help: an explanation of how the option is used
|
||||||
:param secret: true iff the value should be obfuscated in log output
|
:param secret: true iff the value should be obfuscated in log output
|
||||||
|
@ -521,6 +537,7 @@ class Opt(object):
|
||||||
self.dest = dest
|
self.dest = dest
|
||||||
self.short = short
|
self.short = short
|
||||||
self.default = default
|
self.default = default
|
||||||
|
self.positional = positional
|
||||||
self.metavar = metavar
|
self.metavar = metavar
|
||||||
self.help = help
|
self.help = help
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
|
@ -561,64 +578,73 @@ class Opt(object):
|
||||||
:param parser: the CLI option parser
|
:param parser: the CLI option parser
|
||||||
:param group: an optional OptGroup object
|
:param group: an optional OptGroup object
|
||||||
"""
|
"""
|
||||||
container = self._get_optparse_container(parser, group)
|
container = self._get_argparse_container(parser, group)
|
||||||
kwargs = self._get_optparse_kwargs(group)
|
kwargs = self._get_argparse_kwargs(group)
|
||||||
prefix = self._get_optparse_prefix('', group)
|
prefix = self._get_argparse_prefix('', group)
|
||||||
self._add_to_optparse(container, self.name, self.short, kwargs, prefix,
|
self._add_to_argparse(container, self.name, self.short, kwargs, prefix,
|
||||||
self.deprecated_name)
|
self.positional, 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):
|
positional=False, 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 name: the opt name
|
||||||
:param short: the short opt name
|
:param short: the short opt name
|
||||||
:param kwargs: the keyword arguments for add_option()
|
:param kwargs: the keyword arguments for add_argument()
|
||||||
:param prefix: an optional prefix to prepend to the opt name
|
:param prefix: an optional prefix to prepend to the opt name
|
||||||
|
:param position: whether the optional is a positional CLI argument
|
||||||
:raises: DuplicateOptError if a naming confict is detected
|
:raises: DuplicateOptError if a naming confict is detected
|
||||||
"""
|
"""
|
||||||
args = ['--' + prefix + name]
|
def hyphen(arg):
|
||||||
|
return arg if not positional else ''
|
||||||
|
|
||||||
|
args = [hyphen('--') + prefix + name]
|
||||||
if short:
|
if short:
|
||||||
args += ['-' + short]
|
args.append(hyphen('-') + short)
|
||||||
if deprecated_name:
|
if deprecated_name:
|
||||||
args += ['--' + prefix + deprecated_name]
|
args.append(hyphen('--') + 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):
|
try:
|
||||||
"""Returns an optparse.OptionContainer.
|
container.add_argument(*args, **kwargs)
|
||||||
|
except argparse.ArgumentError as e:
|
||||||
|
raise DuplicateOptError(e)
|
||||||
|
|
||||||
:param parser: an optparse.OptionParser
|
def _get_argparse_container(self, parser, group):
|
||||||
|
"""Returns an argparse._ArgumentGroup.
|
||||||
|
|
||||||
|
:param parser: an argparse.ArgumentParser
|
||||||
:param group: an (optional) OptGroup object
|
: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:
|
if group is not None:
|
||||||
return group._get_optparse_group(parser)
|
return group._get_argparse_group(parser)
|
||||||
else:
|
else:
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, **kwargs):
|
def _get_argparse_kwargs(self, group, **kwargs):
|
||||||
"""Build a dict of keyword arguments for optparse's add_option().
|
"""Build a dict of keyword arguments for argparse's add_argument().
|
||||||
|
|
||||||
Most opt types extend this method to customize the behaviour of the
|
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 group: an optional group
|
||||||
:param kwargs: optional keyword arguments to add to
|
:param kwargs: optional keyword arguments to add to
|
||||||
:returns: a dict of keyword arguments
|
:returns: a dict of keyword arguments
|
||||||
"""
|
"""
|
||||||
|
if not self.positional:
|
||||||
dest = self.dest
|
dest = self.dest
|
||||||
if group is not None:
|
if group is not None:
|
||||||
dest = group.name + '_' + dest
|
dest = group.name + '_' + dest
|
||||||
kwargs.update({'dest': dest,
|
kwargs['dest'] = dest
|
||||||
|
else:
|
||||||
|
kwargs['nargs'] = '?'
|
||||||
|
kwargs.update({'default': None,
|
||||||
'metavar': self.metavar,
|
'metavar': self.metavar,
|
||||||
'help': self.help, })
|
'help': self.help, })
|
||||||
return kwargs
|
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.
|
"""Build a prefix for the CLI option name, if required.
|
||||||
|
|
||||||
CLI options in a group are prefixed with the group's name in order
|
CLI options in a group are prefixed with the group's name in order
|
||||||
|
@ -656,6 +682,11 @@ class BoolOpt(Opt):
|
||||||
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
|
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
|
||||||
'0': False, 'no': False, 'false': False, 'off': False}
|
'0': False, 'no': False, 'false': False, 'off': False}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if 'positional' in kwargs:
|
||||||
|
raise ValueError('positional boolean args not supported')
|
||||||
|
super(BoolOpt, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _get_from_config_parser(self, cparser, section):
|
def _get_from_config_parser(self, cparser, section):
|
||||||
"""Retrieve the opt value as a boolean from ConfigParser."""
|
"""Retrieve the opt value as a boolean from ConfigParser."""
|
||||||
def convert_bool(v):
|
def convert_bool(v):
|
||||||
|
@ -671,21 +702,32 @@ class BoolOpt(Opt):
|
||||||
def _add_to_cli(self, parser, group=None):
|
def _add_to_cli(self, parser, group=None):
|
||||||
"""Extends the base class method to add the --nooptname option."""
|
"""Extends the base class method to add the --nooptname option."""
|
||||||
super(BoolOpt, self)._add_to_cli(parser, group)
|
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."""
|
"""Add the --nooptname option to the option parser."""
|
||||||
container = self._get_optparse_container(parser, group)
|
container = self._get_argparse_container(parser, group)
|
||||||
kwargs = self._get_optparse_kwargs(group, action='store_false')
|
kwargs = self._get_argparse_kwargs(group, action='store_false')
|
||||||
prefix = self._get_optparse_prefix('no', group)
|
prefix = self._get_argparse_prefix('no', group)
|
||||||
kwargs["help"] = "The inverse of --" + self.name
|
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)
|
self.positional, self.deprecated_name)
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, action='store_true', **kwargs):
|
def _get_argparse_kwargs(self, group, action='store_true', **kwargs):
|
||||||
"""Extends the base optparse keyword dict for boolean options."""
|
"""Extends the base argparse keyword dict for boolean options."""
|
||||||
return super(BoolOpt,
|
|
||||||
self)._get_optparse_kwargs(group, action=action, **kwargs)
|
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):
|
class IntOpt(Opt):
|
||||||
|
@ -697,10 +739,10 @@ class IntOpt(Opt):
|
||||||
return [int(v) for v in self._cparser_get_with_deprecated(cparser,
|
return [int(v) for v in self._cparser_get_with_deprecated(cparser,
|
||||||
section)]
|
section)]
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, **kwargs):
|
def _get_argparse_kwargs(self, group, **kwargs):
|
||||||
"""Extends the base optparse keyword dict for integer options."""
|
"""Extends the base argparse keyword dict for integer options."""
|
||||||
return super(IntOpt,
|
return super(IntOpt,
|
||||||
self)._get_optparse_kwargs(group, type='int', **kwargs)
|
self)._get_argparse_kwargs(group, type=int, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FloatOpt(Opt):
|
class FloatOpt(Opt):
|
||||||
|
@ -712,10 +754,10 @@ class FloatOpt(Opt):
|
||||||
return [float(v) for v in
|
return [float(v) for v in
|
||||||
self._cparser_get_with_deprecated(cparser, section)]
|
self._cparser_get_with_deprecated(cparser, section)]
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, **kwargs):
|
def _get_argparse_kwargs(self, group, **kwargs):
|
||||||
"""Extends the base optparse keyword dict for float options."""
|
"""Extends the base argparse keyword dict for float options."""
|
||||||
return super(FloatOpt,
|
return super(FloatOpt, self)._get_argparse_kwargs(group,
|
||||||
self)._get_optparse_kwargs(group, type='float', **kwargs)
|
type=float, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ListOpt(Opt):
|
class ListOpt(Opt):
|
||||||
|
@ -725,24 +767,27 @@ class ListOpt(Opt):
|
||||||
is a list containing these strings.
|
is a list containing these strings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class _StoreListAction(argparse.Action):
|
||||||
|
"""
|
||||||
|
An argparse action for parsing an option value into a list.
|
||||||
|
"""
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
if values is not None:
|
||||||
|
values = [a.strip() for a in values.split(',')]
|
||||||
|
setattr(namespace, self.dest, values)
|
||||||
|
|
||||||
def _get_from_config_parser(self, cparser, section):
|
def _get_from_config_parser(self, cparser, section):
|
||||||
"""Retrieve the opt value as a list from ConfigParser."""
|
"""Retrieve the opt value as a list from ConfigParser."""
|
||||||
return [v.split(',') for v in
|
return [[a.strip() for a in v.split(',')] for v in
|
||||||
self._cparser_get_with_deprecated(cparser, section)]
|
self._cparser_get_with_deprecated(cparser, section)]
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, **kwargs):
|
def _get_argparse_kwargs(self, group, **kwargs):
|
||||||
"""Extends the base optparse keyword dict for list options."""
|
"""Extends the base argparse keyword dict for list options."""
|
||||||
return super(ListOpt,
|
return Opt._get_argparse_kwargs(self,
|
||||||
self)._get_optparse_kwargs(group,
|
group,
|
||||||
type='string',
|
action=ListOpt._StoreListAction,
|
||||||
action='callback',
|
|
||||||
callback=self._parse_list,
|
|
||||||
**kwargs)
|
**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(','))
|
|
||||||
|
|
||||||
|
|
||||||
class MultiStrOpt(Opt):
|
class MultiStrOpt(Opt):
|
||||||
|
|
||||||
|
@ -752,10 +797,14 @@ class MultiStrOpt(Opt):
|
||||||
"""
|
"""
|
||||||
multi = True
|
multi = True
|
||||||
|
|
||||||
def _get_optparse_kwargs(self, group, **kwargs):
|
def _get_argparse_kwargs(self, group, **kwargs):
|
||||||
"""Extends the base optparse keyword dict for multi str options."""
|
"""Extends the base argparse keyword dict for multi str options."""
|
||||||
return super(MultiStrOpt,
|
kwargs = super(MultiStrOpt, self)._get_argparse_kwargs(group)
|
||||||
self)._get_optparse_kwargs(group, action='append')
|
if not self.positional:
|
||||||
|
kwargs['action'] = 'append'
|
||||||
|
else:
|
||||||
|
kwargs['nargs'] = '*'
|
||||||
|
return kwargs
|
||||||
|
|
||||||
def _cparser_get_with_deprecated(self, cparser, section):
|
def _cparser_get_with_deprecated(self, cparser, section):
|
||||||
"""If cannot find option as dest try deprecated_name alias."""
|
"""If cannot find option as dest try deprecated_name alias."""
|
||||||
|
@ -765,6 +814,57 @@ class MultiStrOpt(Opt):
|
||||||
return cparser.get(section, [self.dest], multi=True)
|
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):
|
class OptGroup(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -800,19 +900,20 @@ class OptGroup(object):
|
||||||
self.help = help
|
self.help = help
|
||||||
|
|
||||||
self._opts = {} # dict of dicts of (opt:, override:, default:)
|
self._opts = {} # dict of dicts of (opt:, override:, default:)
|
||||||
self._optparse_group = None
|
self._argparse_group = None
|
||||||
|
|
||||||
def _register_opt(self, opt):
|
def _register_opt(self, opt, cli=False):
|
||||||
"""Add an opt to this group.
|
"""Add an opt to this group.
|
||||||
|
|
||||||
:param opt: an Opt object
|
:param opt: an Opt object
|
||||||
|
:param cli: whether this is a CLI option
|
||||||
:returns: False if previously registered, True otherwise
|
:returns: False if previously registered, True otherwise
|
||||||
:raises: DuplicateOptError if a naming conflict is detected
|
:raises: DuplicateOptError if a naming conflict is detected
|
||||||
"""
|
"""
|
||||||
if _is_opt_registered(self._opts, opt):
|
if _is_opt_registered(self._opts, opt):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._opts[opt.dest] = {'opt': opt}
|
self._opts[opt.dest] = {'opt': opt, 'cli': cli}
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -824,16 +925,16 @@ class OptGroup(object):
|
||||||
if opt.dest in self._opts:
|
if opt.dest in self._opts:
|
||||||
del self._opts[opt.dest]
|
del self._opts[opt.dest]
|
||||||
|
|
||||||
def _get_optparse_group(self, parser):
|
def _get_argparse_group(self, parser):
|
||||||
"""Build an optparse.OptionGroup for this group."""
|
if self._argparse_group is None:
|
||||||
if self._optparse_group is None:
|
"""Build an argparse._ArgumentGroup for this group."""
|
||||||
self._optparse_group = optparse.OptionGroup(parser, self.title,
|
self._argparse_group = parser.add_argument_group(self.title,
|
||||||
self.help)
|
self.help)
|
||||||
return self._optparse_group
|
return self._argparse_group
|
||||||
|
|
||||||
def _clear(self):
|
def _clear(self):
|
||||||
"""Clear this group's option parsing state."""
|
"""Clear this group's option parsing state."""
|
||||||
self._optparse_group = None
|
self._argparse_group = None
|
||||||
|
|
||||||
|
|
||||||
class ParseError(iniparser.ParseError):
|
class ParseError(iniparser.ParseError):
|
||||||
|
@ -928,26 +1029,31 @@ class ConfigOpts(collections.Mapping):
|
||||||
self._groups = {}
|
self._groups = {}
|
||||||
|
|
||||||
self._args = None
|
self._args = None
|
||||||
|
|
||||||
self._oparser = None
|
self._oparser = None
|
||||||
self._cparser = None
|
self._cparser = None
|
||||||
self._cli_values = {}
|
self._cli_values = {}
|
||||||
self.__cache = {}
|
self.__cache = {}
|
||||||
self._config_opts = []
|
self._config_opts = []
|
||||||
self._disable_interspersed_args = False
|
|
||||||
|
|
||||||
def _setup(self, project, prog, version, usage, default_config_files):
|
def _pre_setup(self, project, prog, version, usage, default_config_files):
|
||||||
"""Initialize a ConfigOpts object for option parsing."""
|
"""Initialize a ConfigCliParser object for option parsing."""
|
||||||
|
|
||||||
if prog is None:
|
if prog is None:
|
||||||
prog = os.path.basename(sys.argv[0])
|
prog = os.path.basename(sys.argv[0])
|
||||||
|
|
||||||
if default_config_files is None:
|
if default_config_files is None:
|
||||||
default_config_files = find_config_files(project, prog)
|
default_config_files = find_config_files(project, prog)
|
||||||
|
|
||||||
self._oparser = optparse.OptionParser(prog=prog,
|
self._oparser = argparse.ArgumentParser(prog=prog, usage=usage)
|
||||||
version=version,
|
self._oparser.add_argument('--version',
|
||||||
usage=usage)
|
action='version',
|
||||||
if self._disable_interspersed_args:
|
version=version)
|
||||||
self._oparser.disable_interspersed_args()
|
|
||||||
|
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 = [
|
self._config_opts = [
|
||||||
MultiStrOpt('config-file',
|
MultiStrOpt('config-file',
|
||||||
|
@ -1017,18 +1123,23 @@ class ConfigOpts(collections.Mapping):
|
||||||
:raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
|
:raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
|
||||||
RequiredOptError, DuplicateOptError
|
RequiredOptError, DuplicateOptError
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.clear()
|
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._setup(project, prog, version, usage, default_config_files)
|
||||||
|
|
||||||
self._cli_values, leftovers = self._parse_cli_opts(args)
|
self._cli_values = self._parse_cli_opts(args)
|
||||||
|
|
||||||
self._parse_config_files()
|
self._parse_config_files()
|
||||||
|
|
||||||
self._check_required_opts()
|
self._check_required_opts()
|
||||||
|
|
||||||
return leftovers
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""Look up an option value and perform string substitution.
|
"""Look up an option value and perform string substitution.
|
||||||
|
|
||||||
|
@ -1062,17 +1173,21 @@ class ConfigOpts(collections.Mapping):
|
||||||
|
|
||||||
@__clear_cache
|
@__clear_cache
|
||||||
def clear(self):
|
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._args = None
|
||||||
self._cli_values.clear()
|
self._cli_values.clear()
|
||||||
self._oparser = None
|
self._oparser = argparse.ArgumentParser()
|
||||||
self._cparser = None
|
self._cparser = None
|
||||||
self.unregister_opts(self._config_opts)
|
self.unregister_opts(self._config_opts)
|
||||||
for group in self._groups.values():
|
for group in self._groups.values():
|
||||||
group._clear()
|
group._clear()
|
||||||
|
|
||||||
@__clear_cache
|
@__clear_cache
|
||||||
def register_opt(self, opt, group=None):
|
def register_opt(self, opt, group=None, cli=False):
|
||||||
"""Register an option schema.
|
"""Register an option schema.
|
||||||
|
|
||||||
Registering an option schema makes any option value which is previously
|
Registering an option schema makes any option value which is previously
|
||||||
|
@ -1080,17 +1195,19 @@ class ConfigOpts(collections.Mapping):
|
||||||
as an attribute of this object.
|
as an attribute of this object.
|
||||||
|
|
||||||
:param opt: an instance of an Opt sub-class
|
:param opt: an instance of an Opt sub-class
|
||||||
|
:param cli: whether this is a CLI option
|
||||||
:param group: an optional OptGroup object or group name
|
:param group: an optional OptGroup object or group name
|
||||||
:return: False if the opt was already register, True otherwise
|
:return: False if the opt was already register, True otherwise
|
||||||
:raises: DuplicateOptError
|
:raises: DuplicateOptError
|
||||||
"""
|
"""
|
||||||
if group is not None:
|
if group is not None:
|
||||||
return self._get_group(group, autocreate=True)._register_opt(opt)
|
group = self._get_group(group, autocreate=True)
|
||||||
|
return group._register_opt(opt, cli)
|
||||||
|
|
||||||
if _is_opt_registered(self._opts, opt):
|
if _is_opt_registered(self._opts, opt):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._opts[opt.dest] = {'opt': opt}
|
self._opts[opt.dest] = {'opt': opt, 'cli': cli}
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1116,7 +1233,7 @@ class ConfigOpts(collections.Mapping):
|
||||||
if self._args is not None:
|
if self._args is not None:
|
||||||
raise ArgsAlreadyParsedError("cannot register CLI option")
|
raise ArgsAlreadyParsedError("cannot register CLI option")
|
||||||
|
|
||||||
return self.register_opt(opt, group, clear_cache=False)
|
return self.register_opt(opt, group, cli=True, clear_cache=False)
|
||||||
|
|
||||||
@__clear_cache
|
@__clear_cache
|
||||||
def register_cli_opts(self, opts, group=None):
|
def register_cli_opts(self, opts, group=None):
|
||||||
|
@ -1243,9 +1360,10 @@ class ConfigOpts(collections.Mapping):
|
||||||
for info in group._opts.values():
|
for info in group._opts.values():
|
||||||
yield info, group
|
yield info, group
|
||||||
|
|
||||||
def _all_opts(self):
|
def _all_cli_opts(self):
|
||||||
"""A generator function for iteration opts."""
|
"""A generator function for iterating CLI opts."""
|
||||||
for info, group in self._all_opt_infos():
|
for info, group in self._all_opt_infos():
|
||||||
|
if info['cli']:
|
||||||
yield info['opt'], group
|
yield info['opt'], group
|
||||||
|
|
||||||
def _unset_defaults_and_overrides(self):
|
def _unset_defaults_and_overrides(self):
|
||||||
|
@ -1254,31 +1372,6 @@ class ConfigOpts(collections.Mapping):
|
||||||
info.pop('default', None)
|
info.pop('default', None)
|
||||||
info.pop('override', None)
|
info.pop('override', None)
|
||||||
|
|
||||||
def disable_interspersed_args(self):
|
|
||||||
"""Set parsing to stop on the first non-option.
|
|
||||||
|
|
||||||
If this this method is called, then parsing e.g.
|
|
||||||
|
|
||||||
script --verbose cmd --debug /tmp/mything
|
|
||||||
|
|
||||||
will no longer return:
|
|
||||||
|
|
||||||
['cmd', '/tmp/mything']
|
|
||||||
|
|
||||||
as the leftover arguments, but will instead return:
|
|
||||||
|
|
||||||
['cmd', '--debug', '/tmp/mything']
|
|
||||||
|
|
||||||
i.e. argument parsing is stopped at the first non-option argument.
|
|
||||||
"""
|
|
||||||
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."""
|
|
||||||
self._disable_interspersed_args = False
|
|
||||||
|
|
||||||
def find_file(self, name):
|
def find_file(self, name):
|
||||||
"""Locate a file located alongside the config files.
|
"""Locate a file located alongside the config files.
|
||||||
|
|
||||||
|
@ -1377,6 +1470,9 @@ class ConfigOpts(collections.Mapping):
|
||||||
info = self._get_opt_info(name, group)
|
info = self._get_opt_info(name, group)
|
||||||
opt = info['opt']
|
opt = info['opt']
|
||||||
|
|
||||||
|
if isinstance(opt, SubCommandOpt):
|
||||||
|
return self.SubCommandAttr(self, group, opt.dest)
|
||||||
|
|
||||||
if 'override' in info:
|
if 'override' in info:
|
||||||
return info['override']
|
return info['override']
|
||||||
|
|
||||||
|
@ -1401,6 +1497,10 @@ class ConfigOpts(collections.Mapping):
|
||||||
if not opt.multi:
|
if not opt.multi:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
# argparse ignores default=None for nargs='*'
|
||||||
|
if opt.positional and not value:
|
||||||
|
value = opt.default
|
||||||
|
|
||||||
return value + values
|
return value + values
|
||||||
|
|
||||||
if values:
|
if values:
|
||||||
|
@ -1523,12 +1623,10 @@ class ConfigOpts(collections.Mapping):
|
||||||
"""
|
"""
|
||||||
self._args = args
|
self._args = args
|
||||||
|
|
||||||
for opt, group in self._all_opts():
|
for opt, group in self._all_cli_opts():
|
||||||
opt._add_to_cli(self._oparser, group)
|
opt._add_to_cli(self._oparser, group)
|
||||||
|
|
||||||
values, leftovers = self._oparser.parse_args(args)
|
return vars(self._oparser.parse_args(args))
|
||||||
|
|
||||||
return vars(values), leftovers
|
|
||||||
|
|
||||||
class GroupAttr(collections.Mapping):
|
class GroupAttr(collections.Mapping):
|
||||||
|
|
||||||
|
@ -1543,12 +1641,12 @@ class ConfigOpts(collections.Mapping):
|
||||||
:param conf: a ConfigOpts object
|
:param conf: a ConfigOpts object
|
||||||
:param group: an OptGroup object
|
:param group: an OptGroup object
|
||||||
"""
|
"""
|
||||||
self.conf = conf
|
self._conf = conf
|
||||||
self.group = group
|
self._group = group
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""Look up an option value and perform template substitution."""
|
"""Look up an option value and perform template substitution."""
|
||||||
return self.conf._get(name, self.group)
|
return self._conf._get(name, self._group)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Look up an option value and perform string substitution."""
|
"""Look up an option value and perform string substitution."""
|
||||||
|
@ -1556,16 +1654,50 @@ class ConfigOpts(collections.Mapping):
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
"""Return True if key is the name of a registered opt or group."""
|
"""Return True if key is the name of a registered opt or group."""
|
||||||
return key in self.group._opts
|
return key in self._group._opts
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""Iterate over all registered opt and group names."""
|
"""Iterate over all registered opt and group names."""
|
||||||
for key in self.group._opts.keys():
|
for key in self._group._opts.keys():
|
||||||
yield key
|
yield key
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the number of options and option groups."""
|
"""Return the number of options and option groups."""
|
||||||
return len(self.group._opts)
|
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):
|
class StrSubWrapper(object):
|
||||||
|
|
||||||
|
@ -1623,19 +1755,21 @@ class CommonConfigOpts(ConfigOpts):
|
||||||
metavar='FORMAT',
|
metavar='FORMAT',
|
||||||
help='A logging.Formatter log message format string which may '
|
help='A logging.Formatter log message format string which may '
|
||||||
'use any of the available logging.LogRecord attributes. '
|
'use any of the available logging.LogRecord attributes. '
|
||||||
'Default: %default'),
|
'Default: %(default)s'),
|
||||||
StrOpt('log-date-format',
|
StrOpt('log-date-format',
|
||||||
default=DEFAULT_LOG_DATE_FORMAT,
|
default=DEFAULT_LOG_DATE_FORMAT,
|
||||||
metavar='DATE_FORMAT',
|
metavar='DATE_FORMAT',
|
||||||
help='Format string for %(asctime)s in log records. '
|
help='Format string for %%(asctime)s in log records. '
|
||||||
'Default: %default'),
|
'Default: %(default)s'),
|
||||||
StrOpt('log-file',
|
StrOpt('log-file',
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
|
deprecated_name='logfile',
|
||||||
help='(Optional) Name of log file to output to. '
|
help='(Optional) Name of log file to output to. '
|
||||||
'If not set, logging will go to stdout.'),
|
'If not set, logging will go to stdout.'),
|
||||||
StrOpt('log-dir',
|
StrOpt('log-dir',
|
||||||
|
deprecated_name='logdir',
|
||||||
help='(Optional) The directory to keep log files in '
|
help='(Optional) The directory to keep log files in '
|
||||||
'(will be prepended to --logfile)'),
|
'(will be prepended to --log-file)'),
|
||||||
BoolOpt('use-syslog',
|
BoolOpt('use-syslog',
|
||||||
default=False,
|
default=False,
|
||||||
help='Use syslog for logging.'),
|
help='Use syslog for logging.'),
|
||||||
|
|
|
@ -95,12 +95,6 @@ log_opts = [
|
||||||
|
|
||||||
|
|
||||||
generic_log_opts = [
|
generic_log_opts = [
|
||||||
cfg.StrOpt('logdir',
|
|
||||||
default=None,
|
|
||||||
help='Log output to a per-service log file in named directory'),
|
|
||||||
cfg.StrOpt('logfile',
|
|
||||||
default=None,
|
|
||||||
help='Log output to a named file'),
|
|
||||||
cfg.BoolOpt('use_stderr',
|
cfg.BoolOpt('use_stderr',
|
||||||
default=True,
|
default=True,
|
||||||
help='Log output to standard error'),
|
help='Log output to standard error'),
|
||||||
|
@ -148,18 +142,15 @@ def _get_binary_name():
|
||||||
|
|
||||||
|
|
||||||
def _get_log_file_path(binary=None):
|
def _get_log_file_path(binary=None):
|
||||||
logfile = CONF.log_file or CONF.logfile
|
if CONF.log_file and not CONF.log_dir:
|
||||||
logdir = CONF.log_dir or CONF.logdir
|
return CONF.log_file
|
||||||
|
|
||||||
if logfile and not logdir:
|
if CONF.log_file and CONF.log_dir:
|
||||||
return logfile
|
return os.path.join(CONF.log_dir, CONF.log_file)
|
||||||
|
|
||||||
if logfile and logdir:
|
if CONF.log_dir:
|
||||||
return os.path.join(logdir, logfile)
|
|
||||||
|
|
||||||
if logdir:
|
|
||||||
binary = binary or _get_binary_name()
|
binary = binary or _get_binary_name()
|
||||||
return '%s.log' % (os.path.join(logdir, binary),)
|
return '%s.log' % (os.path.join(CONF.log_dir, binary),)
|
||||||
|
|
||||||
|
|
||||||
class ContextAdapter(logging.LoggerAdapter):
|
class ContextAdapter(logging.LoggerAdapter):
|
||||||
|
|
|
@ -137,7 +137,7 @@ class FlatNetworkTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FlatNetworkTestCase, self).setUp()
|
super(FlatNetworkTestCase, self).setUp()
|
||||||
self.tempdir = tempfile.mkdtemp()
|
self.tempdir = tempfile.mkdtemp()
|
||||||
self.flags(logdir=self.tempdir)
|
self.flags(log_dir=self.tempdir)
|
||||||
self.network = network_manager.FlatManager(host=HOST)
|
self.network = network_manager.FlatManager(host=HOST)
|
||||||
self.network.instance_dns_domain = ''
|
self.network.instance_dns_domain = ''
|
||||||
self.network.db = db
|
self.network.db = db
|
||||||
|
@ -1597,7 +1597,7 @@ class FloatingIPTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FloatingIPTestCase, self).setUp()
|
super(FloatingIPTestCase, self).setUp()
|
||||||
self.tempdir = tempfile.mkdtemp()
|
self.tempdir = tempfile.mkdtemp()
|
||||||
self.flags(logdir=self.tempdir)
|
self.flags(log_dir=self.tempdir)
|
||||||
self.network = TestFloatingIPManager()
|
self.network = TestFloatingIPManager()
|
||||||
self.network.db = db
|
self.network.db = db
|
||||||
self.project_id = 'testproject'
|
self.project_id = 'testproject'
|
||||||
|
@ -1944,7 +1944,7 @@ class InstanceDNSTestCase(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(InstanceDNSTestCase, self).setUp()
|
super(InstanceDNSTestCase, self).setUp()
|
||||||
self.tempdir = tempfile.mkdtemp()
|
self.tempdir = tempfile.mkdtemp()
|
||||||
self.flags(logdir=self.tempdir)
|
self.flags(log_dir=self.tempdir)
|
||||||
self.network = TestFloatingIPManager()
|
self.network = TestFloatingIPManager()
|
||||||
self.network.db = db
|
self.network.db = db
|
||||||
self.project_id = 'testproject'
|
self.project_id = 'testproject'
|
||||||
|
|
|
@ -2,6 +2,7 @@ SQLAlchemy>=0.7.8,<=0.7.9
|
||||||
Cheetah==2.4.4
|
Cheetah==2.4.4
|
||||||
amqplib>=0.6.1
|
amqplib>=0.6.1
|
||||||
anyjson>=0.2.4
|
anyjson>=0.2.4
|
||||||
|
argparse
|
||||||
boto
|
boto
|
||||||
eventlet>=0.9.17
|
eventlet>=0.9.17
|
||||||
kombu>=1.0.4
|
kombu>=1.0.4
|
||||||
|
|
Loading…
Reference in New Issue