Module configobj
[hide private]
[frames] | no frames]

Source Code for Module configobj

   1  # configobj.py 
   2  # A config file reader/writer that supports nested sections in config files. 
   3  # Copyright (C) 2005-2010 Michael Foord, Nicola Larosa 
   4  # E-mail: fuzzyman AT voidspace DOT org DOT uk 
   5  #         nico AT tekNico DOT net 
   6   
   7  # ConfigObj 4 
   8  # http://www.voidspace.org.uk/python/configobj.html 
   9   
  10  # Released subject to the BSD License 
  11  # Please see http://www.voidspace.org.uk/python/license.shtml 
  12   
  13  # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml 
  14  # For information about bugfixes, updates and support, please join the 
  15  # ConfigObj mailing list: 
  16  # http://lists.sourceforge.net/lists/listinfo/configobj-develop 
  17  # Comments, suggestions and bug reports welcome. 
  18   
  19  from __future__ import generators 
  20   
  21  import os 
  22  import re 
  23  import sys 
  24   
  25  from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE 
  26   
  27   
  28  # imported lazily to avoid startup performance hit if it isn't used 
  29  compiler = None 
  30   
  31  # A dictionary mapping BOM to 
  32  # the encoding to decode with, and what to set the 
  33  # encoding attribute to. 
  34  BOMS = { 
  35      BOM_UTF8: ('utf_8', None), 
  36      BOM_UTF16_BE: ('utf16_be', 'utf_16'), 
  37      BOM_UTF16_LE: ('utf16_le', 'utf_16'), 
  38      BOM_UTF16: ('utf_16', 'utf_16'), 
  39      } 
  40  # All legal variants of the BOM codecs. 
  41  # TODO: the list of aliases is not meant to be exhaustive, is there a 
  42  #   better way ? 
  43  BOM_LIST = { 
  44      'utf_16': 'utf_16', 
  45      'u16': 'utf_16', 
  46      'utf16': 'utf_16', 
  47      'utf-16': 'utf_16', 
  48      'utf16_be': 'utf16_be', 
  49      'utf_16_be': 'utf16_be', 
  50      'utf-16be': 'utf16_be', 
  51      'utf16_le': 'utf16_le', 
  52      'utf_16_le': 'utf16_le', 
  53      'utf-16le': 'utf16_le', 
  54      'utf_8': 'utf_8', 
  55      'u8': 'utf_8', 
  56      'utf': 'utf_8', 
  57      'utf8': 'utf_8', 
  58      'utf-8': 'utf_8', 
  59      } 
  60   
  61  # Map of encodings to the BOM to write. 
  62  BOM_SET = { 
  63      'utf_8': BOM_UTF8, 
  64      'utf_16': BOM_UTF16, 
  65      'utf16_be': BOM_UTF16_BE, 
  66      'utf16_le': BOM_UTF16_LE, 
  67      None: BOM_UTF8 
  68      } 
  69   
  70   
