blob: cae4eccd37ddc3b783c6697cde875281b9c7bbb7 [file] [log] [blame]
Matthew Treinish481466b2012-12-20 17:16:01 -05001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 Red Hat, Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17r"""
18Configuration options which may be set on the command line or in config files.
19
20The schema for each option is defined using the Opt sub-classes, e.g.:
21
22::
23
24 common_opts = [
25 cfg.StrOpt('bind_host',
26 default='0.0.0.0',
27 help='IP address to listen on'),
28 cfg.IntOpt('bind_port',
29 default=9292,
30 help='Port number to listen on')
31 ]
32
33Options can be strings, integers, floats, booleans, lists or 'multi strings'::
34
35 enabled_apis_opt = cfg.ListOpt('enabled_apis',
36 default=['ec2', 'osapi_compute'],
37 help='List of APIs to enable by default')
38
39 DEFAULT_EXTENSIONS = [
40 'nova.api.openstack.compute.contrib.standard_extensions'
41 ]
42 osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension',
43 default=DEFAULT_EXTENSIONS)
44
45Option schemas are registered with the config manager at runtime, but before
46the option is referenced::
47
48 class ExtensionManager(object):
49
50 enabled_apis_opt = cfg.ListOpt(...)
51
52 def __init__(self, conf):
53 self.conf = conf
54 self.conf.register_opt(enabled_apis_opt)
55 ...
56
57 def _load_extensions(self):
58 for ext_factory in self.conf.osapi_compute_extension:
59 ....
60
61A common usage pattern is for each option schema to be defined in the module or
62class which uses the option::
63
64 opts = ...
65
66 def add_common_opts(conf):
67 conf.register_opts(opts)
68
69 def get_bind_host(conf):
70 return conf.bind_host
71
72 def get_bind_port(conf):
73 return conf.bind_port
74
75An option may optionally be made available via the command line. Such options
76must registered with the config manager before the command line is parsed (for
77the purposes of --help and CLI arg validation)::
78
79 cli_opts = [
80 cfg.BoolOpt('verbose',
81 short='v',
82 default=False,
83 help='Print more verbose output'),
84 cfg.BoolOpt('debug',
85 short='d',
86 default=False,
87 help='Print debugging output'),
88 ]
89
90 def add_common_opts(conf):
91 conf.register_cli_opts(cli_opts)
92
93The config manager has two CLI options defined by default, --config-file
94and --config-dir::
95
96 class ConfigOpts(object):
97
98 def __call__(self, ...):
99
100 opts = [
101 MultiStrOpt('config-file',
102 ...),
103 StrOpt('config-dir',
104 ...),
105 ]
106
107 self.register_cli_opts(opts)
108
109Option values are parsed from any supplied config files using
110openstack.common.iniparser. If none are specified, a default set is used
111e.g. glance-api.conf and glance-common.conf::
112
113 glance-api.conf:
114 [DEFAULT]
115 bind_port = 9292
116
117 glance-common.conf:
118 [DEFAULT]
119 bind_host = 0.0.0.0
120
121Option values in config files override those on the command line. Config files
122are parsed in order, with values in later files overriding those in earlier
123files.
124
125The parsing of CLI args and config files is initiated by invoking the config
126manager e.g.::
127
128 conf = ConfigOpts()
129 conf.register_opt(BoolOpt('verbose', ...))
130 conf(sys.argv[1:])
131 if conf.verbose:
132 ...
133
134Options can be registered as belonging to a group::
135
136 rabbit_group = cfg.OptGroup(name='rabbit',
137 title='RabbitMQ options')
138
139 rabbit_host_opt = cfg.StrOpt('host',
140 default='localhost',
141 help='IP/hostname to listen on'),
142 rabbit_port_opt = cfg.IntOpt('port',
143 default=5672,
144 help='Port number to listen on')
145
146 def register_rabbit_opts(conf):
147 conf.register_group(rabbit_group)
148 # options can be registered under a group in either of these ways:
149 conf.register_opt(rabbit_host_opt, group=rabbit_group)
150 conf.register_opt(rabbit_port_opt, group='rabbit')
151
152If it no group attributes are required other than the group name, the group
153need not be explicitly registered e.g.
154
155 def register_rabbit_opts(conf):
156 # The group will automatically be created, equivalent calling::
157 # conf.register_group(OptGroup(name='rabbit'))
158 conf.register_opt(rabbit_port_opt, group='rabbit')
159
160If no group is specified, options belong to the 'DEFAULT' section of config
161files::
162
163 glance-api.conf:
164 [DEFAULT]
165 bind_port = 9292
166 ...
167
168 [rabbit]
169 host = localhost
170 port = 5672
171 use_ssl = False
172 userid = guest
173 password = guest
174 virtual_host = /
175
176Command-line options in a group are automatically prefixed with the
177group name::
178
179 --rabbit-host localhost --rabbit-port 9999
180
181Option values in the default group are referenced as attributes/properties on
182the config manager; groups are also attributes on the config manager, with
183attributes for each of the options associated with the group::
184
185 server.start(app, conf.bind_port, conf.bind_host, conf)
186
187 self.connection = kombu.connection.BrokerConnection(
188 hostname=conf.rabbit.host,
189 port=conf.rabbit.port,
190 ...)
191
192Option values may reference other values using PEP 292 string substitution::
193
194 opts = [
195 cfg.StrOpt('state_path',
196 default=os.path.join(os.path.dirname(__file__), '../'),
197 help='Top-level directory for maintaining nova state'),
198 cfg.StrOpt('sqlite_db',
199 default='nova.sqlite',
200 help='file name for sqlite'),
201 cfg.StrOpt('sql_connection',
202 default='sqlite:///$state_path/$sqlite_db',
203 help='connection string for sql database'),
204 ]
205
206Note that interpolation can be avoided by using '$$'.
207
208Options may be declared as required so that an error is raised if the user
209does not supply a value for the option.
210
211Options may be declared as secret so that their values are not leaked into
212log files::
213
214 opts = [
215 cfg.StrOpt('s3_store_access_key', secret=True),
216 cfg.StrOpt('s3_store_secret_key', secret=True),
217 ...
218 ]
219
Joe Gordon2b0591d2013-02-14 23:18:39 +0000220This module also contains a global instance of the ConfigOpts class
Matthew Treinish481466b2012-12-20 17:16:01 -0500221in order to support a common usage pattern in OpenStack::
222
223 from tempest.openstack.common import cfg
224
225 opts = [
226 cfg.StrOpt('bind_host', default='0.0.0.0'),
227 cfg.IntOpt('bind_port', default=9292),
228 ]
229
230 CONF = cfg.CONF
231 CONF.register_opts(opts)
232
233 def start(server, app):
234 server.start(app, CONF.bind_port, CONF.bind_host)
235
236Positional command line arguments are supported via a 'positional' Opt
237constructor argument::
238
Joe Gordon2b0591d2013-02-14 23:18:39 +0000239 >>> conf = ConfigOpts()
240 >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True))
Matthew Treinish481466b2012-12-20 17:16:01 -0500241 True
Joe Gordon2b0591d2013-02-14 23:18:39 +0000242 >>> conf(['a', 'b'])
243 >>> conf.bar
Matthew Treinish481466b2012-12-20 17:16:01 -0500244 ['a', 'b']
245
246It is also possible to use argparse "sub-parsers" to parse additional
247command line arguments using the SubCommandOpt class:
248
249 >>> def add_parsers(subparsers):
250 ... list_action = subparsers.add_parser('list')
251 ... list_action.add_argument('id')
252 ...
Joe Gordon2b0591d2013-02-14 23:18:39 +0000253 >>> conf = ConfigOpts()
254 >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
Matthew Treinish481466b2012-12-20 17:16:01 -0500255 True
Joe Gordon2b0591d2013-02-14 23:18:39 +0000256 >>> conf(args=['list', '10'])
257 >>> conf.action.name, conf.action.id
Matthew Treinish481466b2012-12-20 17:16:01 -0500258 ('list', '10')
259
260"""
261
262import argparse
263import collections
264import copy
265import functools
266import glob
267import os
268import string
269import sys
270
271from tempest.openstack.common import iniparser
272
273
274class Error(Exception):
275 """Base class for cfg exceptions."""
276
277 def __init__(self, msg=None):
278 self.msg = msg
279
280 def __str__(self):
281 return self.msg
282
283
284class ArgsAlreadyParsedError(Error):
285 """Raised if a CLI opt is registered after parsing."""
286
287 def __str__(self):
288 ret = "arguments already parsed"
289 if self.msg:
290 ret += ": " + self.msg
291 return ret
292
293
294class NoSuchOptError(Error, AttributeError):
295 """Raised if an opt which doesn't exist is referenced."""
296
297 def __init__(self, opt_name, group=None):
298 self.opt_name = opt_name
299 self.group = group
300
301 def __str__(self):
302 if self.group is None:
303 return "no such option: %s" % self.opt_name
304 else:
305 return "no such option in group %s: %s" % (self.group.name,
306 self.opt_name)
307
308
309class NoSuchGroupError(Error):
310 """Raised if a group which doesn't exist is referenced."""
311
312 def __init__(self, group_name):
313 self.group_name = group_name
314
315 def __str__(self):
316 return "no such group: %s" % self.group_name
317
318
319class DuplicateOptError(Error):
320 """Raised if multiple opts with the same name are registered."""
321
322 def __init__(self, opt_name):
323 self.opt_name = opt_name
324
325 def __str__(self):
326 return "duplicate option: %s" % self.opt_name
327
328
329class RequiredOptError(Error):
330 """Raised if an option is required but no value is supplied by the user."""
331
332 def __init__(self, opt_name, group=None):
333 self.opt_name = opt_name
334 self.group = group
335
336 def __str__(self):
337 if self.group is None:
338 return "value required for option: %s" % self.opt_name
339 else:
340 return "value required for option: %s.%s" % (self.group.name,
341 self.opt_name)
342
343
344class TemplateSubstitutionError(Error):
345 """Raised if an error occurs substituting a variable in an opt value."""
346
347 def __str__(self):
348 return "template substitution error: %s" % self.msg
349
350
351class ConfigFilesNotFoundError(Error):
352 """Raised if one or more config files are not found."""
353
354 def __init__(self, config_files):
355 self.config_files = config_files
356
357 def __str__(self):
358 return ('Failed to read some config files: %s' %
359 string.join(self.config_files, ','))
360
361
362class ConfigFileParseError(Error):
363 """Raised if there is an error parsing a config file."""
364
365 def __init__(self, config_file, msg):
366 self.config_file = config_file
367 self.msg = msg
368
369 def __str__(self):
370 return 'Failed to parse %s: %s' % (self.config_file, self.msg)
371
372
373class ConfigFileValueError(Error):
374 """Raised if a config file value does not match its opt type."""
375 pass
376
377
378def _fixpath(p):
379 """Apply tilde expansion and absolutization to a path."""
380 return os.path.abspath(os.path.expanduser(p))
381
382
383def _get_config_dirs(project=None):
384 """Return a list of directors where config files may be located.
385
386 :param project: an optional project name
387
388 If a project is specified, following directories are returned::
389
390 ~/.${project}/
391 ~/
392 /etc/${project}/
393 /etc/
394
395 Otherwise, these directories::
396
397 ~/
398 /etc/
399 """
400 cfg_dirs = [
401 _fixpath(os.path.join('~', '.' + project)) if project else None,
402 _fixpath('~'),
403 os.path.join('/etc', project) if project else None,
404 '/etc'
405 ]
406
407 return filter(bool, cfg_dirs)
408
409
410def _search_dirs(dirs, basename, extension=""):
411 """Search a list of directories for a given filename.
412
413 Iterator over the supplied directories, returning the first file
414 found with the supplied name and extension.
415
416 :param dirs: a list of directories
417 :param basename: the filename, e.g. 'glance-api'
418 :param extension: the file extension, e.g. '.conf'
419 :returns: the path to a matching file, or None
420 """
421 for d in dirs:
422 path = os.path.join(d, '%s%s' % (basename, extension))
423 if os.path.exists(path):
424 return path
425
426
427def find_config_files(project=None, prog=None, extension='.conf'):
428 """Return a list of default configuration files.
429
430 :param project: an optional project name
431 :param prog: the program name, defaulting to the basename of sys.argv[0]
432 :param extension: the type of the config file
433
434 We default to two config files: [${project}.conf, ${prog}.conf]
435
436 And we look for those config files in the following directories::
437
438 ~/.${project}/
439 ~/
440 /etc/${project}/
441 /etc/
442
443 We return an absolute path for (at most) one of each the default config
444 files, for the topmost directory it exists in.
445
446 For example, if project=foo, prog=bar and /etc/foo/foo.conf, /etc/bar.conf
447 and ~/.foo/bar.conf all exist, then we return ['/etc/foo/foo.conf',
448 '~/.foo/bar.conf']
449
450 If no project name is supplied, we only look for ${prog.conf}.
451 """
452 if prog is None:
453 prog = os.path.basename(sys.argv[0])
454
455 cfg_dirs = _get_config_dirs(project)
456
457 config_files = []
458 if project:
459 config_files.append(_search_dirs(cfg_dirs, project, extension))
460 config_files.append(_search_dirs(cfg_dirs, prog, extension))
461
462 return filter(bool, config_files)
463
464
465def _is_opt_registered(opts, opt):
466 """Check whether an opt with the same name is already registered.
467
468 The same opt may be registered multiple times, with only the first
469 registration having any effect. However, it is an error to attempt
470 to register a different opt with the same name.
471
472 :param opts: the set of opts already registered
473 :param opt: the opt to be registered
474 :returns: True if the opt was previously registered, False otherwise
475 :raises: DuplicateOptError if a naming conflict is detected
476 """
477 if opt.dest in opts:
478 if opts[opt.dest]['opt'] != opt:
479 raise DuplicateOptError(opt.name)
480 return True
481 else:
482 return False
483
484
485def set_defaults(opts, **kwargs):
486 for opt in opts:
487 if opt.dest in kwargs:
488 opt.default = kwargs[opt.dest]
489 break
490
491
492class Opt(object):
493
494 """Base class for all configuration options.
495
496 An Opt object has no public methods, but has a number of public string
497 properties:
498
499 name:
500 the name of the option, which may include hyphens
501 dest:
502 the (hyphen-less) ConfigOpts property which contains the option value
503 short:
504 a single character CLI option name
505 default:
506 the default value of the option
507 positional:
508 True if the option is a positional CLI argument
509 metavar:
510 the name shown as the argument to a CLI option in --help output
511 help:
512 an string explaining how the options value is used
513 """
514 multi = False
515
516 def __init__(self, name, dest=None, short=None, default=None,
517 positional=False, metavar=None, help=None,
518 secret=False, required=False, deprecated_name=None):
519 """Construct an Opt object.
520
521 The only required parameter is the option's name. However, it is
522 common to also supply a default and help string for all options.
523
524 :param name: the option's name
525 :param dest: the name of the corresponding ConfigOpts property
526 :param short: a single character CLI option name
527 :param default: the default value of the option
528 :param positional: True if the option is a positional CLI argument
529 :param metavar: the option argument to show in --help
530 :param help: an explanation of how the option is used
531 :param secret: true iff the value should be obfuscated in log output
532 :param required: true iff a value must be supplied for this option
533 :param deprecated_name: deprecated name option. Acts like an alias
534 """
535 self.name = name
536 if dest is None:
537 self.dest = self.name.replace('-', '_')
538 else:
539 self.dest = dest
540 self.short = short
541 self.default = default
542 self.positional = positional
543 self.metavar = metavar
544 self.help = help
545 self.secret = secret
546 self.required = required
547 if deprecated_name is not None:
548 self.deprecated_name = deprecated_name.replace('-', '_')
549 else:
550 self.deprecated_name = None
551
552 def __ne__(self, another):
553 return vars(self) != vars(another)
554
555 def _get_from_config_parser(self, cparser, section):
556 """Retrieves the option value from a MultiConfigParser object.
557
558 This is the method ConfigOpts uses to look up the option value from
559 config files. Most opt types override this method in order to perform
560 type appropriate conversion of the returned value.
561
562 :param cparser: a ConfigParser object
563 :param section: a section name
564 """
565 return self._cparser_get_with_deprecated(cparser, section)
566
567 def _cparser_get_with_deprecated(self, cparser, section):
568 """If cannot find option as dest try deprecated_name alias."""
569 if self.deprecated_name is not None:
570 return cparser.get(section, [self.dest, self.deprecated_name])
571 return cparser.get(section, [self.dest])
572
573 def _add_to_cli(self, parser, group=None):
574 """Makes the option available in the command line interface.
575
576 This is the method ConfigOpts uses to add the opt to the CLI interface
577 as appropriate for the opt type. Some opt types may extend this method,
578 others may just extend the helper methods it uses.
579
580 :param parser: the CLI option parser
581 :param group: an optional OptGroup object
582 """
583 container = self._get_argparse_container(parser, group)
584 kwargs = self._get_argparse_kwargs(group)
585 prefix = self._get_argparse_prefix('', group)
586 self._add_to_argparse(container, self.name, self.short, kwargs, prefix,
587 self.positional, self.deprecated_name)
588
589 def _add_to_argparse(self, container, name, short, kwargs, prefix='',
590 positional=False, deprecated_name=None):
591 """Add an option to an argparse parser or group.
592
593 :param container: an argparse._ArgumentGroup object
594 :param name: the opt name
595 :param short: the short opt name
596 :param kwargs: the keyword arguments for add_argument()
597 :param prefix: an optional prefix to prepend to the opt name
598 :param position: whether the optional is a positional CLI argument
599 :raises: DuplicateOptError if a naming confict is detected
600 """
601 def hyphen(arg):
602 return arg if not positional else ''
603
604 args = [hyphen('--') + prefix + name]
605 if short:
606 args.append(hyphen('-') + short)
607 if deprecated_name:
608 args.append(hyphen('--') + prefix + deprecated_name)
609
610 try:
611 container.add_argument(*args, **kwargs)
612 except argparse.ArgumentError as e:
613 raise DuplicateOptError(e)
614
615 def _get_argparse_container(self, parser, group):
616 """Returns an argparse._ArgumentGroup.
617
618 :param parser: an argparse.ArgumentParser
619 :param group: an (optional) OptGroup object
620 :returns: an argparse._ArgumentGroup if group is given, else parser
621 """
622 if group is not None:
623 return group._get_argparse_group(parser)
624 else:
625 return parser
626
627 def _get_argparse_kwargs(self, group, **kwargs):
628 """Build a dict of keyword arguments for argparse's add_argument().
629
630 Most opt types extend this method to customize the behaviour of the
631 options added to argparse.
632
633 :param group: an optional group
634 :param kwargs: optional keyword arguments to add to
635 :returns: a dict of keyword arguments
636 """
637 if not self.positional:
638 dest = self.dest
639 if group is not None:
640 dest = group.name + '_' + dest
641 kwargs['dest'] = dest
642 else:
643 kwargs['nargs'] = '?'
644 kwargs.update({'default': None,
645 'metavar': self.metavar,
646 'help': self.help, })
647 return kwargs
648
649 def _get_argparse_prefix(self, prefix, group):
650 """Build a prefix for the CLI option name, if required.
651
652 CLI options in a group are prefixed with the group's name in order
653 to avoid conflicts between similarly named options in different
654 groups.
655
656 :param prefix: an existing prefix to append to (e.g. 'no' or '')
657 :param group: an optional OptGroup object
658 :returns: a CLI option prefix including the group name, if appropriate
659 """
660 if group is not None:
661 return group.name + '-' + prefix
662 else:
663 return prefix
664
665
666class StrOpt(Opt):
667 """
668 String opts do not have their values transformed and are returned as
669 str objects.
670 """
671 pass
672
673
674class BoolOpt(Opt):
675
676 """
677 Bool opts are set to True or False on the command line using --optname or
678 --noopttname respectively.
679
680 In config files, boolean values are case insensitive and can be set using
681 1/0, yes/no, true/false or on/off.
682 """
683
684 _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
685 '0': False, 'no': False, 'false': False, 'off': False}
686
687 def __init__(self, *args, **kwargs):
688 if 'positional' in kwargs:
689 raise ValueError('positional boolean args not supported')
690 super(BoolOpt, self).__init__(*args, **kwargs)
691
692 def _get_from_config_parser(self, cparser, section):
693 """Retrieve the opt value as a boolean from ConfigParser."""
694 def convert_bool(v):
695 value = self._boolean_states.get(v.lower())
696 if value is None:
697 raise ValueError('Unexpected boolean value %r' % v)
698
699 return value
700
701 return [convert_bool(v) for v in
702 self._cparser_get_with_deprecated(cparser, section)]
703
704 def _add_to_cli(self, parser, group=None):
705 """Extends the base class method to add the --nooptname option."""
706 super(BoolOpt, self)._add_to_cli(parser, group)
707 self._add_inverse_to_argparse(parser, group)
708
709 def _add_inverse_to_argparse(self, parser, group):
710 """Add the --nooptname option to the option parser."""
711 container = self._get_argparse_container(parser, group)
712 kwargs = self._get_argparse_kwargs(group, action='store_false')
713 prefix = self._get_argparse_prefix('no', group)
714 kwargs["help"] = "The inverse of --" + self.name
715 self._add_to_argparse(container, self.name, None, kwargs, prefix,
716 self.positional, self.deprecated_name)
717
718 def _get_argparse_kwargs(self, group, action='store_true', **kwargs):
719 """Extends the base argparse keyword dict for boolean options."""
720
721 kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs)
722
723 # metavar has no effect for BoolOpt
724 if 'metavar' in kwargs:
725 del kwargs['metavar']
726
727 if action != 'store_true':
728 action = 'store_false'
729
730 kwargs['action'] = action
731
732 return kwargs
733
734
735class IntOpt(Opt):
736
737 """Int opt values are converted to integers using the int() builtin."""
738
739 def _get_from_config_parser(self, cparser, section):
740 """Retrieve the opt value as a integer from ConfigParser."""
741 return [int(v) for v in self._cparser_get_with_deprecated(cparser,
742 section)]
743
744 def _get_argparse_kwargs(self, group, **kwargs):
745 """Extends the base argparse keyword dict for integer options."""
746 return super(IntOpt,
747 self)._get_argparse_kwargs(group, type=int, **kwargs)
748
749
750class FloatOpt(Opt):
751
752 """Float opt values are converted to floats using the float() builtin."""
753
754 def _get_from_config_parser(self, cparser, section):
755 """Retrieve the opt value as a float from ConfigParser."""
756 return [float(v) for v in
757 self._cparser_get_with_deprecated(cparser, section)]
758
759 def _get_argparse_kwargs(self, group, **kwargs):
760 """Extends the base argparse keyword dict for float options."""
761 return super(FloatOpt, self)._get_argparse_kwargs(group,
762 type=float, **kwargs)
763
764
765class ListOpt(Opt):
766
767 """
768 List opt values are simple string values separated by commas. The opt value
769 is a list containing these strings.
770 """
771
772 class _StoreListAction(argparse.Action):
773 """
774 An argparse action for parsing an option value into a list.
775 """
776 def __call__(self, parser, namespace, values, option_string=None):
777 if values is not None:
778 values = [a.strip() for a in values.split(',')]
779 setattr(namespace, self.dest, values)
780
781 def _get_from_config_parser(self, cparser, section):
782 """Retrieve the opt value as a list from ConfigParser."""
783 return [[a.strip() for a in v.split(',')] for v in
784 self._cparser_get_with_deprecated(cparser, section)]
785
786 def _get_argparse_kwargs(self, group, **kwargs):
787 """Extends the base argparse keyword dict for list options."""
788 return Opt._get_argparse_kwargs(self,
789 group,
790 action=ListOpt._StoreListAction,
791 **kwargs)
792
793
794class MultiStrOpt(Opt):
795
796 """
797 Multistr opt values are string opts which may be specified multiple times.
798 The opt value is a list containing all the string values specified.
799 """
800 multi = True
801
802 def _get_argparse_kwargs(self, group, **kwargs):
803 """Extends the base argparse keyword dict for multi str options."""
804 kwargs = super(MultiStrOpt, self)._get_argparse_kwargs(group)
805 if not self.positional:
806 kwargs['action'] = 'append'
807 else:
808 kwargs['nargs'] = '*'
809 return kwargs
810
811 def _cparser_get_with_deprecated(self, cparser, section):
812 """If cannot find option as dest try deprecated_name alias."""
813 if self.deprecated_name is not None:
814 return cparser.get(section, [self.dest, self.deprecated_name],
815 multi=True)
816 return cparser.get(section, [self.dest], multi=True)
817
818
819class SubCommandOpt(Opt):
820
821 """
822 Sub-command options allow argparse sub-parsers to be used to parse
823 additional command line arguments.
824
825 The handler argument to the SubCommandOpt contructor is a callable
826 which is supplied an argparse subparsers object. Use this handler
827 callable to add sub-parsers.
828
829 The opt value is SubCommandAttr object with the name of the chosen
830 sub-parser stored in the 'name' attribute and the values of other
831 sub-parser arguments available as additional attributes.
832 """
833
834 def __init__(self, name, dest=None, handler=None,
835 title=None, description=None, help=None):
836 """Construct an sub-command parsing option.
837
838 This behaves similarly to other Opt sub-classes but adds a
839 'handler' argument. The handler is a callable which is supplied
840 an subparsers object when invoked. The add_parser() method on
841 this subparsers object can be used to register parsers for
842 sub-commands.
843
844 :param name: the option's name
845 :param dest: the name of the corresponding ConfigOpts property
846 :param title: title of the sub-commands group in help output
847 :param description: description of the group in help output
848 :param help: a help string giving an overview of available sub-commands
849 """
850 super(SubCommandOpt, self).__init__(name, dest=dest, help=help)
851 self.handler = handler
852 self.title = title
853 self.description = description
854
855 def _add_to_cli(self, parser, group=None):
856 """Add argparse sub-parsers and invoke the handler method."""
857 dest = self.dest
858 if group is not None:
859 dest = group.name + '_' + dest
860
861 subparsers = parser.add_subparsers(dest=dest,
862 title=self.title,
863 description=self.description,
864 help=self.help)
865
Joe Gordon2b0591d2013-02-14 23:18:39 +0000866 if self.handler is not None:
Matthew Treinish481466b2012-12-20 17:16:01 -0500867 self.handler(subparsers)
868
869
870class OptGroup(object):
871
872 """
873 Represents a group of opts.
874
875 CLI opts in the group are automatically prefixed with the group name.
876
877 Each group corresponds to a section in config files.
878
879 An OptGroup object has no public methods, but has a number of public string
880 properties:
881
882 name:
883 the name of the group
884 title:
885 the group title as displayed in --help
886 help:
887 the group description as displayed in --help
888 """
889
890 def __init__(self, name, title=None, help=None):
891 """Constructs an OptGroup object.
892
893 :param name: the group name
894 :param title: the group title for --help
895 :param help: the group description for --help
896 """
897 self.name = name
898 if title is None:
899 self.title = "%s options" % title
900 else:
901 self.title = title
902 self.help = help
903
904 self._opts = {} # dict of dicts of (opt:, override:, default:)
905 self._argparse_group = None
906
907 def _register_opt(self, opt, cli=False):
908 """Add an opt to this group.
909
910 :param opt: an Opt object
911 :param cli: whether this is a CLI option
912 :returns: False if previously registered, True otherwise
913 :raises: DuplicateOptError if a naming conflict is detected
914 """
915 if _is_opt_registered(self._opts, opt):
916 return False
917
918 self._opts[opt.dest] = {'opt': opt, 'cli': cli}
919
920 return True
921
922 def _unregister_opt(self, opt):
923 """Remove an opt from this group.
924
925 :param opt: an Opt object
926 """
927 if opt.dest in self._opts:
928 del self._opts[opt.dest]
929
930 def _get_argparse_group(self, parser):
931 if self._argparse_group is None:
932 """Build an argparse._ArgumentGroup for this group."""
933 self._argparse_group = parser.add_argument_group(self.title,
934 self.help)
935 return self._argparse_group
936
937 def _clear(self):
938 """Clear this group's option parsing state."""
939 self._argparse_group = None
940
941
942class ParseError(iniparser.ParseError):
943 def __init__(self, msg, lineno, line, filename):
944 super(ParseError, self).__init__(msg, lineno, line)
945 self.filename = filename
946
947 def __str__(self):
948 return 'at %s:%d, %s: %r' % (self.filename, self.lineno,
949 self.msg, self.line)
950
951
952class ConfigParser(iniparser.BaseParser):
953 def __init__(self, filename, sections):
954 super(ConfigParser, self).__init__()
955 self.filename = filename
956 self.sections = sections
957 self.section = None
958
959 def parse(self):
960 with open(self.filename) as f:
961 return super(ConfigParser, self).parse(f)
962
963 def new_section(self, section):
964 self.section = section
965 self.sections.setdefault(self.section, {})
966
967 def assignment(self, key, value):
968 if not self.section:
969 raise self.error_no_section()
970
971 self.sections[self.section].setdefault(key, [])
972 self.sections[self.section][key].append('\n'.join(value))
973
974 def parse_exc(self, msg, lineno, line=None):
975 return ParseError(msg, lineno, line, self.filename)
976
977 def error_no_section(self):
978 return self.parse_exc('Section must be started before assignment',
979 self.lineno)
980
981
982class MultiConfigParser(object):
983 def __init__(self):
984 self.parsed = []
985
986 def read(self, config_files):
987 read_ok = []
988
989 for filename in config_files:
990 sections = {}
991 parser = ConfigParser(filename, sections)
992
993 try:
994 parser.parse()
995 except IOError:
996 continue
997 self.parsed.insert(0, sections)
998 read_ok.append(filename)
999
1000 return read_ok
1001
1002 def get(self, section, names, multi=False):
1003 rvalue = []
1004 for sections in self.parsed:
1005 if section not in sections:
1006 continue
1007 for name in names:
1008 if name in sections[section]:
1009 if multi:
1010 rvalue = sections[section][name] + rvalue
1011 else:
1012 return sections[section][name]
1013 if multi and rvalue != []:
1014 return rvalue
1015 raise KeyError
1016
1017
1018class ConfigOpts(collections.Mapping):
1019
1020 """
1021 Config options which may be set on the command line or in config files.
1022
1023 ConfigOpts is a configuration option manager with APIs for registering
1024 option schemas, grouping options, parsing option values and retrieving
1025 the values of options.
1026 """
1027
1028 def __init__(self):
1029 """Construct a ConfigOpts object."""
1030 self._opts = {} # dict of dicts of (opt:, override:, default:)
1031 self._groups = {}
1032
1033 self._args = None
1034
1035 self._oparser = None
1036 self._cparser = None
1037 self._cli_values = {}
1038 self.__cache = {}
1039 self._config_opts = []
1040
1041 def _pre_setup(self, project, prog, version, usage, default_config_files):
1042 """Initialize a ConfigCliParser object for option parsing."""
1043
1044 if prog is None:
1045 prog = os.path.basename(sys.argv[0])
1046
1047 if default_config_files is None:
1048 default_config_files = find_config_files(project, prog)
1049
1050 self._oparser = argparse.ArgumentParser(prog=prog, usage=usage)
1051 self._oparser.add_argument('--version',
1052 action='version',
1053 version=version)
1054
1055 return prog, default_config_files
1056
1057 def _setup(self, project, prog, version, usage, default_config_files):
1058 """Initialize a ConfigOpts object for option parsing."""
1059
1060 self._config_opts = [
1061 MultiStrOpt('config-file',
1062 default=default_config_files,
1063 metavar='PATH',
1064 help='Path to a config file to use. Multiple config '
1065 'files can be specified, with values in later '
1066 'files taking precedence. The default files '
1067 ' used are: %s' % (default_config_files, )),
1068 StrOpt('config-dir',
1069 metavar='DIR',
1070 help='Path to a config directory to pull *.conf '
1071 'files from. This file set is sorted, so as to '
1072 'provide a predictable parse order if individual '
1073 'options are over-ridden. The set is parsed after '
1074 'the file(s), if any, specified via --config-file, '
1075 'hence over-ridden options in the directory take '
1076 'precedence.'),
1077 ]
1078 self.register_cli_opts(self._config_opts)
1079
1080 self.project = project
1081 self.prog = prog
1082 self.version = version
1083 self.usage = usage
1084 self.default_config_files = default_config_files
1085
1086 def __clear_cache(f):
1087 @functools.wraps(f)
1088 def __inner(self, *args, **kwargs):
1089 if kwargs.pop('clear_cache', True):
1090 self.__cache.clear()
1091 return f(self, *args, **kwargs)
1092
1093 return __inner
1094
1095 def __call__(self,
1096 args=None,
1097 project=None,
1098 prog=None,
1099 version=None,
1100 usage=None,
1101 default_config_files=None):
1102 """Parse command line arguments and config files.
1103
1104 Calling a ConfigOpts object causes the supplied command line arguments
1105 and config files to be parsed, causing opt values to be made available
1106 as attributes of the object.
1107
1108 The object may be called multiple times, each time causing the previous
1109 set of values to be overwritten.
1110
1111 Automatically registers the --config-file option with either a supplied
1112 list of default config files, or a list from find_config_files().
1113
1114 If the --config-dir option is set, any *.conf files from this
1115 directory are pulled in, after all the file(s) specified by the
1116 --config-file option.
1117
1118 :param args: command line arguments (defaults to sys.argv[1:])
1119 :param project: the toplevel project name, used to locate config files
1120 :param prog: the name of the program (defaults to sys.argv[0] basename)
1121 :param version: the program version (for --version)
1122 :param usage: a usage string (%prog will be expanded)
1123 :param default_config_files: config files to use by default
1124 :returns: the list of arguments left over after parsing options
1125 :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
1126 RequiredOptError, DuplicateOptError
1127 """
1128
1129 self.clear()
1130
1131 prog, default_config_files = self._pre_setup(project,
1132 prog,
1133 version,
1134 usage,
1135 default_config_files)
1136
1137 self._setup(project, prog, version, usage, default_config_files)
1138
1139 self._cli_values = self._parse_cli_opts(args)
1140
1141 self._parse_config_files()
1142
1143 self._check_required_opts()
1144
1145 def __getattr__(self, name):
1146 """Look up an option value and perform string substitution.
1147
1148 :param name: the opt name (or 'dest', more precisely)
1149 :returns: the option value (after string subsititution) or a GroupAttr
1150 :raises: NoSuchOptError,ConfigFileValueError,TemplateSubstitutionError
1151 """
1152 return self._get(name)
1153
1154 def __getitem__(self, key):
1155 """Look up an option value and perform string substitution."""
1156 return self.__getattr__(key)
1157
1158 def __contains__(self, key):
1159 """Return True if key is the name of a registered opt or group."""
1160 return key in self._opts or key in self._groups
1161
1162 def __iter__(self):
1163 """Iterate over all registered opt and group names."""
1164 for key in self._opts.keys() + self._groups.keys():
1165 yield key
1166
1167 def __len__(self):
1168 """Return the number of options and option groups."""
1169 return len(self._opts) + len(self._groups)
1170
1171 def reset(self):
1172 """Clear the object state and unset overrides and defaults."""
1173 self._unset_defaults_and_overrides()
1174 self.clear()
1175
1176 @__clear_cache
1177 def clear(self):
1178 """Clear the state of the object to before it was called.
1179
1180 Any subparsers added using the add_cli_subparsers() will also be
1181 removed as a side-effect of this method.
1182 """
1183 self._args = None
1184 self._cli_values.clear()
1185 self._oparser = argparse.ArgumentParser()
1186 self._cparser = None
1187 self.unregister_opts(self._config_opts)
1188 for group in self._groups.values():
1189 group._clear()
1190
1191 @__clear_cache
1192 def register_opt(self, opt, group=None, cli=False):
1193 """Register an option schema.
1194
1195 Registering an option schema makes any option value which is previously
1196 or subsequently parsed from the command line or config files available
1197 as an attribute of this object.
1198
1199 :param opt: an instance of an Opt sub-class
1200 :param cli: whether this is a CLI option
1201 :param group: an optional OptGroup object or group name
1202 :return: False if the opt was already register, True otherwise
1203 :raises: DuplicateOptError
1204 """
1205 if group is not None:
1206 group = self._get_group(group, autocreate=True)
1207 return group._register_opt(opt, cli)
1208
1209 if _is_opt_registered(self._opts, opt):
1210 return False
1211
1212 self._opts[opt.dest] = {'opt': opt, 'cli': cli}
1213
1214 return True
1215
1216 @__clear_cache
1217 def register_opts(self, opts, group=None):
1218 """Register multiple option schemas at once."""
1219 for opt in opts:
1220 self.register_opt(opt, group, clear_cache=False)
1221
1222 @__clear_cache
1223 def register_cli_opt(self, opt, group=None):
1224 """Register a CLI option schema.
1225
1226 CLI option schemas must be registered before the command line and
1227 config files are parsed. This is to ensure that all CLI options are
1228 show in --help and option validation works as expected.
1229
1230 :param opt: an instance of an Opt sub-class
1231 :param group: an optional OptGroup object or group name
1232 :return: False if the opt was already register, True otherwise
1233 :raises: DuplicateOptError, ArgsAlreadyParsedError
1234 """
1235 if self._args is not None:
1236 raise ArgsAlreadyParsedError("cannot register CLI option")
1237
1238 return self.register_opt(opt, group, cli=True, clear_cache=False)
1239
1240 @__clear_cache
1241 def register_cli_opts(self, opts, group=None):
1242 """Register multiple CLI option schemas at once."""
1243 for opt in opts:
1244 self.register_cli_opt(opt, group, clear_cache=False)
1245
1246 def register_group(self, group):
1247 """Register an option group.
1248
1249 An option group must be registered before options can be registered
1250 with the group.
1251
1252 :param group: an OptGroup object
1253 """
1254 if group.name in self._groups:
1255 return
1256
1257 self._groups[group.name] = copy.copy(group)
1258
1259 @__clear_cache
1260 def unregister_opt(self, opt, group=None):
1261 """Unregister an option.
1262
1263 :param opt: an Opt object
1264 :param group: an optional OptGroup object or group name
1265 :raises: ArgsAlreadyParsedError, NoSuchGroupError
1266 """
1267 if self._args is not None:
1268 raise ArgsAlreadyParsedError("reset before unregistering options")
1269
1270 if group is not None:
1271 self._get_group(group)._unregister_opt(opt)
1272 elif opt.dest in self._opts:
1273 del self._opts[opt.dest]
1274
1275 @__clear_cache
1276 def unregister_opts(self, opts, group=None):
1277 """Unregister multiple CLI option schemas at once."""
1278 for opt in opts:
1279 self.unregister_opt(opt, group, clear_cache=False)
1280
1281 def import_opt(self, name, module_str, group=None):
1282 """Import an option definition from a module.
1283
1284 Import a module and check that a given option is registered.
1285
1286 This is intended for use with global configuration objects
1287 like cfg.CONF where modules commonly register options with
1288 CONF at module load time. If one module requires an option
1289 defined by another module it can use this method to explicitly
1290 declare the dependency.
1291
1292 :param name: the name/dest of the opt
1293 :param module_str: the name of a module to import
1294 :param group: an option OptGroup object or group name
1295 :raises: NoSuchOptError, NoSuchGroupError
1296 """
1297 __import__(module_str)
1298 self._get_opt_info(name, group)
1299
Joe Gordon2b0591d2013-02-14 23:18:39 +00001300 def import_group(self, group, module_str):
1301 """Import an option group from a module.
1302
1303 Import a module and check that a given option group is registered.
1304
1305 This is intended for use with global configuration objects
1306 like cfg.CONF where modules commonly register options with
1307 CONF at module load time. If one module requires an option group
1308 defined by another module it can use this method to explicitly
1309 declare the dependency.
1310
1311 :param group: an option OptGroup object or group name
1312 :param module_str: the name of a module to import
1313 :raises: ImportError, NoSuchGroupError
1314 """
1315 __import__(module_str)
1316 self._get_group(group)
1317
Matthew Treinish481466b2012-12-20 17:16:01 -05001318 @__clear_cache
1319 def set_override(self, name, override, group=None):
1320 """Override an opt value.
1321
1322 Override the command line, config file and default values of a
1323 given option.
1324
1325 :param name: the name/dest of the opt
1326 :param override: the override value
1327 :param group: an option OptGroup object or group name
1328 :raises: NoSuchOptError, NoSuchGroupError
1329 """
1330 opt_info = self._get_opt_info(name, group)
1331 opt_info['override'] = override
1332
1333 @__clear_cache
1334 def set_default(self, name, default, group=None):
1335 """Override an opt's default value.
1336
1337 Override the default value of given option. A command line or
1338 config file value will still take precedence over this default.
1339
1340 :param name: the name/dest of the opt
1341 :param default: the default value
1342 :param group: an option OptGroup object or group name
1343 :raises: NoSuchOptError, NoSuchGroupError
1344 """
1345 opt_info = self._get_opt_info(name, group)
1346 opt_info['default'] = default
1347
1348 @__clear_cache
1349 def clear_override(self, name, group=None):
1350 """Clear an override an opt value.
1351
1352 Clear a previously set override of the command line, config file
1353 and default values of a given option.
1354
1355 :param name: the name/dest of the opt
1356 :param group: an option OptGroup object or group name
1357 :raises: NoSuchOptError, NoSuchGroupError
1358 """
1359 opt_info = self._get_opt_info(name, group)
1360 opt_info.pop('override', None)
1361
1362 @__clear_cache
1363 def clear_default(self, name, group=None):
1364 """Clear an override an opt's default value.
1365
1366 Clear a previously set override of the default value of given option.
1367
1368 :param name: the name/dest of the opt
1369 :param group: an option OptGroup object or group name
1370 :raises: NoSuchOptError, NoSuchGroupError
1371 """
1372 opt_info = self._get_opt_info(name, group)
1373 opt_info.pop('default', None)
1374
1375 def _all_opt_infos(self):
1376 """A generator function for iteration opt infos."""
1377 for info in self._opts.values():
1378 yield info, None
1379 for group in self._groups.values():
1380 for info in group._opts.values():
1381 yield info, group
1382
1383 def _all_cli_opts(self):
1384 """A generator function for iterating CLI opts."""
1385 for info, group in self._all_opt_infos():
1386 if info['cli']:
1387 yield info['opt'], group
1388
1389 def _unset_defaults_and_overrides(self):
1390 """Unset any default or override on all options."""
1391 for info, group in self._all_opt_infos():
1392 info.pop('default', None)
1393 info.pop('override', None)
1394
1395 def find_file(self, name):
1396 """Locate a file located alongside the config files.
1397
1398 Search for a file with the supplied basename in the directories
1399 which we have already loaded config files from and other known
1400 configuration directories.
1401
1402 The directory, if any, supplied by the config_dir option is
1403 searched first. Then the config_file option is iterated over
1404 and each of the base directories of the config_files values
1405 are searched. Failing both of these, the standard directories
1406 searched by the module level find_config_files() function is
1407 used. The first matching file is returned.
1408
1409 :param basename: the filename, e.g. 'policy.json'
1410 :returns: the path to a matching file, or None
1411 """
1412 dirs = []
1413 if self.config_dir:
1414 dirs.append(_fixpath(self.config_dir))
1415
1416 for cf in reversed(self.config_file):
1417 dirs.append(os.path.dirname(_fixpath(cf)))
1418
1419 dirs.extend(_get_config_dirs(self.project))
1420
1421 return _search_dirs(dirs, name)
1422
1423 def log_opt_values(self, logger, lvl):
1424 """Log the value of all registered opts.
1425
1426 It's often useful for an app to log its configuration to a log file at
1427 startup for debugging. This method dumps to the entire config state to
1428 the supplied logger at a given log level.
1429
1430 :param logger: a logging.Logger object
1431 :param lvl: the log level (e.g. logging.DEBUG) arg to logger.log()
1432 """
1433 logger.log(lvl, "*" * 80)
1434 logger.log(lvl, "Configuration options gathered from:")
1435 logger.log(lvl, "command line args: %s", self._args)
1436 logger.log(lvl, "config files: %s", self.config_file)
1437 logger.log(lvl, "=" * 80)
1438
1439 def _sanitize(opt, value):
Attila Fazekasb2902af2013-02-16 16:22:44 +01001440 """Obfuscate values of options declared secret."""
Matthew Treinish481466b2012-12-20 17:16:01 -05001441 return value if not opt.secret else '*' * len(str(value))
1442
1443 for opt_name in sorted(self._opts):
1444 opt = self._get_opt_info(opt_name)['opt']
1445 logger.log(lvl, "%-30s = %s", opt_name,
1446 _sanitize(opt, getattr(self, opt_name)))
1447
1448 for group_name in self._groups:
1449 group_attr = self.GroupAttr(self, self._get_group(group_name))
1450 for opt_name in sorted(self._groups[group_name]._opts):
1451 opt = self._get_opt_info(opt_name, group_name)['opt']
1452 logger.log(lvl, "%-30s = %s",
1453 "%s.%s" % (group_name, opt_name),
1454 _sanitize(opt, getattr(group_attr, opt_name)))
1455
1456 logger.log(lvl, "*" * 80)
1457
1458 def print_usage(self, file=None):
1459 """Print the usage message for the current program."""
1460 self._oparser.print_usage(file)
1461
1462 def print_help(self, file=None):
1463 """Print the help message for the current program."""
1464 self._oparser.print_help(file)
1465
1466 def _get(self, name, group=None):
1467 if isinstance(group, OptGroup):
1468 key = (group.name, name)
1469 else:
1470 key = (group, name)
1471 try:
1472 return self.__cache[key]
1473 except KeyError:
1474 value = self._substitute(self._do_get(name, group))
1475 self.__cache[key] = value
1476 return value
1477
1478 def _do_get(self, name, group=None):
1479 """Look up an option value.
1480
1481 :param name: the opt name (or 'dest', more precisely)
1482 :param group: an OptGroup
1483 :returns: the option value, or a GroupAttr object
1484 :raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
1485 TemplateSubstitutionError
1486 """
1487 if group is None and name in self._groups:
1488 return self.GroupAttr(self, self._get_group(name))
1489
1490 info = self._get_opt_info(name, group)
1491 opt = info['opt']
1492
1493 if isinstance(opt, SubCommandOpt):
1494 return self.SubCommandAttr(self, group, opt.dest)
1495
1496 if 'override' in info:
1497 return info['override']
1498
1499 values = []
1500 if self._cparser is not None:
1501 section = group.name if group is not None else 'DEFAULT'
1502 try:
1503 value = opt._get_from_config_parser(self._cparser, section)
1504 except KeyError:
1505 pass
1506 except ValueError as ve:
1507 raise ConfigFileValueError(str(ve))
1508 else:
1509 if not opt.multi:
1510 # No need to continue since the last value wins
1511 return value[-1]
1512 values.extend(value)
1513
1514 name = name if group is None else group.name + '_' + name
1515 value = self._cli_values.get(name)
1516 if value is not None:
1517 if not opt.multi:
1518 return value
1519
1520 # argparse ignores default=None for nargs='*'
1521 if opt.positional and not value:
1522 value = opt.default
1523
1524 return value + values
1525
1526 if values:
1527 return values
1528
1529 if 'default' in info:
1530 return info['default']
1531
1532 return opt.default
1533
1534 def _substitute(self, value):
1535 """Perform string template substitution.
1536
1537 Substitute any template variables (e.g. $foo, ${bar}) in the supplied
1538 string value(s) with opt values.
1539
1540 :param value: the string value, or list of string values
1541 :returns: the substituted string(s)
1542 """
1543 if isinstance(value, list):
1544 return [self._substitute(i) for i in value]
1545 elif isinstance(value, str):
1546 tmpl = string.Template(value)
1547 return tmpl.safe_substitute(self.StrSubWrapper(self))
1548 else:
1549 return value
1550
1551 def _get_group(self, group_or_name, autocreate=False):
1552 """Looks up a OptGroup object.
1553
1554 Helper function to return an OptGroup given a parameter which can
1555 either be the group's name or an OptGroup object.
1556
1557 The OptGroup object returned is from the internal dict of OptGroup
1558 objects, which will be a copy of any OptGroup object that users of
1559 the API have access to.
1560
1561 :param group_or_name: the group's name or the OptGroup object itself
1562 :param autocreate: whether to auto-create the group if it's not found
1563 :raises: NoSuchGroupError
1564 """
1565 group = group_or_name if isinstance(group_or_name, OptGroup) else None
1566 group_name = group.name if group else group_or_name
1567
Joe Gordon2b0591d2013-02-14 23:18:39 +00001568 if group_name not in self._groups:
1569 if group is not None or not autocreate:
Matthew Treinish481466b2012-12-20 17:16:01 -05001570 raise NoSuchGroupError(group_name)
1571
1572 self.register_group(OptGroup(name=group_name))
1573
1574 return self._groups[group_name]
1575
1576 def _get_opt_info(self, opt_name, group=None):
1577 """Return the (opt, override, default) dict for an opt.
1578
1579 :param opt_name: an opt name/dest
1580 :param group: an optional group name or OptGroup object
1581 :raises: NoSuchOptError, NoSuchGroupError
1582 """
1583 if group is None:
1584 opts = self._opts
1585 else:
1586 group = self._get_group(group)
1587 opts = group._opts
1588
Joe Gordon2b0591d2013-02-14 23:18:39 +00001589 if opt_name not in opts:
Matthew Treinish481466b2012-12-20 17:16:01 -05001590 raise NoSuchOptError(opt_name, group)
1591
1592 return opts[opt_name]
1593
1594 def _parse_config_files(self):
1595 """Parse the config files from --config-file and --config-dir.
1596
1597 :raises: ConfigFilesNotFoundError, ConfigFileParseError
1598 """
1599 config_files = list(self.config_file)
1600
1601 if self.config_dir:
1602 config_dir_glob = os.path.join(self.config_dir, '*.conf')
1603 config_files += sorted(glob.glob(config_dir_glob))
1604
1605 config_files = [_fixpath(p) for p in config_files]
1606
1607 self._cparser = MultiConfigParser()
1608
1609 try:
1610 read_ok = self._cparser.read(config_files)
1611 except iniparser.ParseError as pe:
1612 raise ConfigFileParseError(pe.filename, str(pe))
1613
1614 if read_ok != config_files:
1615 not_read_ok = filter(lambda f: f not in read_ok, config_files)
1616 raise ConfigFilesNotFoundError(not_read_ok)
1617
1618 def _check_required_opts(self):
1619 """Check that all opts marked as required have values specified.
1620
1621 :raises: RequiredOptError
1622 """
1623 for info, group in self._all_opt_infos():
1624 opt = info['opt']
1625
1626 if opt.required:
Joe Gordon2b0591d2013-02-14 23:18:39 +00001627 if 'default' in info or 'override' in info:
Matthew Treinish481466b2012-12-20 17:16:01 -05001628 continue
1629
1630 if self._get(opt.dest, group) is None:
1631 raise RequiredOptError(opt.name, group)
1632
1633 def _parse_cli_opts(self, args):
1634 """Parse command line options.
1635
1636 Initializes the command line option parser and parses the supplied
1637 command line arguments.
1638
1639 :param args: the command line arguments
1640 :returns: a dict of parsed option values
1641 :raises: SystemExit, DuplicateOptError
1642
1643 """
1644 self._args = args
1645
Joe Gordon2b0591d2013-02-14 23:18:39 +00001646 for opt, group in sorted(self._all_cli_opts()):
Matthew Treinish481466b2012-12-20 17:16:01 -05001647 opt._add_to_cli(self._oparser, group)
1648
1649 return vars(self._oparser.parse_args(args))
1650
1651 class GroupAttr(collections.Mapping):
1652
1653 """
1654 A helper class representing the option values of a group as a mapping
1655 and attributes.
1656 """
1657
1658 def __init__(self, conf, group):
1659 """Construct a GroupAttr object.
1660
1661 :param conf: a ConfigOpts object
1662 :param group: an OptGroup object
1663 """
1664 self._conf = conf
1665 self._group = group
1666
1667 def __getattr__(self, name):
1668 """Look up an option value and perform template substitution."""
1669 return self._conf._get(name, self._group)
1670
1671 def __getitem__(self, key):
1672 """Look up an option value and perform string substitution."""
1673 return self.__getattr__(key)
1674
1675 def __contains__(self, key):
1676 """Return True if key is the name of a registered opt or group."""
1677 return key in self._group._opts
1678
1679 def __iter__(self):
1680 """Iterate over all registered opt and group names."""
1681 for key in self._group._opts.keys():
1682 yield key
1683
1684 def __len__(self):
1685 """Return the number of options and option groups."""
1686 return len(self._group._opts)
1687
1688 class SubCommandAttr(object):
1689
1690 """
1691 A helper class representing the name and arguments of an argparse
1692 sub-parser.
1693 """
1694
1695 def __init__(self, conf, group, dest):
1696 """Construct a SubCommandAttr object.
1697
1698 :param conf: a ConfigOpts object
1699 :param group: an OptGroup object
1700 :param dest: the name of the sub-parser
1701 """
1702 self._conf = conf
1703 self._group = group
1704 self._dest = dest
1705
1706 def __getattr__(self, name):
1707 """Look up a sub-parser name or argument value."""
1708 if name == 'name':
1709 name = self._dest
1710 if self._group is not None:
1711 name = self._group.name + '_' + name
1712 return self._conf._cli_values[name]
1713
1714 if name in self._conf:
1715 raise DuplicateOptError(name)
1716
1717 try:
1718 return self._conf._cli_values[name]
1719 except KeyError:
1720 raise NoSuchOptError(name)
1721
1722 class StrSubWrapper(object):
1723
1724 """
1725 A helper class exposing opt values as a dict for string substitution.
1726 """
1727
1728 def __init__(self, conf):
1729 """Construct a StrSubWrapper object.
1730
1731 :param conf: a ConfigOpts object
1732 """
1733 self.conf = conf
1734
1735 def __getitem__(self, key):
1736 """Look up an opt value from the ConfigOpts object.
1737
1738 :param key: an opt name
1739 :returns: an opt value
1740 :raises: TemplateSubstitutionError if attribute is a group
1741 """
1742 value = getattr(self.conf, key)
1743 if isinstance(value, self.conf.GroupAttr):
1744 raise TemplateSubstitutionError(
1745 'substituting group %s not supported' % key)
1746 return value
1747
1748
Joe Gordon2b0591d2013-02-14 23:18:39 +00001749CONF = ConfigOpts()