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.1' 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: 384 break 385 # try "DEFAULT" next 386 val = current_section.get('DEFAULT', {}).get(key) 387 if val is not None: 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 return [_check(entry) for entry in val] 577 return val
578 579
580 - def __setitem__(self, key, value, unrepr=False):
581 """ 582 Correctly set a value. 583 584 Making dictionary values Section instances. 585 (We have to special case 'Section' instances - which are also dicts) 586 587 Keys must be strings. 588 Values need only be strings (or lists of strings) if 589 ``main.stringify`` is set. 590 591 ``unrepr`` must be set when setting a value to a dictionary, without 592 creating a new sub-section. 593 """ 594 if not isinstance(key, basestring): 595 raise ValueError('The key "%s" is not a string.' % key) 596 597 # add the comment 598 if key not in self.comments: 599 self.comments[key] = [] 600 self.inline_comments[key] = '' 601 # remove the entry from defaults 602 if key in self.defaults: 603 self.defaults.remove(key) 604 # 605 if isinstance(value, Section): 606 if key not in self: 607 self.sections.append(key) 608 dict.__setitem__(self, key, value) 609 elif isinstance(value, dict) and not unrepr: 610 # First create the new depth level, 611 # then create the section 612 if key not in self: 613 self.sections.append(key) 614 new_depth = self.depth + 1 615 dict.__setitem__( 616 self, 617 key, 618 Section( 619 self, 620 new_depth, 621 self.main, 622 indict=value, 623 name=key)) 624 else: 625 if key not in self: 626 self.scalars.append(key) 627 if not self.main.stringify: 628 if isinstance(value, basestring): 629 pass 630 elif isinstance(value, (list, tuple)): 631 for entry in value: 632 if not isinstance(entry, basestring): 633 raise TypeError('Value is not a string "%s".' % entry) 634 else: 635 raise TypeError('Value is not a string "%s".' % value) 636 dict.__setitem__(self, key, value)
637 638
639 - def __delitem__(self, key):
640 """Remove items from the sequence when deleting.""" 641 dict. __delitem__(self, key) 642 if key in self.scalars: 643 self.scalars.remove(key) 644 else: 645 self.sections.remove(key) 646 del self.comments[key] 647 del self.inline_comments[key]
648 649
650 - def get(self, key, default=None):
651 """A version of ``get`` that doesn't bypass string interpolation.""" 652 try: 653 return self[key] 654 except KeyError: 655 return default
656 657
658 - def update(self, indict):
659 """ 660 A version of update that uses our ``__setitem__``. 661 """ 662 for entry in indict: 663 self[entry] = indict[entry]
664 665
666 - def pop(self, key, *args):
667 """ 668 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value. 669 If key is not found, d is returned if given, otherwise KeyError is raised' 670 """ 671 val = dict.pop(self, key, *args) 672 if key in self.scalars: 673 del self.comments[key] 674 del self.inline_comments[key] 675 self.scalars.remove(key) 676 elif key in self.sections: 677 del self.comments[key] 678 del self.inline_comments[key] 679 self.sections.remove(key) 680 if self.main.interpolation and isinstance(val, basestring): 681 return self._interpolate(key, val) 682 return val
683 684
685 - def popitem(self):
686 """Pops the first (key,val)""" 687 sequence = (self.scalars + self.sections) 688 if not sequence: 689 raise KeyError(": 'popitem(): dictionary is empty'") 690 key = sequence[0] 691 val = self[key] 692 del self[key] 693 return key, val
694 695
696 - def clear(self):
697 """ 698 A version of clear that also affects scalars/sections 699 Also clears comments and configspec. 700 701 Leaves other attributes alone : 702 depth/main/parent are not affected 703 """ 704 dict.clear(self) 705 self.scalars = [] 706 self.sections = [] 707 self.comments = {} 708 self.inline_comments = {} 709 self.configspec = None 710 self.defaults = [] 711 self.extra_values = []
712 713
714 - def setdefault(self, key, default=None):
715 """A version of setdefault that sets sequence if appropriate.""" 716 try: 717 return self[key] 718 except KeyError: 719 self[key] = default 720 return self[key]
721 722
723 - def items(self):
724 """D.items() -> list of D's (key, value) pairs, as 2-tuples""" 725 return zip((self.scalars + self.sections), self.values())
726 727
728 - def keys(self):
729 """D.keys() -> list of D's keys""" 730 return (self.scalars + self.sections)
731 732
733 - def values(self):
734 """D.values() -> list of D's values""" 735 return [self[key] for key in (self.scalars + self.sections)]
736 737
738 - def iteritems(self):
739 """D.iteritems() -> an iterator over the (key, value) items of D""" 740 return iter(self.items())
741 742
743 - def iterkeys(self):
744 """D.iterkeys() -> an iterator over the keys of D""" 745 return iter((self.scalars + self.sections))
746 747 __iter__ = iterkeys 748 749
750 - def itervalues(self):
751 """D.itervalues() -> an iterator over the values of D""" 752 return iter(self.values())
753 754
755 - def __repr__(self):
756 """x.__repr__() <==> repr(x)""" 757 return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key]))) 758 for key in (self.scalars + self.sections)])
759 760 __str__ = __repr__ 761 __str__.__doc__ = "x.__str__() <==> str(x)" 762 763 764 # Extra methods - not in a normal dictionary 765
766 - def dict(self):
767 """ 768 Return a deepcopy of self as a dictionary. 769 770 All members that are ``Section`` instances are recursively turned to 771 ordinary dictionaries - by calling their ``dict`` method. 772 773 >>> n = a.dict() 774 >>> n == a 775 1 776 >>> n is a 777 0 778 """ 779 newdict = {} 780 for entry in self: 781 this_entry = self[entry] 782 if isinstance(this_entry, Section): 783 this_entry = this_entry.dict() 784 elif isinstance(this_entry, list): 785 # create a copy rather than a reference 786 this_entry = list(this_entry) 787 elif isinstance(this_entry, tuple): 788 # create a copy rather than a reference 789 this_entry = tuple(this_entry) 790 newdict[entry] = this_entry 791 return newdict
792 793
794 - def merge(self, indict):
795 """ 796 A recursive update - useful for merging config files. 797 798 >>> a = '''[section1] 799 ... option1 = True 800 ... [[subsection]] 801 ... more_options = False 802 ... # end of file'''.splitlines() 803 >>> b = '''# File is user.ini 804 ... [section1] 805 ... option1 = False 806 ... # end of file'''.splitlines() 807 >>> c1 = ConfigObj(b) 808 >>> c2 = ConfigObj(a) 809 >>> c2.merge(c1) 810 >>> c2 811 ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}) 812 """ 813 for key, val in indict.items(): 814 if (key in self and isinstance(self[key], dict) and 815 isinstance(val, dict)): 816 self[key].merge(val) 817 else: 818 self[key] = val
819 820
821 - def rename(self, oldkey, newkey):
822 """ 823 Change a keyname to another, without changing position in sequence. 824 825 Implemented so that transformations can be made on keys, 826 as well as on values. (used by encode and decode) 827 828 Also renames comments. 829 """ 830 if oldkey in self.scalars: 831 the_list = self.scalars 832 elif oldkey in self.sections: 833 the_list = self.sections 834 else: 835 raise KeyError('Key "%s" not found.' % oldkey) 836 pos = the_list.index(oldkey) 837 # 838 val = self[oldkey] 839 dict.__delitem__(self, oldkey) 840 dict.__setitem__(self, newkey, val) 841 the_list.remove(oldkey) 842 the_list.insert(pos, newkey) 843 comm = self.comments[oldkey] 844 inline_comment = self.inline_comments[oldkey] 845 del self.comments[oldkey] 846 del self.inline_comments[oldkey] 847 self.comments[newkey] = comm 848 self.inline_comments[newkey] = inline_comment
849 850
851 - def walk(self, function, raise_errors=True, 852 call_on_sections=False, **keywargs):
853 """ 854 Walk every member and call a function on the keyword and value. 855 856 Return a dictionary of the return values 857 858 If the function raises an exception, raise the errror 859 unless ``raise_errors=False``, in which case set the return value to 860 ``False``. 861 862 Any unrecognised keyword arguments you pass to walk, will be pased on 863 to the function you pass in. 864 865 Note: if ``call_on_sections`` is ``True`` then - on encountering a 866 subsection, *first* the function is called for the *whole* subsection, 867 and then recurses into it's members. This means your function must be 868 able to handle strings, dictionaries and lists. This allows you 869 to change the key of subsections as well as for ordinary members. The 870 return value when called on the whole subsection has to be discarded. 871 872 See the encode and decode methods for examples, including functions. 873 874 .. admonition:: caution 875 876 You can use ``walk`` to transform the names of members of a section 877 but you mustn't add or delete members. 878 879 >>> config = '''[XXXXsection] 880 ... XXXXkey = XXXXvalue'''.splitlines() 881 >>> cfg = ConfigObj(config) 882 >>> cfg 883 ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}}) 884 >>> def transform(section, key): 885 ... val = section[key] 886 ... newkey = key.replace('XXXX', 'CLIENT1') 887 ... section.rename(key, newkey) 888 ... if isinstance(val, (tuple, list, dict)): 889 ... pass 890 ... else: 891 ... val = val.replace('XXXX', 'CLIENT1') 892 ... section[newkey] = val 893 >>> cfg.walk(transform, call_on_sections=True) 894 {'CLIENT1section': {'CLIENT1key': None}} 895 >>> cfg 896 ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}) 897 """ 898 out = {} 899 # scalars first 900 for i in range(len(self.scalars)): 901 entry = self.scalars[i] 902 try: 903 val = function(self, entry, **keywargs) 904 # bound again in case name has changed 905 entry = self.scalars[i] 906 out[entry] = val 907 except Exception: 908 if raise_errors: 909 raise 910 else: 911 entry = self.scalars[i] 912 out[entry] = False 913 # then sections 914 for i in range(len(self.sections)): 915 entry = self.sections[i] 916 if call_on_sections: 917 try: 918 function(self, entry, **keywargs) 919 except Exception: 920 if raise_errors: 921 raise 922 else: 923 entry = self.sections[i] 924 out[entry] = False 925 # bound again in case name has changed 926 entry = self.sections[i] 927 # previous result is discarded 928 out[entry] = self[entry].walk( 929 function, 930 raise_errors=raise_errors, 931 call_on_sections=call_on_sections, 932 **keywargs) 933 return out
934 935
936 - def as_bool(self, key):
937 """ 938 Accepts a key as input. The corresponding value must be a string or 939 the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to 940 retain compatibility with Python 2.2. 941 942 If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns 943 ``True``. 944 945 If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns 946 ``False``. 947 948 ``as_bool`` is not case sensitive. 949 950 Any other input will raise a ``ValueError``. 951 952 >>> a = ConfigObj() 953 >>> a['a'] = 'fish' 954 >>> a.as_bool('a') 955 Traceback (most recent call last): 956 ValueError: Value "fish" is neither True nor False 957 >>> a['b'] = 'True' 958 >>> a.as_bool('b') 959 1 960 >>> a['b'] = 'off' 961 >>> a.as_bool('b') 962 0 963 """ 964 val = self[key] 965 if val == True: 966 return True 967 elif val == False: 968 return False 969 else: 970 try: 971 if not isinstance(val, basestring): 972 # TODO: Why do we raise a KeyError here? 973 raise KeyError() 974 else: 975 return self.main._bools[val.lower()] 976 except KeyError: 977 raise ValueError('Value "%s" is neither True nor False' % val)
978 979
980 - def as_int(self, key):
981 """ 982 A convenience method which coerces the specified value to an integer. 983 984 If the value is an invalid literal for ``int``, a ``ValueError`` will 985 be raised. 986 987 >>> a = ConfigObj() 988 >>> a['a'] = 'fish' 989 >>> a.as_int('a') 990 Traceback (most recent call last): 991 ValueError: invalid literal for int() with base 10: 'fish' 992 >>> a['b'] = '1' 993 >>> a.as_int('b') 994 1 995 >>> a['b'] = '3.2' 996 >>> a.as_int('b') 997 Traceback (most recent call last): 998 ValueError: invalid literal for int() with base 10: '3.2' 999 """ 1000 return int(self[key])
1001 1002
1003 - def as_float(self, key):
1004 """ 1005 A convenience method which coerces the specified value to a float. 1006 1007 If the value is an invalid literal for ``float``, a ``ValueError`` will 1008 be raised. 1009 1010 >>> a = ConfigObj() 1011 >>> a['a'] = 'fish' 1012 >>> a.as_float('a') 1013 Traceback (most recent call last): 1014 ValueError: invalid literal for float(): fish 1015 >>> a['b'] = '1' 1016 >>> a.as_float('b') 1017 1.0 1018 >>> a['b'] = '3.2' 1019 >>> a.as_float('b') 1020 3.2000000000000002 1021 """ 1022 return float(self[key])
1023 1024
1025 - def as_list(self, key):
1026 """ 1027 A convenience method which fetches the specified value, guaranteeing 1028 that it is a list. 1029 1030 >>> a = ConfigObj() 1031 >>> a['a'] = 1 1032 >>> a.as_list('a') 1033 [1] 1034 >>> a['a'] = (1,) 1035 >>> a.as_list('a') 1036 [1] 1037 >>> a['a'] = [1] 1038 >>> a.as_list('a') 1039 [1] 1040 """ 1041 result = self[key] 1042 if isinstance(result, (tuple, list)): 1043 return list(result) 1044 return [result]
1045 1046
1047 - def restore_default(self, key):
1048 """ 1049 Restore (and return) default value for the specified key. 1050 1051 This method will only work for a ConfigObj that was created 1052 with a configspec and has been validated. 1053 1054 If there is no default value for this key, ``KeyError`` is raised. 1055 """ 1056 default = self.default_values[key] 1057 dict.__setitem__(self, key, default) 1058 if key not in self.defaults: 1059 self.defaults.append(key) 1060 return default
1061 1062
1063 - def restore_defaults(self):
1064 """ 1065 Recursively restore default values to all members 1066 that have them. 1067 1068 This method will only work for a ConfigObj that was created 1069 with a configspec and has been validated. 1070 1071 It doesn't delete or modify entries without default values. 1072 """ 1073 for key in self.default_values: 1074 self.restore_default(key) 1075 1076 for section in self.sections: 1077 self[section].restore_defaults()
1078 1079
1080 -class ConfigObj(Section):
1081 """An object to read, create, and write config files.""" 1082 1083 _keyword = re.compile(r'''^ # line start 1084 (\s*) # indentation 1085 ( # keyword 1086 (?:".*?")| # double quotes 1087 (?:'.*?')| # single quotes 1088 (?:[^'"=].*?) # no quotes 1089 ) 1090 \s*=\s* # divider 1091 (.*) # value (including list values and comments) 1092 $ # line end 1093 ''', 1094 re.VERBOSE) 1095 1096 _sectionmarker = re.compile(r'''^ 1097 (\s*) # 1: indentation 1098 ((?:\[\s*)+) # 2: section marker open 1099 ( # 3: section name open 1100 (?:"\s*\S.*?\s*")| # at least one non-space with double quotes 1101 (?:'\s*\S.*?\s*')| # at least one non-space with single quotes 1102 (?:[^'"\s].*?) # at least one non-space unquoted 1103 ) # section name close 1104 ((?:\s*\])+) # 4: section marker close 1105 \s*(\#.*)? # 5: optional comment 1106 $''', 1107 re.VERBOSE) 1108 1109 # this regexp pulls list values out as a single string 1110 # or single values and comments 1111 # FIXME: this regex adds a '' to the end of comma terminated lists 1112 # workaround in ``_handle_value`` 1113 _valueexp = re.compile(r'''^ 1114 (?: 1115 (?: 1116 ( 1117 (?: 1118 (?: 1119 (?:".*?")| # double quotes 1120 (?:'.*?')| # single quotes 1121 (?:[^'",\#][^,\#]*?) # unquoted 1122 ) 1123 \s*,\s* # comma 1124 )* # match all list items ending in a comma (if any) 1125 ) 1126 ( 1127 (?:".*?")| # double quotes 1128 (?:'.*?')| # single quotes 1129 (?:[^'",\#\s][^,]*?)| # unquoted 1130 (?:(?<!,)) # Empty value 1131 )? # last item in a list - or string value 1132 )| 1133 (,) # alternatively a single comma - empty list 1134 ) 1135 \s*(\#.*)? # optional comment 1136 $''', 1137 re.VERBOSE) 1138 1139 # use findall to get the members of a list value 1140 _listvalueexp = re.compile(r''' 1141 ( 1142 (?:".*?")| # double quotes 1143 (?:'.*?')| # single quotes 1144 (?:[^'",\#]?.*?) # unquoted 1145 ) 1146 \s*,\s* # comma 1147 ''', 1148 re.VERBOSE) 1149 1150 # this regexp is used for the value 1151 # when lists are switched off 1152 _nolistvalue = re.compile(r'''^ 1153 ( 1154 (?:".*?")| # double quotes 1155 (?:'.*?')| # single quotes 1156 (?:[^'"\#].*?)| # unquoted 1157 (?:) # Empty value 1158 ) 1159 \s*(\#.*)? # optional comment 1160 $''', 1161 re.VERBOSE) 1162 1163 # regexes for finding triple quoted values on one line 1164 _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$") 1165 _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$') 1166 _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$") 1167 _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$') 1168 1169 _triple_quote = { 1170 "'''": (_single_line_single, _multi_line_single), 1171 '"""': (_single_line_double, _multi_line_double), 1172 } 1173 1174 # Used by the ``istrue`` Section method 1175 _bools = { 1176 'yes': True, 'no': False, 1177 'on': True, 'off': False, 1178 '1': True, '0': False, 1179 'true': True, 'false': False, 1180 } 1181 1182
1183 - def __init__(self, infile=None, options=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 Parse a config file or create a config file object. 1190 1191 ``ConfigObj(infile=None, configspec=None, encoding=None, 1192 interpolation=True, raise_errors=False, list_values=True, 1193 create_empty=False, file_error=False, stringify=True, 1194 indent_type=None, default_encoding=None, unrepr=False, 1195 write_empty_values=False, _inspec=False)`` 1196 """ 1197 self._inspec = _inspec 1198 # init the superclass 1199 Section.__init__(self, self, 0, self) 1200 1201 infile = infile or [] 1202 1203 _options = {'configspec': configspec, 1204 'encoding': encoding, 'interpolation': interpolation, 1205 'raise_errors': raise_errors, 'list_values': list_values, 1206 'create_empty': create_empty, 'file_error': file_error, 1207 'stringify': stringify, 'indent_type': indent_type, 1208 'default_encoding': default_encoding, 'unrepr': unrepr, 1209 'write_empty_values': write_empty_values} 1210 1211 if options is None: 1212 options = _options 1213 else: 1214 import warnings 1215 warnings.warn('Passing in an options dictionary to ConfigObj() is ' 1216 'deprecated. Use **options instead.', 1217 DeprecationWarning, stacklevel=2) 1218 1219 # TODO: check the values too. 1220 for entry in options: 1221 if entry not in OPTION_DEFAULTS: 1222 raise TypeError('Unrecognised option "%s".' % entry) 1223 for entry, value in OPTION_DEFAULTS.items(): 1224 if entry not in options: 1225 options[entry] = value 1226 keyword_value = _options[entry] 1227 if value != keyword_value: 1228 options[entry] = keyword_value 1229 1230 # XXXX this ignores an explicit list_values = True in combination 1231 # with _inspec. The user should *never* do that anyway, but still... 1232 if _inspec: 1233 options['list_values'] = False 1234 1235 self._initialise(options) 1236 configspec = options['configspec'] 1237 self._original_configspec = configspec 1238 self._load(infile, configspec)
1239 1240
1241 - def _load(self, infile, configspec):
1242 if isinstance(infile, basestring): 1243 self.filename = infile 1244 if os.path.isfile(infile): 1245 h = open(infile, 'rb') 1246 infile = h.read() or [] 1247 h.close() 1248 elif self.file_error: 1249 # raise an error if the file doesn't exist 1250 raise IOError('Config file not found: "%s".' % self.filename) 1251 else: 1252 # file doesn't already exist 1253 if self.create_empty: 1254 # this is a good test that the filename specified 1255 # isn't impossible - like on a non-existent device 1256 h = open(infile, 'w') 1257 h.write('') 1258 h.close() 1259 infile = [] 1260 1261 elif isinstance(infile, (list, tuple)): 1262 infile = list(infile) 1263 1264 elif isinstance(infile, dict): 1265 # initialise self 1266 # the Section class handles creating subsections 1267 if isinstance(infile, ConfigObj): 1268 # get a copy of our ConfigObj 1269 def set_section(in_section, this_section): 1270 for entry in in_section.scalars: 1271 this_section[entry] = in_section[entry] 1272 for section in in_section.sections: 1273 this_section[section] = {} 1274 set_section(in_section[section], this_section[section])
1275 set_section(infile, self) 1276 1277 else: 1278 for entry in infile: 1279 self[entry] = infile[entry] 1280 del self._errors 1281 1282 if configspec is not None: 1283 self._handle_configspec(configspec) 1284 else: 1285 self.configspec = None 1286 return 1287 1288 elif getattr(infile, 'read', MISSING) is not MISSING: 1289 # This supports file like objects 1290 infile = infile.read() or [] 1291 # needs splitting into lines - but needs doing *after* decoding 1292 # in case it's not an 8 bit encoding 1293 else: 1294 raise TypeError('infile must be a filename, file like object, or list of lines.') 1295 1296 if infile: 1297 # don't do it for the empty ConfigObj 1298 infile = self._handle_bom(infile) 1299 # infile is now *always* a list 1300 # 1301 # Set the newlines attribute (first line ending it finds) 1302 # and strip trailing '\n' or '\r' from lines 1303 for line in infile: 1304 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')): 1305 continue 1306 for end in ('\r\n', '\n', '\r'): 1307 if line.endswith(end): 1308 self.newlines = end 1309 break 1310 break 1311 1312 infile = [line.rstrip('\r\n') for line in infile] 1313 1314 self._parse(infile) 1315 # if we had any errors, now is the time to raise them 1316 if self._errors: 1317 info = "at line %s." % self._errors[0].line_number 1318 if len(self._errors) > 1: 1319 msg = "Parsing failed with several errors.\nFirst error %s" % info 1320 error = ConfigObjError(msg) 1321 else: 1322 error = self._errors[0] 1323 # set the errors attribute; it's a list of tuples: 1324 # (error_type, message, line_number) 1325 error.errors = self._errors 1326 # set the config attribute 1327 error.config = self 1328 raise error 1329 # delete private attributes 1330 del self._errors 1331 1332 if configspec is None: 1333 self.configspec = None 1334 else: 1335 self._handle_configspec(configspec)
1336 1337
1338 - def _initialise(self, options=None):
1339 if options is None: 1340 options = OPTION_DEFAULTS 1341 1342 # initialise a few variables 1343 self.filename = None 1344 self._errors = [] 1345 self.raise_errors = options['raise_errors'] 1346 self.interpolation = options['interpolation'] 1347 self.list_values = options['list_values'] 1348 self.create_empty = options['create_empty'] 1349 self.file_error = options['file_error'] 1350 self.stringify = options['stringify'] 1351 self.indent_type = options['indent_type'] 1352 self.encoding = options['encoding'] 1353 self.default_encoding = options['default_encoding'] 1354 self.BOM = False 1355 self.newlines = None 1356 self.write_empty_values = options['write_empty_values'] 1357 self.unrepr = options['unrepr'] 1358 1359 self.initial_comment = [] 1360 self.final_comment = [] 1361 self.configspec = None 1362 1363 if self._inspec: 1364 self.list_values = False 1365 1366 # Clear section attributes as well 1367 Section._initialise(self)
1368 1369
1370 - def __repr__(self):
1371 return ('ConfigObj({%s})' % 1372 ', '.join([('%s: %s' % (repr(key), repr(self[key]))) 1373 for key in (self.scalars + self.sections)]))
1374 1375
1376 - def _handle_bom(self, infile):
1377 """ 1378 Handle any BOM, and decode if necessary. 1379 1380 If an encoding is specified, that *must* be used - but the BOM should 1381 still be removed (and the BOM attribute set). 1382 1383 (If the encoding is wrongly specified, then a BOM for an alternative 1384 encoding won't be discovered or removed.) 1385 1386 If an encoding is not specified, UTF8 or UTF16 BOM will be detected and 1387 removed. The BOM attribute will be set. UTF16 will be decoded to 1388 unicode. 1389 1390 NOTE: This method must not be called with an empty ``infile``. 1391 1392 Specifying the *wrong* encoding is likely to cause a 1393 ``UnicodeDecodeError``. 1394 1395 ``infile`` must always be returned as a list of lines, but may be 1396 passed in as a single string. 1397 """ 1398 if ((self.encoding is not None) and 1399 (self.encoding.lower() not in BOM_LIST)): 1400 # No need to check for a BOM 1401 # the encoding specified doesn't have one 1402 # just decode 1403 return self._decode(infile, self.encoding) 1404 1405 if isinstance(infile, (list, tuple)): 1406 line = infile[0] 1407 else: 1408 line = infile 1409 if self.encoding is not None: 1410 # encoding explicitly supplied 1411 # And it could have an associated BOM 1412 # TODO: if encoding is just UTF16 - we ought to check for both 1413 # TODO: big endian and little endian versions. 1414 enc = BOM_LIST[self.encoding.lower()] 1415 if enc == 'utf_16': 1416 # For UTF16 we try big endian and little endian 1417 for BOM, (encoding, final_encoding) in BOMS.items(): 1418 if not final_encoding: 1419 # skip UTF8 1420 continue 1421 if infile.startswith(BOM): 1422 ### BOM discovered 1423 ##self.BOM = True 1424 # Don't need to remove BOM 1425 return self._decode(infile, encoding) 1426 1427 # If we get this far, will *probably* raise a DecodeError 1428 # As it doesn't appear to start with a BOM 1429 return self._decode(infile, self.encoding) 1430 1431 # Must be UTF8 1432 BOM = BOM_SET[enc] 1433 if not line.startswith(BOM): 1434 return self._decode(infile, self.encoding) 1435 1436 newline = line[len(BOM):] 1437 1438 # BOM removed 1439 if isinstance(infile, (list, tuple)): 1440 infile[0] = newline 1441 else: 1442 infile = newline 1443 self.BOM = True 1444 return self._decode(infile, self.encoding) 1445 1446 # No encoding specified - so we need to check for UTF8/UTF16 1447 for BOM, (encoding, final_encoding) in BOMS.items(): 1448 if not line.startswith(BOM): 1449 continue 1450 else: 1451 # BOM discovered 1452 self.encoding = final_encoding 1453 if not final_encoding: 1454 self.BOM = True 1455 # UTF8 1456 # remove BOM 1457 newline = line[len(BOM):] 1458 if isinstance(infile, (list, tuple)): 1459 infile[0] = newline 1460 else: 1461 infile = newline 1462 # UTF8 - don't decode 1463 if isinstance(infile, basestring): 1464 return infile.splitlines(True) 1465 else: 1466 return infile 1467 # UTF16 - have to decode 1468 return self._decode(infile, encoding) 1469 1470 # No BOM discovered and no encoding specified, just return 1471 if isinstance(infile, basestring): 1472 # infile read from a file will be a single string 1473 return infile.splitlines(True) 1474 return infile
1475 1476
1477 - def _a_to_u(self, aString):
1478 """Decode ASCII strings to unicode if a self.encoding is specified.""" 1479 if self.encoding: 1480 return aString.decode('ascii') 1481 else: 1482 return aString
1483 1484
1485 - def _decode(self, infile, encoding):
1486 """ 1487 Decode infile to unicode. Using the specified encoding. 1488 1489 if is a string, it also needs converting to a list. 1490 """ 1491 if isinstance(infile, basestring): 1492 # can't be unicode 1493 # NOTE: Could raise a ``UnicodeDecodeError`` 1494 return infile.decode(encoding).splitlines(True) 1495 for i, line in enumerate(infile): 1496 if not isinstance(line, unicode): 1497 # NOTE: The isinstance test here handles mixed lists of unicode/string 1498 # NOTE: But the decode will break on any non-string values 1499 # NOTE: Or could raise a ``UnicodeDecodeError`` 1500 infile[i] = line.decode(encoding) 1501 return infile
1502 1503
1504 - def _decode_element(self, line):
1505 """Decode element to unicode if necessary.""" 1506 if not self.encoding: 1507 return line 1508 if isinstance(line, str) and self.default_encoding: 1509 return line.decode(self.default_encoding) 1510 return line
1511 1512
1513 - def _str(self, value):
1514 """ 1515 Used by ``stringify`` within validate, to turn non-string values 1516 into strings. 1517 """ 1518 if not isinstance(value, basestring): 1519 return str(value) 1520 else: 1521 return value
1522 1523
1524 - def _parse(self, infile):
1525 """Actually parse the config file.""" 1526 temp_list_values = self.list_values 1527 if self.unrepr: 1528 self.list_values = False 1529 1530 comment_list = [] 1531 done_start = False 1532 this_section = self 1533 maxline = len(infile) - 1 1534 cur_index = -1 1535 reset_comment = False 1536 1537 while cur_index < maxline: 1538 if reset_comment: 1539 comment_list = [] 1540 cur_index += 1 1541 line = infile[cur_index] 1542 sline = line.strip() 1543 # do we have anything on the line ? 1544 if not sline or sline.startswith('#'): 1545 reset_comment = False 1546 comment_list.append(line) 1547 continue 1548 1549 if not done_start: 1550 # preserve initial comment 1551 self.initial_comment = comment_list 1552 comment_list = [] 1553 done_start = True 1554 1555 reset_comment = True 1556 # first we check if it's a section marker 1557 mat = self._sectionmarker.match(line) 1558 if mat is not None: 1559 # is a section line 1560 (indent, sect_open, sect_name, sect_close, comment) = mat.groups() 1561 if indent and (self.indent_type is None): 1562 self.indent_type = indent 1563 cur_depth = sect_open.count('[') 1564 if cur_depth != sect_close.count(']'): 1565 self._handle_error("Cannot compute the section depth at line %s.", 1566 NestingError, infile, cur_index) 1567 continue 1568 1569 if cur_depth < this_section.depth: 1570 # the new section is dropping back to a previous level 1571 try: 1572 parent = self._match_depth(this_section, 1573 cur_depth).parent 1574 except SyntaxError: 1575 self._handle_error("Cannot compute nesting level at line %s.", 1576 NestingError, infile, cur_index) 1577 continue 1578 elif cur_depth == this_section.depth: 1579 # the new section is a sibling of the current section 1580 parent = this_section.parent 1581 elif cur_depth == this_section.depth + 1: 1582 # the new section is a child the current section 1583 parent = this_section 1584 else: 1585 self._handle_error("Section too nested at line %s.", 1586 NestingError, infile, cur_index) 1587 1588 sect_name = self._unquote(sect_name) 1589 if sect_name in parent: 1590 self._handle_error('Duplicate section name at line %s.', 1591 DuplicateError, infile, cur_index) 1592 continue 1593 1594 # create the new section 1595 this_section = Section( 1596 parent, 1597 cur_depth, 1598 self, 1599 name=sect_name) 1600 parent[sect_name] = this_section 1601 parent.inline_comments[sect_name] = comment 1602 parent.comments[sect_name] = comment_list 1603 continue 1604 # 1605 # it's not a section marker, 1606 # so it should be a valid ``key = value`` line 1607 mat = self._keyword.match(line) 1608 if mat is None: 1609 # it neither matched as a keyword 1610 # or a section marker 1611 self._handle_error( 1612 'Invalid line at line "%s".', 1613 ParseError, infile, cur_index) 1614 else: 1615 # is a keyword value 1616 # value will include any inline comment 1617 (indent, key, value) = mat.groups() 1618 if indent and (self.indent_type is None): 1619 self.indent_type = indent 1620 # check for a multiline value 1621 if value[:3] in ['"""', "'''"]: 1622 try: 1623 value, comment, cur_index = self._multiline( 1624 value, infile, cur_index, maxline) 1625 except SyntaxError: 1626 self._handle_error( 1627 'Parse error in value at line %s.', 1628 ParseError, infile, cur_index) 1629 continue 1630 else: 1631 if self.unrepr: 1632 comment = '' 1633 try: 1634 value = unrepr(value) 1635 except Exception, e: 1636 if type(e) == UnknownType: 1637 msg = 'Unknown name or type in value at line %s.' 1638 else: 1639 msg = 'Parse error in value at line %s.' 1640 self._handle_error(msg, UnreprError, infile, 1641 cur_index) 1642 continue 1643 else: 1644 if self.unrepr: 1645 comment = '' 1646 try: 1647 value = unrepr(value) 1648 except Exception, e: 1649 if isinstance(e, UnknownType): 1650 msg = 'Unknown name or type in value at line %s.' 1651 else: 1652 msg = 'Parse error in value at line %s.' 1653 self._handle_error(msg, UnreprError, infile, 1654 cur_index) 1655 continue 1656 else: 1657 # extract comment and lists 1658 try: 1659 (value, comment) = self._handle_value(value) 1660 except SyntaxError: 1661 self._handle_error( 1662 'Parse error in value at line %s.', 1663 ParseError, infile, cur_index) 1664 continue 1665 # 1666 key = self._unquote(key) 1667 if key in this_section: 1668 self._handle_error( 1669 'Duplicate keyword name at line %s.', 1670 DuplicateError, infile, cur_index) 1671 continue 1672 # add the key. 1673 # we set unrepr because if we have got this far we will never 1674 # be creating a new section 1675 this_section.__setitem__(key, value, unrepr=True) 1676 this_section.inline_comments[key] = comment 1677 this_section.comments[key] = comment_list 1678 continue 1679 # 1680 if self.indent_type is None: 1681 # no indentation used, set the type accordingly 1682 self.indent_type = '' 1683 1684 # preserve the final comment 1685 if not self and not self.initial_comment: 1686 self.initial_comment = comment_list 1687 elif not reset_comment: 1688 self.final_comment = comment_list 1689 self.list_values = temp_list_values
1690 1691
1692 - def _match_depth(self, sect, depth):
1693 """ 1694 Given a section and a depth level, walk back through the sections 1695 parents to see if the depth level matches a previous section. 1696 1697 Return a reference to the right section, 1698 or raise a SyntaxError. 1699 """ 1700 while depth < sect.depth: 1701 if sect is sect.parent: 1702 # we've reached the top level already 1703 raise SyntaxError() 1704 sect = sect.parent 1705 if sect.depth == depth: 1706 return sect 1707 # shouldn't get here 1708 raise SyntaxError()
1709 1710
1711 - def _handle_error(self, text, ErrorClass, infile, cur_index):
1712 """ 1713 Handle an error according to the error settings. 1714 1715 Either raise the error or store it. 1716 The error will have occured at ``cur_index`` 1717 """ 1718 line = infile[cur_index] 1719 cur_index += 1 1720 message = text % cur_index 1721 error = ErrorClass(message, cur_index, line) 1722 if self.raise_errors: 1723 # raise the error - parsing stops here 1724 raise error 1725 # store the error 1726 # reraise when parsing has finished 1727 self._errors.append(error)
1728 1729
1730 - def _unquote(self, value):
1731 """Return an unquoted version of a value""" 1732 if not value: 1733 # should only happen during parsing of lists 1734 raise SyntaxError 1735 if (value[0] == value[-1]) and (value[0] in ('"', "'")): 1736 value = value[1:-1] 1737 return value
1738 1739
1740 - def _quote(self, value, multiline=True):
1741 """ 1742 Return a safely quoted version of a value. 1743 1744 Raise a ConfigObjError if the value cannot be safely quoted. 1745 If multiline is ``True`` (default) then use triple quotes 1746 if necessary. 1747 1748 * Don't quote values that don't need it. 1749 * Recursively quote members of a list and return a comma joined list. 1750 * Multiline is ``False`` for lists. 1751 * Obey list syntax for empty and single member lists. 1752 1753 If ``list_values=False`` then the value is only quoted if it contains 1754 a ``\\n`` (is multiline) or '#'. 1755 1756 If ``write_empty_values`` is set, and the value is an empty string, it 1757 won't be quoted. 1758 """ 1759 if multiline and self.write_empty_values and value == '': 1760 # Only if multiline is set, so that it is used for values not 1761 # keys, and not values that are part of a list 1762 return '' 1763 1764 if multiline and isinstance(value, (list, tuple)): 1765 if not value: 1766 return ',' 1767 elif len(value) == 1: 1768 return self._quote(value[0], multiline=False) + ',' 1769 return ', '.join([self._quote(val, multiline=False) 1770 for val in value]) 1771 if not isinstance(value, basestring): 1772 if self.stringify: 1773 value = str(value) 1774 else: 1775 raise TypeError('Value "%s" is not a string.' % value) 1776 1777 if not value: 1778 return '""' 1779 1780 no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value 1781 need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value )) 1782 hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value) 1783 check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote 1784 1785 if check_for_single: 1786 if not self.list_values: 1787 # we don't quote if ``list_values=False`` 1788 quot = noquot 1789 # for normal values either single or double quotes will do 1790 elif '\n' in value: 1791 # will only happen if multiline is off - e.g. '\n' in key 1792 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1793 elif ((value[0] not in wspace_plus) and 1794 (value[-1] not in wspace_plus) and 1795 (',' not in value)): 1796 quot = noquot 1797 else: 1798 quot = self._get_single_quote(value) 1799 else: 1800 # if value has '\n' or "'" *and* '"', it will need triple quotes 1801 quot = self._get_triple_quote(value) 1802 1803 if quot == noquot and '#' in value and self.list_values: 1804 quot = self._get_single_quote(value) 1805 1806 return quot % value
1807 1808
1809 - def _get_single_quote(self, value):
1810 if ("'" in value) and ('"' in value): 1811 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1812 elif '"' in value: 1813 quot = squot 1814 else: 1815 quot = dquot 1816 return quot
1817 1818
1819 - def _get_triple_quote(self, value):
1820 if (value.find('"""') != -1) and (value.find("'''") != -1): 1821 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value) 1822 if value.find('"""') == -1: 1823 quot = tdquot 1824 else: 1825 quot = tsquot 1826 return quot
1827 1828
1829 - def _handle_value(self, value):
1830 """ 1831 Given a value string, unquote, remove comment, 1832 handle lists. (including empty and single member lists) 1833 """ 1834 if self._inspec: 1835 # Parsing a configspec so don't handle comments 1836 return (value, '') 1837 # do we look for lists in values ? 1838 if not self.list_values: 1839 mat = self._nolistvalue.match(value) 1840 if mat is None: 1841 raise SyntaxError() 1842 # NOTE: we don't unquote here 1843 return mat.groups() 1844 # 1845 mat = self._valueexp.match(value) 1846 if mat is None: 1847 # the value is badly constructed, probably badly quoted, 1848 # or an invalid list 1849 raise SyntaxError() 1850 (list_values, single, empty_list, comment) = mat.groups() 1851 if (list_values == '') and (single is None): 1852 # change this if you want to accept empty values 1853 raise SyntaxError() 1854 # NOTE: note there is no error handling from here if the regex 1855 # is wrong: then incorrect values will slip through 1856 if empty_list is not None: 1857 # the single comma - meaning an empty list 1858 return ([], comment) 1859 if single is not None: 1860 # handle empty values 1861 if list_values and not single: 1862 # FIXME: the '' is a workaround because our regex now matches 1863 # '' at the end of a list if it has a trailing comma 1864 single = None 1865 else: 1866 single = single or '""' 1867 single = self._unquote(single) 1868 if list_values == '': 1869 # not a list value 1870 return (single, comment) 1871 the_list = self._listvalueexp.findall(list_values) 1872 the_list = [self._unquote(val) for val in the_list] 1873 if single is not None: 1874 the_list += [single] 1875 return (the_list, comment)
1876 1877
1878 - def _multiline(self, value, infile, cur_index, maxline):
1879 """Extract the value, where we are in a multiline situation.""" 1880 quot = value[:3] 1881 newvalue = value[3:] 1882 single_line = self._triple_quote[quot][0] 1883 multi_line = self._triple_quote[quot][1] 1884 mat = single_line.match(value) 1885 if mat is not None: 1886 retval = list(mat.groups()) 1887 retval.append(cur_index) 1888 return retval 1889 elif newvalue.find(quot) != -1: 1890 # somehow the triple quote is missing 1891 raise SyntaxError() 1892 # 1893 while cur_index < maxline: 1894 cur_index += 1 1895 newvalue += '\n' 1896 line = infile[cur_index] 1897 if line.find(quot) == -1: 1898 newvalue += line 1899 else: 1900 # end of multiline, process it 1901 break 1902 else: 1903 # we've got to the end of the config, oops... 1904 raise SyntaxError() 1905 mat = multi_line.match(line) 1906 if mat is None: 1907 # a badly formed line 1908 raise SyntaxError() 1909 (value, comment) = mat.groups() 1910 return (newvalue + value, comment, cur_index)
1911 1912
1913 - def _handle_configspec(self, configspec):
1914 """Parse the configspec.""" 1915 # FIXME: Should we check that the configspec was created with the 1916 # correct settings ? (i.e. ``list_values=False``) 1917 if not isinstance(configspec, ConfigObj): 1918 try: 1919 configspec = ConfigObj(configspec, 1920 raise_errors=True, 1921 file_error=True, 1922 _inspec=True) 1923 except ConfigObjError, e: 1924 # FIXME: Should these errors have a reference 1925 # to the already parsed ConfigObj ? 1926 raise ConfigspecError('Parsing configspec failed: %s' % e) 1927 except IOError, e: 1928 raise IOError('Reading configspec failed: %s' % e) 1929 1930 self.configspec = configspec
1931 1932 1933
1934 - def _set_configspec(self, section, copy):
1935 """ 1936 Called by validate. Handles setting the configspec on subsections 1937 including sections to be validated by __many__ 1938 """ 1939 configspec = section.configspec 1940 many = configspec.get('__many__') 1941 if isinstance(many, dict): 1942 for entry in section.sections: 1943 if entry not in configspec: 1944 section[entry].configspec = many 1945 1946 for entry in configspec.sections: 1947 if entry == '__many__': 1948 continue 1949 if entry not in section: 1950 section[entry] = {} 1951 section[entry]._created = True 1952 if copy: 1953 # copy comments 1954 section.comments[entry] = configspec.comments.get(entry, []) 1955 section.inline_comments[entry] = configspec.inline_comments.get(entry, '') 1956 1957 # Could be a scalar when we expect a section 1958 if isinstance(section[entry], Section): 1959 section[entry].configspec = configspec[entry]
1960 1961
1962 - def _write_line(self, indent_string, entry, this_entry, comment):
1963 """Write an individual line, for the write method""" 1964 # NOTE: the calls to self._quote here handles non-StringType values. 1965 if not self.unrepr: 1966 val = self._decode_element(self._quote(this_entry)) 1967 else: 1968 val = repr(this_entry) 1969 return '%s%s%s%s%s' % (indent_string, 1970 self._decode_element(self._quote(entry, multiline=False)), 1971 self._a_to_u(' = '), 1972 val, 1973 self._decode_element(comment))
1974 1975
1976 - def _write_marker(self, indent_string, depth, entry, comment):
1977 """Write a section marker line""" 1978 return '%s%s%s%s%s' % (indent_string, 1979 self._a_to_u('[' * depth), 1980 self._quote(self._decode_element(entry), multiline=False), 1981 self._a_to_u(']' * depth), 1982 self._decode_element(comment))
1983 1984
1985 - def _handle_comment(self, comment):
1986 """Deal with a comment.""" 1987 if not comment: 1988 return '' 1989 start = self.indent_type 1990 if not comment.startswith('#'): 1991 start += self._a_to_u(' # ') 1992 return (start + comment)
1993 1994 1995 # Public methods 1996
1997 - def write(self, outfile=None, section=None):
1998 """ 1999 Write the current ConfigObj as a file 2000 2001 tekNico: FIXME: use StringIO instead of real files 2002 2003 >>> filename = a.filename 2004 >>> a.filename = 'test.ini' 2005 >>> a.write() 2006 >>> a.filename = filename 2007 >>> a == ConfigObj('test.ini', raise_errors=True) 2008 1 2009 """ 2010 if self.indent_type is None: 2011 # this can be true if initialised from a dictionary 2012 self.indent_type = DEFAULT_INDENT_TYPE 2013 2014 out = [] 2015 cs = self._a_to_u('#') 2016 csp = self._a_to_u('# ') 2017 if section is None: 2018 int_val = self.interpolation 2019 self.interpolation = False 2020 section = self 2021 for line in self.initial_comment: 2022 line = self._decode_element(line) 2023 stripped_line = line.strip() 2024 if stripped_line and not stripped_line.startswith(cs): 2025 line = csp + line 2026 out.append(line) 2027 2028 indent_string = self.indent_type * section.depth 2029 for entry in (section.scalars + section.sections): 2030 if entry in section.defaults: 2031 # don't write out default values 2032 continue 2033 for comment_line in section.comments[entry]: 2034 comment_line = self._decode_element(comment_line.lstrip()) 2035 if comment_line and not comment_line.startswith(cs): 2036 comment_line = csp + comment_line 2037 out.append(indent_string + comment_line) 2038 this_entry = section[entry] 2039 comment = self._handle_comment(section.inline_comments[entry]) 2040 2041 if isinstance(this_entry, dict): 2042 # a section 2043 out.append(self._write_marker( 2044 indent_string, 2045 this_entry.depth, 2046 entry, 2047 comment)) 2048 out.extend(self.write(section=this_entry)) 2049 else: 2050 out.append(self._write_line( 2051 indent_string, 2052 entry, 2053 this_entry, 2054 comment)) 2055 2056 if section is self: 2057 for line in self.final_comment: 2058 line = self._decode_element(line) 2059 stripped_line = line.strip() 2060 if stripped_line and not stripped_line.startswith(cs): 2061 line = csp + line 2062 out.append(line) 2063 self.interpolation = int_val 2064 2065 if section is not self: 2066 return out 2067 2068 if (self.filename is None) and (outfile is None): 2069 # output a list of lines 2070 # might need to encode 2071 # NOTE: This will *screw* UTF16, each line will start with the BOM 2072 if self.encoding: 2073 out = [l.encode(self.encoding) for l in out] 2074 if (self.BOM and ((self.encoding is None) or 2075 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): 2076 # Add the UTF8 BOM 2077 if not out: 2078 out.append('') 2079 out[0] = BOM_UTF8 + out[0] 2080 return out 2081 2082 # Turn the list to a string, joined with correct newlines 2083 newline = self.newlines or os.linesep 2084 output = self._a_to_u(newline).join(out) 2085 if self.encoding: 2086 output = output.encode(self.encoding) 2087 if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)): 2088 # Add the UTF8 BOM 2089 output = BOM_UTF8 + output 2090 2091 if not output.endswith(newline): 2092 output += newline 2093 if outfile is not None: 2094 outfile.write(output) 2095 else: 2096 h = open(self.filename, 'wb') 2097 h.write(output) 2098 h.close()
2099 2100
2101 - def validate(self, validator, preserve_errors=False, copy=False, 2102 section=None):
2103 """ 2104 Test the ConfigObj against a configspec. 2105 2106 It uses the ``validator`` object from *validate.py*. 2107 2108 To run ``validate`` on the current ConfigObj, call: :: 2109 2110 test = config.validate(validator) 2111 2112 (Normally having previously passed in the configspec when the ConfigObj 2113 was created - you can dynamically assign a dictionary of checks to the 2114 ``configspec`` attribute of a section though). 2115 2116 It returns ``True`` if everything passes, or a dictionary of 2117 pass/fails (True/False). If every member of a subsection passes, it 2118 will just have the value ``True``. (It also returns ``False`` if all 2119 members fail). 2120 2121 In addition, it converts the values from strings to their native 2122 types if their checks pass (and ``stringify`` is set). 2123 2124 If ``preserve_errors`` is ``True`` (``False`` is default) then instead 2125 of a marking a fail with a ``False``, it will preserve the actual 2126 exception object. This can contain info about the reason for failure. 2127 For example the ``VdtValueTooSmallError`` indicates that the value 2128 supplied was too small. If a value (or section) is missing it will 2129 still be marked as ``False``. 2130 2131 You must have the validate module to use ``preserve_errors=True``. 2132 2133 You can then use the ``flatten_errors`` function to turn your nested 2134 results dictionary into a flattened list of failures - useful for 2135 displaying meaningful error messages. 2136 """ 2137 if section is None: 2138 if self.configspec is None: 2139 raise ValueError('No configspec supplied.') 2140 if preserve_errors: 2141 # We do this once to remove a top level dependency on the validate module 2142 # Which makes importing configobj faster 2143 from validate import VdtMissingValue 2144 self._vdtMissingValue = VdtMissingValue 2145 2146 section = self 2147 2148 if copy: 2149 section.initial_comment = section.configspec.initial_comment 2150 section.final_comment = section.configspec.final_comment 2151 section.encoding = section.configspec.encoding 2152 section.BOM = section.configspec.BOM 2153 section.newlines = section.configspec.newlines 2154 section.indent_type = section.configspec.indent_type 2155 2156 # 2157 # section.default_values.clear() #?? 2158 configspec = section.configspec 2159 self._set_configspec(section, copy) 2160 2161 2162 def validate_entry(entry, spec, val, missing, ret_true, ret_false): 2163 section.default_values.pop(entry, None) 2164 2165 try: 2166 section.default_values[entry] = validator.get_default_value(configspec[entry]) 2167 except (KeyError, AttributeError, validator.baseErrorClass): 2168 # No default, bad default or validator has no 'get_default_value' 2169 # (e.g. SimpleVal) 2170 pass 2171 2172 try: 2173 check = validator.check(spec, 2174 val, 2175 missing=missing 2176 ) 2177 except validator.baseErrorClass, e: 2178 if not preserve_errors or isinstance(e, self._vdtMissingValue): 2179 out[entry] = False 2180 else: 2181 # preserve the error 2182 out[entry] = e 2183 ret_false = False 2184 ret_true = False 2185 else: 2186 ret_false = False 2187 out[entry] = True 2188 if self.stringify or missing: 2189 # if we are doing type conversion 2190 # or the value is a supplied default 2191 if not self.stringify: 2192 if isinstance(check, (list, tuple)): 2193 # preserve lists 2194 check = [self._str(item) for item in check] 2195 elif missing and check is None: 2196 # convert the None from a default to a '' 2197 check = '' 2198 else: 2199 check = self._str(check) 2200 if (check != val) or missing: 2201 section[entry] = check 2202 if not copy and missing and entry not in section.defaults: 2203 section.defaults.append(entry) 2204 return ret_true, ret_false
2205 2206 # 2207 out = {} 2208 ret_true = True 2209 ret_false = True 2210 2211 unvalidated = [k for k in section.scalars if k not in configspec] 2212 incorrect_sections = [k for k in configspec.sections if k in section.scalars] 2213 incorrect_scalars = [k for k in configspec.scalars if k in section.sections] 2214 2215 for entry in configspec.scalars: 2216 if entry in ('__many__', '___many___'): 2217 # reserved names 2218 continue 2219 if (not entry in section.scalars) or (entry in section.defaults): 2220 # missing entries 2221 # or entries from defaults 2222 missing = True 2223 val = None 2224 if copy and entry not in section.scalars: 2225 # copy comments 2226 section.comments[entry] = ( 2227 configspec.comments.get(entry, [])) 2228 section.inline_comments[entry] = ( 2229 configspec.inline_comments.get(entry, '')) 2230 # 2231 else: 2232 missing = False 2233 val = section[entry] 2234 2235 ret_true, ret_false = validate_entry(entry, configspec[entry], val, 2236 missing, ret_true, ret_false) 2237 2238 many = None 2239 if '__many__' in configspec.scalars: 2240 many = configspec['__many__'] 2241 elif '___many___' in configspec.scalars: 2242 many = configspec['___many___'] 2243 2244 if many is not None: 2245 for entry in unvalidated: 2246 val = section[entry] 2247 ret_true, ret_false = validate_entry(entry, many, val, False, 2248 ret_true, ret_false) 2249 unvalidated = [] 2250 2251 for entry in incorrect_scalars: 2252 ret_true = False 2253 if not preserve_errors: 2254 out[entry] = False 2255 else: 2256 ret_false = False 2257 msg = 'Value %r was provided as a section' % entry 2258 out[entry] = validator.baseErrorClass(msg) 2259 for entry in incorrect_sections: 2260 ret_true = False 2261 if not preserve_errors: 2262 out[entry] = False 2263 else: 2264 ret_false = False 2265 msg = 'Section %r was provided as a single value' % entry 2266 out[entry] = validator.baseErrorClass(msg) 2267 2268 # Missing sections will have been created as empty ones when the 2269 # configspec was read. 2270 for entry in section.sections: 2271 # FIXME: this means DEFAULT is not copied in copy mode 2272 if section is self and entry == 'DEFAULT': 2273 continue 2274 if section[entry].configspec is None: 2275 unvalidated.append(entry) 2276 continue 2277 if copy: 2278 section.comments[entry] = configspec.comments.get(entry, []) 2279 section.inline_comments[entry] = configspec.inline_comments.get(entry, '') 2280 check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) 2281 out[entry] = check 2282 if check == False: 2283 ret_true = False 2284 elif check == True: 2285 ret_false = False 2286 else: 2287 ret_true = False 2288 2289 section.extra_values = unvalidated 2290 if preserve_errors and not section._created: 2291 # If the section wasn't created (i.e. it wasn't missing) 2292 # then we can't return False, we need to preserve errors 2293 ret_false = False 2294 # 2295 if ret_false and preserve_errors and out: 2296 # If we are preserving errors, but all 2297 # the failures are from missing sections / values 2298 # then we can return False. Otherwise there is a 2299 # real failure that we need to preserve. 2300 ret_false = not any(out.values()) 2301 if ret_true: 2302 return True 2303 elif ret_false: 2304 return False 2305 return out 2306 2307
2308 - def reset(self):
2309 """Clear ConfigObj instance and restore to 'freshly created' state.""" 2310 self.clear() 2311 self._initialise() 2312 # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload) 2313 # requires an empty dictionary 2314 self.configspec = None 2315 # Just to be sure ;-) 2316 self._original_configspec = None
2317 2318
2319 - def reload(self):
2320 """ 2321 Reload a ConfigObj from file. 2322 2323 This method raises a ``ReloadError`` if the ConfigObj doesn't have 2324 a filename attribute pointing to a file. 2325 """ 2326 if not isinstance(self.filename, basestring): 2327 raise ReloadError() 2328 2329 filename = self.filename 2330 current_options = {} 2331 for entry in OPTION_DEFAULTS: 2332 if entry == 'configspec': 2333 continue 2334 current_options[entry] = getattr(self, entry) 2335 2336 configspec = self._original_configspec 2337 current_options['configspec'] = configspec 2338 2339 self.clear() 2340 self._initialise(current_options) 2341 self._load(filename, configspec)
2342 2343 2344
2345 -class SimpleVal(object):
2346 """ 2347 A simple validator. 2348 Can be used to check that all members expected are present. 2349 2350 To use it, provide a configspec with all your members in (the value given 2351 will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` 2352 method of your ``ConfigObj``. ``validate`` will return ``True`` if all 2353 members are present, or a dictionary with True/False meaning 2354 present/missing. (Whole missing sections will be replaced with ``False``) 2355 """ 2356
2357 - def __init__(self):
2358 self.baseErrorClass = ConfigObjError
2359
2360 - def check(self, check, member, missing=False):
2361 """A dummy check method, always returns the value unchanged.""" 2362 if missing: 2363 raise self.baseErrorClass() 2364 return member
2365 2366
2367 -def flatten_errors(cfg, res, levels=None, results=None):
2368 """ 2369 An example function that will turn a nested dictionary of results 2370 (as returned by ``ConfigObj.validate``) into a flat list. 2371 2372 ``cfg`` is the ConfigObj instance being checked, ``res`` is the results 2373 dictionary returned by ``validate``. 2374 2375 (This is a recursive function, so you shouldn't use the ``levels`` or 2376 ``results`` arguments - they are used by the function.) 2377 2378 Returns a list of keys that failed. Each member of the list is a tuple:: 2379 2380 ([list of sections...], key, result) 2381 2382 If ``validate`` was called with ``preserve_errors=False`` (the default) 2383 then ``result`` will always be ``False``. 2384 2385 *list of sections* is a flattened list of sections that the key was found 2386 in. 2387 2388 If the section was missing (or a section was expected and a scalar provided 2389 - or vice-versa) then key will be ``None``. 2390 2391 If the value (or section) was missing then ``result`` will be ``False``. 2392 2393 If ``validate`` was called with ``preserve_errors=True`` and a value 2394 was present, but failed the check, then ``result`` will be the exception 2395 object returned. You can use this as a string that describes the failure. 2396 2397 For example *The value "3" is of the wrong type*. 2398 """ 2399 if levels is None: 2400 # first time called 2401 levels = [] 2402 results = [] 2403 if res == True: 2404 return results 2405 if res == False or isinstance(res, Exception): 2406 results.append((levels[:], None, res)) 2407 if levels: 2408 levels.pop() 2409 return results 2410 for (key, val) in res.items(): 2411 if val == True: 2412 continue 2413 if isinstance(cfg.get(key), dict): 2414 # Go down one level 2415 levels.append(key) 2416 flatten_errors(cfg[key], val, levels, results) 2417 continue 2418 results.append((levels[:], key, val)) 2419 # 2420 # Go up one level 2421 if levels: 2422 levels.pop() 2423 # 2424 return results
2425 2426
2427 -def get_extra_values(conf, _prepend=()):
2428 """ 2429 Find all the values and sections not in the configspec from a validated 2430 ConfigObj. 2431 2432 ``get_extra_values`` returns a list of tuples where each tuple represents 2433 either an extra section, or an extra value. 2434 2435 The tuples contain two values, a tuple representing the section the value 2436 is in and the name of the extra values. For extra values in the top level 2437 section the first member will be an empty tuple. For values in the 'foo' 2438 section the first member will be ``('foo',)``. For members in the 'bar' 2439 subsection of the 'foo' section the first member will be ``('foo', 'bar')``. 2440 2441 NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't 2442 been validated it will return an empty list. 2443 """ 2444 out = [] 2445 2446 out.extend((_prepend, name) for name in conf.extra_values) 2447 for name in conf.sections: 2448 if name not in conf.extra_values: 2449 out.extend(get_extra_values(conf[name], _prepend + (name,))) 2450 return out
2451 2452 2453 """*A programming language is a medium of expression.* - Paul Graham""" 2454