Package logilab :: Package common :: Module configuration
[frames] | no frames]

Source Code for Module logilab.common.configuration

   1  # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
   2  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 
   3  # 
   4  # This file is part of logilab-common. 
   5  # 
   6  # logilab-common is free software: you can redistribute it and/or modify it under 
   7  # the terms of the GNU Lesser General Public License as published by the Free 
   8  # Software Foundation, either version 2.1 of the License, or (at your option) any 
   9  # later version. 
  10  # 
  11  # logilab-common is distributed in the hope that it will be useful, but WITHOUT 
  12  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  13  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
  14  # details. 
  15  # 
  16  # You should have received a copy of the GNU Lesser General Public License along 
  17  # with logilab-common.  If not, see <http://www.gnu.org/licenses/>. 
  18  """Classes to handle advanced configuration in simple to complex applications. 
  19   
  20  Allows to load the configuration from a file or from command line 
  21  options, to generate a sample configuration file or to display 
  22  program's usage. Fills the gap between optik/optparse and ConfigParser 
  23  by adding data types (which are also available as a standalone optik 
  24  extension in the `optik_ext` module). 
  25   
  26   
  27  Quick start: simplest usage 
  28  --------------------------- 
  29   
  30  .. python :: 
  31   
  32    >>> import sys 
  33    >>> from logilab.common.configuration import Configuration 
  34    >>> options = [('dothis', {'type':'yn', 'default': True, 'metavar': '<y or n>'}), 
  35    ...            ('value', {'type': 'string', 'metavar': '<string>'}), 
  36    ...            ('multiple', {'type': 'csv', 'default': ('yop',), 
  37    ...                          'metavar': '<comma separated values>', 
  38    ...                          'help': 'you can also document the option'}), 
  39    ...            ('number', {'type': 'int', 'default':2, 'metavar':'<int>'}), 
  40    ...           ] 
  41    >>> config = Configuration(options=options, name='My config') 
  42    >>> print config['dothis'] 
  43    True 
  44    >>> print config['value'] 
  45    None 
  46    >>> print config['multiple'] 
  47    ('yop',) 
  48    >>> print config['number'] 
  49    2 
  50    >>> print config.help() 
  51    Usage:  [options] 
  52   
  53    Options: 
  54      -h, --help            show this help message and exit 
  55      --dothis=<y or n> 
  56      --value=<string> 
  57      --multiple=<comma separated values> 
  58                            you can also document the option [current: none] 
  59      --number=<int> 
  60   
  61    >>> f = open('myconfig.ini', 'w') 
  62    >>> f.write('''[MY CONFIG] 
  63    ... number = 3 
  64    ... dothis = no 
  65    ... multiple = 1,2,3 
  66    ... ''') 
  67    >>> f.close() 
  68    >>> config.load_file_configuration('myconfig.ini') 
  69    >>> print config['dothis'] 
  70    False 
  71    >>> print config['value'] 
  72    None 
  73    >>> print config['multiple'] 
  74    ['1', '2', '3'] 
  75    >>> print config['number'] 
  76    3 
  77    >>> sys.argv = ['mon prog', '--value', 'bacon', '--multiple', '4,5,6', 
  78    ...             'nonoptionargument'] 
  79    >>> print config.load_command_line_configuration() 
  80    ['nonoptionargument'] 
  81    >>> print config['value'] 
  82    bacon 
  83    >>> config.generate_config() 
  84    # class for simple configurations which don't need the 
  85    # manager / providers model and prefer delegation to inheritance 
  86    # 
  87    # configuration values are accessible through a dict like interface 
  88    # 
  89    [MY CONFIG] 
  90   
  91    dothis=no 
  92   
  93    value=bacon 
  94   
  95    # you can also document the option 
  96    multiple=4,5,6 
  97   
  98    number=3 
  99    >>> 
 100  """ 
 101  __docformat__ = "restructuredtext en" 
 102   
 103  __all__ = ('OptionsManagerMixIn', 'OptionsProviderMixIn', 
 104             'ConfigurationMixIn', 'Configuration', 
 105             'OptionsManager2ConfigurationAdapter') 
 106   
 107  import os 
 108  import sys 
 109  import re 
 110  from os.path import exists, expanduser 
 111  from copy import copy 
 112  from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \ 
 113       DuplicateSectionError 
 114  from warnings import warn 
 115   
 116  from logilab.common.compat import callable, raw_input, str_encode as _encode 
 117   
 118  from logilab.common.textutils import normalize_text, unquote 
 119  from logilab.common import optik_ext as optparse 
 120   
 121  OptionError = optparse.OptionError 
 122   
 123  REQUIRED = [] 
 124   
