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 not self.comments.has_key(key):
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):
364
if not self.has_key(key):
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
370
if not self.has_key(key):
371
self.sections.append(key)
372
new_depth = self.depth + 1
383
if not self.has_key(key):
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
... print >> sys.stderr, type(entry)
682
... raise AssertionError, 'decode failed.'
683
... if isinstance(val[entry], dict):
684
... testuni(val[entry])
685
... elif not isinstance(val[entry], unicode):
686
... raise AssertionError, 'decode failed.'
688
>>> m.encode('ascii')
692
def decode(section, key, encoding=encoding):
695
if isinstance(val, (list, tuple)):
698
newval.append(entry.decode(encoding))
699
elif isinstance(val, dict):
702
newval = val.decode(encoding)
703
newkey = key.decode(encoding)
704
section.rename(key, newkey)
705
section[newkey] = newval
706
# using ``call_on_sections`` allows us to modify section names
707
self.walk(decode, call_on_sections=True)
709
def encode(self, encoding):
711
Encode all strings and values from unicode,
712
using the specified encoding.
714
Works with subsections and list values.
715
Uses the ``walk`` method.
717
def encode(section, key, encoding=encoding):
720
if isinstance(val, (list, tuple)):
723
newval.append(entry.encode(encoding))
724
elif isinstance(val, dict):
727
newval = val.encode(encoding)
728
newkey = key.encode(encoding)
729
section.rename(key, newkey)
730
section[newkey] = newval
731
self.walk(encode, call_on_sections=True)
733
def istrue(self, key):
734
"""A deprecated version of ``as_bool``."""
735
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
736
'instead.', DeprecationWarning)
737
return self.as_bool(key)
739
def as_bool(self, key):
741
Accepts a key as input. The corresponding value must be a string or
742
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
743
retain compatibility with Python 2.2.
745
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
748
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
751
``as_bool`` is not case sensitive.
753
Any other input will raise a ``ValueError``.
758
Traceback (most recent call last):
759
ValueError: Value "fish" is neither True nor False
774
if not isinstance(val, StringTypes):
777
return self.main._bools[val.lower()]
779
raise ValueError('Value "%s" is neither True nor False' % val)
781
def as_int(self, key):
783
A convenience method which coerces the specified value to an integer.
785
If the value is an invalid literal for ``int``, a ``ValueError`` will
791
Traceback (most recent call last):
792
ValueError: invalid literal for int(): fish
798
Traceback (most recent call last):
799
ValueError: invalid literal for int(): 3.2
801
return int(self[key])
803
def as_float(self, key):
805
A convenience method which coerces the specified value to a float.
807
If the value is an invalid literal for ``float``, a ``ValueError`` will
813
Traceback (most recent call last):
814
ValueError: invalid literal for float(): fish
822
return float(self[key])
825
class ConfigObj(Section):
827
An object to read, create, and write config files.
829
Testing with duplicate keys and sections.
839
>>> ConfigObj(c.split('\\n'), raise_errors = True)
840
Traceback (most recent call last):
841
DuplicateError: Duplicate section name at line 5.
849
... 'member1' = value
853
>>> ConfigObj(d.split('\\n'), raise_errors = True)
854
Traceback (most recent call last):
855
DuplicateError: Duplicate keyword name at line 6.
858
_keyword = re.compile(r'''^ # line start
861
(?:".*?")| # double quotes
862
(?:'.*?')| # single quotes
863
(?:[^'"=].*?) # no quotes
866
(.*) # value (including list values and comments)
871
_sectionmarker = re.compile(r'''^
872
(\s*) # 1: indentation
873
((?:\[\s*)+) # 2: section marker open
874
( # 3: section name open
875
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
876
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
877
(?:[^'"\s].*?) # at least one non-space unquoted
878
) # section name close
879
((?:\s*\])+) # 4: section marker close
880
\s*(\#.*)? # 5: optional comment
884
# this regexp pulls list values out as a single string
885
# or single values and comments
886
_valueexp = re.compile(r'''^
892
(?:".*?")| # double quotes
893
(?:'.*?')| # single quotes
894
(?:[^'",\#][^,\#]*?) # unquoted
897
)* # match all list items ending in a comma (if any)
900
(?:".*?")| # double quotes
901
(?:'.*?')| # single quotes
902
(?:[^'",\#\s][^,]*?) # unquoted
903
)? # last item in a list - or string value
905
(,) # alternatively a single comma - empty list
907
\s*(\#.*)? # optional comment
911
# use findall to get the members of a list value
912
_listvalueexp = re.compile(r'''
914
(?:".*?")| # double quotes
915
(?:'.*?')| # single quotes
916
(?:[^'",\#].*?) # unquoted
922
# this regexp is used for the value
923
# when lists are switched off
924
_nolistvalue = re.compile(r'''^
926
(?:".*?")| # double quotes
927
(?:'.*?')| # single quotes
928
(?:[^'"\#].*?) # unquoted
930
\s*(\#.*)? # optional comment
934
# regexes for finding triple quoted values on one line
935
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
936
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
937
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
938
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
941
"'''": (_single_line_single, _multi_line_single),
942
'"""': (_single_line_double, _multi_line_double),
945
# Used by the ``istrue`` Section method
947
'yes': True, 'no': False,
948
'on': True, 'off': False,
949
'1': True, '0': False,
950
'true': True, 'false': False,
953
def __init__(self, infile=None, options=None, **kwargs):
955
Parse or create a config file object.
957
``ConfigObj(infile=None, options=None, **kwargs)``
963
# keyword arguments take precedence over an options dictionary
964
options.update(kwargs)
965
# init the superclass
966
Section.__init__(self, self, 0, self)
968
defaults = OPTION_DEFAULTS.copy()
969
for entry in options.keys():
970
if entry not in defaults.keys():
971
raise TypeError, 'Unrecognised option "%s".' % entry
972
# TODO: check the values too.
974
# Add any explicit options to the defaults
975
defaults.update(options)
977
# initialise a few variables
980
self.raise_errors = defaults['raise_errors']
981
self.interpolation = defaults['interpolation']
982
self.list_values = defaults['list_values']
983
self.create_empty = defaults['create_empty']
984
self.file_error = defaults['file_error']
985
self.stringify = defaults['stringify']
986
self.indent_type = defaults['indent_type']
987
self.encoding = defaults['encoding']
988
self.default_encoding = defaults['default_encoding']
992
self.initial_comment = []
993
self.final_comment = []
995
if isinstance(infile, StringTypes):
996
self.filename = infile
997
if os.path.isfile(infile):
998
infile = open(infile).read() or []
999
elif self.file_error:
1000
# raise an error if the file doesn't exist
1001
raise IOError, 'Config file not found: "%s".' % self.filename
1003
# file doesn't already exist
1004
if self.create_empty:
1005
# this is a good test that the filename specified
1006
# isn't impossible - like on a non existent device
1007
h = open(infile, 'w')
1011
elif isinstance(infile, (list, tuple)):
1012
infile = list(infile)
1013
elif isinstance(infile, dict):
1015
# the Section class handles creating subsections
1016
if isinstance(infile, ConfigObj):
1017
# get a copy of our ConfigObj
1018
infile = infile.dict()
1019
for entry in infile:
1020
self[entry] = infile[entry]
1022
if defaults['configspec'] is not None:
1023
self._handle_configspec(defaults['configspec'])
1025
self.configspec = None
1027
elif hasattr(infile, 'read'):
1028
# This supports file like objects
1029
infile = infile.read() or []
1030
# needs splitting into lines - but needs doing *after* decoding
1031
# in case it's not an 8 bit encoding
1033
raise TypeError, ('infile must be a filename,'
1034
' file like object, or list of lines.')
1037
# don't do it for the empty ConfigObj
1038
infile = self._handle_bom(infile)
1039
# infile is now *always* a list
1041
# Set the newlines attribute (first line ending it finds)
1042
# and strip trailing '\n' or '\r' from lines
1044
if (not line) or (line[-1] not in '\r\n'):
1046
for end in ('\r\n', '\n', '\r'):
1047
if line.endswith(end):
1051
infile = [line.rstrip('\r\n') for line in infile]
1054
# if we had any errors, now is the time to raise them
1056
error = ConfigObjError("Parsing failed.")
1057
# set the errors attribute; it's a list of tuples:
1058
# (error_type, message, line_number)
1059
error.errors = self._errors
1060
# set the config attribute
1063
# delete private attributes
1066
if defaults['configspec'] is None:
1067
self.configspec = None
1069
self._handle_configspec(defaults['configspec'])
1071
def _handle_bom(self, infile):
1073
Handle any BOM, and decode if necessary.
1075
If an encoding is specified, that *must* be used - but the BOM should
1076
still be removed (and the BOM attribute set).
1078
(If the encoding is wrongly specified, then a BOM for an alternative
1079
encoding won't be discovered or removed.)
1081
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1082
removed. The BOM attribute will be set. UTF16 will be decoded to
1085
NOTE: This method must not be called with an empty ``infile``.
1087
Specifying the *wrong* encoding is likely to cause a
1088
``UnicodeDecodeError``.
1090
``infile`` must always be returned as a list of lines, but may be
1091
passed in as a single string.
1093
if ((self.encoding is not None) and
1094
(self.encoding.lower() not in BOM_LIST)):
1095
# No need to check for a BOM
1096
# encoding specified doesn't have one
1098
return self._decode(infile, self.encoding)
1100
if isinstance(infile, (list, tuple)):
1104
if self.encoding is not None:
1105
# encoding explicitly supplied
1106
# And it could have an associated BOM
1107
# TODO: if encoding is just UTF16 - we ought to check for both
1108
# TODO: big endian and little endian versions.
1109
enc = BOM_LIST[self.encoding.lower()]
1111
# For UTF16 we try big endian and little endian
1112
for BOM, (encoding, final_encoding) in BOMS.items():
1113
if not final_encoding:
1116
if infile.startswith(BOM):
1119
# Don't need to remove BOM
1120
return self._decode(infile, encoding)
1122
# If we get this far, will *probably* raise a DecodeError
1123
# As it doesn't appear to start with a BOM
1124
return self._decode(infile, self.encoding)
1128
if not line.startswith(BOM):
1129
return self._decode(infile, self.encoding)
1131
newline = line[len(BOM):]
1134
if isinstance(infile, (list, tuple)):
1139
return self._decode(infile, self.encoding)
1141
# No encoding specified - so we need to check for UTF8/UTF16
1142
for BOM, (encoding, final_encoding) in BOMS.items():
1143
if not line.startswith(BOM):
1147
self.encoding = final_encoding
1148
if not final_encoding:
1152
newline = line[len(BOM):]
1153
if isinstance(infile, (list, tuple)):
1157
# UTF8 - don't decode
1158
if isinstance(infile, StringTypes):
1159
return infile.splitlines(True)
1162
# UTF16 - have to decode
1163
return self._decode(infile, encoding)
1165
# No BOM discovered and no encoding specified, just return
1166
if isinstance(infile, StringTypes):
1167
# infile read from a file will be a single string
1168
return infile.splitlines(True)
1172
def _a_to_u(self, string):
1173
"""Decode ascii strings to unicode if a self.encoding is specified."""
1174
if not self.encoding:
1177
return string.decode('ascii')
1179
def _decode(self, infile, encoding):
1181
Decode infile to unicode. Using the specified encoding.
1183
if is a string, it also needs converting to a list.
1185
if isinstance(infile, StringTypes):
1187
# NOTE: Could raise a ``UnicodeDecodeError``
1188
return infile.decode(encoding).splitlines(True)
1189
for i, line in enumerate(infile):
1190
if not isinstance(line, unicode):
1191
# NOTE: The isinstance test here handles mixed lists of unicode/string
1192
# NOTE: But the decode will break on any non-string values
1193
# NOTE: Or could raise a ``UnicodeDecodeError``
1194
infile[i] = line.decode(encoding)
1197
def _decode_element(self, line):
1198
"""Decode element to unicode if necessary."""
1199
if not self.encoding:
1201
if isinstance(line, str) and self.default_encoding:
1202
return line.decode(self.default_encoding)
1205
def _str(self, value):
1207
Used by ``stringify`` within validate, to turn non-string values
1210
if not isinstance(value, StringTypes):
1215
def _parse(self, infile):
1217
Actually parse the config file
1219
Testing Interpolation
1222
>>> c['DEFAULT'] = {
1224
... 'userdir': 'c:\\\\home',
1228
>>> c['section'] = {
1229
... 'a': '%(datadir)s\\\\some path\\\\file.py',
1230
... 'b': '%(userdir)s\\\\some path\\\\file.py',
1231
... 'c': 'Yo %(a)s',
1232
... 'd': '%(not_here)s',
1235
>>> c['section']['DEFAULT'] = {
1236
... 'datadir': 'c:\\\\silly_test',
1237
... 'a': 'hello - %(b)s',
1239
>>> c['section']['a'] == 'c:\\\\silly_test\\\\some path\\\\file.py'
1241
>>> c['section']['b'] == 'c:\\\\home\\\\some path\\\\file.py'
1243
>>> c['section']['c'] == 'Yo hello - goodbye'
1246
Switching Interpolation Off
1248
>>> c.interpolation = False
1249
>>> c['section']['a'] == '%(datadir)s\\\\some path\\\\file.py'
1251
>>> c['section']['b'] == '%(userdir)s\\\\some path\\\\file.py'
1253
>>> c['section']['c'] == 'Yo %(a)s'
1256
Testing the interpolation errors.
1258
>>> c.interpolation = True
1259
>>> c['section']['d']
1260
Traceback (most recent call last):
1261
MissingInterpolationOption: missing option "not_here" in interpolation.
1262
>>> c['section']['e']
1263
Traceback (most recent call last):
1264
InterpolationDepthError: max interpolation depth exceeded in value "%(c)s".
1266
Testing our quoting.
1268
>>> i._quote('\"""\'\'\'')
1269
Traceback (most recent call last):
1270
SyntaxError: EOF while scanning triple-quoted string
1272
... i._quote('\\n', multiline=False)
1273
... except ConfigObjError, e:
1275
'Value "\\n" cannot be safely quoted.'
1276
>>> k._quote(' "\' ', multiline=False)
1277
Traceback (most recent call last):
1278
SyntaxError: EOL while scanning single-quoted string
1280
Testing with "stringify" off.
1281
>>> c.stringify = False
1283
Traceback (most recent call last):
1284
TypeError: Value is not a string "1".
1289
maxline = len(infile) - 1
1291
reset_comment = False
1292
while cur_index < maxline:
1296
line = infile[cur_index]
1297
sline = line.strip()
1298
# do we have anything on the line ?
1299
if not sline or sline.startswith('#'):
1300
reset_comment = False
1301
comment_list.append(line)
1304
# preserve initial comment
1305
self.initial_comment = comment_list
1308
reset_comment = True
1309
# first we check if it's a section marker
1310
mat = self._sectionmarker.match(line)
1311
## print >> sys.stderr, sline, mat
1314
(indent, sect_open, sect_name, sect_close, comment) = (
1316
if indent and (self.indent_type is None):
1317
self.indent_type = indent[0]
1318
cur_depth = sect_open.count('[')
1319
if cur_depth != sect_close.count(']'):
1321
"Cannot compute the section depth at line %s.",
1322
NestingError, infile, cur_index)
1324
if cur_depth < this_section.depth:
1325
# the new section is dropping back to a previous level
1327
parent = self._match_depth(
1332
"Cannot compute nesting level at line %s.",
1333
NestingError, infile, cur_index)
1335
elif cur_depth == this_section.depth:
1336
# the new section is a sibling of the current section
1337
parent = this_section.parent
1338
elif cur_depth == this_section.depth + 1:
1339
# the new section is a child the current section
1340
parent = this_section
1343
"Section too nested at line %s.",
1344
NestingError, infile, cur_index)
1346
sect_name = self._unquote(sect_name)
1347
if parent.has_key(sect_name):
1348
## print >> sys.stderr, sect_name
1350
'Duplicate section name at line %s.',
1351
DuplicateError, infile, cur_index)
1353
# create the new section
1354
this_section = Section(
1359
parent[sect_name] = this_section
1360
parent.inline_comments[sect_name] = comment
1361
parent.comments[sect_name] = comment_list
1362
## print >> sys.stderr, parent[sect_name] is this_section
1365
# it's not a section marker,
1366
# so it should be a valid ``key = value`` line
1367
mat = self._keyword.match(line)
1368
## print >> sys.stderr, sline, mat
1370
# is a keyword value
1371
# value will include any inline comment
1372
(indent, key, value) = mat.groups()
1373
if indent and (self.indent_type is None):
1374
self.indent_type = indent[0]
1375
# check for a multiline value
1376
if value[:3] in ['"""', "'''"]:
1378
(value, comment, cur_index) = self._multiline(
1379
value, infile, cur_index, maxline)
1382
'Parse error in value at line %s.',
1383
ParseError, infile, cur_index)
1386
# extract comment and lists
1388
(value, comment) = self._handle_value(value)
1391
'Parse error in value at line %s.',
1392
ParseError, infile, cur_index)
1395
## print >> sys.stderr, sline
1396
key = self._unquote(key)
1397
if this_section.has_key(key):
1399
'Duplicate keyword name at line %s.',
1400
DuplicateError, infile, cur_index)
1403
## print >> sys.stderr, this_section.name
1404
this_section[key] = value
1405
this_section.inline_comments[key] = comment
1406
this_section.comments[key] = comment_list
1407
## print >> sys.stderr, key, this_section[key]
1408
## if this_section.name is not None:
1409
## print >> sys.stderr, this_section
1410
## print >> sys.stderr, this_section.parent
1411
## print >> sys.stderr, this_section.parent[this_section.name]
1414
# it neither matched as a keyword
1415
# or a section marker
1417
'Invalid line at line "%s".',
1418
ParseError, infile, cur_index)
1419
if self.indent_type is None:
1420
# no indentation used, set the type accordingly
1421
self.indent_type = ''
1422
# preserve the final comment
1423
if not self and not self.initial_comment:
1424
self.initial_comment = comment_list
1426
self.final_comment = comment_list
1428
def _match_depth(self, sect, depth):
1430
Given a section and a depth level, walk back through the sections
1431
parents to see if the depth level matches a previous section.
1433
Return a reference to the right section,
1434
or raise a SyntaxError.
1436
while depth < sect.depth:
1437
if sect is sect.parent:
1438
# we've reached the top level already
1441
if sect.depth == depth:
1443
# shouldn't get here
1446
def _handle_error(self, text, ErrorClass, infile, cur_index):
1448
Handle an error according to the error settings.
1450
Either raise the error or store it.
1451
The error will have occured at ``cur_index``
1453
line = infile[cur_index]
1454
message = text % cur_index
1455
error = ErrorClass(message, cur_index, line)
1456
if self.raise_errors:
1457
# raise the error - parsing stops here
1460
# reraise when parsing has finished
1461
self._errors.append(error)
1463
def _unquote(self, value):
1464
"""Return an unquoted version of a value"""
1465
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1469
def _quote(self, value, multiline=True):
1471
Return a safely quoted version of a value.
1473
Raise a ConfigObjError if the value cannot be safely quoted.
1474
If multiline is ``True`` (default) then use triple quotes
1477
Don't quote values that don't need it.
1478
Recursively quote members of a list and return a comma joined list.
1479
Multiline is ``False`` for lists.
1480
Obey list syntax for empty and single member lists.
1482
If ``list_values=False`` then the value is only quoted if it contains
1483
a ``\n`` (is multiline).
1485
if isinstance(value, (list, tuple)):
1488
elif len(value) == 1:
1489
return self._quote(value[0], multiline=False) + ','
1490
return ', '.join([self._quote(val, multiline=False)
1492
if not isinstance(value, StringTypes):
1496
raise TypeError, 'Value "%s" is not a string.' % value
1500
wspace_plus = ' \r\t\n\v\t\'"'
1505
if (not self.list_values and '\n' not in value) or not (multiline and
1506
((("'" in value) and ('"' in value)) or ('\n' in value))):
1507
if not self.list_values:
1508
# we don't quote if ``list_values=False``
1510
# for normal values either single or double quotes will do
1512
# will only happen if multiline is off - e.g. '\n' in key
1513
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1515
elif ((value[0] not in wspace_plus) and
1516
(value[-1] not in wspace_plus) and
1517
(',' not in value)):
1520
if ("'" in value) and ('"' in value):
1521
raise ConfigObjError, (
1522
'Value "%s" cannot be safely quoted.' % value)
1528
# if value has '\n' or "'" *and* '"', it will need triple quotes
1529
if (value.find('"""') != -1) and (value.find("'''") != -1):
1530
raise ConfigObjError, (
1531
'Value "%s" cannot be safely quoted.' % value)
1532
if value.find('"""') == -1:
1538
def _handle_value(self, value):
1540
Given a value string, unquote, remove comment,
1541
handle lists. (including empty and single member lists)
1543
Testing list values.
1545
>>> testconfig3 = '''
1548
... c = test1, test2 , test3
1549
... d = test1, test2, test3,
1551
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1554
>>> d['b'] == ['test']
1556
>>> d['c'] == ['test1', 'test2', 'test3']
1558
>>> d['d'] == ['test1', 'test2', 'test3']
1561
Testing with list values off.
1564
... testconfig3.split('\\n'),
1565
... raise_errors=True,
1566
... list_values=False)
1569
>>> e['b'] == 'test,'
1571
>>> e['c'] == 'test1, test2 , test3'
1573
>>> e['d'] == 'test1, test2, test3,'
1576
Testing creating from a dictionary.
1599
>>> g = ConfigObj(f)
1603
Testing we correctly detect badly built list values (4 of them).
1605
>>> testconfig4 = '''
1609
... dummy = ,,hello, goodbye
1612
... ConfigObj(testconfig4.split('\\n'))
1613
... except ConfigObjError, e:
1617
Testing we correctly detect badly quoted values (4 of them).
1619
>>> testconfig5 = '''
1620
... config = "hello # comment
1622
... fish = 'goodbye # comment
1623
... dummy = "hello again
1626
... ConfigObj(testconfig5.split('\\n'))
1627
... except ConfigObjError, e:
1631
# do we look for lists in values ?
1632
if not self.list_values:
1633
mat = self._nolistvalue.match(value)
1636
(value, comment) = mat.groups()
1637
# NOTE: we don't unquote here
1638
return (value, comment)
1639
mat = self._valueexp.match(value)
1641
# the value is badly constructed, probably badly quoted,
1642
# or an invalid list
1644
(list_values, single, empty_list, comment) = mat.groups()
1645
if (list_values == '') and (single is None):
1646
# change this if you want to accept empty values
1648
# NOTE: note there is no error handling from here if the regex
1649
# is wrong: then incorrect values will slip through
1650
if empty_list is not None:
1651
# the single comma - meaning an empty list
1652
return ([], comment)
1653
if single is not None:
1654
single = self._unquote(single)
1655
if list_values == '':
1657
return (single, comment)
1658
the_list = self._listvalueexp.findall(list_values)
1659
the_list = [self._unquote(val) for val in the_list]
1660
if single is not None:
1661
the_list += [single]
1662
return (the_list, comment)
1664
def _multiline(self, value, infile, cur_index, maxline):
1666
Extract the value, where we are in a multiline situation
1668
Testing multiline values.
1671
... 'name4': ' another single line value ',
1672
... 'multi section': {
1673
... 'name4': '\\n Well, this is a\\n multiline '
1675
... 'name2': '\\n Well, this is a\\n multiline '
1677
... 'name3': '\\n Well, this is a\\n multiline '
1679
... 'name1': '\\n Well, this is a\\n multiline '
1682
... 'name2': ' another single line value ',
1683
... 'name3': ' a single line value ',
1684
... 'name1': ' a single line value ',
1689
newvalue = value[3:]
1690
single_line = self._triple_quote[quot][0]
1691
multi_line = self._triple_quote[quot][1]
1692
mat = single_line.match(value)
1694
retval = list(mat.groups())
1695
retval.append(cur_index)
1697
elif newvalue.find(quot) != -1:
1698
# somehow the triple quote is missing
1701
while cur_index < maxline:
1704
line = infile[cur_index]
1705
if line.find(quot) == -1:
1708
# end of multiline, process it
1711
# we've got to the end of the config, oops...
1713
mat = multi_line.match(line)
1715
# a badly formed line
1717
(value, comment) = mat.groups()
1718
return (newvalue + value, comment, cur_index)
1720
def _handle_configspec(self, configspec):
1721
"""Parse the configspec."""
1723
configspec = ConfigObj(
1728
except ConfigObjError, e:
1729
# FIXME: Should these errors have a reference
1730
# to the already parsed ConfigObj ?
1731
raise ConfigspecError('Parsing configspec failed: %s' % e)
1733
raise IOError('Reading configspec failed: %s' % e)
1734
self._set_configspec_value(configspec, self)
1736
def _set_configspec_value(self, configspec, section):
1737
"""Used to recursively set configspec values."""
1738
if '__many__' in configspec.sections:
1739
section.configspec['__many__'] = configspec['__many__']
1740
if len(configspec.sections) > 1:
1741
# FIXME: can we supply any useful information here ?
1742
raise RepeatSectionError
1743
for entry in configspec.scalars:
1744
section.configspec[entry] = configspec[entry]
1745
for entry in configspec.sections:
1746
if entry == '__many__':
1748
if not section.has_key(entry):
1750
self._set_configspec_value(configspec[entry], section[entry])
1752
def _handle_repeat(self, section, configspec):
1753
"""Dynamically assign configspec for repeated section."""
1755
section_keys = configspec.sections
1756
scalar_keys = configspec.scalars
1757
except AttributeError:
1758
section_keys = [entry for entry in configspec
1759
if isinstance(configspec[entry], dict)]
1760
scalar_keys = [entry for entry in configspec
1761
if not isinstance(configspec[entry], dict)]
1762
if '__many__' in section_keys and len(section_keys) > 1:
1763
# FIXME: can we supply any useful information here ?
1764
raise RepeatSectionError
1767
for entry in scalar_keys:
1768
val = configspec[entry]
1769
scalars[entry] = val
1770
for entry in section_keys:
1771
val = configspec[entry]
1772
if entry == '__many__':
1773
scalars[entry] = val
1775
sections[entry] = val
1777
section.configspec = scalars
1778
for entry in sections:
1779
if not section.has_key(entry):
1781
self._handle_repeat(section[entry], sections[entry])
1783
def _write_line(self, indent_string, entry, this_entry, comment):
1784
"""Write an individual line, for the write method"""
1785
# NOTE: the calls to self._quote here handles non-StringType values.
1786
return '%s%s%s%s%s' % (
1788
self._decode_element(self._quote(entry, multiline=False)),
1789
self._a_to_u(' = '),
1790
self._decode_element(self._quote(this_entry)),
1791
self._decode_element(comment))
1793
def _write_marker(self, indent_string, depth, entry, comment):
1794
"""Write a section marker line"""
1795
return '%s%s%s%s%s' % (
1797
self._a_to_u('[' * depth),
1798
self._quote(self._decode_element(entry), multiline=False),
1799
self._a_to_u(']' * depth),
1800
self._decode_element(comment))
1802
def _handle_comment(self, comment):
1804
Deal with a comment.
1806
>>> filename = a.filename
1807
>>> a.filename = None
1808
>>> values = a.write()
1810
>>> while index < 23:
1812
... line = values[index-1]
1813
... assert line.endswith('# comment ' + str(index))
1814
>>> a.filename = filename
1816
>>> start_comment = ['# Initial Comment', '', '#']
1817
>>> end_comment = ['', '#', '# Final Comment']
1818
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1819
>>> nc = ConfigObj(newconfig)
1820
>>> nc.initial_comment
1821
['# Initial Comment', '', '#']
1822
>>> nc.final_comment
1823
['', '#', '# Final Comment']
1824
>>> nc.initial_comment == start_comment
1826
>>> nc.final_comment == end_comment
1831
if self.indent_type == '\t':
1832
start = self._a_to_u('\t')
1834
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
1835
if not comment.startswith('#'):
1836
start += _a_to_u('# ')
1837
return (start + comment)
1839
def _compute_indent_string(self, depth):
1841
Compute the indent string, according to current indent_type and depth
1843
if self.indent_type == '':
1844
# no indentation at all
1846
if self.indent_type == '\t':
1848
if self.indent_type == ' ':
1849
return ' ' * NUM_INDENT_SPACES * depth
1854
def write(self, outfile=None, section=None):
1856
Write the current ConfigObj as a file
1858
tekNico: FIXME: use StringIO instead of real files
1860
>>> filename = a.filename
1861
>>> a.filename = 'test.ini'
1863
>>> a.filename = filename
1864
>>> a == ConfigObj('test.ini', raise_errors=True)
1866
>>> os.remove('test.ini')
1867
>>> b.filename = 'test.ini'
1869
>>> b == ConfigObj('test.ini', raise_errors=True)
1871
>>> os.remove('test.ini')
1872
>>> i.filename = 'test.ini'
1874
>>> i == ConfigObj('test.ini', raise_errors=True)
1876
>>> os.remove('test.ini')
1878
>>> a['DEFAULT'] = {'a' : 'fish'}
1879
>>> a['a'] = '%(a)s'
1881
['a = %(a)s', '[DEFAULT]', 'a = fish']
1883
if self.indent_type is None:
1884
# this can be true if initialised from a dictionary
1885
self.indent_type = DEFAULT_INDENT_TYPE
1888
cs = self._a_to_u('#')
1889
csp = self._a_to_u('# ')
1891
int_val = self.interpolation
1892
self.interpolation = False
1894
for line in self.initial_comment:
1895
line = self._decode_element(line)
1896
stripped_line = line.strip()
1897
if stripped_line and not stripped_line.startswith(cs):
1901
indent_string = self._a_to_u(
1902
self._compute_indent_string(section.depth))
1903
for entry in (section.scalars + section.sections):
1904
if entry in section.defaults:
1905
# don't write out default values
1907
for comment_line in section.comments[entry]:
1908
comment_line = self._decode_element(comment_line.lstrip())
1909
if comment_line and not comment_line.startswith(cs):
1910
comment_line = csp + comment_line
1911
out.append(indent_string + comment_line)
1912
this_entry = section[entry]
1913
comment = self._handle_comment(section.inline_comments[entry])
1915
if isinstance(this_entry, dict):
1917
out.append(self._write_marker(
1922
out.extend(self.write(section=this_entry))
1924
out.append(self._write_line(
1931
for line in self.final_comment:
1932
line = self._decode_element(line)
1933
stripped_line = line.strip()
1934
if stripped_line and not stripped_line.startswith(cs):
1937
self.interpolation = int_val
1939
if section is not self:
1942
if (self.filename is None) and (outfile is None):
1943
# output a list of lines
1944
# might need to encode
1945
# NOTE: This will *screw* UTF16, each line will start with the BOM
1947
out = [l.encode(self.encoding) for l in out]
1948
if (self.BOM and ((self.encoding is None) or
1949
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1953
out[0] = BOM_UTF8 + out[0]
1956
# Turn the list to a string, joined with correct newlines
1957
output = (self._a_to_u(self.newlines or os.linesep)
1960
output = output.encode(self.encoding)
1961
if (self.BOM and ((self.encoding is None) or
1962
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1964
output = BOM_UTF8 + output
1965
if outfile is not None:
1966
outfile.write(output)
1968
h = open(self.filename, 'w')
1972
def validate(self, validator, preserve_errors=False, section=None):
1974
Test the ConfigObj against a configspec.
1976
It uses the ``validator`` object from *validate.py*.
1978
To run ``validate`` on the current ConfigObj, call: ::
1980
test = config.validate(validator)
1982
(Normally having previously passed in the configspec when the ConfigObj
1983
was created - you can dynamically assign a dictionary of checks to the
1984
``configspec`` attribute of a section though).
1986
It returns ``True`` if everything passes, or a dictionary of
1987
pass/fails (True/False). If every member of a subsection passes, it
1988
will just have the value ``True``. (It also returns ``False`` if all
1991
In addition, it converts the values from strings to their native
1992
types if their checks pass (and ``stringify`` is set).
1994
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
1995
of a marking a fail with a ``False``, it will preserve the actual
1996
exception object. This can contain info about the reason for failure.
1997
For example the ``VdtValueTooSmallError`` indeicates that the value
1998
supplied was too small. If a value (or section) is missing it will
1999
still be marked as ``False``.
2001
You must have the validate module to use ``preserve_errors=True``.
2003
You can then use the ``flatten_errors`` function to turn your nested
2004
results dictionary into a flattened list of failures - useful for
2005
displaying meaningful error messages.
2008
... from validate import Validator
2009
... except ImportError:
2010
... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
2027
... '''.split('\\n')
2028
... configspec = '''
2029
... test1= integer(30,50)
2032
... test4=float(6.0)
2034
... test1=integer(30,50)
2037
... test4=float(6.0)
2039
... test1=integer(30,50)
2042
... test4=float(6.0)
2043
... '''.split('\\n')
2044
... val = Validator()
2045
... c1 = ConfigObj(config, configspec=configspec)
2046
... test = c1.validate(val)
2057
... 'sub section': {
2066
>>> val.check(c1.configspec['test4'], c1['test4'])
2067
Traceback (most recent call last):
2068
VdtValueTooSmallError: the value "5.0" is too small.
2070
>>> val_test_config = '''
2075
... key2 = 1.1, 3.0, 17, 6.8
2078
... key2 = True'''.split('\\n')
2079
>>> val_test_configspec = '''
2084
... key2 = float_list(4)
2086
... key = option(option1, option2)
2087
... key2 = boolean'''.split('\\n')
2088
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
2089
>>> val_test.validate(val)
2091
>>> val_test['key'] = 'text not a digit'
2092
>>> val_res = val_test.validate(val)
2093
>>> val_res == {'key2': True, 'section': True, 'key': False}
2095
>>> configspec = '''
2096
... test1=integer(30,50, default=40)
2097
... test2=string(default="hello")
2098
... test3=integer(default=3)
2099
... test4=float(6.0, default=6.0)
2101
... test1=integer(30,50, default=40)
2102
... test2=string(default="hello")
2103
... test3=integer(default=3)
2104
... test4=float(6.0, default=6.0)
2106
... test1=integer(30,50, default=40)
2107
... test2=string(default="hello")
2108
... test3=integer(default=3)
2109
... test4=float(6.0, default=6.0)
2110
... '''.split('\\n')
2111
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2113
{'test1': '30', 'section': {'sub section': {}}}
2114
>>> default_test.validate(val)
2116
>>> default_test == {
2118
... 'test2': 'hello',
2123
... 'test2': 'hello',
2126
... 'sub section': {
2129
... 'test2': 'hello',
2136
Now testing with repeated sections : BIG TEST
2138
>>> repeated_1 = '''
2140
... [[__many__]] # spec for a dog
2141
... fleas = boolean(default=True)
2142
... tail = option(long, short, default=long)
2143
... name = string(default=rover)
2144
... [[[__many__]]] # spec for a puppy
2145
... name = string(default="son of rover")
2146
... age = float(default=0.0)
2148
... [[__many__]] # spec for a cat
2149
... fleas = boolean(default=True)
2150
... tail = option(long, short, default=short)
2151
... name = string(default=pussy)
2152
... [[[__many__]]] # spec for a kitten
2153
... name = string(default="son of pussy")
2154
... age = float(default=0.0)
2155
... '''.split('\\n')
2156
>>> repeated_2 = '''
2159
... # blank dogs with puppies
2160
... # should be filled in by the configspec
2175
... # blank cats with kittens
2176
... # should be filled in by the configspec
2189
... '''.split('\\n')
2190
>>> repeated_3 = '''
2201
... '''.split('\\n')
2202
>>> repeated_4 = '''
2205
... name = string(default=Michael)
2206
... age = float(default=0.0)
2207
... sex = option(m, f, default=m)
2208
... '''.split('\\n')
2209
>>> repeated_5 = '''
2212
... fleas = boolean(default=True)
2213
... tail = option(long, short, default=short)
2214
... name = string(default=pussy)
2215
... [[[description]]]
2216
... height = float(default=3.3)
2217
... weight = float(default=6)
2219
... fur = option(black, grey, brown, "tortoise shell", default=black)
2220
... condition = integer(0,10, default=5)
2221
... '''.split('\\n')
2222
>>> from validate import Validator
2223
>>> val= Validator()
2224
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
2225
>>> repeater.validate(val)
2232
... 'name': 'rover',
2233
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2234
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2235
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2240
... 'name': 'rover',
2241
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2242
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2243
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2248
... 'name': 'rover',
2249
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2250
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2251
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2257
... 'tail': 'short',
2258
... 'name': 'pussy',
2259
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2260
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2261
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2265
... 'tail': 'short',
2266
... 'name': 'pussy',
2267
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2268
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2269
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2273
... 'tail': 'short',
2274
... 'name': 'pussy',
2275
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2276
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2277
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2282
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2283
>>> repeater.validate(val)
2287
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2288
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2289
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2292
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2293
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2294
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2298
>>> repeater = ConfigObj(configspec=repeated_4)
2299
>>> repeater['Michael'] = {}
2300
>>> repeater.validate(val)
2303
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2306
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2308
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2309
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2312
>>> repeater.validate(val)
2315
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2319
... 'tail': 'short',
2320
... 'name': 'pussy',
2321
... 'description': {
2323
... 'height': 3.2999999999999998,
2324
... 'coat': {'fur': 'black', 'condition': 5},
2329
... 'tail': 'short',
2330
... 'name': 'pussy',
2331
... 'description': {
2333
... 'height': 3.2999999999999998,
2334
... 'coat': {'fur': 'black', 'condition': 5},
2339
... 'tail': 'short',
2340
... 'name': 'pussy',
2341
... 'description': {
2343
... 'height': 3.2999999999999998,
2344
... 'coat': {'fur': 'black', 'condition': 5},
2351
Test that interpolation is preserved for validated string values.
2352
Also check that interpolation works in configspecs.
2354
>>> t['DEFAULT'] = {}
2355
>>> t['DEFAULT']['test'] = 'a'
2356
>>> t['test'] = '%(test)s'
2360
>>> t.configspec = {'test': 'string'}
2363
>>> t.interpolation = False
2365
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2367
... 'interpolated string = string(default="fuzzy-%(man)s")',
2371
>>> c = ConfigObj(configspec=specs)
2374
>>> c['interpolated string']
2377
FIXME: Above tests will fail if we couldn't import Validator (the ones
2378
that don't raise errors will produce different output and still fail as
2382
if self.configspec is None:
2383
raise ValueError, 'No configspec supplied.'
2385
if VdtMissingValue is None:
2386
raise ImportError('Missing validate module.')
2389
spec_section = section.configspec
2390
if '__many__' in section.configspec:
2391
many = spec_section['__many__']
2392
# dynamically assign the configspecs
2393
# for the sections below
2394
for entry in section.sections:
2395
self._handle_repeat(section[entry], many)
2400
for entry in spec_section:
2401
if entry == '__many__':
2403
if (not entry in section.scalars) or (entry in section.defaults):
2405
# or entries from defaults
2410
val = section[entry]
2412
check = validator.check(spec_section[entry],
2416
except validator.baseErrorClass, e:
2417
if not preserve_errors or isinstance(e, VdtMissingValue):
2420
# preserve the error
2427
if self.stringify or missing:
2428
# if we are doing type conversion
2429
# or the value is a supplied default
2430
if not self.stringify:
2431
if isinstance(check, (list, tuple)):
2433
check = [self._str(item) for item in check]
2434
elif missing and check is None:
2435
# convert the None from a default to a ''
2438
check = self._str(check)
2439
if (check != val) or missing:
2440
section[entry] = check
2441
if missing and entry not in section.defaults:
2442
section.defaults.append(entry)
2444
# FIXME: Will this miss missing sections ?
2445
for entry in section.sections:
2446
if section is self and entry == 'DEFAULT':
2448
check = self.validate(validator, preserve_errors=preserve_errors,
2449
section=section[entry])
2466
class SimpleVal(object):
2469
Can be used to check that all members expected are present.
2471
To use it, provide a configspec with all your members in (the value given
2472
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2473
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2474
members are present, or a dictionary with True/False meaning
2475
present/missing. (Whole missing sections will be replaced with ``False``)
2477
>>> val = SimpleVal()
2493
... '''.split('\\n')
2494
>>> configspec = '''
2509
... '''.split('\\n')
2510
>>> o = ConfigObj(config, configspec=configspec)
2513
>>> o = ConfigObj(configspec=configspec)
2519
self.baseErrorClass = ConfigObjError
2521
def check(self, check, member, missing=False):
2522
"""A dummy check method, always returns the value unchanged."""
2524
raise self.baseErrorClass
2527
# Check / processing functions for options
2528
def flatten_errors(cfg, res, levels=None, results=None):
2530
An example function that will turn a nested dictionary of results
2531
(as returned by ``ConfigObj.validate``) into a flat list.
2533
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2534
dictionary returned by ``validate``.
2536
(This is a recursive function, so you shouldn't use the ``levels`` or
2537
``results`` arguments - they are used by the function.
2539
Returns a list of keys that failed. Each member of the list is a tuple :
2542
([list of sections...], key, result)
2544
If ``validate`` was called with ``preserve_errors=False`` (the default)
2545
then ``result`` will always be ``False``.
2547
*list of sections* is a flattened list of sections that the key was found
2550
If the section was missing then key will be ``None``.
2552
If the value (or section) was missing then ``result`` will be ``False``.
2554
If ``validate`` was called with ``preserve_errors=True`` and a value
2555
was present, but failed the check, then ``result`` will be the exception
2556
object returned. You can use this as a string that describes the failure.
2558
For example *The value "3" is of the wrong type*.
2560
# FIXME: is the ordering of the output arbitrary ?
2562
>>> vtor = validate.Validator()
2568
... another_option = Probably
2570
... another_option = True
2577
... option1 = boolean()
2578
... option2 = boolean()
2579
... option3 = boolean(default=Bad_value)
2581
... option1 = boolean()
2582
... option2 = boolean()
2583
... option3 = boolean(default=Bad_value)
2585
... another_option = boolean()
2587
... another_option = boolean()
2590
... value2 = integer
2591
... value3 = integer(0, 10)
2592
... [[[section3b-sub]]]
2595
... another_option = boolean()
2597
>>> cs = my_cfg.split('\\n')
2598
>>> ini = my_ini.split('\\n')
2599
>>> cfg = ConfigObj(ini, configspec=cs)
2600
>>> res = cfg.validate(vtor, preserve_errors=True)
2602
>>> for entry in flatten_errors(cfg, res):
2603
... section_list, key, error = entry
2604
... section_list.insert(0, '[root]')
2605
... if key is not None:
2606
... section_list.append(key)
2608
... section_list.append('[missing]')
2609
... section_string = ', '.join(section_list)
2610
... errors.append((section_string, ' = ', error))
2612
>>> for entry in errors:
2613
... print entry[0], entry[1], (entry[2] or 0)
2615
[root], option3 = the value "Bad_value" is of the wrong type.
2616
[root], section1, option2 = 0
2617
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2618
[root], section2, another_option = the value "Probably" is of the wrong type.
2619
[root], section3, section3b, section3b-sub, [missing] = 0
2620
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2621
[root], section3, section3b, value3 = the value "11" is too big.
2622
[root], section4, [missing] = 0
2631
results.append((levels[:], None, False))
2635
for (key, val) in res.items():
2638
if isinstance(cfg.get(key), dict):
2641
flatten_errors(cfg[key], val, levels, results)
2643
results.append((levels[:], key, val))
2652
# FIXME: test error code for badly built multiline values
2653
# FIXME: test handling of StringIO
2654
# FIXME: test interpolation with writing
2658
Dummy function to hold some of the doctests.
2695
... 'keys11': 'val1',
2696
... 'keys13': 'val3',
2697
... 'keys12': 'val2',
2700
... 'section 2 sub 1': {
2703
... 'keys21': 'val1',
2704
... 'keys22': 'val2',
2705
... 'keys23': 'val3',
2710
... 'a' = b # !"$%^&*(),::;'@~#= 33
2711
... "b" = b #= 6, 33
2712
... ''' .split('\\n')
2713
>>> t2 = ConfigObj(t)
2714
>>> assert t2 == {'a': 'b', 'b': 'b'}
2715
>>> t2.inline_comments['b'] = ''
2717
>>> assert t2.write() == ['','b = b', '']
2719
# Test ``list_values=False`` stuff
2721
... key1 = no quotes
2722
... key2 = 'single quotes'
2723
... key3 = "double quotes"
2724
... key4 = "list", 'with', several, "quotes"
2726
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2727
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2728
... 'key3': '"double quotes"',
2729
... 'key4': '"list", \\'with\\', several, "quotes"'
2732
>>> cfg = ConfigObj(list_values=False)
2733
>>> cfg['key1'] = 'Multiline\\nValue'
2734
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2736
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2737
>>> cfg.list_values = True
2738
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2739
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2742
Test flatten_errors:
2744
>>> from validate import Validator, VdtValueTooSmallError
2760
... '''.split('\\n')
2761
>>> configspec = '''
2762
... test1= integer(30,50)
2765
... test4=float(6.0)
2767
... test1=integer(30,50)
2770
... test4=float(6.0)
2772
... test1=integer(30,50)
2775
... test4=float(6.0)
2776
... '''.split('\\n')
2777
>>> val = Validator()
2778
>>> c1 = ConfigObj(config, configspec=configspec)
2779
>>> res = c1.validate(val)
2780
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2781
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2783
>>> res = c1.validate(val, preserve_errors=True)
2784
>>> check = flatten_errors(c1, res)
2788
(['section', 'sub section'], 'test4')
2790
(['section'], 'test4')
2791
>>> for entry in check:
2792
... isinstance(entry[2], VdtValueTooSmallError)
2793
... print str(entry[2])
2795
the value "5.0" is too small.
2797
the value "5.0" is too small.
2799
the value "5.0" is too small.
2801
Test unicode handling, BOM, write witha file like object and line endings :
2803
... # initial comment
2804
... # inital comment 2
2806
... test1 = some value
2808
... test2 = another value # inline comment
2809
... # section comment
2810
... [section] # inline comment
2811
... test = test # another inline comment
2815
... # final comment2
2817
>>> u = u_base.encode('utf_8').splitlines(True)
2818
>>> u[0] = BOM_UTF8 + u[0]
2819
>>> uc = ConfigObj(u)
2820
>>> uc.encoding = None
2823
>>> uc == {'test1': 'some value', 'test2': 'another value',
2824
... 'section': {'test': 'test', 'test2': 'test2'}}
2826
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2829
>>> isinstance(uc['test1'], unicode)
2835
>>> uc['latin1'] = "This costs lot's of "
2836
>>> a_list = uc.write()
2839
>>> isinstance(a_list[0], str)
2841
>>> a_list[0].startswith(BOM_UTF8)
2843
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2844
>>> uc = ConfigObj(u)
2847
>>> uc.newlines = '\\r'
2848
>>> from cStringIO import StringIO
2849
>>> file_like = StringIO()
2850
>>> uc.write(file_like)
2851
>>> file_like.seek(0)
2852
>>> uc2 = ConfigObj(file_like)
2855
>>> uc2.filename == None
2857
>>> uc2.newlines == '\\r'
2861
if __name__ == '__main__':
2862
# run the code tests in doctest format
2865
key1= val # comment 1
2866
key2= val # comment 2
2869
key1= val # comment 5
2870
key2= val # comment 6
2873
key1= val # comment 9
2874
key2= val # comment 10
2876
[[lev2ba]] # comment 12
2877
key1= val # comment 13
2879
[[lev2bb]] # comment 15
2880
key1= val # comment 16
2882
[lev1c] # comment 18
2884
[[lev2c]] # comment 20
2886
[[[lev3c]]] # comment 22
2887
key1 = val # comment 23"""
2893
["section 1"] # comment
2902
[['section 2 sub 1']]
2907
name1 = """ a single line value """ # comment
2908
name2 = \''' another single line value \''' # comment
2909
name3 = """ a single line value """
2910
name4 = \''' another single line value \'''
2927
\''' # I guess this is a comment too
2931
m = sys.modules.get('__main__')
2932
globs = m.__dict__.copy()
2933
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2934
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2935
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2937
'INTP_VER': INTP_VER,
2942
doctest.testmod(m, globs=globs)
2953
Better support for configuration from multiple files, including tracking
2954
*where* the original file came from and writing changes to the correct
2958
Make ``newline`` an option (as well as an attribute) ?
2960
``UTF16`` encoded files, when returned as a list of lines, will have the
2961
BOM at the start of every line. Should this be removed from all but the
2964
Option to set warning type for unicode decode ? (Defaults to strict).
2966
A method to optionally remove uniform indentation from multiline values.
2967
(do as an example of using ``walk`` - along with string-escape)
2969
Should the results dictionary from validate be an ordered dictionary if
2970
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2972
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2974
Implement some of the sequence methods (which include slicing) from the
2977
INCOMPATIBLE CHANGES
2978
====================
2980
(I have removed a lot of needless complications - this list is probably not
2981
conclusive, many option/attribute/method names have changed)
2985
The only valid divider is '='
2987
We've removed line continuations with '\'
2989
No recursive lists in values
2993
No distinction between flatfiles and non flatfiles
2995
Change in list syntax - use commas to indicate list, not parentheses
2996
(square brackets and parentheses are no longer recognised as lists)
2998
';' is no longer valid for comments and no multiline comments
3002
We don't allow empty values - have to use '' or ""
3004
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
3005
initialise it as an empty section.
3007
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
3008
replaced by triple quote, multiple line values.
3010
The ``newline``, ``force_return``, and ``default`` options have gone
3012
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
3013
with the ``encode`` and ``decode`` methods.
3015
``fileerror`` and ``createempty`` options have become ``file_error`` and
3018
Partial configspecs (for specifying the order members should be written
3019
out and which should be present) have gone. The configspec is no longer
3020
used to specify order for the ``write`` method.
3022
Exceeding the maximum depth of recursion in string interpolation now
3023
raises an error ``InterpolationDepthError``.
3025
Specifying a value for interpolation which doesn't exist now raises an
3026
error ``MissingInterpolationOption`` (instead of merely being ignored).
3028
The ``writein`` method has been removed.
3030
The comments attribute is now a list (``inline_comments`` equates to the
3031
old comments attribute)
3036
``validate`` doesn't report *extra* values or sections.
3038
You can't have a keyword with the same name as a section (in the same
3039
section). They are both dictionary keys - so they would overlap.
3041
ConfigObj doesn't quote and unquote values if ``list_values=False``.
3042
This means that leading or trailing whitespace in values will be lost when
3043
writing. (Unless you manually quote).
3045
Interpolation checks first the 'DEFAULT' subsection of the current
3046
section, next it checks the 'DEFAULT' section of the parent section,
3047
last it checks the 'DEFAULT' section of the main section.
3049
Logically a 'DEFAULT' section should apply to all subsections of the *same
3050
parent* - this means that checking the 'DEFAULT' subsection in the
3051
*current section* is not necessarily logical ?
3053
In order to simplify unicode support (which is possibly of limited value
3054
in a config file) I have removed automatic support and added the
3055
``encode`` and ``decode methods, which can be used to transform keys and
3056
entries. Because the regex looks for specific values on inital parsing
3057
(i.e. the quotes and the equals signs) it can only read ascii compatible
3058
encodings. For unicode use ``UTF8``, which is ASCII compatible.
3060
Does it matter that we don't support the ':' divider, which is supported
3061
by ``ConfigParser`` ?
3063
The regular expression correctly removes the value -
3064
``"'hello', 'goodbye'"`` and then unquote just removes the front and
3065
back quotes (called from ``_handle_value``). What should we do ??
3066
(*ought* to raise exception because it's an invalid value if lists are
3067
off *sigh*. This is not what you want if you want to do your own list
3068
processing - would be *better* in this case not to unquote.)
3070
String interpolation and validation don't play well together. When
3071
validation changes type it sets the value. This will correctly fetch the
3072
value using interpolation - but then overwrite the interpolation reference.
3073
If the value is unchanged by validation (it's a string) - but other types
3080
List values allow you to specify multiple values for a keyword. This
3081
maps to a list as the resulting Python object when parsed.
3083
The syntax for lists is easy. A list is a comma separated set of values.
3084
If these values contain quotes, the hash mark, or commas, then the values
3085
can be surrounded by quotes. e.g. : ::
3087
keyword = value1, 'value 2', "value 3"
3089
If a value needs to be a list, but only has one member, then you indicate
3090
this with a trailing comma. e.g. : ::
3092
keyword = "single value",
3094
If a value needs to be a list, but it has no members, then you indicate
3095
this with a single comma. e.g. : ::
3097
keyword = , # an empty list
3099
Using triple quotes it will be possible for single values to contain
3100
newlines and *both* single quotes and double quotes. Triple quotes aren't
3101
allowed in list values. This means that the members of list values can't
3102
contain carriage returns (or line feeds :-) or both quote values.
3110
Removed ``BOM_UTF8`` from ``__all__``.
3112
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3113
*only* ``True`` for the ``UTF16`` encoding.
3115
File like objects no longer need a ``seek`` attribute.
3117
ConfigObj no longer keeps a reference to file like objects. Instead the
3118
``write`` method takes a file like object as an optional argument. (Which
3119
will be used in preference of the ``filename`` attribute if htat exists as
3122
Full unicode support added. New options/attributes ``encoding``,
3123
``default_encoding``.
3125
utf16 files decoded to unicode.
3127
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3128
written out at the start of the file. (It will normally only be ``True`` if
3129
the utf8 BOM was found when the file was read.)
3131
File paths are *not* converted to absolute paths, relative paths will
3132
remain relative as the ``filename`` attribute.
3134
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3140
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3141
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3142
``enumerate`` is needed for all versions ofPython 2.2)
3144
Deprecated ``istrue``, replaced it with ``as_bool``.
3146
Added ``as_int`` and ``as_float``.
3148
utf8 and utf16 BOM handled in an endian agnostic way.
3153
Validation no longer done on the 'DEFAULT' section (only in the root
3154
level). This allows interpolation in configspecs.
3156
Change in validation syntax implemented in validate 0.2.1
3163
Added ``merge``, a recursive update.
3165
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3168
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3170
Fixed bug where a config file is *all* comment, the comment will now be
3171
``initial_comment`` rather than ``final_comment``.
3176
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3181
Fixed bug in ``Section.walk`` when transforming names as well as values.
3183
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3186
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3187
are multiline values.
3189
List values are written as ``item, item`` rather than ``item,item``.
3196
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3204
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3205
a reference to the new section.
3210
Removed ``PositionError``.
3212
Allowed quotes around keys as documented.
3214
Fixed bug with commas in comments. (matched as a list value)
3221
Fixed bug in initialising ConfigObj from a ConfigObj.
3223
Changed the mailing list address.
3230
Fixed bug in ``Section.__delitem__`` oops.
3235
Interpolation is switched off before writing out files.
3237
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3238
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3240
Moved the doctests from the ``__init__`` method to a separate function.
3241
(For the sake of IDE calltips).
3248
String values unchanged by validation *aren't* reset. This preserves
3249
interpolation in string values.
3254
None from a default is turned to '' if stringify is off - because setting
3255
a value to None raises an error.
3264
Actually added the RepeatSectionError class ;-)
3269
If ``stringify`` is off - list values are preserved by the ``validate``
3277
Fixed ``simpleVal``.
3279
Added ``RepeatSectionError`` error if you have additional sections in a
3280
section with a ``__many__`` (repeated) section.
3284
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
3285
mutated the self._infile, self._index and self._maxline attributes into
3286
local variables and method parameters
3288
Reshaped the ConfigObj._multiline method to better reflect its semantics
3290
Changed the "default_test" test in ConfigObj.validate to check the fix for
3291
the bug in validate.Validator.check
3298
Updated comments at top
3305
Implemented repeated sections.
3309
Added test for interpreter version: raises RuntimeError if earlier than
3317
Implemented default values in configspecs.
3321
Fixed naked except: clause in validate that was silencing the fact
3322
that Python2.2 does not have dict.pop
3329
Bug fix causing error if file didn't exist.
3336
Adjusted doctests for Python 2.2.3 compatibility
3343
Added the inline_comments attribute
3345
We now preserve and rewrite all comments in the config file
3347
configspec is now a section attribute
3349
The validate method changes values in place
3351
Added InterpolationError
3353
The errors now have line number, line, and message attributes. This
3354
simplifies error handling
3363
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3366
Replaced ``basestring`` with ``types.StringTypes``
3368
Removed the ``writein`` method
3377
Indentation in config file is not significant anymore, subsections are
3378
designated by repeating square brackets
3380
Adapted all tests and docs to the new format
3394
Reformatted final docstring in ReST format, indented it for easier folding
3396
Code tests converted to doctest format, and scattered them around
3397
in various docstrings
3399
Walk method rewritten using scalars and sections attributes
3406
Changed Validator and SimpleVal "test" methods to "check"
3413
Changed Section.sequence to Section.scalars and Section.sections
3415
Added Section.configspec
3417
Sections in the root section now have no extra indentation
3419
Comments now better supported in Section and preserved by ConfigObj
3421
Comments also written out
3423
Implemented initial_comment and final_comment
3425
A scalar value after a section will now raise an error
3430
Fixed a couple of bugs
3432
Can now pass a tuple instead of a list
3434
Simplified dict and walk methods
3436
Added __str__ to Section
3448
The stringify option implemented. On by default.
3453
Renamed private attributes with a single underscore prefix.
3455
Changes to interpolation - exceeding recursion depth, or specifying a
3456
missing value, now raise errors.
3458
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3459
``is True`` and ``is False``)
3461
Added test for duplicate section and member (and fixed bug)
3475
Now properly handles values including comments and lists.
3477
Better error handling.
3479
String interpolation.
3481
Some options implemented.
3483
You can pass a Section a dictionary to initialise it.
3485
Setting a Section member to a dictionary will create a Section instance.
3492
Experimental reader.
3494
A reasonably elegant implementation - a basic reader in 160 lines of code.
3496
*A programming language is a medium of expression.* - Paul Graham