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

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