125 -class UnsupportedAction(Exception):
126 """raised by set_option when it doesn't know what to do for an action"""
127 128
129 -def _get_encoding(encoding, stream):
130 encoding = encoding or getattr(stream, 'encoding', None) 131 if not encoding: 132 import locale 133 encoding = locale.getpreferredencoding() 134 return encoding
135 136 137 # validation functions ######################################################## 138
139 -def choice_validator(optdict, name, value):
140 """validate and return a converted value for option of type 'choice' 141 """ 142 if not value in optdict['choices']: 143 msg = "option %s: invalid value: %r, should be in %s" 144 raise optparse.OptionValueError(msg % (name, value, optdict['choices'])) 145 return value
146
147 -def multiple_choice_validator(optdict, name, value):
148 """validate and return a converted value for option of type 'choice' 149 """ 150 choices = optdict['choices'] 151 values = optparse.check_csv(None, name, value) 152 for value in values: 153 if not value in choices: 154 msg = "option %s: invalid value: %r, should be in %s" 155 raise optparse.OptionValueError(msg % (name, value, choices)) 156 return values
157
158 -def csv_validator(optdict, name, value):
159 """validate and return a converted value for option of type 'csv' 160 """ 161 return optparse.check_csv(None, name, value)
162
163 -def yn_validator(optdict, name, value):
164 """validate and return a converted value for option of type 'yn' 165 """ 166 return optparse.check_yn(None, name, value)
167
168 -def named_validator(optdict, name, value):
169 """validate and return a converted value for option of type 'named' 170 """ 171 return optparse.check_named(None, name, value)
172
173 -def file_validator(optdict, name, value):
174 """validate and return a filepath for option of type 'file'""" 175 return optparse.check_file(None, name, value)
176
177 -def color_validator(optdict, name, value):
178 """validate and return a valid color for option of type 'color'""" 179 return optparse.check_color(None, name, value)
180
181 -def password_validator(optdict, name, value):
182 """validate and return a string for option of type 'password'""" 183 return optparse.check_password(None, name, value)
184
185 -def date_validator(optdict, name, value):
186 """validate and return a mx DateTime object for option of type 'date'""" 187 return optparse.check_date(None, name, value)
188
189 -def time_validator(optdict, name, value):
190 """validate and return a time object for option of type 'time'""" 191 return optparse.check_time(None, name, value)
192
193 -def bytes_validator(optdict, name, value):
194 """validate and return an integer for option of type 'bytes'""" 195 return optparse.check_bytes(None, name, value)
196 197 198 VALIDATORS = {'string': unquote, 199 'int': int, 200 'float': float, 201 'file': file_validator, 202 'font': unquote, 203 'color': color_validator, 204 'regexp': re.compile, 205 'csv': csv_validator, 206 'yn': yn_validator, 207 'bool': yn_validator, 208 'named': named_validator, 209 'password': password_validator, 210 'date': date_validator, 211 'time': time_validator, 212 'bytes': bytes_validator, 213 'choice': choice_validator, 214 'multiple_choice': multiple_choice_validator, 215 } 216
217 -def _call_validator(opttype, optdict, option, value):
218 if opttype not in VALIDATORS: 219 raise Exception('Unsupported type "%s"' % opttype) 220 try: 221 return VALIDATORS[opttype](optdict, option, value) 222 except TypeError: 223 try: 224 return VALIDATORS[opttype](value) 225 except optparse.OptionValueError: 226 raise 227 except: 228 raise optparse.OptionValueError('%s value (%r) should be of type %s' % 229 (option, value, opttype))
230 231 # user input functions ######################################################## 232
233 -def input_password(optdict, question='password:'):
234 from getpass import getpass 235 while True: 236 value = getpass(question) 237 value2 = getpass('confirm: ') 238 if value == value2: 239 return value 240 print 'password mismatch, try again'
241
242 -def input_string(optdict, question):
243 value = raw_input(question).strip() 244 return value or None
245
246 -def _make_input_function(opttype):
247 def input_validator(optdict, question): 248 while True: 249 value = raw_input(question) 250 if not value.strip(): 251 return None 252 try: 253 return _call_validator(opttype, optdict, None, value) 254 except optparse.OptionValueError, ex: 255 msg = str(ex).split(':', 1)[-1].strip() 256 print 'bad value: %s' % msg
257 return input_validator 258 259 INPUT_FUNCTIONS = { 260 'string': input_string, 261 'password': input_password, 262 } 263 264 for opttype in VALIDATORS.keys(): 265 INPUT_FUNCTIONS.setdefault(opttype, _make_input_function(opttype)) 266
267 -def expand_default(self, option):
268 """monkey patch OptionParser.expand_default since we have a particular 269 way to handle defaults to avoid overriding values in the configuration 270 file 271 """ 272 if self.parser is None or not self.default_tag: 273 return option.help 274 optname = option._long_opts[0][2:] 275 try: 276 provider = self.parser.options_manager._all_options[optname] 277 except KeyError: 278 value = None 279 else: 280 optdict = provider.get_option_def(optname) 281 optname = provider.option_name(optname, optdict) 282 value = getattr(provider.config, optname, optdict) 283 value = format_option_value(optdict, value) 284 if value is optparse.NO_DEFAULT or not value: 285 value = self.NO_DEFAULT_VALUE 286 return option.help.replace(self.default_tag, str(value))
287 288
289 -def convert(value, optdict, name=''):
290 """return a validated value for an option according to its type 291 292 optional argument name is only used for error message formatting 293 """ 294 try: 295 _type = optdict['type'] 296 except KeyError: 297 # FIXME 298 return value 299 return _call_validator(_type, optdict, name, value)
300
301 -def comment(string):
302 """return string as a comment""" 303 lines = [line.strip() for line in string.splitlines()] 304 return '# ' + ('%s# ' % os.linesep).join(lines)
305
306 -def format_time(value):
307 if not value: 308 return '0' 309 if value != int(value): 310 return '%.2fs' % value 311 value = int(value) 312 nbmin, nbsec = divmod(value, 60) 313 if nbsec: 314 return '%ss' % value 315 nbhour, nbmin_ = divmod(nbmin, 60) 316 if nbmin_: 317 return '%smin' % nbmin 318 nbday, nbhour_ = divmod(nbhour, 24) 319 if nbhour_: 320 return '%sh' % nbhour 321 return '%sd' % nbday
322
323 -def format_bytes(value):
324 if not value: 325 return '0' 326 if value != int(value): 327 return '%.2fB' % value 328 value = int(value) 329 prevunit = 'B' 330 for unit in ('KB', 'MB', 'GB', 'TB'): 331 next, remain = divmod(value, 1024) 332 if remain: 333 return '%s%s' % (value, prevunit) 334 prevunit = unit 335 value = next 336 return '%s%s' % (value, unit)
337
338 -def format_option_value(optdict, value):
339 """return the user input's value from a 'compiled' value""" 340 if isinstance(value, (list, tuple)): 341 value = ','.join(value) 342 elif isinstance(value, dict): 343 value = ','.join(['%s:%s' % (k, v) for k, v in value.items()]) 344 elif hasattr(value, 'match'): # optdict.get('type') == 'regexp' 345 # compiled regexp 346 value = value.pattern 347 elif optdict.get('type') == 'yn': 348 value = value and 'yes' or 'no' 349 elif isinstance(value, (str, unicode)) and value.isspace(): 350 value = "'%s'" % value 351 elif optdict.get('type') == 'time' and isinstance(value, (float, int, long)): 352 value = format_time(value) 353 elif optdict.get('type') == 'bytes' and hasattr(value, '__int__'): 354 value = format_bytes(value) 355 return value
356
357 -def ini_format_section(stream, section, options, encoding=None, doc=None):
358 """format an options section using the INI format""" 359 encoding = _get_encoding(encoding, stream) 360 if doc: 361 print >> stream, _encode(comment(doc), encoding) 362 print >> stream, '[%s]' % section 363 ini_format(stream, options, encoding)
364
365 -def ini_format(stream, options, encoding):
366 """format options using the INI format""" 367 for optname, optdict, value in options: 368 value = format_option_value(optdict, value) 369 help = optdict.get('help') 370 if help: 371 help = normalize_text(help, line_len=79, indent='# ') 372 print >> stream 373 print >> stream, _encode(help, encoding) 374 else: 375 print >> stream 376 if value is None: 377 print >> stream, '#%s=' % optname 378 else: 379 value = _encode(value, encoding).strip() 380 print >> stream, '%s=%s' % (optname, value)
381 382 format_section = ini_format_section 383
384 -def rest_format_section(stream, section, options, encoding=None, doc=None):
385 """format an options section using the INI format""" 386 encoding = _get_encoding(encoding, stream) 387 if section: 388 print >> stream, '%s\n%s' % (section, "'"*len(section)) 389 if doc: 390 print >> stream, _encode(normalize_text(doc, line_len=79, indent=''), 391 encoding) 392 print >> stream 393 for optname, optdict, value in options: 394 help = optdict.get('help') 395 print >> stream, ':%s:' % optname 396 if help: 397 help = normalize_text(help, line_len=79, indent=' ') 398 print >> stream, _encode(help, encoding) 399 if value: 400 value = _encode(format_option_value(optdict, value), encoding) 401 print >> stream, '' 402 print >> stream, ' Default: ``%s``' % value.replace("`` ", "```` ``")
403 404
405 -class OptionsManagerMixIn(object):
406 """MixIn to handle a configuration from both a configuration file and 407 command line options 408 """ 409
410 - def __init__(self, usage, config_file=None, version=None, quiet=0):
411 self.config_file = config_file 412 self.reset_parsers(usage, version=version) 413 # list of registered options providers 414 self.options_providers = [] 415 # dictionary associating option name to checker 416 self._all_options = {} 417 self._short_options = {} 418 self._nocallback_options = {} 419 self._mygroups = dict() 420 # verbosity 421 self.quiet = quiet 422 self._maxlevel = 0
423
424 - def reset_parsers(self, usage='', version=None):
425 # configuration file parser 426 self.cfgfile_parser = ConfigParser() 427 # command line parser 428 self.cmdline_parser = optparse.OptionParser(usage=usage, version=version) 429 self.cmdline_parser.options_manager = self 430 self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
431
432 - def register_options_provider(self, provider, own_group=True):
433 """register an options provider""" 434 assert provider.priority <= 0, "provider's priority can't be >= 0" 435 for i in range(len(self.options_providers)): 436 if provider.priority > self.options_providers[i].priority: 437 self.options_providers.insert(i, provider) 438 break 439 else: 440 self.options_providers.append(provider) 441 non_group_spec_options = [option for option in provider.options 442 if 'group' not in option[1]] 443 groups = getattr(provider, 'option_groups', ()) 444 if own_group and non_group_spec_options: 445 self.add_option_group(provider.name.upper(), provider.__doc__, 446 non_group_spec_options, provider) 447 else: 448 for opt, optdict in non_group_spec_options: 449 self.add_optik_option(provider, self.cmdline_parser, opt, optdict) 450 for gname, gdoc in groups: 451 gname = gname.upper() 452 goptions = [option for option in provider.options 453 if option[1].get('group', '').upper() == gname] 454 self.add_option_group(gname, gdoc, goptions, provider)
455
456 - def add_option_group(self, group_name, doc, options, provider):
457 """add an option group including the listed options 458 """ 459 assert options 460 # add option group to the command line parser 461 if group_name in self._mygroups: 462 group = self._mygroups[group_name] 463 else: 464 group = optparse.OptionGroup(self.cmdline_parser, 465 title=group_name.capitalize()) 466 self.cmdline_parser.add_option_group(group) 467 group.level = provider.level 468 self._mygroups[group_name] = group 469 # add section to the config file 470 if group_name != "DEFAULT": 471 self.cfgfile_parser.add_section(group_name) 472 # add provider's specific options 473 for opt, optdict in options: 474 self.add_optik_option(provider, group, opt, optdict)
475
476 - def add_optik_option(self, provider, optikcontainer, opt, optdict):
477 if 'inputlevel' in optdict: 478 warn('[0.50] "inputlevel" in option dictionary for %s is deprecated,' 479 ' use "level"' % opt, DeprecationWarning) 480 optdict['level'] = optdict.pop('inputlevel') 481 args, optdict = self.optik_option(provider, opt, optdict) 482 option = optikcontainer.add_option(*args, **optdict) 483 self._all_options[opt] = provider 484 self._maxlevel = max(self._maxlevel, option.level or 0)
485
486 - def optik_option(self, provider, opt, optdict):
487 """get our personal option definition and return a suitable form for 488 use with optik/optparse 489 """ 490 optdict = copy(optdict) 491 others = {} 492 if 'action' in optdict: 493 self._nocallback_options[provider] = opt 494 else: 495 optdict['action'] = 'callback' 496 optdict['callback'] = self.cb_set_provider_option 497 # default is handled here and *must not* be given to optik if you 498 # want the whole machinery to work 499 if 'default' in optdict: 500 if (optparse.OPTPARSE_FORMAT_DEFAULT and 'help' in optdict and 501 optdict.get('default') is not None and 502 not optdict['action'] in ('store_true', 'store_false')): 503 optdict['help'] += ' [current: %default]' 504 del optdict['default'] 505 args = ['--' + str(opt)] 506 if 'short' in optdict: 507 self._short_options[optdict['short']] = opt 508 args.append('-' + optdict['short']) 509 del optdict['short'] 510 # cleanup option definition dict before giving it to optik 511 for key in optdict.keys(): 512 if not key in self._optik_option_attrs: 513 optdict.pop(key) 514 return args, optdict
515
516 - def cb_set_provider_option(self, option, opt, value, parser):
517 """optik callback for option setting""" 518 if opt.startswith('--'): 519 # remove -- on long option 520 opt = opt[2:] 521 else: 522 # short option, get its long equivalent 523 opt = self._short_options[opt[1:]] 524 # trick since we can't set action='store_true' on options 525 if value is None: 526 value = 1 527 self.global_set_option(opt, value)
528
529 - def global_set_option(self, opt, value):
530 """set option on the correct option provider""" 531 self._all_options[opt].set_option(opt, value)
532
533 - def generate_config(self, stream=None, skipsections=(), encoding=None):
534 """write a configuration file according to the current configuration 535 into the given stream or stdout 536 """ 537 options_by_section = {} 538 sections = [] 539 for provider in self.options_providers: 540 for section, options in provider.options_by_section(): 541 if section is None: 542 section = provider.name 543 if section in skipsections: 544 continue 545 options = [(n, d, v) for (n, d, v) in options 546 if d.get('type') is not None] 547 if not options: 548 continue 549 if not section in sections: 550 sections.append(section) 551 alloptions = options_by_section.setdefault(section, []) 552 alloptions += options 553 stream = stream or sys.stdout 554 encoding = _get_encoding(encoding, stream) 555 printed = False 556 for section in sections: 557 if printed: 558 print >> stream, '\n' 559 format_section(stream, section.upper(), options_by_section[section], 560 encoding) 561 printed = True
562
563 - def generate_manpage(self, pkginfo, section=1, stream=None):
564 """write a man page for the current configuration into the given 565 stream or stdout 566 """ 567 self._monkeypatch_expand_default() 568 try: 569 optparse.generate_manpage(self.cmdline_parser, pkginfo, 570 section, stream=stream or sys.stdout, 571 level=self._maxlevel) 572 finally: 573 self._unmonkeypatch_expand_default()
574 575 # initialization methods ################################################## 576
577 - def load_provider_defaults(self):
578 """initialize configuration using default values""" 579 for provider in self.options_providers: 580 provider.load_defaults()
581
582 - def load_file_configuration(self, config_file=None):
583 """load the configuration from file""" 584 self.read_config_file(config_file) 585 self.load_config_file()
586
587 - def read_config_file(self, config_file=None):
588 """read the configuration file but do not load it (i.e. dispatching 589 values to each options provider) 590 """ 591 helplevel = 1 592 while helplevel <= self._maxlevel: 593 opt = '-'.join(['long'] * helplevel) + '-help' 594 if opt in self._all_options: 595 break # already processed 596 def helpfunc(option, opt, val, p, level=helplevel): 597 print self.help(level) 598 sys.exit(0)
599 helpmsg = '%s verbose help.' % ' '.join(['more'] * helplevel) 600 optdict = {'action' : 'callback', 'callback' : helpfunc, 601 'help' : helpmsg} 602 provider = self.options_providers[0] 603 self.add_optik_option(provider, self.cmdline_parser, opt, optdict) 604 provider.options += ( (opt, optdict), ) 605 helplevel += 1 606 if config_file is None: 607 config_file = self.config_file 608 if config_file is not None: 609 config_file = expanduser(config_file) 610 if config_file and exists(config_file): 611 parser = self.cfgfile_parser 612 parser.read([config_file]) 613 # normalize sections'title 614 for sect, values in parser._sections.items(): 615 if not sect.isupper() and values: 616 parser._sections[sect.upper()] = values 617 elif not self.quiet: 618 msg = 'No config file found, using default configuration' 619 print >> sys.stderr, msg 620 return
621
622 - def input_config(self, onlysection=None, inputlevel=0, stream=None):
623 """interactively get configuration values by asking to the user and generate 624 a configuration file 625 """ 626 if onlysection is not None: 627 onlysection = onlysection.upper() 628 for provider in self.options_providers: 629 for section, option, optdict in provider.all_options(): 630 if onlysection is not None and section != onlysection: 631 continue 632 if not 'type' in optdict: 633 # ignore action without type (callback, store_true...) 634 continue 635 provider.input_option(option, optdict, inputlevel) 636 # now we can generate the configuration file 637 if stream is not None: 638 self.generate_config(stream)
639
640 - def load_config_file(self):
641 """dispatch values previously read from a configuration file to each 642 options provider) 643 """ 644 parser = self.cfgfile_parser 645 for provider in self.options_providers: 646 for section, option, optdict in provider.all_options(): 647 try: 648 value = parser.get(section, option) 649 provider.set_option(option, value, optdict=optdict) 650 except (NoSectionError, NoOptionError), ex: 651 continue
652
653 - def load_configuration(self, **kwargs):
654 """override configuration according to given parameters 655 """ 656 for opt, opt_value in kwargs.items(): 657 opt = opt.replace('_', '-') 658 provider = self._all_options[opt] 659 provider.set_option(opt, opt_value)
660
661 - def load_command_line_configuration(self, args=None):
662 """override configuration according to command line parameters 663 664 return additional arguments 665 """ 666 self._monkeypatch_expand_default() 667 try: 668 if args is None: 669 args = sys.argv[1:] 670 else: 671 args = list(args) 672 (options, args) = self.cmdline_parser.parse_args(args=args) 673 for provider in self._nocallback_options.keys(): 674 config = provider.config 675 for attr in config.__dict__.keys(): 676 value = getattr(options, attr, None) 677 if value is None: 678 continue 679 setattr(config, attr, value) 680 return args 681 finally: 682 self._unmonkeypatch_expand_default()
683 684 685 # help methods ############################################################ 686
687 - def add_help_section(self, title, description, level=0):
688 """add a dummy option section for help purpose """ 689 group = optparse.OptionGroup(self.cmdline_parser, 690 title=title.capitalize(), 691 description=description) 692 group.level = level 693 self._maxlevel = max(self._maxlevel, level) 694 self.cmdline_parser.add_option_group(group)
695
696 - def _monkeypatch_expand_default(self):
697 # monkey patch optparse to deal with our default values 698 try: 699 self.__expand_default_backup = optparse.HelpFormatter.expand_default 700 optparse.HelpFormatter.expand_default = expand_default 701 except AttributeError: 702 # python < 2.4: nothing to be done 703 pass
704 - def _unmonkeypatch_expand_default(self):
705 # remove monkey patch 706 if hasattr(optparse.HelpFormatter, 'expand_default'): 707 # unpatch optparse to avoid side effects 708 optparse.HelpFormatter.expand_default = self.__expand_default_backup
709
710 - def help(self, level=0):
711 """return the usage string for available options """ 712 self.cmdline_parser.formatter.output_level = level 713 self._monkeypatch_expand_default() 714 try: 715 return self.cmdline_parser.format_help() 716 finally: 717 self._unmonkeypatch_expand_default()
718 719
720 -class Method(object):
721 """used to ease late binding of default method (so you can define options 722 on the class using default methods on the configuration instance) 723 """
724 - def __init__(self, methname):
725 self.method = methname 726 self._inst = None
727
728 - def bind(self, instance):
729 """bind the method to its instance""" 730 if self._inst is None: 731 self._inst = instance
732
733 - def __call__(self, *args, **kwargs):
734 assert self._inst, 'unbound method' 735 return getattr(self._inst, self.method)(*args, **kwargs)
736 737
738 -class OptionsProviderMixIn(object):
739 """Mixin to provide options to an OptionsManager""" 740 741 # those attributes should be overridden 742 priority = -1 743 name = 'default' 744 options = () 745 level = 0 746
747 - def __init__(self):
748 self.config = optparse.Values() 749 for option in self.options: 750 try: 751 option, optdict = option 752 except ValueError: 753 raise Exception('Bad option: %r' % option) 754 if isinstance(optdict.get('default'), Method): 755 optdict['default'].bind(self) 756 elif isinstance(optdict.get('callback'), Method): 757 optdict['callback'].bind(self) 758 self.load_defaults()
759
760 - def load_defaults(self):
761 """initialize the provider using default values""" 762 for opt, optdict in self.options: 763 action = optdict.get('action') 764 if action != 'callback': 765 # callback action have no default 766 default = self.option_default(opt, optdict) 767 if default is REQUIRED: 768 continue 769 self.set_option(opt, default, action, optdict)
770
771 - def option_default(self, opt, optdict=None):
772 """return the default value for an option""" 773 if optdict is None: 774 optdict = self.get_option_def(opt) 775 default = optdict.get('default') 776 if callable(default): 777 default = default() 778 return default
779
780 - def option_name(self, opt, optdict=None):
781 """get the config attribute corresponding to opt 782 """ 783 if optdict is None: 784 optdict = self.get_option_def(opt) 785 return optdict.get('dest', opt.replace('-', '_'))
786
787 - def option_value(self, opt):
788 """get the current value for the given option""" 789 return getattr(self.config, self.option_name(opt), None)
790
791 - def set_option(self, opt, value, action=None, optdict=None):
792 """method called to set an option (registered in the options list) 793 """ 794 # print "************ setting option", opt," to value", value 795 if optdict is None: 796 optdict = self.get_option_def(opt) 797 if value is not None: 798 value = convert(value, optdict, opt) 799 if action is None: 800 action = optdict.get('action', 'store') 801 if optdict.get('type') == 'named': # XXX need specific handling 802 optname = self.option_name(opt, optdict) 803 currentvalue = getattr(self.config, optname, None) 804 if currentvalue: 805 currentvalue.update(value) 806 value = currentvalue 807 if action == 'store': 808 setattr(self.config, self.option_name(opt, optdict), value) 809 elif action in ('store_true', 'count'): 810 setattr(self.config, self.option_name(opt, optdict), 0) 811 elif action == 'store_false': 812 setattr(self.config, self.option_name(opt, optdict), 1) 813 elif action == 'append': 814 opt = self.option_name(opt, optdict) 815 _list = getattr(self.config, opt, None) 816 if _list is None: 817 if isinstance(value, (list, tuple)): 818 _list = value 819 elif value is not None: 820 _list = [] 821 _list.append(value) 822 setattr(self.config, opt, _list) 823 elif isinstance(_list, tuple): 824 setattr(self.config, opt, _list + (value,)) 825 else: 826 _list.append(value) 827 elif action == 'callback': 828 optdict['callback'](None, opt, value, None) 829 else: 830 raise UnsupportedAction(action)
831
832 - def input_option(self, option, optdict, inputlevel=99):
833 default = self.option_default(option, optdict) 834 if default is REQUIRED: 835 defaultstr = '(required): ' 836 elif optdict.get('level', 0) > inputlevel: 837 self.set_option(option, default, optdict=optdict) 838 return 839 elif optdict['type'] == 'password' or default is None: 840 defaultstr = ': ' 841 else: 842 defaultstr = '(default: %s): ' % format_option_value(optdict, default) 843 print ':%s:' % option 844 print optdict.get('help') or option 845 inputfunc = INPUT_FUNCTIONS[optdict['type']] 846 value = inputfunc(optdict, defaultstr) 847 while default is REQUIRED and not value: 848 print 'please specify a value' 849 value = inputfunc(optdict, '%s: ' % option) 850 if value is None and default is not None: 851 value = default 852 self.set_option(option, value, optdict=optdict)
853
854 - def get_option_def(self, opt):
855 """return the dictionary defining an option given it's name""" 856 assert self.options 857 for option in self.options: 858 if option[0] == opt: 859 return option[1] 860 raise OptionError('no such option %s in section %r' 861 % (opt, self.name), opt)
862 863
864 - def all_options(self):
865 """return an iterator on available options for this provider 866 option are actually described by a 3-uple: 867 (section, option name, option dictionary) 868 """ 869 for section, options in self.options_by_section(): 870 if section is None: 871 if self.name is None: 872 continue 873 section = self.name.upper() 874 for option, optiondict, value in options: 875 yield section, option, optiondict
876
877 - def options_by_section(self):
878 """return an iterator on options grouped by section 879 880 (section, [list of (optname, optdict, optvalue)]) 881 """ 882 sections = {} 883 for optname, optdict in self.options: 884 sections.setdefault(optdict.get('group'), []).append( 885 (optname, optdict, self.option_value(optname))) 886 if None in sections: 887 yield None, sections.pop(None) 888 for section, options in sections.items(): 889 yield section.upper(), options
890
891 - def options_and_values(self, options=None):
892 if options is None: 893 options = self.options 894 for optname, optdict in options: 895 yield (optname, optdict, self.option_value(optname))
896 897
898 -class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn):
899 """basic mixin for simple configurations which don't need the 900 manager / providers model 901 """
902 - def __init__(self, *args, **kwargs):
903 if not args: 904 kwargs.setdefault('usage', '') 905 kwargs.setdefault('quiet', 1) 906 OptionsManagerMixIn.__init__(self, *args, **kwargs) 907 OptionsProviderMixIn.__init__(self) 908 if not getattr(self, 'option_groups', None): 909 self.option_groups = [] 910 for option, optdict in self.options: 911 try: 912 gdef = (optdict['group'].upper(), '') 913 except KeyError: 914 continue 915 if not gdef in self.option_groups: 916 self.option_groups.append(gdef) 917 self.register_options_provider(self, own_group=0)
918
919 - def register_options(self, options):
920 """add some options to the configuration""" 921 options_by_group = {} 922 for optname, optdict in options: 923 options_by_group.setdefault(optdict.get('group', self.name.upper()), []).append((optname, optdict)) 924 for group, options in options_by_group.items(): 925 self.add_option_group(group, None, options, self) 926 self.options += tuple(options)
927
928 - def load_defaults(self):
930
931 - def __iter__(self):
932 return iter(self.config.__dict__.iteritems())
933
934 - def __getitem__(self, key):
935 try: 936 return getattr(self.config, self.option_name(key)) 937 except (optparse.OptionValueError, AttributeError): 938 raise KeyError(key)
939
940 - def __setitem__(self, key, value):
941 self.set_option(key, value)
942
943 - def get(self, key, default=None):
944 try: 945 return getattr(self.config, self.option_name(key)) 946 except (OptionError, AttributeError): 947 return default
948 949
950 -class Configuration(ConfigurationMixIn):
951 """class for simple configurations which don't need the 952 manager / providers model and prefer delegation to inheritance 953 954 configuration values are accessible through a dict like interface 955 """ 956
957 - def __init__(self, config_file=None, options=None, name=None, 958 usage=None, doc=None, version=None):
959 if options is not None: 960 self.options = options 961 if name is not None: 962 self.name = name 963 if doc is not None: 964 self.__doc__ = doc 965 super(Configuration, self).__init__(config_file=config_file, usage=usage, version=version)
966 967
968 -class OptionsManager2ConfigurationAdapter(object):
969 """Adapt an option manager to behave like a 970 `logilab.common.configuration.Configuration` instance 971 """
972 - def __init__(self, provider):
973 self.config = provider
974
975 - def __getattr__(self, key):
976 return getattr(self.config, key)
977
978 - def __getitem__(self, key):
979 provider = self.config._all_options[key] 980 try: 981 return getattr(provider.config, provider.option_name(key)) 982 except AttributeError: 983 raise KeyError(key)
984
985 - def __setitem__(self, key, value):
986 self.config.global_set_option(self.config.option_name(key), value)
987
988 - def get(self, key, default=None):
989 provider = self.config._all_options[key] 990 try: 991 return getattr(provider.config, provider.option_name(key)) 992 except AttributeError: 993 return default
994 995
996 -def read_old_config(newconfig, changes, configfile):
997 """initialize newconfig from a deprecated configuration file 998 999 possible changes: 1000 * ('renamed', oldname, newname) 1001 * ('moved', option, oldgroup, newgroup) 1002 * ('typechanged', option, oldtype, newvalue) 1003 """ 1004 # build an index of changes 1005 changesindex = {} 1006 for action in changes: 1007 if action[0] == 'moved': 1008 option, oldgroup, newgroup = action[1:] 1009 changesindex.setdefault(option, []).append((action[0], oldgroup, newgroup)) 1010 continue 1011 if action[0] == 'renamed': 1012 oldname, newname = action[1:] 1013 changesindex.setdefault(newname, []).append((action[0], oldname)) 1014 continue 1015 if action[0] == 'typechanged': 1016 option, oldtype, newvalue = action[1:] 1017 changesindex.setdefault(option, []).append((action[0], oldtype, newvalue)) 1018 continue 1019 if action[1] in ('added', 'removed'): 1020 continue # nothing to do here 1021 raise Exception('unknown change %s' % action[0]) 1022 # build a config object able to read the old config 1023 options = [] 1024 for optname, optdef in newconfig.options: 1025 for action in changesindex.pop(optname, ()): 1026 if action[0] == 'moved': 1027 oldgroup, newgroup = action[1:] 1028 optdef = optdef.copy() 1029 optdef['group'] = oldgroup 1030 elif action[0] == 'renamed': 1031 optname = action[1] 1032 elif action[0] == 'typechanged': 1033 oldtype = action[1] 1034 optdef = optdef.copy() 1035 optdef['type'] = oldtype 1036 options.append((optname, optdef)) 1037 if changesindex: 1038 raise Exception('unapplied changes: %s' % changesindex) 1039 oldconfig = Configuration(options=options, name=newconfig.name) 1040 # read the old config 1041 oldconfig.load_file_configuration(configfile) 1042 # apply values reverting changes 1043 changes.reverse() 1044 done = set() 1045 for action in changes: 1046 if action[0] == 'renamed': 1047 oldname, newname = action[1:] 1048 newconfig[newname] = oldconfig[oldname] 1049 done.add(newname) 1050 elif action[0] == 'typechanged': 1051 optname, oldtype, newvalue = action[1:] 1052 newconfig[optname] = newvalue 1053 done.add(optname) 1054 for optname, optdef in newconfig.options: 1055 if optdef.get('type') and not optname in done: 1056 newconfig.set_option(optname, oldconfig[optname], optdict=optdef)
1057 1058
1059 -def merge_options(options):
1060 """preprocess options to remove duplicate""" 1061 alloptions = {} 1062 options = list(options) 1063 for i in range(len(options)-1, -1, -1): 1064 optname, optdict = options[i] 1065 if optname in alloptions: 1066 options.pop(i) 1067 alloptions[optname].update(optdict) 1068 else: 1069 alloptions[optname] = optdict 1070 return tuple(options)
1071