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