71 -def match_utf8(encoding):
72 return BOM_LIST.get(encoding.lower()) == 'utf_8'
73 74 75 # Quote strings used for writing values 76 squot = "'%s'" 77 dquot = '"%s"' 78 noquot = "%s" 79 wspace_plus = ' \r\n\v\t\'"' 80 tsquot = '"""%s"""' 81 tdquot = "'''%s'''" 82 83 # Sentinel for use in getattr calls to replace hasattr 84 MISSING = object() 85 86 __version__ = '4.7.2' 87 88 try: 89 any 90 except NameError:
91 - def any(iterable):
92 for entry in iterable: 93 if entry: 94 return True 95 return False
96 97 98 __all__ = ( 99 '__version__', 100 'DEFAULT_INDENT_TYPE', 101 'DEFAULT_INTERPOLATION', 102 'ConfigObjError', 103 'NestingError', 104 'ParseError', 105 'DuplicateError', 106 'ConfigspecError', 107 'ConfigObj', 108 'SimpleVal', 109 'InterpolationError', 110 'InterpolationLoopError', 111 'MissingInterpolationOption', 112 'RepeatSectionError', 113 'ReloadError', 114 'UnreprError', 115 'UnknownType', 116 'flatten_errors', 117 'get_extra_values' 118 ) 119 120 DEFAULT_INTERPOLATION = 'configparser' 121 DEFAULT_INDENT_TYPE = ' ' 122 MAX_INTERPOL_DEPTH = 10 123 124 OPTION_DEFAULTS = { 125 'interpolation': True, 126 'raise_errors': False, 127 'list_values': True, 128 'create_empty': False, 129 'file_error': False, 130 'configspec': None, 131 'stringify': True, 132 # option may be set to one of ('', ' ', '\t') 133 'indent_type': None, 134 'encoding': None, 135 'default_encoding': None, 136 'unrepr': False, 137 'write_empty_values': False, 138 } 139 140 141
142 -def getObj(s):
143 global compiler 144 if compiler is None: 145 import compiler 146 s = "a=" + s 147 p = compiler.parse(s) 148 return p.getChildren()[1].getChildren()[0].getChildren()[1]
149 150
151 -class UnknownType(Exception):
152 pass
153 154
155 -class Builder(object):
156
157 - def build(self, o):
158 m = getattr(self, 'build_' + o.__class__.__name__, None) 159 if m is None: 160 raise UnknownType(o.__class__.__name__) 161 return m(o)
162
163 - def build_List(self, o):
164 return map(self.build, o.getChildren())
165
166 - def build_Const(self, o):
167 return o.value
168
169 - def build_Dict(self, o):
170 d = {} 171 i = iter(map(self.build, o.getChildren())) 172 for el in i: 173 d[el] = i.next() 174 return d
175
176 - def build_Tuple(self, o):
177 return tuple(self.build_List(o))
178
179 - def build_Name(self, o):
180 if o.name == 'None': 181 return None 182 if o.name == 'True': 183 return True 184 if o.name == 'False': 185 return False 186 187 # An undefined Name 188 raise UnknownType('Undefined Name')
189
190 - def build_Add(self, o):
191 real, imag = map(self.build_Const, o.getChildren()) 192 try: 193 real = float(real) 194 except TypeError: 195 raise UnknownType('Add') 196 if not isinstance(imag, complex) or imag.real != 0.0: 197 raise UnknownType('Add') 198 return real+imag
199
200 - def build_Getattr(self, o):
201 parent = self.build(o.expr) 202 return getattr(parent, o.attrname)
203
204 - def build_UnarySub(self, o):
205 return -self.build_Const(o.getChildren()[0])
206
207 - def build_UnaryAdd(self, o):
208 return self.build_Const(o.getChildren()[0])
209 210 211 _builder = Builder() 212 213
214 -def unrepr(s):
215 if not s: 216 return s 217 return _builder.build(getObj(s))
218 219 220
221 -class ConfigObjError(SyntaxError):
222 """ 223 This is the base class for all errors that ConfigObj raises. 224 It is a subclass of SyntaxError. 225 """
226 - def __init__(self, message='', line_number=None, line=''):
227 self.line = line 228 self.line_number = line_number 229 SyntaxError.__init__(self, message)
230 231
232 -class NestingError(ConfigObjError):
233 """ 234 This error indicates a level of nesting that doesn't match. 235 """
236 237
238 -class ParseError(ConfigObjError):
239 """ 240 This error indicates that a line is badly written. 241 It is neither a valid ``key = value`` line, 242 nor a valid section marker line. 243 """
244 245
246 -class ReloadError(IOError):
247 """ 248 A 'reload' operation failed. 249 This exception is a subclass of ``IOError``. 250 """
251 - def __init__(self):
252 IOError.__init__(self, 'reload failed, filename is not set.')
253 254
255 -class DuplicateError(ConfigObjError):
256 """ 257 The keyword or section specified already exists. 258 """
259 260
261 -class ConfigspecError(ConfigObjError):
262 """ 263 An error occured whilst parsing a configspec. 264 """
265 266
267 -class InterpolationError(ConfigObjError):
268 """Base class for the two interpolation errors."""
269 270
271 -class InterpolationLoopError(InterpolationError):
272 """Maximum interpolation depth exceeded in string interpolation.""" 273
274 - def __init__(self, option):
275 InterpolationError.__init__( 276 self, 277 'interpolation loop detected in value "%s".' % option)
278 279
280 -class RepeatSectionError(ConfigObjError):
281 """ 282 This error indicates additional sections in a section with a 283 ``__many__`` (repeated) section. 284 """
285 286
287 -class MissingInterpolationOption(InterpolationError):
288 """A value specified for interpolation was missing."""
289 - def __init__(self, option):
290 msg = 'missing option "%s" in interpolation.' % option 291 InterpolationError.__init__(self, msg)
292 293
294 -class UnreprError(ConfigObjError):
295 """An error parsing in unrepr mode."""
296 297 298
299 -class InterpolationEngine(object):
300 """ 301 A helper class to help perform string interpolation. 302 303 This class is an abstract base class; its descendants perform 304 the actual work. 305 """ 306 307 # compiled regexp to use in self.interpolate() 308 _KEYCRE = re.compile(r"%\(([^)]*)\)s") 309 _cookie = '%' 310
311 - def __init__(self, section):
312 # the Section instance that "owns" this engine 313 self.section = section
314 315
316 - def interpolate(self, key, value):
317 # short-cut 318 if not self._cookie in value: 319 return value 320 321 def recursive_interpolate(key, value, section, backtrail): 322 """The function that does the actual work. 323 324 ``value``: the string we're trying to interpolate. 325 ``section``: the section in which that string was found 326 ``backtrail``: a dict to keep track of where we've been, 327 to detect and prevent infinite recursion loops 328 329 This is similar to a depth-first-search algorithm. 330 """ 331 # Have we been here already? 332 if (key, section.name) in backtrail: 333 # Yes - infinite loop detected 334 raise InterpolationLoopError(key) 335 # Place a marker on our backtrail so we won't come back here again 336 backtrail[(key, section.name)] = 1 337 338 # Now start the actual work 339 match = self._KEYCRE.search(value) 340 while match: 341 # The actual parsing of the match is implementation-dependent, 342 # so delegate to our helper function 343 k, v, s = self._parse_match(match) 344 if k is None: 345 # That's the signal that no further interpolation is needed 346 replacement = v 347 else: 348 # Further interpolation may be needed to obtain final value 349 replacement = recursive_interpolate(k, v, s, backtrail) 350 # Replace the matched string with its final value 351 start, end = match.span() 352 value = ''.join((value[:start], replacement, value[end:])) 353 new_search_start = start + len(replacement) 354 # Pick up the next interpolation key, if any, for next time 355 # through the while loop 356 match = self._KEYCRE.search(value, new_search_start) 357 358 # Now safe to come back here again; remove marker from backtrail 359 del backtrail[(key, section.name)] 360 361 return value
362 363 # Back in interpolate(), all we have to do is kick off the recursive 364 # function with appropriate starting values 365 value = recursive_interpolate(key, value, self.section, {}) 366 return value
367 368
369 - def _fetch(self, key):
370 """Helper function to fetch values from owning section. 371 372 Returns a 2-tuple: the value, and the section where it was found. 373 """ 374 # switch off interpolation before we try and fetch anything ! 375 save_interp = self.section.main.interpolation 376 self.section.main.interpolation = False 377 378 # Start at section that "owns" this InterpolationEngine 379 current_section = self.section 380 while True: 381 # try the current section first 382 val = current_section.get(key) 383 if val is not None and not isinstance(val, Section): 384 break 385 # try "DEFAULT" next 386 val = current_section.get('DEFAULT', {}).get(key) 387 if val is not None and not isinstance(val, Section): 388 break 389 # move up to parent and try again 390 # top-level's parent is itself 391 if current_section.parent is current_section: 392 # reached top level, time to give up 393 break 394 current_section = current_section.parent 395 396 # restore interpolation to previous value before returning 397 self.section.main.interpolation = save_interp 398 if val is None: 399 raise MissingInterpolationOption(key) 400 return val, current_section
401 402
403 - def _parse_match(self, match):
404 """Implementation-dependent helper function. 405 406 Will be passed a match object corresponding to the interpolation 407 key we just found (e.g., "%(foo)s" or "$foo"). Should look up that 408 key in the appropriate config file section (using the ``_fetch()`` 409 helper function) and return a 3-tuple: (key, value, section) 410 411 ``key`` is the name of the key we're looking for 412 ``value`` is the value found for that key 413 ``section`` is a reference to the section where it was found 414 415 ``key`` and ``section`` should be None if no further 416 interpolation should be performed on the resulting value 417 (e.g., if we interpolated "$$" and returned "$"). 418 """ 419 raise NotImplementedError()
420 421 422
423 -class ConfigParserInterpolation(InterpolationEngine):
424 """Behaves like ConfigParser.""" 425 _cookie = '%' 426 _KEYCRE = re.compile(r"%\(([^)]*)\)s") 427
428 - def _parse_match(self, match):
429 key = match.group(1) 430 value, section = self._fetch(key) 431 return key, value, section
432 433 434
435 -class TemplateInterpolation(InterpolationEngine):
436 """Behaves like string.Template.""" 437 _cookie = '$' 438 _delimiter = '$' 439 _KEYCRE = re.compile(r""" 440 \$(?: 441 (?P<escaped>\$) | # Two $ signs 442 (?P<named>[_a-z][_a-z0-9]*) | # $name format 443 {(?P<braced>[^}]*)} # ${name} format 444 ) 445 """, re.IGNORECASE | re.VERBOSE) 446
447 - def _parse_match(self, match):
448 # Valid name (in or out of braces): fetch value from section 449 key = match.group('named') or match.group('braced') 450 if key is not None: 451 value, section = self._fetch(key) 452 return key, value, section 453 # Escaped delimiter (e.g., $$): return single delimiter 454 if match.group('escaped') is not None: 455 # Return None for key and section to indicate it's time to stop 456 return None, self._delimiter, None 457 # Anything else: ignore completely, just return it unchanged 458 return None, match.group(), None
459 460 461 interpolation_engines = { 462 'configparser': ConfigParserInterpolation, 463 'template': TemplateInterpolation, 464 } 465 466
467 -def __newobj__(cls, *args):
468 # Hack for pickle 469 return cls.__new__(cls, *args)
470
471 -class Section(dict):
472 """ 473 A dictionary-like object that represents a section in a config file. 474 475 It does string interpolation if the 'interpolation' attribute 476 of the 'main' object is set to True. 477 478 Interpolation is tried first from this object, then from the 'DEFAULT' 479 section of this object, next from the parent and its 'DEFAULT' section, 480 and so on until the main object is reached. 481 482 A Section will behave like an ordered dictionary - following the 483 order of the ``scalars`` and ``sections`` attributes. 484 You can use this to change the order of members. 485 486 Iteration follows the order: scalars, then sections. 487 """ 488 489
490 - def __setstate__(self, state):
491 dict.update(self, state[0]) 492 self.__dict__.update(state[1])
493
494 - def __reduce__(self):
495 state = (dict(self), self.__dict__) 496 return (__newobj__, (self.__class__,), state)
497 498
499 - def __init__(self, parent, depth, main, indict=None, name=None):
500 """ 501 * parent is the section above 502 * depth is the depth level of this section 503 * main is the main ConfigObj 504 * indict is a dictionary to initialise the section with 505 """ 506 if indict is None: 507 indict = {} 508 dict.__init__(self) 509 # used for nesting level *and* interpolation 510 self.parent = parent 511 # used for the interpolation attribute 512 self.main = main 513 # level of nesting depth of this Section 514 self.depth = depth 515 # purely for information 516 self.name = name 517 # 518 self._initialise() 519 # we do this explicitly so that __setitem__ is used properly 520 # (rather than just passing to ``dict.__init__``) 521 for entry, value in indict.iteritems(): 522 self[entry] = value
523 524
525 - def _initialise(self):
526 # the sequence of scalar values in this Section 527 self.scalars = [] 528 # the sequence of sections in this Section 529 self.sections = [] 530 # for comments :-) 531 self.comments = {} 532 self.inline_comments = {} 533 # the configspec 534 self.configspec = None 535 # for defaults 536 self.defaults = [] 537 self.default_values = {} 538 self.extra_values = [] 539 self._created = False
540 541
542 - def _interpolate(self, key, value):
543 try: 544 # do we already have an interpolation engine? 545 engine = self._interpolation_engine 546 except AttributeError: 547 # not yet: first time running _interpolate(), so pick the engine 548 name = self.main.interpolation 549 if name == True: # note that "if name:" would be incorrect here 550 # backwards-compatibility: interpolation=True means use default 551 name = DEFAULT_INTERPOLATION 552 name = name.lower() # so that "Template", "template", etc. all work 553 class_ = interpolation_engines.get(name, None) 554 if class_ is None: 555 # invalid value for self.main.interpolation 556 self.main.interpolation = False 557 return value 558 else: 559 # save reference to engine so we don't have to do this again 560 engine = self._interpolation_engine = class_(self) 561 # let the engine do the actual work 562 return engine.interpolate(key, value)
563 564
565 - def __getitem__(self, key):
566 """Fetch the item and do string interpolation.""" 567 val = dict.__getitem__(self, key) 568 if self.main.interpolation: 569 if isinstance(val, basestring): 570 return self._interpolate(key, val) 571 if isinstance(val, list): 572 def _check(entry): 573 if isinstance(entry, basestring): 574 return self._interpolate(key, entry) 575 return entry
576 new = [_check(entry) for entry in val] 577 if new != val: 578 return new 579 return val
580 581
582 - def __setitem__(self, key, value, unrepr=False):
583 """ 584 Correctly set a value. 585 586 Making dictionary values Section instances. 587 (We have to special case 'Section' instances - which are also dicts) 588 589 Keys must be strings. 590 Values need only be strings (or lists of strings) if 591 ``main.stringify`` is set. 592 593 ``unrepr`` must be set when setting a value to a dictionary, without 594 creating a new sub-section. 595 """ 596 if not isinstance(key, basestring): 597 raise ValueError('The key "%s" is not a string.' % key) 598 599 # add the comment 600 if key not in self.comments: 601 self.comments[key] = [] 602 self.inline_comments[key] = '' 603 # remove the entry from defaults 604 if key in self.defaults: 605 self.defaults.remove(key) 606 # 607 if isinstance(value, Section): 608 if key not in self: 609 self.sections.append(key) 610 dict.__setitem__(self, key, value) 611 elif isinstance(value, dict) and not unrepr: 612 # First create the new depth level, 613 # then create the section 614 if key not in self: 615 self.sections.append(key) 616 new_depth = self.depth + 1 617 dict.__setitem__( 618 self, 619 key, 620 Section( 621 self, 622 new_depth, 623 self.main, 624 indict=value, 625 name=key)) 626 else: 627 if key not in self: 628 self.scalars.append(key) 629 if not self.main.stringify: 630 if isinstance(value, basestring): 631 pass 632 elif isinstance(value, (list, tuple)): 633 for entry in value: 634 if not isinstance(entry, basestring): 635 raise TypeError('Value is not a string "%s".' % entry) 636 else: 637 raise TypeError('Value is not a string "%s".' % value) 638 dict.__setitem__(self, key, value)
639 640
641 - def __delitem__(self, key):
642 """Remove items from the sequence when deleting.""" 643 dict. __delitem__(self, key) 644 if key in self.scalars: 645 self.scalars.remove(key) 646 else: 647 self.sections.remove(key) 648 del self.comments[key] 649 del self.inline_comments[key]
650 651
652 - def get(self, key, default=None):
653 """A version of ``get`` that doesn't bypass string interpolation.""" 654 try: 655 return self[key] 656 except KeyError: 657 return default
658 659
660 - def update(self, indict):
661 """ 662 A version of update that uses our ``__setitem__``. 663 """ 664 for entry in indict: 665 self[entry] = indict[entry]
666 667
668 - def pop(self, key, default=MISSING):
669 """ 670 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value. 671 If key is not found, d is returned if given, otherwise KeyError is raised' 672 """ 673 try: 674 val = self[key] 675 except KeyError: 676 if default is MISSING: 677 raise 678 val = default 679 else: 680 del self[key] 681 return val
682 683
684 - def popitem(self):
685 """Pops the first (key,val)""" 686 sequence = (self.scalars + self.sections) 687 if not sequence: 688 raise KeyError(": 'popitem(): dictionary is empty'") 689 key = sequence[0] 690 val = self[key] 691 del self[key] 692 return key, val
693 694
695 - def clear(self):
696 """ 697 A version of clear that also affects scalars/sections 698 Also clears comments and configspec. 699 700 Leaves other attributes alone : 701 depth/main/parent are not affected 702 """ 703 dict.clear(self) 704 self.scalars = [] 705 self.sections = [] 706 self.comments = {} 707 self.inline_comments = {} 708 self.configspec = None 709 self.defaults = [] 710 self.extra_values = []
711 712
713 - def setdefault(self, key, default=None):
714 """A version of setdefault that sets sequence if appropriate.""" 715 try: 716 return self[key] 717 except KeyError: 718 self[key] = default 719 return self[key]
720 721
722 - def items(self):
723 """D.items() -> list of D's (key, value) pairs, as 2-tuples""" 724 return zip((self.scalars + self.sections), self.values())
725 726
727 - def keys(self):
728 """D.keys() -> list of D's keys""" 729 return (self.scalars + self.sections)
730 731
732 - def values(self):
733 """D.values() -> list of D's values""" 734 return [self[key] for key in (self.scalars + self.sections)]
735 736
737 - def iteritems(self):
738 """D.iteritems() -> an iterator over the (key, value) items of D""" 739 return iter(self.items())
740 741
742 - def iterkeys(self):
743 """D.iterkeys() -> an iterator over the keys of D""" 744 return iter((self.scalars + self.sections))
745 746 __iter__ = iterkeys 747 748
749 - def itervalues(self):
750 """D.itervalues() -> an iterator over the values of D""" 751 return iter(self.values())
752 753
754 - def __repr__(self):
755 """x.__repr__() <==> repr(x)""" 756 def _getval(key): 757 try: 758 return self[key] 759 except MissingInterpolationOption: 760 return dict.__getitem__(self, key)
761 return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) 762 for key in (self.scalars + self.sections)]) 763 764 __str__ = __repr__ 765 __str__.__doc__ = "x.__str__() <==> str(x)" 766 767 768 # Extra methods - not in a normal dictionary 769
770 - def dict(self):
771 """ 772 Return a deepcopy of self as a dictionary. 773 774 All members that are ``Section`` instances are recursively turned to 775 ordinary dictionaries - by calling their ``dict`` method. 776 777 >>> n = a.dict() 778 >>> n == a 779 1 780 >>> n is a 781 0 782 """ 783 newdict = {} 784 for entry in self: 785 this_entry = self[entry] 786 if isinstance(this_entry, Section): 787 this_entry = this_entry.dict() 788 elif isinstance(this_entry, list): 789 # create a copy rather than a reference 790 this_entry = list(this_entry) 791 elif isinstance(this_entry, tuple): 792 # create a copy rather than a reference 793 this_entry = tuple(this_entry) 794 newdict[entry] = this_entry 795 return newdict
796 797
798 - def merge(self, indict):
799 """ 800 A recursive update - useful for merging config files. 801 802 >>> a = '''[section1] 803 ... option1 = True 804 ... [[subsection]] 805 ... more_options = False 806 ... # end of file'''.splitlines() 807 >>> b = '''# File is user.ini 808 ... [section1] 809 ... option1 = False 810 ... # end of file'''.splitlines() 811 >>> c1 = ConfigObj(b) 812 >>> c2 = ConfigObj(a) 813 >>> c2.merge(c1) 814 >>> c2 815 ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) 816 """ 817 for key, val in indict.items(): 818 if (key in self and isinstance(self[key], dict) and 819 isinstance(val, dict)): 820 self[key].merge(val) 821 else: 822 self[key] = val
823 824
825 - def rename(self, oldkey, newkey):
826 """ 827 Change a keyname to another, without changing position in sequence. 828 829 Implemented so that transformations can be made on keys, 830 as well as on values. (used by encode and decode) 831 832 Also renames comments. 833 """ 834 if oldkey in self.scalars: 835 the_list = self.scalars 836 elif oldkey in self.sections: 837 the_list = self.sections 838 else: 839 raise KeyError('Key "%s" not found.' % oldkey) 840 pos = the_list.index(oldkey) 841 # 842 val = self[oldkey] 843 dict.__delitem__(self, oldkey) 844 dict.__setitem__(self, newkey, val) 845 the_list.remove(oldkey) 846 the_list.insert(pos, newkey) 847 comm = self.comments[oldkey] 848 inline_comment = self.inline_comments[oldkey] 849 del self.comments[oldkey] 850 del self.inline_comments[oldkey] 851 self.comments[newkey] = comm 852 self.inline_comments[newkey] = inline_comment
853 854
855 - def walk(self, function, raise_errors=True, 856 call_on_sections=False, **keywargs):
857 """ 858 Walk every member and call a function on the keyword and value. 859 860 Return a dictionary of the return values 861 862 If the function raises an exception, raise the errror 863 unless ``raise_errors=False``, in which case set the return value to 864 ``False``. 865 866 Any unrecognised keyword arguments you pass to walk, will be pased on 867 to the function you pass in. 868 869 Note: if ``call_on_sections`` is ``True`` then - on encountering a 870 subsection, *first* the function is called for the *whole* subsection, 871 and then recurses into it's members. This means your function must be 872 able to handle strings, dictionaries and lists. This allows you 873 to change the key of subsections as well as for ordinary members. The 874 return value when called on the whole subsection has to be discarded. 875 876 See the encode and decode methods for examples, including functions. 877 878 .. admonition:: caution 879 880 You can use ``walk`` to transform the names of members of a section 881 but you mustn't add or delete members. 882 883 >>> config = '''[XXXXsection] 884 ... XXXXkey = XXXXvalue'''.splitlines() 885 >>> cfg = ConfigObj(config) 886 >>> cfg 887 ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) 888 >>> def transform(section, key): 889 ... val = section[key] 890 ... newkey = key.replace('XXXX', 'CLIENT1') 891 ... section.rename(key, newkey) 892 ... if isinstance(val, (tuple, list, dict)): 893 ... pass 894 ... else: 895 ... val = val.replace('XXXX', 'CLIENT1') 896 ... section[newkey] = val 897 >>> cfg.walk(transform, call_on_sections=True) 898 {'CLIENT1section': {'CLIENT1key': None}} 899 >>> cfg 900 ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) 901 """ 902 out = {} 903 # scalars first 904 for i in range(len(self.scalars)): 905 entry = self.scalars[i] 906 try: 907 val = function(self, entry, **keywargs) 908 # bound again in case name has changed 909 entry = self.scalars[i] 910 out[entry] = val 911 except Exception: 912 if raise_errors: 913 raise 914 else: 915 entry = self.scalars[i] 916 out[entry] = False 917 # then sections 918 for i in range(len(self.sections)): 919 entry = self.sections[i] 920 if call_on_sections: 921 try: 922 function(self, entry, **keywargs) 923 except Exception: 924 if raise_errors: 925 raise 926 else: 927 entry = self.sections[i] 928 out[entry] = False 929 # bound again in case name has changed 930 entry = self.sections[i] 931 # previous result is discarded 932 out[entry] = self[entry].walk( 933 function, 934 raise_errors=raise_errors, 935 call_on_sections=call_on_sections, 936 **keywargs) 937 return out
938 939
940 - def as_bool(self, key):
941 """ 942 Accepts a key as input. The corresponding value must be a string or 943 the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to 944 retain compatibility with Python 2.2. 945 946 If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns 947 ``True``. 948 949 If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns 950 ``False``. 951 952 ``as_bool`` is not case sensitive. 953 954 Any other input will raise a ``ValueError``. 955 956 >>> a = ConfigObj() 957 >>> a['a'] = 'fish' 958 >>> a.as_bool('a') 959 Traceback (most recent call last): 960 ValueError: Value "fish" is neither True nor False 961 >>> a['b'] = 'True' 962 >>> a.as_bool('b') 963 1 964 >>> a['b'] = 'off' 965 >>> a.as_bool('b') 966 0 967 """ 968 val = self[key] 969 if val == True: 970 return True 971 elif val == False: 972 return False 973 else: 974 try: 975 if not isinstance(val, basestring): 976 # TODO: Why do we raise a KeyError here? 977 raise KeyError() 978 else: 979 return self.main._bools[val.lower()] 980 except KeyError: 981 raise ValueError('Value "%s" is neither True nor False' % val)
982 983
984 - def as_int(self, key):
985 """ 986 A convenience method which coerces the specified value to an integer. 987 988 If the value is an invalid literal for ``int``, a ``ValueError`` will 989 be raised. 990 991 >>> a = ConfigObj() 992 >>> a['a'] = 'fish' 993 >>> a.as_int('a') 994 Traceback (most recent call last): 995 ValueError: invalid literal for int() with base 10: 'fish' 996 >>> a['b'] = '1' 997 >>> a.as_int('b') 998 1 999 >>> a['b'] = '3.2' 1000 >>> a.as_int('b') 1001 Traceback (most recent call last): 1002 ValueError: invalid literal for int() with base 10: '3.2' 1003 """ 1004 return int(self[key])
1005 1006
1007 - def as_float(self, key):
1008 """ 1009 A convenience method which coerces the specified value to a float. 1010 1011 If the value is an invalid literal for ``float``, a ``ValueError`` will 1012 be raised. 1013 """ 1014 return float(self[key])
1015 1016
1017 - def as_list(self, key):
1018 """ 1019 A convenience method which fetches the specified value, guaranteeing 1020 that it is a list. 1021 1022 >>> a = ConfigObj() 1023 >>> a['a'] = 1 1024 >>> a.as_list('a') 1025 [1] 1026 >>> a['a'] = (1,) 1027 >>> a.as_list('a') 1028 [1] 1029 >>> a['a'] = [1] 1030 >>> a.as_list('a') 1031 [1] 1032 """ 1033 result = self[key] 1034 if isinstance(result, (tuple, list)): 1035 return list(result) 1036 return [result]
1037 1038
1039 - def restore_default(self, key):
1040 """ 1041 Restore (and return) default value for the specified key. 1042 1043 This method will only work for a ConfigObj that was created 1044 with a configspec and has been validated. 1045 1046 If there is no default value for this key, ``KeyError`` is raised. 1047 """ 1048 default = self.default_values[key] 1049 dict.__setitem__(self, key, default) 1050 if key not in self.defaults: 1051 self.defaults.append(key) 1052 return default
1053 1054
1055 - def restore_defaults(self):
1056 """ 1057 Recursively restore default values to all members 1058 that have them. 1059 1060 This method will only work for a ConfigObj that was created 1061 with a configspec and has been validated. 1062 1063 It doesn't delete or modify entries without default values. 1064 """ 1065 for key in self.default_values: 1066 self.restore_default(key) 1067 1068 for section in self.sections: 1069 self[section].restore_defaults()
1070 1071
1072 -class ConfigObj(Section):
1073 """An object to read, create, and write config files.""" 1074 1075 _keyword = re.compile(r'''^ # line start 1076 (\s*) # indentation 1077 ( # keyword 1078 (?:".*?")| # double quotes 1079 (?:'.*?')| # single quotes 1080 (?:[^'"=].*?) # no quotes 1081 ) 1082 \s*=\s* # divider 1083 (.*) # value (including list values and comments) 1084 $ # line end 1085 ''', 1086 re.VERBOSE) 1087 1088 _sectionmarker = re.compile(r'''^ 1089 (\s*) # 1: indentation 1090 ((?:\[\s*)+) # 2: section marker open 1091 ( # 3: section name open 1092 (?:"\s*\S.*?\s*")| # at least one non-space with double quotes 1093 (?:'\s*\S.*?\s*')| # at least one non-space with single quotes 1094 (?:[^'"\s].*?) # at least one non-space unquoted 1095 ) # section name close 1096 ((?:\s*\])+) # 4: section marker close 1097 \s*(\#.*)? # 5: optional comment 1098 $''', 1099 re.VERBOSE) 1100 1101 # this regexp pulls list values out as a single string 1102 # or single values and comments 1103 # FIXME: this regex adds a '' to the end of comma terminated lists 1104 # workaround in ``_handle_value`` 1105 _valueexp = re.compile(r'''^ 1106 (?: 1107 (?: 1108 ( 1109 (?: 1110 (?: 1111 (?:".*?")| # double quotes 1112 (?:'.*?')| # single quotes 1113 (?:[^'",\#][^,\#]*?) # unquoted 1114 ) 1115 \s*,\s* # comma 1116 )* # match all list items ending in a comma (if any) 1117 ) 1118 ( 1119 (?:".*?")| # double quotes 1120 (?:'.*?')| # single quotes 1121 (?:[^'",\#\s][^,]*?)| # unquoted 1122 (?:(?<!,)) # Empty value 1123 )? # last item in a list - or string value 1124 )| 1125 (,) # alternatively a single comma - empty list 1126 ) 1127 \s*(\#.*)? # optional comment 1128 $''', 1129 re.VERBOSE) 1130 1131 # use findall to get the members of a list value 1132 _listvalueexp = re.compile(r''' 1133 ( 1134 (?:".*?")| # double quotes 1135 (?:'.*?')| # single quotes 1136 (?:[^'",\#]?.*?) # unquoted 1137 ) 1138 \s*,\s* # comma 1139 ''', 1140 re.VERBOSE) 1141 1142 # this regexp is used for the value 1143 # when lists are switched off 1144 _nolistvalue = re.compile(r'''^ 1145 ( 1146 (?:".*?")| # double quotes 1147 (?:'.*?')| # single quotes 1148 (?:[^'"\#].*?)| # unquoted 1149 (?:) # Empty value 1150 ) 1151 \s*(\#.*)? # optional comment 1152 $''', 1153 re.VERBOSE) 1154 1155 # regexes for finding triple quoted values on one line 1156 _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$") 1157 _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$') 1158 _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$") 1159 _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$') 1160 1161 _triple_quote = { 1162 "'''": (_single_line_single, _multi_line_single), 1163 '"""': (_single_line_double, _multi_line_double), 1164 } 1165 1166 # Used by the ``istrue`` Section method 1167 _bools = { 1168 'yes': True, 'no': False, 1169 'on': True, 'off': False, 1170 '1': True, '0': False, 1171 'true': True, 'false': False, 1172 } 1173 1174
1175 - def __init__(self, infile=None, options=None, configspec=None, encoding=None, 1176 interpolation=True, raise_errors=False, list_values=True, 1177 create_empty=False, file_error=False, stringify=True, 1178 indent_type=None, default_encoding=None, unrepr=False, 1179 write_empty_values=False, _inspec=False):
1180 """ 1181 Parse a config file or create a config file object. 1182 1183 ``ConfigObj(infile=None, configspec=None, encoding=None, 1184 interpolation=True, raise_errors=False, list_values=True, 1185 create_empty=False, file_error=False, stringify=True, 1186 indent_type=None, default_encoding=None, unrepr=False, 1187 write_empty_values=False, _inspec=False)`` 1188 """ 1189 self._inspec = _inspec 1190 # init the superclass 1191 Section.__init__(self, self, 0, self) 1192 1193 infile = infile or [] 1194 1195 _options = {'configspec': configspec, 1196 'encoding': encoding, 'interpolation': interpolation, 1197 'raise_errors': raise_errors, 'list_values': list_values, 1198 'create_empty': create_empty, 'file_error': file_error, 1199 'stringify': stringify, 'indent_type': indent_type, 1200 'default_encoding': default_encoding, 'unrepr': unrepr, 1201 'write_empty_values': write_empty_values} 1202 1203 if options is None: 1204 options = _options 1205 else: 1206 import warnings 1207 warnings.warn('Passing in an options dictionary to ConfigObj() is ' 1208 'deprecated. Use **options instead.', 1209 DeprecationWarning, stacklevel=2) 1210 1211 # TODO: check the values too. 1212 for entry in options: 1213 if entry not in OPTION_DEFAULTS: 1214 raise TypeError('Unrecognised option "%s".' % entry) 1215 for entry, value in OPTION_DEFAULTS.items(): 1216 if entry not in options: 1217 options[entry] = value 1218 keyword_value = _options[entry] 1219 if value != keyword_value: 1220 options[entry] = keyword_value 1221 1222 # XXXX this ignores an explicit list_values = True in combination 1223 # with _inspec. The user should *never* do that anyway, but still... 1224 if _inspec: 1225 options['list_values'] = False 1226 1227 self._initialise(options) 1228 configspec = options['configspec'] 1229 self._original_configspec = configspec 1230 self._load(infile, configspec)
1231 1232
1233 - def _load(self, infile, configspec):
1234 if isinstance(infile, basestring): 1235 self.filename = infile 1236 if os.path.isfile(infile): 1237 h = open(infile, 'rb') 1238 infile = h.read() or [] 1239 h.close() 1240 elif self.file_error: 1241 # raise an error if the file doesn't exist 1242 raise IOError('Config file not found: "%s".' % self.filename) 1243 else: 1244 # file doesn't already exist 1245 if self.create_empty: 1246 # this is a good test that the filename specified 1247 # isn't impossible - like on a non-existent device 1248 h = open(infile, 'w') 1249 h.write('') 1250 h.close() 1251 infile = [] 1252 1253 elif isinstance(infile, (list, tuple)): 1254 infile = list(infile) 1255 1256 elif isinstance(infile, dict): 1257 # initialise self 1258 # the Section class handles creating subsections 1259 if isinstance(infile, ConfigObj): 1260 # get a copy of our ConfigObj 1261 def set_section(in_section, this_section): 1262 for entry in in_section.scalars: 1263 this_section[entry] = in_section[entry] 1264 for section in in_section.sections: 1265 this_section[section] = {} 1266 set_section(in_section[section], this_section[section])
1267 set_section(infile, self) 1268 1269 else: 1270 for entry in infile: 1271 self[entry] = infile[entry] 1272 del self._errors 1273 1274 if configspec is not None: 1275 self._handle_configspec(configspec) 1276 else: 1277 self.configspec = None 1278 return 1279 1280 elif getattr(infile, 'read', MISSING) is not MISSING: 1281 # This supports file like objects 1282 infile = infile.read() or [] 1283 # needs splitting into lines - but needs doing *after* decoding 1284 # in case it's not an 8 bit encoding 1285 else: 1286 raise TypeError('infile must be a filename, file like object, or list of lines.') 1287 1288 if infile: 1289 # don't do it for the empty ConfigObj 1290 infile = self._handle_bom(infile) 1291 # infile is now *always* a list 1292 # 1293 # Set the newlines attribute (first line ending it finds) 1294 # and strip trailing '\n' or '\r' from lines 1295 for line in infile: 1296 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')): 1297 continue 1298 for end in ('\r\n', '\n', '\r'): 1299 if line.endswith(end): 1300 self.newlines = end 1301 break 1302 break 1303 1304 infile = [line.rstrip('\r\n') for line in infile] 1305 1306 self._parse(infile) 1307 # if we had any errors, now is the time to raise them 1308 if self._errors: 1309 info = "at line %s." % self._errors[0].line_number 1310 if len(self._errors) > 1: 1311 msg = "Parsing failed with several errors.\nFirst error %s" % info 1312 error = ConfigObjError(msg) 1313 else: 1314 error = self._errors[0] 1315 # set the errors attribute; it's a list of tuples: 1316 # (error_type, message, line_number) 1317 error.errors = self._errors 1318 # set the config attribute 1319 error.config = self 1320 raise error 1321 # delete private attributes 1322 del self._errors 1323 1324 if configspec is None: 1325 self.configspec = None 1326 else: 1327 self._handle_configspec(configspec)
1328 1329
1330 - def _initialise(self, options=None):
1331 if options is None: 1332 options = OPTION_DEFAULTS 1333 1334 # initialise a few variables 1335 self.filename = None 1336 self._errors = [] 1337 self.raise_errors = options['raise_errors'] 1338 self.interpolation = options['interpolation'] 1339 self.list_values = options['list_values'] 1340 self.create_empty = options['create_empty'] 1341 self.file_error = options['file_error'] 1342 self.stringify = options['stringify'] 1343 self.indent_type = options['indent_type'] 1344 self.encoding = options['encoding'] 1345 self.default_encoding = options['default_encoding'] 1346 self.BOM = False 1347 self.newlines = None 1348 self.write_empty_values = options['write_empty_values'] 1349 self.unrepr = options['unrepr'] 1350 1351 self.initial_comment = [] 1352 self.final_comment = [] 1353 self.configspec = None 1354 1355 if self._inspec: 1356 self.list_values = False 1357 1358 # Clear section attributes as well 1359 Section._initialise(self)
1360 1361
1362 - def __repr__(self):
1363 def _getval(key): 1364 try: 1365 return self[key] 1366 except MissingInterpolationOption: 1367 return dict.__getitem__(self, key)
1368 return ('ConfigObj({%s})' % 1369 ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) 1370 for key in (self.scalars + self.sections)])) 1371 1372
1373 - def _handle_bom(self, infile):
1374 """ 1375 Handle any BOM, and decode if necessary. 1376 1377 If an encoding is specified, that *must* be used - but the BOM should 1378 still be removed (and the BOM attribute set). 1379 1380 (If the encoding is wrongly specified, then a BOM for an alternative 1381 encoding won't be discovered or removed.) 1382 1383 If an encoding is not specified, UTF8 or UTF16 BOM will be detected and 1384 removed. The BOM attribute will be set. UTF16 will be decoded to 1385 unicode. 1386 1387 NOTE: This method must not be called with an empty ``infile``. 1388 1389 Specifying the *wrong* encoding is likely to cause a 1390 ``UnicodeDecodeError``. 1391 1392 ``infile`` must always be returned as a list of lines, but may be 1393 passed in as a single string. 1394 """ 1395 if ((self.encoding is not None) and 1396 (self.encoding.lower() not in BOM_LIST)): 1397 # No need to check for a BOM 1398 # the encoding specified doesn't have one 1399 # just decode 1400 return self._decode(infile, self.encoding) 1401 1402 if isinstance(infile, (list, tuple)): 1403 line = infile[0] 1404 else: 1405 line = infile 1406 if self.encoding is not None: 1407 # encoding explicitly supplied 1408 # And it could have an associated BOM 1409 # TODO: if encoding is just UTF16 - we ought to check for both 1410 # TODO: big endian and little endian versions. 1411 enc = BOM_LIST[self.encoding.lower()] 1412 if enc == 'utf_16': 1413 # For UTF16 we try big endian and little endian 1414 for BOM, (encoding, final_encoding) in BOMS.items(): 1415 if not final_encoding: 1416 # skip UTF8 1417 continue 1418 if infile.startswith(BOM): 1419 ### BOM discovered 1420 ##self.BOM = True 1421 # Don't need to remove BOM 1422 return self._decode(infile, encoding) 1423 1424 # If we get this far, will *probably* raise a DecodeError 1425 # As it doesn't appear to start with a BOM 1426 return self._decode(infile, self.encoding) 1427 1428 # Must be UTF8 1429 BOM = BOM_SET[enc] 1430 if not line.startswith(BOM): 1431 return self._decode(infile, self.encoding) 1432 1433 newline = line[len(BOM):] 1434 1435 # BOM removed 1436 if isinstance(infile, (list, tuple)): 1437 infile[0] = newline 1438 else: 1439 infile = newline 1440 self.BOM = True 1441 return self._decode(infile, self.encoding) 1442 1443 # No encoding specified - so we need to check for UTF8/UTF16 1444 for BOM, (encoding, final_encoding) in BOMS.items(): 1445 if not line.startswith(BOM): 1446 continue 1447 else: 1448 # BOM discovered 1449 self.encoding = final_encoding 1450 if not final_encoding: 1451 self.BOM = True 1452 # UTF8 1453 # remove BOM 1454 newline = line[len(BOM):] 1455 if isinstance(infile, (list, tuple)): 1456 infile[0] = newline 1457 else: 1458 infile = newline 1459 # UTF8 - don't decode 1460 if isinstance(infile, basestring): 1461 return infile.splitlines(True) 1462 else: 1463 return infile 1464 # UTF16 - have to decode 1465 return self._decode(infile, encoding) 1466 1467 # No BOM discovered and no encoding specified, just return 1468 if isinstance(infile, basestring): 1469 # infile read from a file will be a single string 1470 return infile.splitlines(True) 1471 return infile
1472 1473
1474 - def _a_to_u(self, aString):
1475 """Decode ASCII strings to unicode if a self.encoding is specified.""" 1476 if self.encoding: 1477 return aString.decode('ascii') 1478 else: 1479 return aString
1480 1481
1482 - def _decode(self, infile, encoding):
1483 """ 1484 Decode infile to unicode. Using the specified encoding. 1485 1486 if is a string, it also needs converting to a list. 1487 """ 1488 if isinstance(infile, basestring): 1489 # can't be unicode 1490 # NOTE: Could raise a ``UnicodeDecodeError`` 1491 return infile.decode(encoding).splitlines(True) 1492 for i, line in enumerate(infile): 1493 if not isinstance(line, unicode): 1494 # NOTE: The isinstance test here handles mixed lists of unicode/string 1495 # NOTE: But the decode will break on any non-string values 1496 # NOTE: Or could raise a ``UnicodeDecodeError`` 1497 infile[i] = line.decode(encoding) 1498 return infile
1499 1500
1501 - def _decode_element(self, line):
1502 """Decode element to unicode if necessary.""" 1503 if not self.encoding: 1504 return line 1505 if isinstance(line, str) and self.default_encoding: 1506 return line.decode(self.default_encoding) 1507 return line
1508 1509
1510 - def _str(self, value):
1511 """ 1512 Used by ``stringify`` within validate, to turn non-string values 1513 into strings. 1514 """ 1515 if not isinstance(value, basestring): 1516 return str(value) 1517 else: 1518 return value
1519 1520
1521 - def _parse(self, infile):
1522 """Actually parse the config file.""" 1523 temp_list_values = self.list_values 1524 if self.unrepr: 1525 self.list_values = False 1526 1527 comment_list = [] 1528 done_start = False 1529 this_section = self 1530 maxline = len(infile) - 1 1531 cur_index = -1 1532 reset_comment = False 1533 1534 while cur_index < maxline: 1535 if reset_comment: 1536 comment_list = [] 1537 cur_index += 1 1538 line = infile[cur_index] 1539 sline = line.strip() 1540 # do we have anything on the line ? 1541 if not sline or sline.startswith('#'): 1542 reset_comment = False 1543 comment_list.append(line) 1544 continue 1545 1546 if not done_start: 1547 # preserve initial comment 1548 self.initial_comment = comment_list 1549 comment_list = [] 1550 done_start = True 1551 1552 reset_comment = True 1553 # first we check if it's a section marker 1554 mat = self._sectionmarker.match(line) 1555 if mat is not None: 1556 # is a section line 1557 (indent, sect_open, sect_name, sect_close, comment) = mat.groups() 1558 if indent and (self.indent_type is None): 1559 self.indent_type = indent 1560 cur_depth = sect_open.count('[') 1561 if cur_depth != sect_close.count(']'): 1562 self._handle_error("Cannot compute the section depth at line %s.", 1563 NestingError, infile, cur_index) 1564 continue 1565 1566 if cur_depth < this_section.depth: 1567 # the new section is dropping back to a previous level 1568 try: 1569 parent = self._match_depth(this_section, 1570 cur_depth).parent 1571 except SyntaxError: 1572 self._handle_error("Cannot compute nesting level at line %s.", 1573 NestingError, infile, cur_index) 1574 continue 1575 elif cur_depth == this_section.depth: 1576 # the new section is a sibling of the current section 1577 parent = this_section.parent 1578 elif cur_depth == this_section.depth + 1: 1579 # the new section is a child the current section 1580 parent = this_section 1581 else: 1582 self._handle_error("Section too nested at line %s.", 1583 NestingError, infile, cur_index) 1584 1585 sect_name = self._unquote(sect_name) 1586 if sect_name in parent: 1587 self._handle_error('Duplicate section name at line %s.', 1588 DuplicateError, infile, cur_index) 1589 continue 1590 1591 # create the new section 1592 this_section = Section( 1593 parent, 1594 cur_depth, 1595 self, 1596 name=sect_name) 1597 parent[sect_name] = this_section 1598 parent.inline_comments[sect_name] = comment 1599 parent.comments[sect_name] = comment_list 1600 continue 1601 # 1602 # it's not a section marker, 1603 # so it should be a valid ``key = value`` line 1604 mat = self._keyword.match(line) 1605 if mat is None: 1606 # it neither matched as a keyword 1607 # or a section marker 1608 self._handle_error( 1609 'Invalid line at line "%s".', 1610 ParseError, infile, cur_index) 1611 else: 1612 # is a keyword value 1613 # value will include any inline comment 1614 (indent, key, value) = mat.groups() 1615 if indent and (self.indent_type is None): 1616 self.indent_type = indent 1617 # check for a multiline value 1618 if value[:3] in ['"""', "'''"]: 1619 try: 1620 value, comment, cur_index = self._multiline( 1621 value, infile, cur_index, maxline) 1622 except SyntaxError: 1623 self._handle_error( 1624 'Parse error in value at line %s.', 1625 ParseError, infile, cur_index) 1626 continue 1627 else: 1628 if self.unrepr: 1629 comment = '' 1630 try: 1631 value = unrepr(value) 1632 except Exception, e: 1633 if type(e) == UnknownType: 1634 msg = 'Unknown name or type in value at line %s.' 1635 else: 1636 msg = 'Parse error in value at line %s.' 1637 self._handle_error(msg, UnreprError, infile, 1638 cur_index) 1639 continue 1640 else: 1641 if self.unrepr: 1642 comment = '' 1643 try: 1644 value = unrepr(value) 1645 except Exception, e: 1646 if isinstance(e, UnknownType): 1647 msg = 'Unknown name or type in value at line %s.' 1648 else: 1649 msg = 'Parse error in value at line %s.' 1650 self._handle_error(msg, UnreprError, infile, 1651 cur_index) 1652 continue 1653 else: 1654 # extract comment and lists 1655 try: 1656 (value, comment) = self._handle_value(value) 1657 except SyntaxError: 1658 self._handle_error( 1659 'Parse error in value at line %s.', 1660 ParseError, infile, cur_index) 1661 continue 1662 # 1663 key = self._unquote(key) 1664 if key in this_section: 1665 self._handle_error( 1666 'Duplicate keyword name at line %s.', 1667 DuplicateError, infile, cur_index) 1668 continue 1669 # add the key. 1670 # we set unrepr because if we have got this far we will never 1671 # be creating a new section 1672 this_section.__setitem__(key, value, unrepr=True) 1673 this_section.inline_comments[key] = comment 1674 this_section.comments[key] = comment_list 1675 continue 1676 # 1677 if self.indent_type is None: 1678 # no indentation used, set the type accordingly 1679 self.indent_type = '' 1680 1681 # preserve the final comment 1682 if not self and not self.initial_comment: 1683 self.initial_comment = comment_list 1684 elif not reset_comment: 1685 self.final_comment = comment_list 1686 self.list_values = temp_list_values
1687 1688
1689 - def _match_depth(self, sect, depth):
1690 """ 1691 Given a section and a depth level, walk back through the sections 1692 parents to see if the depth level matches a previous section. 1693 1694 Return a reference to the right section, 1695 or raise a SyntaxError. 1696 """ 1697 while depth < sect.depth: 1698 if sect is sect.parent: 1699 # we've reached the top level already 1700 raise SyntaxError() 1701 sect = sect.parent 1702 if sect.depth == depth: 1703 return sect 1704 # shouldn't get here 1705 raise SyntaxError()
1706 1707
1708 - def _handle_error(self, text, ErrorClass, infile, cur_index):
1709 """ 1710 Handle an error according to the error settings. 1711 1712 Either raise the error or store it. 1713 The error will have occured at ``cur_index`` 1714 """ 1715 line = infile[cur_index] 1716 cur_index += 1 1717 message = text % cur_index 1718 error = ErrorClass(message, cur_index, line) 1719 if self.raise_errors: 1720 # raise the error - parsing stops here 1721 raise error 1722 # store the error 1723 # reraise when parsing has finished 1724 self._errors.append(error)
1725 1726
1727 - def _unquote(self, value):
1728 """Return an unquoted version of a value""" 1729 if not value: 1730 # should only happen during parsing of lists 1731 raise SyntaxError 1732 if (value[0] == value[-1]) and (value[0] in ('"', "'")): 1733 value = value[1:-1] 1734 return value
1735 1736
1737 - def _quote(self, value, multiline=True):
1738 """ 1739 Return a safely quoted version of a value. 1740 1741 Raise a ConfigObjError if the value cannot be safely quoted. 1742 If multiline is ``True`` (default) then use triple quotes 1743 if necessary. 1744 1745 * Don't quote values that don't need it. 1746 * Recursively quote members of a list and return a comma joined list. 1747 * Multiline is ``False`` for lists. 1748 * Obey list syntax for empty and single member lists. 1749 1750 If ``list_values=False`` then the value is only quoted if it contains 1751 a ``\\n`` (is multiline) or '#'. 1752 1753 If ``write_empty_values`` is set, and the value is an empty string, it 1754 won't be quoted. 1755 """ 1756 if multiline and self.write_empty_values and value == '': 1757 # Only if multiline is set, so that it is used for values not 1758 # keys, and not values that are part of a list 1759 return '' 1760 1761 if multiline and isinstance(value, (list, tuple)): 1762 if not value: 1763 return ',' 1764 elif len(value) == 1: 1765 return self._quote(value[0], multiline=False) + ',' 1766 return ', '.join([self._quote(val, multiline=False) 1767 for val in value]) 1768 if not isinstance(value, basestring): 1769 if self.stringify: 1770 value = str(value) 1771 else: 1772 raise TypeError('Value "%s" is not a string.' % value) 1773 1774 if not value: 1775 return '""' 1776 1777 no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value 1778 need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value )) 1779 hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value) 1780 check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote 1781 1782 if check_for_single: 1783 if not self.list_values: 1784 # we don't quote if ``list_values=False`` 1785 quot = noquot 1786 # for normal values either single or double quotes will do 1787 elif '\n' in value: 1788 # will only happen if multiline is off - e.g. '\n' in key 1789 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1790 elif ((value[0] not in wspace_plus) and 1791 (value[-1] not in wspace_plus) and 1792 (',' not in value)): 1793 quot = noquot 1794 else: 1795 quot = self._get_single_quote(value) 1796 else: 1797 # if value has '\n' or "'" *and* '"', it will need triple quotes 1798 quot = self._get_triple_quote(value) 1799 1800 if quot == noquot and '#' in value and self.list_values: 1801 quot = self._get_single_quote(value) 1802 1803 return quot % value
1804 1805
1806 - def _get_single_quote(self, value):
1807 if ("'" in value) and ('"' in value): 1808 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1809 elif '"' in value: 1810 quot = squot 1811 else: 1812 quot = dquot 1813 return quot
1814 1815
1816 - def _get_triple_quote(self, value):
1817 if (value.find('"""') != -1) and (value.find("'''") != -1): 1818 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1819 if value.find('"""') == -1: 1820 quot = tsquot 1821 else: 1822 quot = tdquot 1823 return quot
1824 1825
1826 - def _handle_value(self, value):
1827 """ 1828 Given a value string, unquote, remove comment, 1829 handle lists. (including empty and single member lists) 1830 """ 1831 if self._inspec: 1832 # Parsing a configspec so don't handle comments 1833 return (value, '') 1834 # do we look for lists in values ? 1835 if not self.list_values: 1836 mat = self._nolistvalue.match(value) 1837 if mat is None: 1838 raise SyntaxError() 1839 # NOTE: we don't unquote here 1840 return mat.groups() 1841 # 1842 mat = self._valueexp.match(value) 1843 if mat is None: 1844 # the value is badly constructed, probably badly quoted, 1845 # or an invalid list 1846 raise SyntaxError() 1847 (list_values, single, empty_list, comment) = mat.groups() 1848 if (list_values == '') and (single is None): 1849 # change this if you want to accept empty values 1850 raise SyntaxError() 1851 # NOTE: note there is no error handling from here if the regex 1852 # is wrong: then incorrect values will slip through 1853 if empty_list is not None: 1854 # the single comma - meaning an empty list 1855 return ([], comment) 1856 if single is not None: 1857 # handle empty values 1858 if list_values and not single: 1859 # FIXME: the '' is a workaround because our regex now matches 1860 # '' at the end of a list if it has a trailing comma 1861 single = None 1862 else: 1863 single = single or '""' 1864 single = self._unquote(single) 1865 if list_values == '': 1866 # not a list value 1867 return (single, comment) 1868 the_list = self._listvalueexp.findall(list_values) 1869 the_list = [self._unquote(val) for val in the_list] 1870 if single is not None: 1871 the_list += [single] 1872 return (the_list, comment)
1873 1874
1875 - def _multiline(self, value, infile, cur_index, maxline):
1876 """Extract the value, where we are in a multiline situation.""" 1877 quot = value[:3] 1878 newvalue = value[3:] 1879 single_line = self._triple_quote[quot][0] 1880 multi_line = self._triple_quote[quot][1] 1881 mat = single_line.match(value) 1882 if mat is not None: 1883 retval = list(mat.groups()) 1884 retval.append(cur_index) 1885 return retval 1886 elif newvalue.find(quot) != -1: 1887 # somehow the triple quote is missing 1888 raise SyntaxError() 1889 # 1890 while cur_index < maxline: 1891 cur_index += 1 1892 newvalue += '\n' 1893 line = infile[cur_index] 1894 if line.find(quot) == -1: 1895 newvalue += line 1896 else: 1897 # end of multiline, process it 1898 break 1899 else: 1900 # we've got to the end of the config, oops... 1901 raise SyntaxError() 1902 mat = multi_line.match(line) 1903 if mat is None: 1904 # a badly formed line 1905 raise SyntaxError() 1906 (value, comment) = mat.groups() 1907 return (newvalue + value, comment, cur_index)
1908 1909
1910 - def _handle_configspec(self, configspec):
1911 """Parse the configspec.""" 1912 # FIXME: Should we check that the configspec was created with the 1913 # correct settings ? (i.e. ``list_values=False``) 1914 if not isinstance(configspec, ConfigObj): 1915 try: 1916 configspec = ConfigObj(configspec, 1917 raise_errors=True, 1918 file_error=True, 1919 _inspec=True) 1920 except ConfigObjError, e: 1921 # FIXME: Should these errors have a reference 1922 # to the already parsed ConfigObj ? 1923 raise ConfigspecError('Parsing configspec failed: %s' % e) 1924 except IOError, e: 1925 raise IOError('Reading configspec failed: %s' % e) 1926 1927 self.configspec = configspec
1928 1929 1930
1931 - def _set_configspec(self, section, copy):
1932 """ 1933 Called by validate. Handles setting the configspec on subsections 1934 including sections to be validated by __many__ 1935 """ 1936 configspec = section.configspec 1937 many = configspec.get('__many__') 1938 if isinstance(many, dict): 1939 for entry in section.sections: 1940 if entry not in configspec: 1941 section[entry].configspec = many 1942 1943 for entry in configspec.sections: 1944 if entry == '__many__': 1945 continue 1946 if entry not in section: 1947 section[entry] = {} 1948 section[entry]._created = True 1949 if copy: 1950 # copy comments 1951 section.comments[entry] = configspec.comments.get(entry, []) 1952 section.inline_comments[entry] = configspec.inline_comments.get(entry, '') 1953 1954 # Could be a scalar when we expect a section 1955 if isinstance(section[entry], Section): 1956 section[entry].configspec = configspec[entry]
1957 1958
1959 - def _write_line(self, indent_string, entry, this_entry, comment):
1960 """Write an individual line, for the write method""" 1961 # NOTE: the calls to self._quote here handles non-StringType values. 1962 if not self.unrepr: 1963 val = self._decode_element(self._quote(this_entry)) 1964 else: 1965 val = repr(this_entry) 1966 return '%s%s%s%s%s' % (indent_string, 1967 self._decode_element(self._quote(entry, multiline=False)), 1968 self._a_to_u(' = '), 1969 val, 1970 self._decode_element(comment))
1971 1972
1973 - def _write_marker(self, indent_string, depth, entry, comment):
1974 """Write a section marker line""" 1975 return '%s%s%s%s%s' % (indent_string, 1976 self._a_to_u('[' * depth), 1977 self._quote(self._decode_element(entry), multiline=False), 1978 self._a_to_u(']' * depth), 1979 self._decode_element(comment))
1980 1981
1982 - def _handle_comment(self, comment):
1983 """Deal with a comment.""" 1984 if not comment: 1985 return '' 1986 start = self.indent_type 1987 if not comment.startswith('#'): 1988 start += self._a_to_u(' # ') 1989 return (start + comment)
1990 1991 1992 # Public methods 1993
1994 - def write(self, outfile=None, section=None):
1995 """ 1996 Write the current ConfigObj as a file 1997 1998 tekNico: FIXME: use StringIO instead of real files 1999 2000 >>> filename = a.filename 2001 >>> a.filename = 'test.ini' 2002 >>> a.write() 2003 >>> a.filename = filename 2004 >>> a == ConfigObj('test.ini', raise_errors=True) 2005 1 2006 >>> import os 2007 >>> os.remove('test.ini') 2008 """ 2009 if self.indent_type is None: 2010 # this can be true if initialised from a dictionary 2011 self.indent_type = DEFAULT_INDENT_TYPE 2012 2013 out = [] 2014 cs = self._a_to_u('#') 2015 csp = self._a_to_u('# ') 2016 if section is None: 2017 int_val = self.interpolation 2018 self.interpolation = False 2019 section = self 2020 for line in self.initial_comment: 2021 line = self._decode_element(line) 2022 stripped_line = line.strip() 2023 if stripped_line and not stripped_line.startswith(cs): 2024 line = csp + line 2025 out.append(line) 2026 2027 indent_string = self.indent_type * section.depth 2028 for entry in (section.scalars + section.sections): 2029 if entry in section.defaults: 2030 # don't write out default values 2031 continue 2032 for comment_line in section.comments[entry]: 2033 comment_line = self._decode_element(comment_line.lstrip()) 2034 if comment_line and not comment_line.startswith(cs): 2035 comment_line = csp + comment_line 2036 out.append(indent_string + comment_line) 2037 this_entry = section[entry] 2038 comment = self._handle_comment(section.inline_comments[entry]) 2039 2040 if isinstance(this_entry, dict): 2041 # a section 2042 out.append(self._write_marker( 2043 indent_string, 2044 this_entry.depth, 2045 entry, 2046 comment)) 2047 out.extend(self.write(section=this_entry)) 2048 else: 2049 out.append(self._write_line( 2050 indent_string, 2051 entry, 2052 this_entry, 2053 comment)) 2054 2055 if section is self: 2056 for line in self.final_comment: 2057 line = self._decode_element(line) 2058 stripped_line = line.strip() 2059 if stripped_line and not stripped_line.startswith(cs): 2060 line = csp + line 2061 out.append(line) 2062 self.interpolation = int_val 2063 2064 if section is not self: 2065 return out 2066 2067 if (self.filename is None) and (outfile is None): 2068 # output a list of lines 2069 # might need to encode 2070 # NOTE: This will *screw* UTF16, each line will start with the BOM 2071 if self.encoding: 2072 out = [l.encode(self.encoding) for l in out] 2073 if (self.BOM and ((self.encoding is None) or 2074 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): 2075 # Add the UTF8 BOM 2076 if not out: 2077 out.append('') 2078 out[0] = BOM_UTF8 + out[0] 2079 return out 2080 2081 # Turn the list to a string, joined with correct newlines 2082 newline = self.newlines or os.linesep 2083 if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w' 2084 and sys.platform == 'win32' and newline == '\r\n'): 2085 # Windows specific hack to avoid writing '\r\r\n' 2086 newline = '\n' 2087 output = self._a_to_u(newline).join(out) 2088 if self.encoding: 2089 output = output.encode(self.encoding) 2090 if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)): 2091 # Add the UTF8 BOM 2092 output = BOM_UTF8 + output 2093 2094 if not output.endswith(newline): 2095 output += newline 2096 if outfile is not None: 2097 outfile.write(output) 2098 else: 2099 h = open(self.filename, 'wb') 2100 h.write(output) 2101 h.close()
2102 2103
2104 - def validate(self, validator, preserve_errors=False, copy=False, 2105 section=None):
2106 """ 2107 Test the ConfigObj against a configspec. 2108 2109 It uses the ``validator`` object from *validate.py*. 2110 2111 To run ``validate`` on the current ConfigObj, call: :: 2112 2113 test = config.validate(validator) 2114 2115 (Normally having previously passed in the configspec when the ConfigObj 2116 was created - you can dynamically assign a dictionary of checks to the 2117 ``configspec`` attribute of a section though). 2118 2119 It returns ``True`` if everything passes, or a dictionary of 2120 pass/fails (True/False). If every member of a subsection passes, it 2121 will just have the value ``True``. (It also returns ``False`` if all 2122 members fail). 2123 2124 In addition, it converts the values from strings to their native 2125 types if their checks pass (and ``stringify`` is set). 2126 2127 If ``preserve_errors`` is ``True`` (``False`` is default) then instead 2128 of a marking a fail with a ``False``, it will preserve the actual 2129 exception object. This can contain info about the reason for failure. 2130 For example the ``VdtValueTooSmallError`` indicates that the value 2131 supplied was too small. If a value (or section) is missing it will 2132 still be marked as ``False``. 2133 2134 You must have the validate module to use ``preserve_errors=True``. 2135 2136 You can then use the ``flatten_errors`` function to turn your nested 2137 results dictionary into a flattened list of failures - useful for 2138 displaying meaningful error messages. 2139 """ 2140 if section is None: 2141 if self.configspec is None: 2142 raise ValueError('No configspec supplied.') 2143 if preserve_errors: 2144 # We do this once to remove a top level dependency on the validate module 2145 # Which makes importing configobj faster 2146 from validate import VdtMissingValue 2147 self._vdtMissingValue = VdtMissingValue 2148 2149 section = self 2150 2151 if copy: 2152 section.initial_comment = section.configspec.initial_comment 2153 section.final_comment = section.configspec.final_comment 2154 section.encoding = section.configspec.encoding 2155 section.BOM = section.configspec.BOM 2156 section.newlines = section.configspec.newlines 2157 section.indent_type = section.configspec.indent_type 2158 2159 # 2160 # section.default_values.clear() #?? 2161 configspec = section.configspec 2162 self._set_configspec(section, copy) 2163 2164 2165 def validate_entry(entry, spec, val, missing, ret_true, ret_false): 2166 section.default_values.pop(entry, None) 2167 2168 try: 2169 section.default_values[entry] = validator.get_default_value(configspec[entry]) 2170 except (KeyError, AttributeError, validator.baseErrorClass): 2171 # No default, bad default or validator has no 'get_default_value' 2172 # (e.g. SimpleVal) 2173 pass 2174 2175 try: 2176 check = validator.check(spec, 2177 val, 2178 missing=missing 2179 ) 2180 except validator.baseErrorClass, e: 2181 if not preserve_errors or isinstance(e, self._vdtMissingValue): 2182 out[entry] = False 2183 else: 2184 # preserve the error 2185 out[entry] = e 2186 ret_false = False 2187 ret_true = False 2188 else: 2189 ret_false = False 2190 out[entry] = True 2191 if self.stringify or missing: 2192 # if we are doing type conversion 2193 # or the value is a supplied default 2194 if not self.stringify: 2195 if isinstance(check, (list, tuple)): 2196 # preserve lists 2197 check = [self._str(item) for item in check] 2198 elif missing and check is None: 2199 # convert the None from a default to a '' 2200 check = '' 2201 else: 2202 check = self._str(check) 2203 if (check != val) or missing: 2204 section[entry] = check 2205 if not copy and missing and entry not in section.defaults: 2206 section.defaults.append(entry) 2207 return ret_true, ret_false
2208 2209 # 2210 out = {} 2211 ret_true = True 2212 ret_false = True 2213 2214 unvalidated = [k for k in section.scalars if k not in configspec] 2215 incorrect_sections = [k for k in configspec.sections if k in section.scalars] 2216 incorrect_scalars = [k for k in configspec.scalars if k in section.sections] 2217 2218 for entry in configspec.scalars: 2219 if entry in ('__many__', '___many___'): 2220 # reserved names 2221 continue 2222 if (not entry in section.scalars) or (entry in section.defaults): 2223 # missing entries 2224 # or entries from defaults 2225 missing = True 2226 val = None 2227 if copy and entry not in section.scalars: 2228 # copy comments 2229 section.comments[entry] = ( 2230 configspec.comments.get(entry, [])) 2231 section.inline_comments[entry] = ( 2232 configspec.inline_comments.get(entry, '')) 2233 # 2234 else: 2235 missing = False 2236 val = section[entry] 2237 2238 ret_true, ret_false = validate_entry(entry, configspec[entry], val, 2239 missing, ret_true, ret_false) 2240 2241 many = None 2242 if '__many__' in configspec.scalars: 2243 many = configspec['__many__'] 2244 elif '___many___' in configspec.scalars: 2245 many = configspec['___many___'] 2246 2247 if many is not None: 2248 for entry in unvalidated: 2249 val = section[entry] 2250 ret_true, ret_false = validate_entry(entry, many, val, False, 2251 ret_true, ret_false) 2252 unvalidated = [] 2253 2254 for entry in incorrect_scalars: 2255 ret_true = False 2256 if not preserve_errors: 2257 out[entry] = False 2258 else: 2259 ret_false = False 2260 msg = 'Value %r was provided as a section' % entry 2261 out[entry] = validator.baseErrorClass(msg) 2262 for entry in incorrect_sections: 2263 ret_true = False 2264 if not preserve_errors: 2265 out[entry] = False 2266 else: 2267 ret_false = False 2268 msg = 'Section %r was provided as a single value' % entry 2269 out[entry] = validator.baseErrorClass(msg) 2270 2271 # Missing sections will have been created as empty ones when the 2272 # configspec was read. 2273 for entry in section.sections: 2274 # FIXME: this means DEFAULT is not copied in copy mode 2275 if section is self and entry == 'DEFAULT': 2276 continue 2277 if section[entry].configspec is None: 2278 unvalidated.append(entry) 2279 continue 2280 if copy: 2281 section.comments[entry] = configspec.comments.get(entry, []) 2282 section.inline_comments[entry] = configspec.inline_comments.get(entry, '') 2283 check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) 2284 out[entry] = check 2285 if check == False: 2286 ret_true = False 2287 elif check == True: 2288 ret_false = False 2289 else: 2290 ret_true = False 2291 2292 section.extra_values = unvalidated 2293 if preserve_errors and not section._created: 2294 # If the section wasn't created (i.e. it wasn't missing) 2295 # then we can't return False, we need to preserve errors 2296 ret_false = False 2297 # 2298 if ret_false and preserve_errors and out: 2299 # If we are preserving errors, but all 2300 # the failures are from missing sections / values 2301 # then we can return False. Otherwise there is a 2302 # real failure that we need to preserve. 2303 ret_false = not any(out.values()) 2304 if ret_true: 2305 return True 2306 elif ret_false: 2307 return False 2308 return out 2309 2310
2311 - def reset(self):
2312 """Clear ConfigObj instance and restore to 'freshly created' state.""" 2313 self.clear() 2314 self._initialise() 2315 # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload) 2316 # requires an empty dictionary 2317 self.configspec = None 2318 # Just to be sure ;-) 2319 self._original_configspec = None
2320 2321
2322 - def reload(self):
2323 """ 2324 Reload a ConfigObj from file. 2325 2326 This method raises a ``ReloadError`` if the ConfigObj doesn't have 2327 a filename attribute pointing to a file. 2328 """ 2329 if not isinstance(self.filename, basestring): 2330 raise ReloadError() 2331 2332 filename = self.filename 2333 current_options = {} 2334 for entry in OPTION_DEFAULTS: 2335 if entry == 'configspec': 2336 continue 2337 current_options[entry] = getattr(self, entry) 2338 2339 configspec = self._original_configspec 2340 current_options['configspec'] = configspec 2341 2342 self.clear() 2343 self._initialise(current_options) 2344 self._load(filename, configspec)
2345 2346 2347
2348 -class SimpleVal(object):
2349 """ 2350 A simple validator. 2351 Can be used to check that all members expected are present. 2352 2353 To use it, provide a configspec with all your members in (the value given 2354 will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` 2355 method of your ``ConfigObj``. ``validate`` will return ``True`` if all 2356 members are present, or a dictionary with True/False meaning 2357 present/missing. (Whole missing sections will be replaced with ``False``) 2358 """ 2359
2360 - def __init__(self):
2361 self.baseErrorClass = ConfigObjError
2362
2363 - def check(self, check, member, missing=False):
2364 """A dummy check method, always returns the value unchanged.""" 2365 if missing: 2366 raise self.baseErrorClass() 2367 return member
2368 2369
2370 -def flatten_errors(cfg, res, levels=None, results=None):
2371 """ 2372 An example function that will turn a nested dictionary of results 2373 (as returned by ``ConfigObj.validate``) into a flat list. 2374 2375 ``cfg`` is the ConfigObj instance being checked, ``res`` is the results 2376 dictionary returned by ``validate``. 2377 2378 (This is a recursive function, so you shouldn't use the ``levels`` or 2379 ``results`` arguments - they are used by the function.) 2380 2381 Returns a list of keys that failed. Each member of the list is a tuple:: 2382 2383 ([list of sections...], key, result) 2384 2385 If ``validate`` was called with ``preserve_errors=False`` (the default) 2386 then ``result`` will always be ``False``. 2387 2388 *list of sections* is a flattened list of sections that the key was found 2389 in. 2390 2391 If the section was missing (or a section was expected and a scalar provided 2392 - or vice-versa) then key will be ``None``. 2393 2394 If the value (or section) was missing then ``result`` will be ``False``. 2395 2396 If ``validate`` was called with ``preserve_errors=True`` and a value 2397 was present, but failed the check, then ``result`` will be the exception 2398 object returned. You can use this as a string that describes the failure. 2399 2400 For example *The value "3" is of the wrong type*. 2401 """ 2402 if levels is None: 2403 # first time called 2404 levels = [] 2405 results = [] 2406 if res == True: 2407 return results 2408 if res == False or isinstance(res, Exception): 2409 results.append((levels[:], None, res)) 2410 if levels: 2411 levels.pop() 2412 return results 2413 for (key, val) in res.items(): 2414 if val == True: 2415 continue 2416 if isinstance(cfg.get(key), dict): 2417 # Go down one level 2418 levels.append(key) 2419 flatten_errors(cfg[key], val, levels, results) 2420 continue 2421 results.append((levels[:], key, val)) 2422 # 2423 # Go up one level 2424 if levels: 2425 levels.pop() 2426 # 2427 return results
2428 2429
2430 -def get_extra_values(conf, _prepend=()):
2431 """ 2432 Find all the values and sections not in the configspec from a validated 2433 ConfigObj. 2434 2435 ``get_extra_values`` returns a list of tuples where each tuple represents 2436 either an extra section, or an extra value. 2437 2438 The tuples contain two values, a tuple representing the section the value 2439 is in and the name of the extra values. For extra values in the top level 2440 section the first member will be an empty tuple. For values in the 'foo' 2441 section the first member will be ``('foo',)``. For members in the 'bar' 2442 subsection of the 'foo' section the first member will be ``('foo', 'bar')``. 2443 2444 NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't 2445 been validated it will return an empty list. 2446 """ 2447 out = [] 2448 2449 out.extend([(_prepend, name) for name in conf.extra_values]) 2450 for name in conf.sections: 2451 if name not in conf.extra_values: 2452 out.extend(get_extra_values(conf[name], _prepend + (name,))) 2453 return out
2454 2455 2456 """*A programming language is a medium of expression.* - Paul Graham""" 2457