Package logilab-common-0 :: Package 39 :: Package 0 :: Module configuration
[frames] | no frames]

Source Code for Module logilab-common-0.39.0.configuration

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