Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1 | # 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 | |
| 17 | r""" |
| 18 | Configuration options which may be set on the command line or in config files. |
| 19 | |
| 20 | The 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 | |
| 33 | Options 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 | |
| 45 | Option schemas are registered with the config manager at runtime, but before |
| 46 | the 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 | |
| 61 | A common usage pattern is for each option schema to be defined in the module or |
| 62 | class 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 | |
| 75 | An option may optionally be made available via the command line. Such options |
| 76 | must registered with the config manager before the command line is parsed (for |
| 77 | the 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 | |
| 93 | The config manager has two CLI options defined by default, --config-file |
| 94 | and --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 | |
| 109 | Option values are parsed from any supplied config files using |
| 110 | openstack.common.iniparser. If none are specified, a default set is used |
| 111 | e.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 | |
| 121 | Option values in config files override those on the command line. Config files |
| 122 | are parsed in order, with values in later files overriding those in earlier |
| 123 | files. |
| 124 | |
| 125 | The parsing of CLI args and config files is initiated by invoking the config |
| 126 | manager e.g.:: |
| 127 | |
| 128 | conf = ConfigOpts() |
| 129 | conf.register_opt(BoolOpt('verbose', ...)) |
| 130 | conf(sys.argv[1:]) |
| 131 | if conf.verbose: |
| 132 | ... |
| 133 | |
| 134 | Options 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 | |
| 152 | If it no group attributes are required other than the group name, the group |
| 153 | need 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 | |
| 160 | If no group is specified, options belong to the 'DEFAULT' section of config |
| 161 | files:: |
| 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 | |
| 176 | Command-line options in a group are automatically prefixed with the |
| 177 | group name:: |
| 178 | |
| 179 | --rabbit-host localhost --rabbit-port 9999 |
| 180 | |
| 181 | Option values in the default group are referenced as attributes/properties on |
| 182 | the config manager; groups are also attributes on the config manager, with |
| 183 | attributes 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 | |
| 192 | Option 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 | |
| 206 | Note that interpolation can be avoided by using '$$'. |
| 207 | |
| 208 | Options may be declared as required so that an error is raised if the user |
| 209 | does not supply a value for the option. |
| 210 | |
| 211 | Options may be declared as secret so that their values are not leaked into |
| 212 | log 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 220 | This module also contains a global instance of the ConfigOpts class |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 221 | in 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 | |
| 236 | Positional command line arguments are supported via a 'positional' Opt |
| 237 | constructor argument:: |
| 238 | |
Joe Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 239 | >>> conf = ConfigOpts() |
| 240 | >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True)) |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 241 | True |
Joe Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 242 | >>> conf(['a', 'b']) |
| 243 | >>> conf.bar |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 244 | ['a', 'b'] |
| 245 | |
| 246 | It is also possible to use argparse "sub-parsers" to parse additional |
| 247 | command 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 253 | >>> conf = ConfigOpts() |
| 254 | >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers)) |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 255 | True |
Joe Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 256 | >>> conf(args=['list', '10']) |
| 257 | >>> conf.action.name, conf.action.id |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 258 | ('list', '10') |
| 259 | |
| 260 | """ |
| 261 | |
| 262 | import argparse |
| 263 | import collections |
| 264 | import copy |
| 265 | import functools |
| 266 | import glob |
| 267 | import os |
| 268 | import string |
| 269 | import sys |
| 270 | |
| 271 | from tempest.openstack.common import iniparser |
| 272 | |
| 273 | |
| 274 | class 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 | |
| 284 | class 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 | |
| 294 | class 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 | |
| 309 | class 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 | |
| 319 | class 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 | |
| 329 | class 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 | |
| 344 | class 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 | |
| 351 | class 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 | |
| 362 | class 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 | |
| 373 | class ConfigFileValueError(Error): |
| 374 | """Raised if a config file value does not match its opt type.""" |
| 375 | pass |
| 376 | |
| 377 | |
| 378 | def _fixpath(p): |
| 379 | """Apply tilde expansion and absolutization to a path.""" |
| 380 | return os.path.abspath(os.path.expanduser(p)) |
| 381 | |
| 382 | |
| 383 | def _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 | |
| 410 | def _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 | |
| 427 | def 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 | |
| 465 | def _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 | |
| 485 | def 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 | |
| 492 | class 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 | |
| 666 | class StrOpt(Opt): |
| 667 | """ |
| 668 | String opts do not have their values transformed and are returned as |
| 669 | str objects. |
| 670 | """ |
| 671 | pass |
| 672 | |
| 673 | |
| 674 | class 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 | |
| 735 | class 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 | |
| 750 | class 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 | |
| 765 | class 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 | |
| 794 | class 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 | |
| 819 | class 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 866 | if self.handler is not None: |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 867 | self.handler(subparsers) |
| 868 | |
| 869 | |
| 870 | class 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 | |
| 942 | class 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 | |
| 952 | class 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 | |
| 982 | class 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 | |
| 1018 | class 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1300 | 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 Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1318 | @__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 Fazekas | b2902af | 2013-02-16 16:22:44 +0100 | [diff] [blame] | 1440 | """Obfuscate values of options declared secret.""" |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1441 | 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1568 | if group_name not in self._groups: |
| 1569 | if group is not None or not autocreate: |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1570 | 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1589 | if opt_name not in opts: |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1590 | 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1627 | if 'default' in info or 'override' in info: |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1628 | 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1646 | for opt, group in sorted(self._all_cli_opts()): |
Matthew Treinish | 481466b | 2012-12-20 17:16:01 -0500 | [diff] [blame] | 1647 | 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 Gordon | 2b0591d | 2013-02-14 23:18:39 +0000 | [diff] [blame] | 1749 | CONF = ConfigOpts() |