2
# A config file reader/writer that supports nested sections in config files.
3
# Copyright (C) 2005 Michael Foord, Nicola Larosa
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
# nico AT tekNico DOT net
8
# http://www.voidspace.org.uk/python/configobj.html
10
# Released subject to the BSD License
11
# Please see http://www.voidspace.org.uk/python/license.shtml
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.
19
from __future__ import generators
29
... 'member': 'value',
31
>>> x = ConfigObj(z.write())
37
INTP_VER = sys.version_info[:2]
39
raise RuntimeError("Python v.2.2 or later needed")
42
from types import StringTypes
43
from warnings import warn
44
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
46
# A dictionary mapping BOM to
47
# the encoding to decode with, and what to set the
48
# encoding attribute to.
50
BOM_UTF8: ('utf_8', None),
51
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
52
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
53
BOM_UTF16: ('utf_16', 'utf_16'),
55
# All legal variants of the BOM codecs.
56
# TODO: the list of aliases is not meant to be exhaustive, is there a
63
'utf16_be': 'utf16_be',
64
'utf_16_be': 'utf16_be',
65
'utf-16be': 'utf16_be',
66
'utf16_le': 'utf16_le',
67
'utf_16_le': 'utf16_le',
68
'utf-16le': 'utf16_le',
76
# Map of encodings to the BOM to write.
80
'utf16_be': BOM_UTF16_BE,
81
'utf16_le': BOM_UTF16_LE,
86
from validate import VdtMissingValue
88
VdtMissingValue = None
94
"""enumerate for Python 2.2."""
106
__version__ = '4.2.0beta2'
108
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
110
__docformat__ = "restructuredtext en"
112
# NOTE: Does it make sense to have the following in __all__ ?
113
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
114
# NOTE: If used as from configobj import...
115
# NOTE: They are effectively read only
118
'DEFAULT_INDENT_TYPE',
120
'MAX_INTERPOL_DEPTH',
128
'InterpolationError',
129
'InterpolationDepthError',
130
'MissingInterpolationOption',
131
'RepeatSectionError',
136
DEFAULT_INDENT_TYPE = ' '
137
NUM_INDENT_SPACES = 4
138
MAX_INTERPOL_DEPTH = 10
141
'interpolation': True,
142
'raise_errors': False,
144
'create_empty': False,
148
# option may be set to one of ('', ' ', '\t')
151
'default_encoding': None,
154
class ConfigObjError(SyntaxError):
156
This is the base class for all errors that ConfigObj raises.
157
It is a subclass of SyntaxError.
159
>>> raise ConfigObjError
160
Traceback (most recent call last):
163
def __init__(self, message='', line_number=None, line=''):
165
self.line_number = line_number
166
self.message = message
167
SyntaxError.__init__(self, message)
169
class NestingError(ConfigObjError):
171
This error indicates a level of nesting that doesn't match.
173
>>> raise NestingError
174
Traceback (most recent call last):
178
class ParseError(ConfigObjError):
180
This error indicates that a line is badly written.
181
It is neither a valid ``key = value`` line,
182
nor a valid section marker line.
185
Traceback (most recent call last):
189
class DuplicateError(ConfigObjError):
191
The keyword or section specified already exists.
193
>>> raise DuplicateError
194
Traceback (most recent call last):
198
class ConfigspecError(ConfigObjError):
200
An error occured whilst parsing a configspec.
202
>>> raise ConfigspecError
203
Traceback (most recent call last):
207
class InterpolationError(ConfigObjError):
208
"""Base class for the two interpolation errors."""
210
class InterpolationDepthError(InterpolationError):
211
"""Maximum interpolation depth exceeded in string interpolation."""
213
def __init__(self, option):
215
>>> raise InterpolationDepthError('yoda')
216
Traceback (most recent call last):
217
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
219
InterpolationError.__init__(
221
'max interpolation depth exceeded in value "%s".' % option)
223
class RepeatSectionError(ConfigObjError):
225
This error indicates additional sections in a section with a
226
``__many__`` (repeated) section.
228
>>> raise RepeatSectionError
229
Traceback (most recent call last):
233
class MissingInterpolationOption(InterpolationError):
234
"""A value specified for interpolation was missing."""
236
def __init__(self, option):
238
>>> raise MissingInterpolationOption('yoda')
239
Traceback (most recent call last):
240
MissingInterpolationOption: missing option "yoda" in interpolation.
242
InterpolationError.__init__(
244
'missing option "%s" in interpolation.' % option)
248
A dictionary-like object that represents a section in a config file.
250
It does string interpolation if the 'interpolate' attribute
251
of the 'main' object is set to True.
253
Interpolation is tried first from the 'DEFAULT' section of this object,
254
next from the 'DEFAULT' section of the parent, lastly the main object.
256
A Section will behave like an ordered dictionary - following the
257
order of the ``scalars`` and ``sections`` attributes.
258
You can use this to change the order of members.
260
Iteration follows the order: scalars, then sections.
263
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
265
def __init__(self, parent, depth, main, indict=None, name=None):
267
* parent is the section above
268
* depth is the depth level of this section
269
* main is the main ConfigObj
270
* indict is a dictionary to initialise the section with
275
# used for nesting level *and* interpolation
277
# used for the interpolation attribute
279
# level of nesting depth of this Section
281
# the sequence of scalar values in this Section
283
# the sequence of sections in this Section
285
# purely for information
289
self.inline_comments = {}
295
# we do this explicitly so that __setitem__ is used properly
296
# (rather than just passing to ``dict.__init__``)
298
self[entry] = indict[entry]
300
def _interpolate(self, value):
301
"""Nicked from ConfigParser."""
302
depth = MAX_INTERPOL_DEPTH
303
# loop through this until it's done
306
if value.find("%(") != -1:
307
value = self._KEYCRE.sub(self._interpolation_replace, value)
311
raise InterpolationDepthError(value)
314
def _interpolation_replace(self, match):
320
# switch off interpolation before we try and fetch anything !
321
self.main.interpolation = False
322
# try the 'DEFAULT' member of *this section* first
323
val = self.get('DEFAULT', {}).get(s)
324
# try the 'DEFAULT' member of the *parent section* next
326
val = self.parent.get('DEFAULT', {}).get(s)
327
# last, try the 'DEFAULT' member of the *main section*
329
val = self.main.get('DEFAULT', {}).get(s)
330
self.main.interpolation = True
332
raise MissingInterpolationOption(s)
335
def __getitem__(self, key):
336
"""Fetch the item and do string interpolation."""
337
val = dict.__getitem__(self, key)
338
if self.main.interpolation and isinstance(val, StringTypes):
339
return self._interpolate(val)
342
def __setitem__(self, key, value):
344
Correctly set a value.
346
Making dictionary values Section instances.
347
(We have to special case 'Section' instances - which are also dicts)
349
Keys must be strings.
350
Values need only be strings (or lists of strings) if
351
``main.stringify`` is set.
353
if not isinstance(key, StringTypes):
354
raise ValueError, 'The key "%s" is not a string.' % key
356
if key not in self.comments:
357
self.comments[key] = []
358
self.inline_comments[key] = ''
359
# remove the entry from defaults
360
if key in self.defaults:
361
self.defaults.remove(key)
363
if isinstance(value, Section):
365
self.sections.append(key)
366
dict.__setitem__(self, key, value)
367
elif isinstance(value, dict):
368
# First create the new depth level,
369
# then create the section
371
self.sections.append(key)
372
new_depth = self.depth + 1
384
self.scalars.append(key)
385
if not self.main.stringify:
386
if isinstance(value, StringTypes):
388
elif isinstance(value, (list, tuple)):
390
if not isinstance(entry, StringTypes):
392
'Value is not a string "%s".' % entry)
394
raise TypeError, 'Value is not a string "%s".' % value
395
dict.__setitem__(self, key, value)
397
def __delitem__(self, key):
398
"""Remove items from the sequence when deleting."""
399
dict. __delitem__(self, key)
400
if key in self.scalars:
401
self.scalars.remove(key)
403
self.sections.remove(key)
404
del self.comments[key]
405
del self.inline_comments[key]
407
def get(self, key, default=None):
408
"""A version of ``get`` that doesn't bypass string interpolation."""
414
def update(self, indict):
416
A version of update that uses our ``__setitem__``.
419
self[entry] = indict[entry]
422
def pop(self, key, *args):
424
val = dict.pop(self, key, *args)
425
if key in self.scalars:
426
del self.comments[key]
427
del self.inline_comments[key]
428
self.scalars.remove(key)
429
elif key in self.sections:
430
del self.comments[key]
431
del self.inline_comments[key]
432
self.sections.remove(key)
433
if self.main.interpolation and isinstance(val, StringTypes):
434
return self._interpolate(val)
438
"""Pops the first (key,val)"""
439
sequence = (self.scalars + self.sections)
441
raise KeyError, ": 'popitem(): dictionary is empty'"
449
A version of clear that also affects scalars/sections
450
Also clears comments and configspec.
452
Leaves other attributes alone :
453
depth/main/parent are not affected
459
self.inline_comments = {}
462
def setdefault(self, key, default=None):
463
"""A version of setdefault that sets sequence if appropriate."""
472
return zip((self.scalars + self.sections), self.values())
476
return (self.scalars + self.sections)
480
return [self[key] for key in (self.scalars + self.sections)]
484
return iter(self.items())
488
return iter((self.scalars + self.sections))
492
def itervalues(self):
494
return iter(self.values())
497
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
498
for key in (self.scalars + self.sections)])
502
# Extra methods - not in a normal dictionary
506
Return a deepcopy of self as a dictionary.
508
All members that are ``Section`` instances are recursively turned to
509
ordinary dictionaries - by calling their ``dict`` method.
519
this_entry = self[entry]
520
if isinstance(this_entry, Section):
521
this_entry = this_entry.dict()
522
elif isinstance(this_entry, (list, tuple)):
523
# create a copy rather than a reference
524
this_entry = list(this_entry)
525
newdict[entry] = this_entry
528
def merge(self, indict):
530
A recursive update - useful for merging config files.
532
>>> a = '''[section1]
535
... more_options = False
536
... # end of file'''.splitlines()
537
>>> b = '''# File is user.ini
540
... # end of file'''.splitlines()
541
>>> c1 = ConfigObj(b)
542
>>> c2 = ConfigObj(a)
545
{'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
547
for key, val in indict.items():
548
if (key in self and isinstance(self[key], dict) and
549
isinstance(val, dict)):
554
def rename(self, oldkey, newkey):
556
Change a keyname to another, without changing position in sequence.
558
Implemented so that transformations can be made on keys,
559
as well as on values. (used by encode and decode)
561
Also renames comments.
563
if oldkey in self.scalars:
564
the_list = self.scalars
565
elif oldkey in self.sections:
566
the_list = self.sections
568
raise KeyError, 'Key "%s" not found.' % oldkey
569
pos = the_list.index(oldkey)
572
dict.__delitem__(self, oldkey)
573
dict.__setitem__(self, newkey, val)
574
the_list.remove(oldkey)
575
the_list.insert(pos, newkey)
576
comm = self.comments[oldkey]
577
inline_comment = self.inline_comments[oldkey]
578
del self.comments[oldkey]
579
del self.inline_comments[oldkey]
580
self.comments[newkey] = comm
581
self.inline_comments[newkey] = inline_comment
583
def walk(self, function, raise_errors=True,
584
call_on_sections=False, **keywargs):
586
Walk every member and call a function on the keyword and value.
588
Return a dictionary of the return values
590
If the function raises an exception, raise the errror
591
unless ``raise_errors=False``, in which case set the return value to
594
Any unrecognised keyword arguments you pass to walk, will be pased on
595
to the function you pass in.
597
Note: if ``call_on_sections`` is ``True`` then - on encountering a
598
subsection, *first* the function is called for the *whole* subsection,
599
and then recurses into it's members. This means your function must be
600
able to handle strings, dictionaries and lists. This allows you
601
to change the key of subsections as well as for ordinary members. The
602
return value when called on the whole subsection has to be discarded.
604
See the encode and decode methods for examples, including functions.
608
You can use ``walk`` to transform the names of members of a section
609
but you mustn't add or delete members.
611
>>> config = '''[XXXXsection]
612
... XXXXkey = XXXXvalue'''.splitlines()
613
>>> cfg = ConfigObj(config)
615
{'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
616
>>> def transform(section, key):
617
... val = section[key]
618
... newkey = key.replace('XXXX', 'CLIENT1')
619
... section.rename(key, newkey)
620
... if isinstance(val, (tuple, list, dict)):
623
... val = val.replace('XXXX', 'CLIENT1')
624
... section[newkey] = val
625
>>> cfg.walk(transform, call_on_sections=True)
626
{'CLIENT1section': {'CLIENT1key': None}}
628
{'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
632
for i in range(len(self.scalars)):
633
entry = self.scalars[i]
635
val = function(self, entry, **keywargs)
636
# bound again in case name has changed
637
entry = self.scalars[i]
643
entry = self.scalars[i]
646
for i in range(len(self.sections)):
647
entry = self.sections[i]
650
function(self, entry, **keywargs)
655
entry = self.sections[i]
657
# bound again in case name has changed
658
entry = self.sections[i]
659
# previous result is discarded
660
out[entry] = self[entry].walk(
662
raise_errors=raise_errors,
663
call_on_sections=call_on_sections,
667
def decode(self, encoding):
669
Decode all strings and values to unicode, using the specified encoding.
671
Works with subsections and list values.
673
Uses the ``walk`` method.
675
Testing ``encode`` and ``decode``.
677
>>> m.decode('ascii')
678
>>> def testuni(val):
679
... for entry in val:
680
... if not isinstance(entry, unicode):
681
... sys.stderr.write(type(entry))
682
... sys.stderr.write('\n')
683
... raise AssertionError, 'decode failed.'
684
... if isinstance(val[entry], dict):
685
... testuni(val[entry])
686
... elif not isinstance(val[entry], unicode):
687
... raise AssertionError, 'decode failed.'
689
>>> m.encode('ascii')
693
def decode(section, key, encoding=encoding):
696
if isinstance(val, (list, tuple)):
699
newval.append(entry.decode(encoding))
700
elif isinstance(val, dict):
703
newval = val.decode(encoding)
704
newkey = key.decode(encoding)
705
section.rename(key, newkey)
706
section[newkey] = newval
707
# using ``call_on_sections`` allows us to modify section names
708
self.walk(decode, call_on_sections=True)
710
def encode(self, encoding):
712
Encode all strings and values from unicode,
713
using the specified encoding.
715
Works with subsections and list values.
716
Uses the ``walk`` method.
718
def encode(section, key, encoding=encoding):
721
if isinstance(val, (list, tuple)):
724
newval.append(entry.encode(encoding))
725
elif isinstance(val, dict):
728
newval = val.encode(encoding)
729
newkey = key.encode(encoding)
730
section.rename(key, newkey)
731
section[newkey] = newval
732
self.walk(encode, call_on_sections=True)
734
def istrue(self, key):
735
"""A deprecated version of ``as_bool``."""
736
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
737
'instead.', DeprecationWarning)
738
return self.as_bool(key)
740
def as_bool(self, key):
742
Accepts a key as input. The corresponding value must be a string or
743
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
744
retain compatibility with Python 2.2.
746
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
749
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
752
``as_bool`` is not case sensitive.
754
Any other input will raise a ``ValueError``.
759
Traceback (most recent call last):
760
ValueError: Value "fish" is neither True nor False
775
if not isinstance(val, StringTypes):
778
return self.main._bools[val.lower()]
780
raise ValueError('Value "%s" is neither True nor False' % val)
782
def as_int(self, key):
784
A convenience method which coerces the specified value to an integer.
786
If the value is an invalid literal for ``int``, a ``ValueError`` will
792
Traceback (most recent call last):
793
ValueError: invalid literal for int(): fish
799
Traceback (most recent call last):
800
ValueError: invalid literal for int(): 3.2
802
return int(self[key])
804
def as_float(self, key):
806
A convenience method which coerces the specified value to a float.
808
If the value is an invalid literal for ``float``, a ``ValueError`` will
814
Traceback (most recent call last):
815
ValueError: invalid literal for float(): fish
823
return float(self[key])
826
class ConfigObj(Section):
828
An object to read, create, and write config files.
830
Testing with duplicate keys and sections.
840
>>> ConfigObj(c.split('\\n'), raise_errors = True)
841
Traceback (most recent call last):
842
DuplicateError: Duplicate section name at line 5.
850
... 'member1' = value
854
>>> ConfigObj(d.split('\\n'), raise_errors = True)
855
Traceback (most recent call last):
856
DuplicateError: Duplicate keyword name at line 6.
859
_keyword = re.compile(r'''^ # line start
862
(?:".*?")| # double quotes
863
(?:'.*?')| # single quotes
864
(?:[^'"=].*?) # no quotes
867
(.*) # value (including list values and comments)
872
_sectionmarker = re.compile(r'''^
873
(\s*) # 1: indentation
874
((?:\[\s*)+) # 2: section marker open
875
( # 3: section name open
876
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
877
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
878
(?:[^'"\s].*?) # at least one non-space unquoted
879
) # section name close
880
((?:\s*\])+) # 4: section marker close
881
\s*(\#.*)? # 5: optional comment
885
# this regexp pulls list values out as a single string
886
# or single values and comments
887
_valueexp = re.compile(r'''^
893
(?:".*?")| # double quotes
894
(?:'.*?')| # single quotes
895
(?:[^'",\#][^,\#]*?) # unquoted
898
)* # match all list items ending in a comma (if any)
901
(?:".*?")| # double quotes
902
(?:'.*?')| # single quotes
903
(?:[^'",\#\s][^,]*?) # unquoted
904
)? # last item in a list - or string value
906
(,) # alternatively a single comma - empty list
908
\s*(\#.*)? # optional comment
912
# use findall to get the members of a list value
913
_listvalueexp = re.compile(r'''
915
(?:".*?")| # double quotes
916
(?:'.*?')| # single quotes
917
(?:[^'",\#].*?) # unquoted
923
# this regexp is used for the value
924
# when lists are switched off
925
_nolistvalue = re.compile(r'''^
927
(?:".*?")| # double quotes
928
(?:'.*?')| # single quotes
929
(?:[^'"\#].*?) # unquoted
931
\s*(\#.*)? # optional comment
935
# regexes for finding triple quoted values on one line
936
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
937
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
938
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
939
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
942
"'''": (_single_line_single, _multi_line_single),
943
'"""': (_single_line_double, _multi_line_double),
946
# Used by the ``istrue`` Section method
948
'yes': True, 'no': False,
949
'on': True, 'off': False,
950
'1': True, '0': False,
951
'true': True, 'false': False,
954
def __init__(self, infile=None, options=None, **kwargs):
956
Parse or create a config file object.
958
``ConfigObj(infile=None, options=None, **kwargs)``
964
# keyword arguments take precedence over an options dictionary
965
options.update(kwargs)
966
# init the superclass
967
Section.__init__(self, self, 0, self)
969
defaults = OPTION_DEFAULTS.copy()
970
for entry in options.keys():
971
if entry not in defaults.keys():
972
raise TypeError, 'Unrecognised option "%s".' % entry
973
# TODO: check the values too.
975
# Add any explicit options to the defaults
976
defaults.update(options)
978
# initialise a few variables
981
self.raise_errors = defaults['raise_errors']
982
self.interpolation = defaults['interpolation']
983
self.list_values = defaults['list_values']
984
self.create_empty = defaults['create_empty']
985
self.file_error = defaults['file_error']
986
self.stringify = defaults['stringify']
987
self.indent_type = defaults['indent_type']
988
self.encoding = defaults['encoding']
989
self.default_encoding = defaults['default_encoding']
993
self.initial_comment = []
994
self.final_comment = []
996
if isinstance(infile, StringTypes):
997
self.filename = infile
998
if os.path.isfile(infile):
999
infile = open(infile).read() or []
1000
elif self.file_error:
1001
# raise an error if the file doesn't exist
1002
raise IOError, 'Config file not found: "%s".' % self.filename
1004
# file doesn't already exist
1005
if self.create_empty:
1006
# this is a good test that the filename specified
1007
# isn't impossible - like on a non existent device
1008
h = open(infile, 'w')
1012
elif isinstance(infile, (list, tuple)):
1013
infile = list(infile)
1014
elif isinstance(infile, dict):
1016
# the Section class handles creating subsections
1017
if isinstance(infile, ConfigObj):
1018
# get a copy of our ConfigObj
1019
infile = infile.dict()
1020
for entry in infile:
1021
self[entry] = infile[entry]
1023
if defaults['configspec'] is not None:
1024
self._handle_configspec(defaults['configspec'])
1026
self.configspec = None
1028
elif getattr(infile, 'read', None) is not None:
1029
# This supports file like objects
1030
infile = infile.read() or []
1031
# needs splitting into lines - but needs doing *after* decoding
1032
# in case it's not an 8 bit encoding
1034
raise TypeError, ('infile must be a filename,'
1035
' file like object, or list of lines.')
1038
# don't do it for the empty ConfigObj
1039
infile = self._handle_bom(infile)
1040
# infile is now *always* a list
1042
# Set the newlines attribute (first line ending it finds)
1043
# and strip trailing '\n' or '\r' from lines
1045
if (not line) or (line[-1] not in '\r\n'):
1047
for end in ('\r\n', '\n', '\r'):
1048
if line.endswith(end):
1052
infile = [line.rstrip('\r\n') for line in infile]
1055
# if we had any errors, now is the time to raise them
1057
error = ConfigObjError("Parsing failed.")
1058
# set the errors attribute; it's a list of tuples:
1059
# (error_type, message, line_number)
1060
error.errors = self._errors
1061
# set the config attribute
1064
# delete private attributes
1067
if defaults['configspec'] is None:
1068
self.configspec = None
1070
self._handle_configspec(defaults['configspec'])
1072
def _handle_bom(self, infile):
1074
Handle any BOM, and decode if necessary.
1076
If an encoding is specified, that *must* be used - but the BOM should
1077
still be removed (and the BOM attribute set).
1079
(If the encoding is wrongly specified, then a BOM for an alternative
1080
encoding won't be discovered or removed.)
1082
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1083
removed. The BOM attribute will be set. UTF16 will be decoded to
1086
NOTE: This method must not be called with an empty ``infile``.
1088
Specifying the *wrong* encoding is likely to cause a
1089
``UnicodeDecodeError``.
1091
``infile`` must always be returned as a list of lines, but may be
1092
passed in as a single string.
1094
if ((self.encoding is not None) and
1095
(self.encoding.lower() not in BOM_LIST)):
1096
# No need to check for a BOM
1097
# encoding specified doesn't have one
1099
return self._decode(infile, self.encoding)
1101
if isinstance(infile, (list, tuple)):
1105
if self.encoding is not None:
1106
# encoding explicitly supplied
1107
# And it could have an associated BOM
1108
# TODO: if encoding is just UTF16 - we ought to check for both
1109
# TODO: big endian and little endian versions.
1110
enc = BOM_LIST[self.encoding.lower()]
1112
# For UTF16 we try big endian and little endian
1113
for BOM, (encoding, final_encoding) in BOMS.items():
1114
if not final_encoding:
1117
if infile.startswith(BOM):
1120
# Don't need to remove BOM
1121
return self._decode(infile, encoding)
1123
# If we get this far, will *probably* raise a DecodeError
1124
# As it doesn't appear to start with a BOM
1125
return self._decode(infile, self.encoding)
1129
if not line.startswith(BOM):
1130
return self._decode(infile, self.encoding)
1132
newline = line[len(BOM):]
1135
if isinstance(infile, (list, tuple)):
1140
return self._decode(infile, self.encoding)
1142
# No encoding specified - so we need to check for UTF8/UTF16
1143
for BOM, (encoding, final_encoding) in BOMS.items():
1144
if not line.startswith(BOM):
1148
self.encoding = final_encoding
1149
if not final_encoding:
1153
newline = line[len(BOM):]
1154
if isinstance(infile, (list, tuple)):
1158
# UTF8 - don't decode
1159
if isinstance(infile, StringTypes):
1160
return infile.splitlines(True)
1163
# UTF16 - have to decode
1164
return self._decode(infile, encoding)
1166
# No BOM discovered and no encoding specified, just return
1167
if isinstance(infile, StringTypes):
1168
# infile read from a file will be a single string
1169
return infile.splitlines(True)
1173
def _a_to_u(self, string):
1174
"""Decode ascii strings to unicode if a self.encoding is specified."""
1175
if not self.encoding:
1178
return string.decode('ascii')
1180
def _decode(self, infile, encoding):
1182
Decode infile to unicode. Using the specified encoding.
1184
if is a string, it also needs converting to a list.
1186
if isinstance(infile, StringTypes):
1188
# NOTE: Could raise a ``UnicodeDecodeError``
1189
return infile.decode(encoding).splitlines(True)
1190
for i, line in enumerate(infile):
1191
if not isinstance(line, unicode):
1192
# NOTE: The isinstance test here handles mixed lists of unicode/string
1193
# NOTE: But the decode will break on any non-string values
1194
# NOTE: Or could raise a ``UnicodeDecodeError``
1195
infile[i] = line.decode(encoding)
1198
def _decode_element(self, line):
1199
"""Decode element to unicode if necessary."""
1200
if not self.encoding:
1202
if isinstance(line, str) and self.default_encoding:
1203
return line.decode(self.default_encoding)
1206
def _str(self, value):
1208
Used by ``stringify`` within validate, to turn non-string values
1211
if not isinstance(value, StringTypes):
1216
def _parse(self, infile):
1218
Actually parse the config file
1220
Testing Interpolation
1223
>>> c['DEFAULT'] = {
1225
... 'userdir': 'c:\\\\home',
1229
>>> c['section'] = {
1230
... 'a': '%(datadir)s\\\\some path\\\\file.py',
1231
... 'b': '%(userdir)s\\\\some path\\\\file.py',
1232
... 'c': 'Yo %(a)s',
1233
... 'd': '%(not_here)s',
1236
>>> c['section']['DEFAULT'] = {
1237
... 'datadir': 'c:\\\\silly_test',
1238
... 'a': 'hello - %(b)s',
1240
>>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
1242
>>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
1244
>>> c['section']['c'] == 'Yo hello - goodbye'
1247
Switching Interpolation Off
1249
>>> c.interpolation = False
1250
>>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
1252
>>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
1254
>>> c['section']['c'] == 'Yo %(a)s'
1257
Testing the interpolation errors.
1259
>>> c.interpolation = True
1260
>>> c['section']['d']
1261
Traceback (most recent call last):
1262
MissingInterpolationOption: missing option "not_here" in interpolation.
1263
>>> c['section']['e']
1264
Traceback (most recent call last):
1265
InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
1267
Testing our quoting.
1269
>>> i._quote('\"""\'\'\'')
1270
Traceback (most recent call last):
1271
SyntaxError: EOF while scanning triple-quoted string
1273
... i._quote('\\n', multiline=False)
1274
... except ConfigObjError, e:
1276
'Value "\\n" cannot be safely quoted.'
1277
>>> k._quote(' "\' ', multiline=False)
1278
Traceback (most recent call last):
1279
SyntaxError: EOL while scanning single-quoted string
1281
Testing with "stringify" off.
1282
>>> c.stringify = False
1284
Traceback (most recent call last):
1285
TypeError: Value is not a string "1".
1290
maxline = len(infile) - 1
1292
reset_comment = False
1293
while cur_index < maxline:
1297
line = infile[cur_index]
1298
sline = line.strip()
1299
# do we have anything on the line ?
1300
if not sline or sline.startswith('#'):
1301
reset_comment = False
1302
comment_list.append(line)
1305
# preserve initial comment
1306
self.initial_comment = comment_list
1309
reset_comment = True
1310
# first we check if it's a section marker
1311
mat = self._sectionmarker.match(line)
1312
## sys.stderr.write('%s %s\n' % (sline, mat))
1315
(indent, sect_open, sect_name, sect_close, comment) = (
1317
if indent and (self.indent_type is None):
1318
self.indent_type = indent[0]
1319
cur_depth = sect_open.count('[')
1320
if cur_depth != sect_close.count(']'):
1322
"Cannot compute the section depth at line %s.",
1323
NestingError, infile, cur_index)
1325
if cur_depth < this_section.depth:
1326
# the new section is dropping back to a previous level
1328
parent = self._match_depth(
1333
"Cannot compute nesting level at line %s.",
1334
NestingError, infile, cur_index)
1336
elif cur_depth == this_section.depth:
1337
# the new section is a sibling of the current section
1338
parent = this_section.parent
1339
elif cur_depth == this_section.depth + 1:
1340
# the new section is a child the current section
1341
parent = this_section
1344
"Section too nested at line %s.",
1345
NestingError, infile, cur_index)
1347
sect_name = self._unquote(sect_name)
1348
if sect_name in parent:
1349
## sys.stderr.write(sect_name)
1350
## sys.stderr.write('\n')
1352
'Duplicate section name at line %s.',
1353
DuplicateError, infile, cur_index)
1355
# create the new section
1356
this_section = Section(
1361
parent[sect_name] = this_section
1362
parent.inline_comments[sect_name] = comment
1363
parent.comments[sect_name] = comment_list
1364
## sys.stderr.write(parent[sect_name] is this_section)
1365
## sys.stderr.write('\n')
1368
# it's not a section marker,
1369
# so it should be a valid ``key = value`` line
1370
mat = self._keyword.match(line)
1371
## sys.stderr.write('%s %s\n' % (sline, mat))
1373
# is a keyword value
1374
# value will include any inline comment
1375
(indent, key, value) = mat.groups()
1376
if indent and (self.indent_type is None):
1377
self.indent_type = indent[0]
1378
# check for a multiline value
1379
if value[:3] in ['"""', "'''"]:
1381
(value, comment, cur_index) = self._multiline(
1382
value, infile, cur_index, maxline)
1385
'Parse error in value at line %s.',
1386
ParseError, infile, cur_index)
1389
# extract comment and lists
1391
(value, comment) = self._handle_value(value)
1394
'Parse error in value at line %s.',
1395
ParseError, infile, cur_index)
1398
## sys.stderr.write(sline)
1399
## sys.stderr.write('\n')
1400
key = self._unquote(key)
1401
if key in this_section:
1403
'Duplicate keyword name at line %s.',
1404
DuplicateError, infile, cur_index)
1407
## sys.stderr.write(this_section.name + '\n')
1408
this_section[key] = value
1409
this_section.inline_comments[key] = comment
1410
this_section.comments[key] = comment_list
1411
## sys.stderr.write('%s %s\n' % (key, this_section[key]))
1412
## if this_section.name is not None:
1413
## sys.stderr.write(this_section + '\n')
1414
## sys.stderr.write(this_section.parent + '\n')
1415
## sys.stderr.write(this_section.parent[this_section.name])
1416
## sys.stderr.write('\n')
1419
# it neither matched as a keyword
1420
# or a section marker
1422
'Invalid line at line "%s".',
1423
ParseError, infile, cur_index)
1424
if self.indent_type is None:
1425
# no indentation used, set the type accordingly
1426
self.indent_type = ''
1427
# preserve the final comment
1428
if not self and not self.initial_comment:
1429
self.initial_comment = comment_list
1431
self.final_comment = comment_list
1433
def _match_depth(self, sect, depth):
1435
Given a section and a depth level, walk back through the sections
1436
parents to see if the depth level matches a previous section.
1438
Return a reference to the right section,
1439
or raise a SyntaxError.
1441
while depth < sect.depth:
1442
if sect is sect.parent:
1443
# we've reached the top level already
1446
if sect.depth == depth:
1448
# shouldn't get here
1451
def _handle_error(self, text, ErrorClass, infile, cur_index):
1453
Handle an error according to the error settings.
1455
Either raise the error or store it.
1456
The error will have occured at ``cur_index``
1458
line = infile[cur_index]
1460
message = text % cur_index
1461
error = ErrorClass(message, cur_index, line)
1462
if self.raise_errors:
1463
# raise the error - parsing stops here
1466
# reraise when parsing has finished
1467
self._errors.append(error)
1469
def _unquote(self, value):
1470
"""Return an unquoted version of a value"""
1471
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1475
def _quote(self, value, multiline=True):
1477
Return a safely quoted version of a value.
1479
Raise a ConfigObjError if the value cannot be safely quoted.
1480
If multiline is ``True`` (default) then use triple quotes
1483
Don't quote values that don't need it.
1484
Recursively quote members of a list and return a comma joined list.
1485
Multiline is ``False`` for lists.
1486
Obey list syntax for empty and single member lists.
1488
If ``list_values=False`` then the value is only quoted if it contains
1489
a ``\n`` (is multiline).
1491
if isinstance(value, (list, tuple)):
1494
elif len(value) == 1:
1495
return self._quote(value[0], multiline=False) + ','
1496
return ', '.join([self._quote(val, multiline=False)
1498
if not isinstance(value, StringTypes):
1502
raise TypeError, 'Value "%s" is not a string.' % value
1506
wspace_plus = ' \r\t\n\v\t\'"'
1511
if (not self.list_values and '\n' not in value) or not (multiline and
1512
((("'" in value) and ('"' in value)) or ('\n' in value))):
1513
if not self.list_values:
1514
# we don't quote if ``list_values=False``
1516
# for normal values either single or double quotes will do
1518
# will only happen if multiline is off - e.g. '\n' in key
1519
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1521
elif ((value[0] not in wspace_plus) and
1522
(value[-1] not in wspace_plus) and
1523
(',' not in value)):
1526
if ("'" in value) and ('"' in value):
1527
raise ConfigObjError, (
1528
'Value "%s" cannot be safely quoted.' % value)
1534
# if value has '\n' or "'" *and* '"', it will need triple quotes
1535
if (value.find('"""') != -1) and (value.find("'''") != -1):
1536
raise ConfigObjError, (
1537
'Value "%s" cannot be safely quoted.' % value)
1538
if value.find('"""') == -1:
1544
def _handle_value(self, value):
1546
Given a value string, unquote, remove comment,
1547
handle lists. (including empty and single member lists)
1549
Testing list values.
1551
>>> testconfig3 = '''
1554
... c = test1, test2 , test3
1555
... d = test1, test2, test3,
1557
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1560
>>> d['b'] == ['test']
1562
>>> d['c'] == ['test1', 'test2', 'test3']
1564
>>> d['d'] == ['test1', 'test2', 'test3']
1567
Testing with list values off.
1570
... testconfig3.split('\\n'),
1571
... raise_errors=True,
1572
... list_values=False)
1575
>>> e['b'] == 'test,'
1577
>>> e['c'] == 'test1, test2 , test3'
1579
>>> e['d'] == 'test1, test2, test3,'
1582
Testing creating from a dictionary.
1605
>>> g = ConfigObj(f)
1609
Testing we correctly detect badly built list values (4 of them).
1611
>>> testconfig4 = '''
1615
... dummy = ,,hello, goodbye
1618
... ConfigObj(testconfig4.split('\\n'))
1619
... except ConfigObjError, e:
1623
Testing we correctly detect badly quoted values (4 of them).
1625
>>> testconfig5 = '''
1626
... config = "hello # comment
1628
... fish = 'goodbye # comment
1629
... dummy = "hello again
1632
... ConfigObj(testconfig5.split('\\n'))
1633
... except ConfigObjError, e:
1637
# do we look for lists in values ?
1638
if not self.list_values:
1639
mat = self._nolistvalue.match(value)
1642
(value, comment) = mat.groups()
1643
# NOTE: we don't unquote here
1644
return (value, comment)
1645
mat = self._valueexp.match(value)
1647
# the value is badly constructed, probably badly quoted,
1648
# or an invalid list
1650
(list_values, single, empty_list, comment) = mat.groups()
1651
if (list_values == '') and (single is None):
1652
# change this if you want to accept empty values
1654
# NOTE: note there is no error handling from here if the regex
1655
# is wrong: then incorrect values will slip through
1656
if empty_list is not None:
1657
# the single comma - meaning an empty list
1658
return ([], comment)
1659
if single is not None:
1660
single = self._unquote(single)
1661
if list_values == '':
1663
return (single, comment)
1664
the_list = self._listvalueexp.findall(list_values)
1665
the_list = [self._unquote(val) for val in the_list]
1666
if single is not None:
1667
the_list += [single]
1668
return (the_list, comment)
1670
def _multiline(self, value, infile, cur_index, maxline):
1672
Extract the value, where we are in a multiline situation
1674
Testing multiline values.
1677
... 'name4': ' another single line value ',
1678
... 'multi section': {
1679
... 'name4': '\\n Well, this is a\\n multiline '
1681
... 'name2': '\\n Well, this is a\\n multiline '
1683
... 'name3': '\\n Well, this is a\\n multiline '
1685
... 'name1': '\\n Well, this is a\\n multiline '
1688
... 'name2': ' another single line value ',
1689
... 'name3': ' a single line value ',
1690
... 'name1': ' a single line value ',
1695
newvalue = value[3:]
1696
single_line = self._triple_quote[quot][0]
1697
multi_line = self._triple_quote[quot][1]
1698
mat = single_line.match(value)
1700
retval = list(mat.groups())
1701
retval.append(cur_index)
1703
elif newvalue.find(quot) != -1:
1704
# somehow the triple quote is missing
1707
while cur_index < maxline:
1710
line = infile[cur_index]
1711
if line.find(quot) == -1:
1714
# end of multiline, process it
1717
# we've got to the end of the config, oops...
1719
mat = multi_line.match(line)
1721
# a badly formed line
1723
(value, comment) = mat.groups()
1724
return (newvalue + value, comment, cur_index)
1726
def _handle_configspec(self, configspec):
1727
"""Parse the configspec."""
1729
configspec = ConfigObj(
1734
except ConfigObjError, e:
1735
# FIXME: Should these errors have a reference
1736
# to the already parsed ConfigObj ?
1737
raise ConfigspecError('Parsing configspec failed: %s' % e)
1739
raise IOError('Reading configspec failed: %s' % e)
1740
self._set_configspec_value(configspec, self)
1742
def _set_configspec_value(self, configspec, section):
1743
"""Used to recursively set configspec values."""
1744
if '__many__' in configspec.sections:
1745
section.configspec['__many__'] = configspec['__many__']
1746
if len(configspec.sections) > 1:
1747
# FIXME: can we supply any useful information here ?
1748
raise RepeatSectionError
1749
for entry in configspec.scalars:
1750
section.configspec[entry] = configspec[entry]
1751
for entry in configspec.sections:
1752
if entry == '__many__':
1754
if entry not in section:
1756
self._set_configspec_value(configspec[entry], section[entry])
1758
def _handle_repeat(self, section, configspec):
1759
"""Dynamically assign configspec for repeated section."""
1761
section_keys = configspec.sections
1762
scalar_keys = configspec.scalars
1763
except AttributeError:
1764
section_keys = [entry for entry in configspec
1765
if isinstance(configspec[entry], dict)]
1766
scalar_keys = [entry for entry in configspec
1767
if not isinstance(configspec[entry], dict)]
1768
if '__many__' in section_keys and len(section_keys) > 1:
1769
# FIXME: can we supply any useful information here ?
1770
raise RepeatSectionError
1773
for entry in scalar_keys:
1774
val = configspec[entry]
1775
scalars[entry] = val
1776
for entry in section_keys:
1777
val = configspec[entry]
1778
if entry == '__many__':
1779
scalars[entry] = val
1781
sections[entry] = val
1783
section.configspec = scalars
1784
for entry in sections:
1785
if entry not in section:
1787
self._handle_repeat(section[entry], sections[entry])
1789
def _write_line(self, indent_string, entry, this_entry, comment):
1790
"""Write an individual line, for the write method"""
1791
# NOTE: the calls to self._quote here handles non-StringType values.
1792
return '%s%s%s%s%s' % (
1794
self._decode_element(self._quote(entry, multiline=False)),
1795
self._a_to_u(' = '),
1796
self._decode_element(self._quote(this_entry)),
1797
self._decode_element(comment))
1799
def _write_marker(self, indent_string, depth, entry, comment):
1800
"""Write a section marker line"""
1801
return '%s%s%s%s%s' % (
1803
self._a_to_u('[' * depth),
1804
self._quote(self._decode_element(entry), multiline=False),
1805
self._a_to_u(']' * depth),
1806
self._decode_element(comment))
1808
def _handle_comment(self, comment):
1810
Deal with a comment.
1812
>>> filename = a.filename
1813
>>> a.filename = None
1814
>>> values = a.write()
1816
>>> while index < 23:
1818
... line = values[index-1]
1819
... assert line.endswith('# comment ' + str(index))
1820
>>> a.filename = filename
1822
>>> start_comment = ['# Initial Comment', '', '#']
1823
>>> end_comment = ['', '#', '# Final Comment']
1824
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1825
>>> nc = ConfigObj(newconfig)
1826
>>> nc.initial_comment
1827
['# Initial Comment', '', '#']
1828
>>> nc.final_comment
1829
['', '#', '# Final Comment']
1830
>>> nc.initial_comment == start_comment
1832
>>> nc.final_comment == end_comment
1837
if self.indent_type == '\t':
1838
start = self._a_to_u('\t')
1840
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1841
if not comment.startswith('#'):
1842
start += _a_to_u('# ')
1843
return (start + comment)
1845
def _compute_indent_string(self, depth):
1847
Compute the indent string, according to current indent_type and depth
1849
if self.indent_type == '':
1850
# no indentation at all
1852
if self.indent_type == '\t':
1854
if self.indent_type == ' ':
1855
return ' ' * NUM_INDENT_SPACES * depth
1860
def write(self, outfile=None, section=None):
1862
Write the current ConfigObj as a file
1864
tekNico: FIXME: use StringIO instead of real files
1866
>>> filename = a.filename
1867
>>> a.filename = 'test.ini'
1869
>>> a.filename = filename
1870
>>> a == ConfigObj('test.ini', raise_errors=True)
1872
>>> os.remove('test.ini')
1873
>>> b.filename = 'test.ini'
1875
>>> b == ConfigObj('test.ini', raise_errors=True)
1877
>>> os.remove('test.ini')
1878
>>> i.filename = 'test.ini'
1880
>>> i == ConfigObj('test.ini', raise_errors=True)
1882
>>> os.remove('test.ini')
1884
>>> a['DEFAULT'] = {'a' : 'fish'}
1885
>>> a['a'] = '%(a)s'
1887
['a = %(a)s', '[DEFAULT]', 'a = fish']
1889
if self.indent_type is None:
1890
# this can be true if initialised from a dictionary
1891
self.indent_type = DEFAULT_INDENT_TYPE
1894
cs = self._a_to_u('#')
1895
csp = self._a_to_u('# ')
1897
int_val = self.interpolation
1898
self.interpolation = False
1900
for line in self.initial_comment:
1901
line = self._decode_element(line)
1902
stripped_line = line.strip()
1903
if stripped_line and not stripped_line.startswith(cs):
1907
indent_string = self._a_to_u(
1908
self._compute_indent_string(section.depth))
1909
for entry in (section.scalars + section.sections):
1910
if entry in section.defaults:
1911
# don't write out default values
1913
for comment_line in section.comments[entry]:
1914
comment_line = self._decode_element(comment_line.lstrip())
1915
if comment_line and not comment_line.startswith(cs):
1916
comment_line = csp + comment_line
1917
out.append(indent_string + comment_line)
1918
this_entry = section[entry]
1919
comment = self._handle_comment(section.inline_comments[entry])
1921
if isinstance(this_entry, dict):
1923
out.append(self._write_marker(
1928
out.extend(self.write(section=this_entry))
1930
out.append(self._write_line(
1937
for line in self.final_comment:
1938
line = self._decode_element(line)
1939
stripped_line = line.strip()
1940
if stripped_line and not stripped_line.startswith(cs):
1943
self.interpolation = int_val
1945
if section is not self:
1948
if (self.filename is None) and (outfile is None):
1949
# output a list of lines
1950
# might need to encode
1951
# NOTE: This will *screw* UTF16, each line will start with the BOM
1953
out = [l.encode(self.encoding) for l in out]
1954
if (self.BOM and ((self.encoding is None) or
1955
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1959
out[0] = BOM_UTF8 + out[0]
1962
# Turn the list to a string, joined with correct newlines
1963
output = (self._a_to_u(self.newlines or os.linesep)
1966
output = output.encode(self.encoding)
1967
if (self.BOM and ((self.encoding is None) or
1968
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1970
output = BOM_UTF8 + output
1971
if outfile is not None:
1972
outfile.write(output)
1974
h = open(self.filename, 'w')
1978
def validate(self, validator, preserve_errors=False, section=None):
1980
Test the ConfigObj against a configspec.
1982
It uses the ``validator`` object from *validate.py*.
1984
To run ``validate`` on the current ConfigObj, call: ::
1986
test = config.validate(validator)
1988
(Normally having previously passed in the configspec when the ConfigObj
1989
was created - you can dynamically assign a dictionary of checks to the
1990
``configspec`` attribute of a section though).
1992
It returns ``True`` if everything passes, or a dictionary of
1993
pass/fails (True/False). If every member of a subsection passes, it
1994
will just have the value ``True``. (It also returns ``False`` if all
1997
In addition, it converts the values from strings to their native
1998
types if their checks pass (and ``stringify`` is set).
2000
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2001
of a marking a fail with a ``False``, it will preserve the actual
2002
exception object. This can contain info about the reason for failure.
2003
For example the ``VdtValueTooSmallError`` indeicates that the value
2004
supplied was too small. If a value (or section) is missing it will
2005
still be marked as ``False``.
2007
You must have the validate module to use ``preserve_errors=True``.
2009
You can then use the ``flatten_errors`` function to turn your nested
2010
results dictionary into a flattened list of failures - useful for
2011
displaying meaningful error messages.
2014
... from validate import Validator
2015
... except ImportError:
2016
... sys.stderr.write('Cannot import the Validator object, skipping the related tests\n')
2033
... '''.split('\\n')
2034
... configspec = '''
2035
... test1= integer(30,50)
2038
... test4=float(6.0)
2040
... test1=integer(30,50)
2043
... test4=float(6.0)
2045
... test1=integer(30,50)
2048
... test4=float(6.0)
2049
... '''.split('\\n')
2050
... val = Validator()
2051
... c1 = ConfigObj(config, configspec=configspec)
2052
... test = c1.validate(val)
2063
... 'sub section': {
2072
>>> val.check(c1.configspec['test4'], c1['test4'])
2073
Traceback (most recent call last):
2074
VdtValueTooSmallError: the value "5.0" is too small.
2076
>>> val_test_config = '''
2081
... key2 = 1.1, 3.0, 17, 6.8
2084
... key2 = True'''.split('\\n')
2085
>>> val_test_configspec = '''
2090
... key2 = float_list(4)
2092
... key = option(option1, option2)
2093
... key2 = boolean'''.split('\\n')
2094
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
2095
>>> val_test.validate(val)
2097
>>> val_test['key'] = 'text not a digit'
2098
>>> val_res = val_test.validate(val)
2099
>>> val_res == {'key2': True, 'section': True, 'key': False}
2101
>>> configspec = '''
2102
... test1=integer(30,50, default=40)
2103
... test2=string(default="hello")
2104
... test3=integer(default=3)
2105
... test4=float(6.0, default=6.0)
2107
... test1=integer(30,50, default=40)
2108
... test2=string(default="hello")
2109
... test3=integer(default=3)
2110
... test4=float(6.0, default=6.0)
2112
... test1=integer(30,50, default=40)
2113
... test2=string(default="hello")
2114
... test3=integer(default=3)
2115
... test4=float(6.0, default=6.0)
2116
... '''.split('\\n')
2117
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2119
{'test1': '30', 'section': {'sub section': {}}}
2120
>>> default_test.validate(val)
2122
>>> default_test == {
2124
... 'test2': 'hello',
2129
... 'test2': 'hello',
2132
... 'sub section': {
2135
... 'test2': 'hello',
2142
Now testing with repeated sections : BIG TEST
2144
>>> repeated_1 = '''
2146
... [[__many__]] # spec for a dog
2147
... fleas = boolean(default=True)
2148
... tail = option(long, short, default=long)
2149
... name = string(default=rover)
2150
... [[[__many__]]] # spec for a puppy
2151
... name = string(default="son of rover")
2152
... age = float(default=0.0)
2154
... [[__many__]] # spec for a cat
2155
... fleas = boolean(default=True)
2156
... tail = option(long, short, default=short)
2157
... name = string(default=pussy)
2158
... [[[__many__]]] # spec for a kitten
2159
... name = string(default="son of pussy")
2160
... age = float(default=0.0)
2161
... '''.split('\\n')
2162
>>> repeated_2 = '''
2165
... # blank dogs with puppies
2166
... # should be filled in by the configspec
2181
... # blank cats with kittens
2182
... # should be filled in by the configspec
2195
... '''.split('\\n')
2196
>>> repeated_3 = '''
2207
... '''.split('\\n')
2208
>>> repeated_4 = '''
2211
... name = string(default=Michael)
2212
... age = float(default=0.0)
2213
... sex = option(m, f, default=m)
2214
... '''.split('\\n')
2215
>>> repeated_5 = '''
2218
... fleas = boolean(default=True)
2219
... tail = option(long, short, default=short)
2220
... name = string(default=pussy)
2221
... [[[description]]]
2222
... height = float(default=3.3)
2223
... weight = float(default=6)
2225
... fur = option(black, grey, brown, "tortoise shell", default=black)
2226
... condition = integer(0,10, default=5)
2227
... '''.split('\\n')
2228
>>> from validate import Validator
2229
>>> val= Validator()
2230
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
2231
>>> repeater.validate(val)
2238
... 'name': 'rover',
2239
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2240
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2241
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2246
... 'name': 'rover',
2247
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2248
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2249
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2254
... 'name': 'rover',
2255
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2256
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2257
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2263
... 'tail': 'short',
2264
... 'name': 'pussy',
2265
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2266
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2267
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2271
... 'tail': 'short',
2272
... 'name': 'pussy',
2273
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2274
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2275
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2279
... 'tail': 'short',
2280
... 'name': 'pussy',
2281
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2282
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2283
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2288
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2289
>>> repeater.validate(val)
2293
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2294
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2295
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2298
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2299
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2300
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2304
>>> repeater = ConfigObj(configspec=repeated_4)
2305
>>> repeater['Michael'] = {}
2306
>>> repeater.validate(val)
2309
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2312
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2314
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2315
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2318
>>> repeater.validate(val)
2321
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2325
... 'tail': 'short',
2326
... 'name': 'pussy',
2327
... 'description': {
2329
... 'height': 3.2999999999999998,
2330
... 'coat': {'fur': 'black', 'condition': 5},
2335
... 'tail': 'short',
2336
... 'name': 'pussy',
2337
... 'description': {
2339
... 'height': 3.2999999999999998,
2340
... 'coat': {'fur': 'black', 'condition': 5},
2345
... 'tail': 'short',
2346
... 'name': 'pussy',
2347
... 'description': {
2349
... 'height': 3.2999999999999998,
2350
... 'coat': {'fur': 'black', 'condition': 5},
2357
Test that interpolation is preserved for validated string values.
2358
Also check that interpolation works in configspecs.
2360
>>> t['DEFAULT'] = {}
2361
>>> t['DEFAULT']['test'] = 'a'
2362
>>> t['test'] = '%(test)s'
2366
>>> t.configspec = {'test': 'string'}
2369
>>> t.interpolation = False
2371
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2373
... 'interpolated string = string(default="fuzzy-%(man)s")',
2377
>>> c = ConfigObj(configspec=specs)
2380
>>> c['interpolated string']
2383
FIXME: Above tests will fail if we couldn't import Validator (the ones
2384
that don't raise errors will produce different output and still fail as
2388
if self.configspec is None:
2389
raise ValueError, 'No configspec supplied.'
2391
if VdtMissingValue is None:
2392
raise ImportError('Missing validate module.')
2395
spec_section = section.configspec
2396
if '__many__' in section.configspec:
2397
many = spec_section['__many__']
2398
# dynamically assign the configspecs
2399
# for the sections below
2400
for entry in section.sections:
2401
self._handle_repeat(section[entry], many)
2406
for entry in spec_section:
2407
if entry == '__many__':
2409
if (not entry in section.scalars) or (entry in section.defaults):
2411
# or entries from defaults
2416
val = section[entry]
2418
check = validator.check(spec_section[entry],
2422
except validator.baseErrorClass, e:
2423
if not preserve_errors or isinstance(e, VdtMissingValue):
2426
# preserve the error
2433
if self.stringify or missing:
2434
# if we are doing type conversion
2435
# or the value is a supplied default
2436
if not self.stringify:
2437
if isinstance(check, (list, tuple)):
2439
check = [self._str(item) for item in check]
2440
elif missing and check is None:
2441
# convert the None from a default to a ''
2444
check = self._str(check)
2445
if (check != val) or missing:
2446
section[entry] = check
2447
if missing and entry not in section.defaults:
2448
section.defaults.append(entry)
2450
# FIXME: Will this miss missing sections ?
2451
for entry in section.sections:
2452
if section is self and entry == 'DEFAULT':
2454
check = self.validate(validator, preserve_errors=preserve_errors,
2455
section=section[entry])
2472
class SimpleVal(object):
2475
Can be used to check that all members expected are present.
2477
To use it, provide a configspec with all your members in (the value given
2478
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2479
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2480
members are present, or a dictionary with True/False meaning
2481
present/missing. (Whole missing sections will be replaced with ``False``)
2483
>>> val = SimpleVal()
2499
... '''.split('\\n')
2500
>>> configspec = '''
2515
... '''.split('\\n')
2516
>>> o = ConfigObj(config, configspec=configspec)
2519
>>> o = ConfigObj(configspec=configspec)
2525
self.baseErrorClass = ConfigObjError
2527
def check(self, check, member, missing=False):
2528
"""A dummy check method, always returns the value unchanged."""
2530
raise self.baseErrorClass
2533
# Check / processing functions for options
2534
def flatten_errors(cfg, res, levels=None, results=None):
2536
An example function that will turn a nested dictionary of results
2537
(as returned by ``ConfigObj.validate``) into a flat list.
2539
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2540
dictionary returned by ``validate``.
2542
(This is a recursive function, so you shouldn't use the ``levels`` or
2543
``results`` arguments - they are used by the function.
2545
Returns a list of keys that failed. Each member of the list is a tuple :
2548
([list of sections...], key, result)
2550
If ``validate`` was called with ``preserve_errors=False`` (the default)
2551
then ``result`` will always be ``False``.
2553
*list of sections* is a flattened list of sections that the key was found
2556
If the section was missing then key will be ``None``.
2558
If the value (or section) was missing then ``result`` will be ``False``.
2560
If ``validate`` was called with ``preserve_errors=True`` and a value
2561
was present, but failed the check, then ``result`` will be the exception
2562
object returned. You can use this as a string that describes the failure.
2564
For example *The value "3" is of the wrong type*.
2566
# FIXME: is the ordering of the output arbitrary ?
2568
>>> vtor = validate.Validator()
2574
... another_option = Probably
2576
... another_option = True
2583
... option1 = boolean()
2584
... option2 = boolean()
2585
... option3 = boolean(default=Bad_value)
2587
... option1 = boolean()
2588
... option2 = boolean()
2589
... option3 = boolean(default=Bad_value)
2591
... another_option = boolean()
2593
... another_option = boolean()
2596
... value2 = integer
2597
... value3 = integer(0, 10)
2598
... [[[section3b-sub]]]
2601
... another_option = boolean()
2603
>>> cs = my_cfg.split('\\n')
2604
>>> ini = my_ini.split('\\n')
2605
>>> cfg = ConfigObj(ini, configspec=cs)
2606
>>> res = cfg.validate(vtor, preserve_errors=True)
2608
>>> for entry in flatten_errors(cfg, res):
2609
... section_list, key, error = entry
2610
... section_list.insert(0, '[root]')
2611
... if key is not None:
2612
... section_list.append(key)
2614
... section_list.append('[missing]')
2615
... section_string = ', '.join(section_list)
2616
... errors.append((section_string, ' = ', error))
2618
>>> for entry in errors:
2619
... print entry[0], entry[1], (entry[2] or 0)
2621
[root], option3 = the value "Bad_value" is of the wrong type.
2622
[root], section1, option2 = 0
2623
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2624
[root], section2, another_option = the value "Probably" is of the wrong type.
2625
[root], section3, section3b, section3b-sub, [missing] = 0
2626
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2627
[root], section3, section3b, value3 = the value "11" is too big.
2628
[root], section4, [missing] = 0
2637
results.append((levels[:], None, False))
2641
for (key, val) in res.items():
2644
if isinstance(cfg.get(key), dict):
2647
flatten_errors(cfg[key], val, levels, results)
2649
results.append((levels[:], key, val))
2658
# FIXME: test error code for badly built multiline values
2659
# FIXME: test handling of StringIO
2660
# FIXME: test interpolation with writing
2664
Dummy function to hold some of the doctests.
2701
... 'keys11': 'val1',
2702
... 'keys13': 'val3',
2703
... 'keys12': 'val2',
2706
... 'section 2 sub 1': {
2709
... 'keys21': 'val1',
2710
... 'keys22': 'val2',
2711
... 'keys23': 'val3',
2716
... 'a' = b # !"$%^&*(),::;'@~#= 33
2717
... "b" = b #= 6, 33
2718
... ''' .split('\\n')
2719
>>> t2 = ConfigObj(t)
2720
>>> assert t2 == {'a': 'b', 'b': 'b'}
2721
>>> t2.inline_comments['b'] = ''
2723
>>> assert t2.write() == ['','b = b', '']
2725
# Test ``list_values=False`` stuff
2727
... key1 = no quotes
2728
... key2 = 'single quotes'
2729
... key3 = "double quotes"
2730
... key4 = "list", 'with', several, "quotes"
2732
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2733
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2734
... 'key3': '"double quotes"',
2735
... 'key4': '"list", \\'with\\', several, "quotes"'
2738
>>> cfg = ConfigObj(list_values=False)
2739
>>> cfg['key1'] = 'Multiline\\nValue'
2740
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2742
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2743
>>> cfg.list_values = True
2744
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2745
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2748
Test flatten_errors:
2750
>>> from validate import Validator, VdtValueTooSmallError
2766
... '''.split('\\n')
2767
>>> configspec = '''
2768
... test1= integer(30,50)
2771
... test4=float(6.0)
2773
... test1=integer(30,50)
2776
... test4=float(6.0)
2778
... test1=integer(30,50)
2781
... test4=float(6.0)
2782
... '''.split('\\n')
2783
>>> val = Validator()
2784
>>> c1 = ConfigObj(config, configspec=configspec)
2785
>>> res = c1.validate(val)
2786
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2787
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2789
>>> res = c1.validate(val, preserve_errors=True)
2790
>>> check = flatten_errors(c1, res)
2794
(['section', 'sub section'], 'test4')
2796
(['section'], 'test4')
2797
>>> for entry in check:
2798
... isinstance(entry[2], VdtValueTooSmallError)
2799
... print str(entry[2])
2801
the value "5.0" is too small.
2803
the value "5.0" is too small.
2805
the value "5.0" is too small.
2807
Test unicode handling, BOM, write witha file like object and line endings :
2809
... # initial comment
2810
... # inital comment 2
2812
... test1 = some value
2814
... test2 = another value # inline comment
2815
... # section comment
2816
... [section] # inline comment
2817
... test = test # another inline comment
2821
... # final comment2
2823
>>> u = u_base.encode('utf_8').splitlines(True)
2824
>>> u[0] = BOM_UTF8 + u[0]
2825
>>> uc = ConfigObj(u)
2826
>>> uc.encoding = None
2829
>>> uc == {'test1': 'some value', 'test2': 'another value',
2830
... 'section': {'test': 'test', 'test2': 'test2'}}
2832
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2835
>>> isinstance(uc['test1'], unicode)
2841
>>> uc['latin1'] = "This costs lot's of "
2842
>>> a_list = uc.write()
2845
>>> isinstance(a_list[0], str)
2847
>>> a_list[0].startswith(BOM_UTF8)
2849
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2850
>>> uc = ConfigObj(u)
2853
>>> uc.newlines = '\\r'
2854
>>> from cStringIO import StringIO
2855
>>> file_like = StringIO()
2856
>>> uc.write(file_like)
2857
>>> file_like.seek(0)
2858
>>> uc2 = ConfigObj(file_like)
2861
>>> uc2.filename is None
2863
>>> uc2.newlines == '\\r'
2867
if __name__ == '__main__':
2868
# run the code tests in doctest format
2871
key1= val # comment 1
2872
key2= val # comment 2
2875
key1= val # comment 5
2876
key2= val # comment 6
2879
key1= val # comment 9
2880
key2= val # comment 10
2882
[[lev2ba]] # comment 12
2883
key1= val # comment 13
2885
[[lev2bb]] # comment 15
2886
key1= val # comment 16
2888
[lev1c] # comment 18
2890
[[lev2c]] # comment 20
2892
[[[lev3c]]] # comment 22
2893
key1 = val # comment 23"""
2899
["section 1"] # comment
2908
[['section 2 sub 1']]
2913
name1 = """ a single line value """ # comment
2914
name2 = \''' another single line value \''' # comment
2915
name3 = """ a single line value """
2916
name4 = \''' another single line value \'''
2933
\''' # I guess this is a comment too
2937
m = sys.modules.get('__main__')
2938
globs = m.__dict__.copy()
2939
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2940
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2941
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2943
'INTP_VER': INTP_VER,
2948
doctest.testmod(m, globs=globs)
2959
Better support for configuration from multiple files, including tracking
2960
*where* the original file came from and writing changes to the correct
2964
Make ``newline`` an option (as well as an attribute) ?
2966
``UTF16`` encoded files, when returned as a list of lines, will have the
2967
BOM at the start of every line. Should this be removed from all but the
2970
Option to set warning type for unicode decode ? (Defaults to strict).
2972
A method to optionally remove uniform indentation from multiline values.
2973
(do as an example of using ``walk`` - along with string-escape)
2975
Should the results dictionary from validate be an ordered dictionary if
2976
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2978
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2980
Implement some of the sequence methods (which include slicing) from the
2983
INCOMPATIBLE CHANGES
2984
====================
2986
(I have removed a lot of needless complications - this list is probably not
2987
conclusive, many option/attribute/method names have changed)
2991
The only valid divider is '='
2993
We've removed line continuations with '\'
2995
No recursive lists in values
2999
No distinction between flatfiles and non flatfiles
3001
Change in list syntax - use commas to indicate list, not parentheses
3002
(square brackets and parentheses are no longer recognised as lists)
3004
';' is no longer valid for comments and no multiline comments
3008
We don't allow empty values - have to use '' or ""
3010
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
3011
initialise it as an empty section.
3013
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
3014
replaced by triple quote, multiple line values.
3016
The ``newline``, ``force_return``, and ``default`` options have gone
3018
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
3019
with the ``encode`` and ``decode`` methods.
3021
``fileerror`` and ``createempty`` options have become ``file_error`` and
3024
Partial configspecs (for specifying the order members should be written
3025
out and which should be present) have gone. The configspec is no longer
3026
used to specify order for the ``write`` method.
3028
Exceeding the maximum depth of recursion in string interpolation now
3029
raises an error ``InterpolationDepthError``.
3031
Specifying a value for interpolation which doesn't exist now raises an
3032
error ``MissingInterpolationOption`` (instead of merely being ignored).
3034
The ``writein`` method has been removed.
3036
The comments attribute is now a list (``inline_comments`` equates to the
3037
old comments attribute)
3042
``validate`` doesn't report *extra* values or sections.
3044
You can't have a keyword with the same name as a section (in the same
3045
section). They are both dictionary keys - so they would overlap.
3047
ConfigObj doesn't quote and unquote values if ``list_values=False``.
3048
This means that leading or trailing whitespace in values will be lost when
3049
writing. (Unless you manually quote).
3051
Interpolation checks first the 'DEFAULT' subsection of the current
3052
section, next it checks the 'DEFAULT' section of the parent section,
3053
last it checks the 'DEFAULT' section of the main section.
3055
Logically a 'DEFAULT' section should apply to all subsections of the *same
3056
parent* - this means that checking the 'DEFAULT' subsection in the
3057
*current section* is not necessarily logical ?
3059
In order to simplify unicode support (which is possibly of limited value
3060
in a config file) I have removed automatic support and added the
3061
``encode`` and ``decode methods, which can be used to transform keys and
3062
entries. Because the regex looks for specific values on inital parsing
3063
(i.e. the quotes and the equals signs) it can only read ascii compatible
3064
encodings. For unicode use ``UTF8``, which is ASCII compatible.
3066
Does it matter that we don't support the ':' divider, which is supported
3067
by ``ConfigParser`` ?
3069
The regular expression correctly removes the value -
3070
``"'hello', 'goodbye'"`` and then unquote just removes the front and
3071
back quotes (called from ``_handle_value``). What should we do ??
3072
(*ought* to raise exception because it's an invalid value if lists are
3073
off *sigh*. This is not what you want if you want to do your own list
3074
processing - would be *better* in this case not to unquote.)
3076
String interpolation and validation don't play well together. When
3077
validation changes type it sets the value. This will correctly fetch the
3078
value using interpolation - but then overwrite the interpolation reference.
3079
If the value is unchanged by validation (it's a string) - but other types
3086
List values allow you to specify multiple values for a keyword. This
3087
maps to a list as the resulting Python object when parsed.
3089
The syntax for lists is easy. A list is a comma separated set of values.
3090
If these values contain quotes, the hash mark, or commas, then the values
3091
can be surrounded by quotes. e.g. : ::
3093
keyword = value1, 'value 2', "value 3"
3095
If a value needs to be a list, but only has one member, then you indicate
3096
this with a trailing comma. e.g. : ::
3098
keyword = "single value",
3100
If a value needs to be a list, but it has no members, then you indicate
3101
this with a single comma. e.g. : ::
3103
keyword = , # an empty list
3105
Using triple quotes it will be possible for single values to contain
3106
newlines and *both* single quotes and double quotes. Triple quotes aren't
3107
allowed in list values. This means that the members of list values can't
3108
contain carriage returns (or line feeds :-) or both quote values.
3116
Removed ``BOM_UTF8`` from ``__all__``.
3118
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3119
*only* ``True`` for the ``UTF16`` encoding.
3121
File like objects no longer need a ``seek`` attribute.
3123
ConfigObj no longer keeps a reference to file like objects. Instead the
3124
``write`` method takes a file like object as an optional argument. (Which
3125
will be used in preference of the ``filename`` attribute if htat exists as
3128
Full unicode support added. New options/attributes ``encoding``,
3129
``default_encoding``.
3131
utf16 files decoded to unicode.
3133
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3134
written out at the start of the file. (It will normally only be ``True`` if
3135
the utf8 BOM was found when the file was read.)
3137
File paths are *not* converted to absolute paths, relative paths will
3138
remain relative as the ``filename`` attribute.
3140
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3146
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3147
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3148
``enumerate`` is needed for all versions ofPython 2.2)
3150
Deprecated ``istrue``, replaced it with ``as_bool``.
3152
Added ``as_int`` and ``as_float``.
3154
utf8 and utf16 BOM handled in an endian agnostic way.
3159
Validation no longer done on the 'DEFAULT' section (only in the root
3160
level). This allows interpolation in configspecs.
3162
Change in validation syntax implemented in validate 0.2.1
3169
Added ``merge``, a recursive update.
3171
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3174
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3176
Fixed bug where a config file is *all* comment, the comment will now be
3177
``initial_comment`` rather than ``final_comment``.
3182
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3187
Fixed bug in ``Section.walk`` when transforming names as well as values.
3189
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3192
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3193
are multiline values.
3195
List values are written as ``item, item`` rather than ``item,item``.
3202
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3210
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3211
a reference to the new section.
3216
Removed ``PositionError``.
3218
Allowed quotes around keys as documented.
3220
Fixed bug with commas in comments. (matched as a list value)
3227
Fixed bug in initialising ConfigObj from a ConfigObj.
3229
Changed the mailing list address.
3236
Fixed bug in ``Section.__delitem__`` oops.
3241
Interpolation is switched off before writing out files.
3243
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3244
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3246
Moved the doctests from the ``__init__`` method to a separate function.
3247
(For the sake of IDE calltips).
3254
String values unchanged by validation *aren't* reset. This preserves
3255
interpolation in string values.
3260
None from a default is turned to '' if stringify is off - because setting
3261
a value to None raises an error.
3270
Actually added the RepeatSectionError class ;-)
3275
If ``stringify`` is off - list values are preserved by the ``validate``
3283
Fixed ``simpleVal``.
3285
Added ``RepeatSectionError`` error if you have additional sections in a
3286
section with a ``__many__`` (repeated) section.
3290
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
3291
mutated the self._infile, self._index and self._maxline attributes into
3292
local variables and method parameters
3294
Reshaped the ConfigObj._multiline method to better reflect its semantics
3296
Changed the "default_test" test in ConfigObj.validate to check the fix for
3297
the bug in validate.Validator.check
3304
Updated comments at top
3311
Implemented repeated sections.
3315
Added test for interpreter version: raises RuntimeError if earlier than
3323
Implemented default values in configspecs.
3327
Fixed naked except: clause in validate that was silencing the fact
3328
that Python2.2 does not have dict.pop
3335
Bug fix causing error if file didn't exist.
3342
Adjusted doctests for Python 2.2.3 compatibility
3349
Added the inline_comments attribute
3351
We now preserve and rewrite all comments in the config file
3353
configspec is now a section attribute
3355
The validate method changes values in place
3357
Added InterpolationError
3359
The errors now have line number, line, and message attributes. This
3360
simplifies error handling
3369
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3372
Replaced ``basestring`` with ``types.StringTypes``
3374
Removed the ``writein`` method
3383
Indentation in config file is not significant anymore, subsections are
3384
designated by repeating square brackets
3386
Adapted all tests and docs to the new format
3400
Reformatted final docstring in ReST format, indented it for easier folding
3402
Code tests converted to doctest format, and scattered them around
3403
in various docstrings
3405
Walk method rewritten using scalars and sections attributes
3412
Changed Validator and SimpleVal "test" methods to "check"
3419
Changed Section.sequence to Section.scalars and Section.sections
3421
Added Section.configspec
3423
Sections in the root section now have no extra indentation
3425
Comments now better supported in Section and preserved by ConfigObj
3427
Comments also written out
3429
Implemented initial_comment and final_comment
3431
A scalar value after a section will now raise an error
3436
Fixed a couple of bugs
3438
Can now pass a tuple instead of a list
3440
Simplified dict and walk methods
3442
Added __str__ to Section
3454
The stringify option implemented. On by default.
3459
Renamed private attributes with a single underscore prefix.
3461
Changes to interpolation - exceeding recursion depth, or specifying a
3462
missing value, now raise errors.
3464
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3465
``is True`` and ``is False``)
3467
Added test for duplicate section and member (and fixed bug)
3481
Now properly handles values including comments and lists.
3483
Better error handling.
3485
String interpolation.
3487
Some options implemented.
3489
You can pass a Section a dictionary to initialise it.
3491
Setting a Section member to a dictionary will create a Section instance.
3498
Experimental reader.
3500
A reasonably elegant implementation - a basic reader in 160 lines of code.
3502
*A programming language is a medium of expression.* - Paul Graham