166
256
self.message = message
167
257
SyntaxError.__init__(self, message)
169
260
class NestingError(ConfigObjError):
171
262
This error indicates a level of nesting that doesn't match.
173
>>> raise NestingError
174
Traceback (most recent call last):
178
266
class ParseError(ConfigObjError):
180
268
This error indicates that a line is badly written.
181
269
It is neither a valid ``key = value`` line,
182
270
nor a valid section marker line.
185
Traceback (most recent call last):
274
class ReloadError(IOError):
276
A 'reload' operation failed.
277
This exception is a subclass of ``IOError``.
280
IOError.__init__(self, 'reload failed, filename is not set.')
189
283
class DuplicateError(ConfigObjError):
191
285
The keyword or section specified already exists.
193
>>> raise DuplicateError
194
Traceback (most recent call last):
198
289
class ConfigspecError(ConfigObjError):
200
291
An error occured whilst parsing a configspec.
202
>>> raise ConfigspecError
203
Traceback (most recent call last):
207
295
class InterpolationError(ConfigObjError):
208
296
"""Base class for the two interpolation errors."""
210
class InterpolationDepthError(InterpolationError):
299
class InterpolationLoopError(InterpolationError):
211
300
"""Maximum interpolation depth exceeded in string interpolation."""
213
302
def __init__(self, option):
215
>>> raise InterpolationDepthError('yoda')
216
Traceback (most recent call last):
217
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
219
303
InterpolationError.__init__(
221
'max interpolation depth exceeded in value "%s".' % option)
305
'interpolation loop detected in value "%s".' % option)
223
308
class RepeatSectionError(ConfigObjError):
225
310
This error indicates additional sections in a section with a
226
311
``__many__`` (repeated) section.
228
>>> raise RepeatSectionError
229
Traceback (most recent call last):
233
315
class MissingInterpolationOption(InterpolationError):
234
316
"""A value specified for interpolation was missing."""
236
318
def __init__(self, option):
238
>>> raise MissingInterpolationOption('yoda')
239
Traceback (most recent call last):
240
MissingInterpolationOption: missing option "yoda" in interpolation.
242
319
InterpolationError.__init__(
244
321
'missing option "%s" in interpolation.' % option)
324
class UnreprError(ConfigObjError):
325
"""An error parsing in unrepr mode."""
329
class InterpolationEngine(object):
331
A helper class to help perform string interpolation.
333
This class is an abstract base class; its descendants perform
337
# compiled regexp to use in self.interpolate()
338
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
340
def __init__(self, section):
341
# the Section instance that "owns" this engine
342
self.section = section
345
def interpolate(self, key, value):
346
def recursive_interpolate(key, value, section, backtrail):
347
"""The function that does the actual work.
349
``value``: the string we're trying to interpolate.
350
``section``: the section in which that string was found
351
``backtrail``: a dict to keep track of where we've been,
352
to detect and prevent infinite recursion loops
354
This is similar to a depth-first-search algorithm.
356
# Have we been here already?
357
if backtrail.has_key((key, section.name)):
358
# Yes - infinite loop detected
359
raise InterpolationLoopError(key)
360
# Place a marker on our backtrail so we won't come back here again
361
backtrail[(key, section.name)] = 1
363
# Now start the actual work
364
match = self._KEYCRE.search(value)
366
# The actual parsing of the match is implementation-dependent,
367
# so delegate to our helper function
368
k, v, s = self._parse_match(match)
370
# That's the signal that no further interpolation is needed
373
# Further interpolation may be needed to obtain final value
374
replacement = recursive_interpolate(k, v, s, backtrail)
375
# Replace the matched string with its final value
376
start, end = match.span()
377
value = ''.join((value[:start], replacement, value[end:]))
378
new_search_start = start + len(replacement)
379
# Pick up the next interpolation key, if any, for next time
380
# through the while loop
381
match = self._KEYCRE.search(value, new_search_start)
383
# Now safe to come back here again; remove marker from backtrail
384
del backtrail[(key, section.name)]
388
# Back in interpolate(), all we have to do is kick off the recursive
389
# function with appropriate starting values
390
value = recursive_interpolate(key, value, self.section, {})
394
def _fetch(self, key):
395
"""Helper function to fetch values from owning section.
397
Returns a 2-tuple: the value, and the section where it was found.
399
# switch off interpolation before we try and fetch anything !
400
save_interp = self.section.main.interpolation
401
self.section.main.interpolation = False
403
# Start at section that "owns" this InterpolationEngine
404
current_section = self.section
406
# try the current section first
407
val = current_section.get(key)
411
val = current_section.get('DEFAULT', {}).get(key)
414
# move up to parent and try again
415
# top-level's parent is itself
416
if current_section.parent is current_section:
417
# reached top level, time to give up
419
current_section = current_section.parent
421
# restore interpolation to previous value before returning
422
self.section.main.interpolation = save_interp
424
raise MissingInterpolationOption(key)
425
return val, current_section
428
def _parse_match(self, match):
429
"""Implementation-dependent helper function.
431
Will be passed a match object corresponding to the interpolation
432
key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
433
key in the appropriate config file section (using the ``_fetch()``
434
helper function) and return a 3-tuple: (key, value, section)
436
``key`` is the name of the key we're looking for
437
``value`` is the value found for that key
438
``section`` is a reference to the section where it was found
440
``key`` and ``section`` should be None if no further
441
interpolation should be performed on the resulting value
442
(e.g., if we interpolated "$$" and returned "$").
444
raise NotImplementedError()
448
class ConfigParserInterpolation(InterpolationEngine):
449
"""Behaves like ConfigParser."""
450
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
452
def _parse_match(self, match):
454
value, section = self._fetch(key)
455
return key, value, section
459
class TemplateInterpolation(InterpolationEngine):
460
"""Behaves like string.Template."""
462
_KEYCRE = re.compile(r"""
464
(?P<escaped>\$) | # Two $ signs
465
(?P<named>[_a-z][_a-z0-9]*) | # $name format
466
{(?P<braced>[^}]*)} # ${name} format
468
""", re.IGNORECASE | re.VERBOSE)
470
def _parse_match(self, match):
471
# Valid name (in or out of braces): fetch value from section
472
key = match.group('named') or match.group('braced')
474
value, section = self._fetch(key)
475
return key, value, section
476
# Escaped delimiter (e.g., $$): return single delimiter
477
if match.group('escaped') is not None:
478
# Return None for key and section to indicate it's time to stop
479
return None, self._delimiter, None
480
# Anything else: ignore completely, just return it unchanged
481
return None, match.group(), None
484
interpolation_engines = {
485
'configparser': ConfigParserInterpolation,
486
'template': TemplateInterpolation,
246
491
class Section(dict):
248
493
A dictionary-like object that represents a section in a config file.
250
It does string interpolation if the 'interpolate' attribute
495
It does string interpolation if the 'interpolation' attribute
251
496
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.
498
Interpolation is tried first from this object, then from the 'DEFAULT'
499
section of this object, next from the parent and its 'DEFAULT' section,
500
and so on until the main object is reached.
256
502
A Section will behave like an ordered dictionary - following the
257
503
order of the ``scalars`` and ``sections`` attributes.
279
523
# level of nesting depth of this Section
280
524
self.depth = depth
525
# purely for information
529
# we do this explicitly so that __setitem__ is used properly
530
# (rather than just passing to ``dict.__init__``)
531
for entry, value in indict.iteritems():
535
def _initialise(self):
281
536
# the sequence of scalar values in this Section
282
537
self.scalars = []
283
538
# the sequence of sections in this Section
284
539
self.sections = []
285
# purely for information
287
540
# for comments :-)
288
541
self.comments = {}
289
542
self.inline_comments = {}
290
543
# for the configspec
291
544
self.configspec = {}
546
self._configspec_comments = {}
547
self._configspec_inline_comments = {}
548
self._cs_section_comments = {}
549
self._cs_section_inline_comments = {}
293
551
self.defaults = []
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)
552
self.default_values = {}
555
def _interpolate(self, key, value):
557
# do we already have an interpolation engine?
558
engine = self._interpolation_engine
559
except AttributeError:
560
# not yet: first time running _interpolate(), so pick the engine
561
name = self.main.interpolation
562
if name == True: # note that "if name:" would be incorrect here
563
# backwards-compatibility: interpolation=True means use default
564
name = DEFAULT_INTERPOLATION
565
name = name.lower() # so that "Template", "template", etc. all work
566
class_ = interpolation_engines.get(name, None)
568
# invalid value for self.main.interpolation
569
self.main.interpolation = False
311
raise InterpolationDepthError(value)
572
# save reference to engine so we don't have to do this again
573
engine = self._interpolation_engine = class_(self)
574
# let the engine do the actual work
575
return engine.interpolate(key, 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
578
def __getitem__(self, key):
336
579
"""Fetch the item and do string interpolation."""
337
580
val = dict.__getitem__(self, key)
338
581
if self.main.interpolation and isinstance(val, StringTypes):
339
return self._interpolate(val)
582
return self._interpolate(key, val)
342
def __setitem__(self, key, value):
586
def __setitem__(self, key, value, unrepr=False):
344
588
Correctly set a value.
950
1238
'true': True, 'false': False,
953
1242
def __init__(self, infile=None, options=None, **kwargs):
955
Parse or create a config file object.
1244
Parse a config file or create a config file object.
957
1246
``ConfigObj(infile=None, options=None, **kwargs)``
1248
# init the superclass
1249
Section.__init__(self, self, 0, self)
959
1251
if infile is None:
961
1253
if options is None:
1256
options = dict(options)
963
1258
# keyword arguments take precedence over an options dictionary
964
1259
options.update(kwargs)
965
# init the superclass
966
Section.__init__(self, self, 0, self)
968
1261
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
1262
# TODO: check the values too.
1263
for entry in options:
1264
if entry not in defaults:
1265
raise TypeError('Unrecognised option "%s".' % entry)
974
1267
# Add any explicit options to the defaults
975
1268
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 = []
1269
self._initialise(defaults)
1270
configspec = defaults['configspec']
1271
self._original_configspec = configspec
1272
self._load(infile, configspec)
1275
def _load(self, infile, configspec):
995
1276
if isinstance(infile, StringTypes):
996
1277
self.filename = infile
997
1278
if os.path.isfile(infile):
998
infile = open(infile).read() or []
1279
h = open(infile, 'rb')
1280
infile = h.read() or []
999
1282
elif self.file_error:
1000
1283
# raise an error if the file doesn't exist
1001
raise IOError, 'Config file not found: "%s".' % self.filename
1284
raise IOError('Config file not found: "%s".' % self.filename)
1003
1286
# file doesn't already exist
1004
1287
if self.create_empty:
1005
1288
# this is a good test that the filename specified
1006
# isn't impossible - like on a non existent device
1289
# isn't impossible - like on a non-existent device
1007
1290
h = open(infile, 'w')
1011
1295
elif isinstance(infile, (list, tuple)):
1012
1296
infile = list(infile)
1013
1298
elif isinstance(infile, dict):
1014
1299
# initialise self
1015
1300
# the Section class handles creating subsections
1016
1301
if isinstance(infile, ConfigObj):
1017
1302
# get a copy of our ConfigObj
1018
1303
infile = infile.dict()
1019
1305
for entry in infile:
1020
1306
self[entry] = infile[entry]
1021
1307
del self._errors
1022
if defaults['configspec'] is not None:
1023
self._handle_configspec(defaults['configspec'])
1309
if configspec is not None:
1310
self._handle_configspec(configspec)
1025
1312
self.configspec = None
1027
1315
elif getattr(infile, 'read', None) is not None:
1028
1316
# This supports file like objects
1029
1317
infile = infile.read() or []
1030
1318
# needs splitting into lines - but needs doing *after* decoding
1031
1319
# 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.')
1321
raise TypeError('infile must be a filename, file like object, or list of lines.')
1037
1324
# don't do it for the empty ConfigObj
1038
1325
infile = self._handle_bom(infile)
1382
1651
'Parse error in value at line %s.',
1383
1652
ParseError, infile, cur_index)
1658
value = unrepr(value)
1659
except Exception, e:
1660
if type(e) == UnknownType:
1661
msg = 'Unknown name or type in value at line %s.'
1663
msg = 'Parse error in value at line %s.'
1664
self._handle_error(msg, UnreprError, infile,
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)
1671
value = unrepr(value)
1672
except Exception, e:
1673
if isinstance(e, UnknownType):
1674
msg = 'Unknown name or type in value at line %s.'
1676
msg = 'Parse error in value at line %s.'
1677
self._handle_error(msg, UnreprError, infile,
1681
# extract comment and lists
1683
(value, comment) = self._handle_value(value)
1686
'Parse error in value at line %s.',
1687
ParseError, infile, cur_index)
1395
## print >> sys.stderr, sline
1396
1690
key = self._unquote(key)
1397
if key in this_section:
1691
if this_section.has_key(key):
1398
1692
self._handle_error(
1399
1693
'Duplicate keyword name at line %s.',
1400
1694
DuplicateError, infile, cur_index)
1403
## print >> sys.stderr, this_section.name
1404
this_section[key] = value
1697
# we set unrepr because if we have got this far we will never
1698
# be creating a new section
1699
this_section.__setitem__(key, value, unrepr=True)
1405
1700
this_section.inline_comments[key] = comment
1406
1701
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
1704
if self.indent_type is None:
1420
1705
# no indentation used, set the type accordingly
1421
1706
self.indent_type = ''
1422
1708
# preserve the final comment
1423
1709
if not self and not self.initial_comment:
1424
1710
self.initial_comment = comment_list
1711
elif not reset_comment:
1426
1712
self.final_comment = comment_list
1713
self.list_values = temp_list_values
1428
1716
def _match_depth(self, sect, depth):
1494
1793
if self.stringify:
1495
1794
value = str(value)
1497
raise TypeError, 'Value "%s" is not a string.' % value
1501
wspace_plus = ' \r\t\n\v\t\'"'
1796
raise TypeError('Value "%s" is not a string.' % value)
1506
if (not self.list_values and '\n' not in value) or not (multiline and
1507
((("'" in value) and ('"' in value)) or ('\n' in value))):
1801
no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1802
need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1803
hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1804
check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1806
if check_for_single:
1508
1807
if not self.list_values:
1509
1808
# we don't quote if ``list_values=False``
1511
1810
# for normal values either single or double quotes will do
1512
1811
elif '\n' in value:
1513
1812
# will only happen if multiline is off - e.g. '\n' in key
1514
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1813
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1516
1814
elif ((value[0] not in wspace_plus) and
1517
1815
(value[-1] not in wspace_plus) and
1518
1816
(',' not in value)):
1521
if ("'" in value) and ('"' in value):
1522
raise ConfigObjError, (
1523
'Value "%s" cannot be safely quoted.' % value)
1819
quot = self._get_single_quote(value)
1529
1821
# if value has '\n' or "'" *and* '"', it will need triple quotes
1530
if (value.find('"""') != -1) and (value.find("'''") != -1):
1531
raise ConfigObjError, (
1532
'Value "%s" cannot be safely quoted.' % value)
1533
if value.find('"""') == -1:
1822
quot = self._get_triple_quote(value)
1824
if quot == noquot and '#' in value and self.list_values:
1825
quot = self._get_single_quote(value)
1537
1827
return quot % value
1830
def _get_single_quote(self, value):
1831
if ("'" in value) and ('"' in value):
1832
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1840
def _get_triple_quote(self, value):
1841
if (value.find('"""') != -1) and (value.find("'''") != -1):
1842
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1843
if value.find('"""') == -1:
1539
1850
def _handle_value(self, value):
1541
1852
Given a value string, unquote, remove comment,
1542
1853
handle lists. (including empty and single member lists)
1544
Testing list values.
1546
>>> testconfig3 = '''
1549
... c = test1, test2 , test3
1550
... d = test1, test2, test3,
1552
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1555
>>> d['b'] == ['test']
1557
>>> d['c'] == ['test1', 'test2', 'test3']
1559
>>> d['d'] == ['test1', 'test2', 'test3']
1562
Testing with list values off.
1565
... testconfig3.split('\\n'),
1566
... raise_errors=True,
1567
... list_values=False)
1570
>>> e['b'] == 'test,'
1572
>>> e['c'] == 'test1, test2 , test3'
1574
>>> e['d'] == 'test1, test2, test3,'
1577
Testing creating from a dictionary.
1600
>>> g = ConfigObj(f)
1604
Testing we correctly detect badly built list values (4 of them).
1606
>>> testconfig4 = '''
1610
... dummy = ,,hello, goodbye
1613
... ConfigObj(testconfig4.split('\\n'))
1614
... except ConfigObjError, e:
1618
Testing we correctly detect badly quoted values (4 of them).
1620
>>> testconfig5 = '''
1621
... config = "hello # comment
1623
... fish = 'goodbye # comment
1624
... dummy = "hello again
1627
... ConfigObj(testconfig5.split('\\n'))
1628
... except ConfigObjError, e:
1632
1855
# do we look for lists in values ?
1633
1856
if not self.list_values:
1634
1857
mat = self._nolistvalue.match(value)
1635
1858
if mat is None:
1637
(value, comment) = mat.groups()
1638
1860
# NOTE: we don't unquote here
1639
return (value, comment)
1640
1863
mat = self._valueexp.match(value)
1641
1864
if mat is None:
1642
1865
# the value is badly constructed, probably badly quoted,
1643
1866
# or an invalid list
1645
1868
(list_values, single, empty_list, comment) = mat.groups()
1646
1869
if (list_values == '') and (single is None):
1647
1870
# change this if you want to accept empty values
1649
1872
# NOTE: note there is no error handling from here if the regex
1650
1873
# is wrong: then incorrect values will slip through
1651
1874
if empty_list is not None:
1652
1875
# the single comma - meaning an empty list
1653
1876
return ([], comment)
1654
1877
if single is not None:
1655
single = self._unquote(single)
1878
# handle empty values
1879
if list_values and not single:
1880
# FIXME: the '' is a workaround because our regex now matches
1881
# '' at the end of a list if it has a trailing comma
1884
single = single or '""'
1885
single = self._unquote(single)
1656
1886
if list_values == '':
1657
1887
# not a list value
1658
1888
return (single, comment)
1774
2007
scalars[entry] = val
1776
2009
sections[entry] = val
1778
2011
section.configspec = scalars
1779
2012
for entry in sections:
1780
if entry not in section:
2013
if not section.has_key(entry):
1781
2014
section[entry] = {}
1782
2015
self._handle_repeat(section[entry], sections[entry])
1784
2018
def _write_line(self, indent_string, entry, this_entry, comment):
1785
2019
"""Write an individual line, for the write method"""
1786
2020
# NOTE: the calls to self._quote here handles non-StringType values.
1787
return '%s%s%s%s%s' % (
1789
self._decode_element(self._quote(entry, multiline=False)),
1790
self._a_to_u(' = '),
1791
self._decode_element(self._quote(this_entry)),
1792
self._decode_element(comment))
2022
val = self._decode_element(self._quote(this_entry))
2024
val = repr(this_entry)
2025
return '%s%s%s%s%s' % (indent_string,
2026
self._decode_element(self._quote(entry, multiline=False)),
2027
self._a_to_u(' = '),
2029
self._decode_element(comment))
1794
2032
def _write_marker(self, indent_string, depth, entry, comment):
1795
2033
"""Write a section marker line"""
1796
return '%s%s%s%s%s' % (
1798
self._a_to_u('[' * depth),
1799
self._quote(self._decode_element(entry), multiline=False),
1800
self._a_to_u(']' * depth),
1801
self._decode_element(comment))
2034
return '%s%s%s%s%s' % (indent_string,
2035
self._a_to_u('[' * depth),
2036
self._quote(self._decode_element(entry), multiline=False),
2037
self._a_to_u(']' * depth),
2038
self._decode_element(comment))
1803
2041
def _handle_comment(self, comment):
1805
Deal with a comment.
1807
>>> filename = a.filename
1808
>>> a.filename = None
1809
>>> values = a.write()
1811
>>> while index < 23:
1813
... line = values[index-1]
1814
... assert line.endswith('# comment ' + str(index))
1815
>>> a.filename = filename
1817
>>> start_comment = ['# Initial Comment', '', '#']
1818
>>> end_comment = ['', '#', '# Final Comment']
1819
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1820
>>> nc = ConfigObj(newconfig)
1821
>>> nc.initial_comment
1822
['# Initial Comment', '', '#']
1823
>>> nc.final_comment
1824
['', '#', '# Final Comment']
1825
>>> nc.initial_comment == start_comment
1827
>>> nc.final_comment == end_comment
2042
"""Deal with a comment."""
1830
2043
if not comment:
1832
if self.indent_type == '\t':
1833
start = self._a_to_u('\t')
1835
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
2045
start = self.indent_type
1836
2046
if not comment.startswith('#'):
1837
start += _a_to_u('# ')
2047
start += self._a_to_u(' # ')
1838
2048
return (start + comment)
1840
def _compute_indent_string(self, depth):
1842
Compute the indent string, according to current indent_type and depth
1844
if self.indent_type == '':
1845
# no indentation at all
1847
if self.indent_type == '\t':
1849
if self.indent_type == ' ':
1850
return ' ' * NUM_INDENT_SPACES * depth
1853
2051
# Public methods
2004
2189
You can then use the ``flatten_errors`` function to turn your nested
2005
2190
results dictionary into a flattened list of failures - useful for
2006
2191
displaying meaningful error messages.
2009
... from validate import Validator
2010
... except ImportError:
2011
... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
2028
... '''.split('\\n')
2029
... configspec = '''
2030
... test1= integer(30,50)
2033
... test4=float(6.0)
2035
... test1=integer(30,50)
2038
... test4=float(6.0)
2040
... test1=integer(30,50)
2043
... test4=float(6.0)
2044
... '''.split('\\n')
2045
... val = Validator()
2046
... c1 = ConfigObj(config, configspec=configspec)
2047
... test = c1.validate(val)
2058
... 'sub section': {
2067
>>> val.check(c1.configspec['test4'], c1['test4'])
2068
Traceback (most recent call last):
2069
VdtValueTooSmallError: the value "5.0" is too small.
2071
>>> val_test_config = '''
2076
... key2 = 1.1, 3.0, 17, 6.8
2079
... key2 = True'''.split('\\n')
2080
>>> val_test_configspec = '''
2085
... key2 = float_list(4)
2087
... key = option(option1, option2)
2088
... key2 = boolean'''.split('\\n')
2089
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
2090
>>> val_test.validate(val)
2092
>>> val_test['key'] = 'text not a digit'
2093
>>> val_res = val_test.validate(val)
2094
>>> val_res == {'key2': True, 'section': True, 'key': False}
2096
>>> configspec = '''
2097
... test1=integer(30,50, default=40)
2098
... test2=string(default="hello")
2099
... test3=integer(default=3)
2100
... test4=float(6.0, default=6.0)
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)
2111
... '''.split('\\n')
2112
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2114
{'test1': '30', 'section': {'sub section': {}}}
2115
>>> default_test.validate(val)
2117
>>> default_test == {
2119
... 'test2': 'hello',
2124
... 'test2': 'hello',
2127
... 'sub section': {
2130
... 'test2': 'hello',
2137
Now testing with repeated sections : BIG TEST
2139
>>> repeated_1 = '''
2141
... [[__many__]] # spec for a dog
2142
... fleas = boolean(default=True)
2143
... tail = option(long, short, default=long)
2144
... name = string(default=rover)
2145
... [[[__many__]]] # spec for a puppy
2146
... name = string(default="son of rover")
2147
... age = float(default=0.0)
2149
... [[__many__]] # spec for a cat
2150
... fleas = boolean(default=True)
2151
... tail = option(long, short, default=short)
2152
... name = string(default=pussy)
2153
... [[[__many__]]] # spec for a kitten
2154
... name = string(default="son of pussy")
2155
... age = float(default=0.0)
2156
... '''.split('\\n')
2157
>>> repeated_2 = '''
2160
... # blank dogs with puppies
2161
... # should be filled in by the configspec
2176
... # blank cats with kittens
2177
... # should be filled in by the configspec
2190
... '''.split('\\n')
2191
>>> repeated_3 = '''
2202
... '''.split('\\n')
2203
>>> repeated_4 = '''
2206
... name = string(default=Michael)
2207
... age = float(default=0.0)
2208
... sex = option(m, f, default=m)
2209
... '''.split('\\n')
2210
>>> repeated_5 = '''
2213
... fleas = boolean(default=True)
2214
... tail = option(long, short, default=short)
2215
... name = string(default=pussy)
2216
... [[[description]]]
2217
... height = float(default=3.3)
2218
... weight = float(default=6)
2220
... fur = option(black, grey, brown, "tortoise shell", default=black)
2221
... condition = integer(0,10, default=5)
2222
... '''.split('\\n')
2223
>>> from validate import Validator
2224
>>> val= Validator()
2225
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
2226
>>> repeater.validate(val)
2233
... 'name': 'rover',
2234
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2235
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2236
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2241
... 'name': 'rover',
2242
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2243
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2244
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2249
... 'name': 'rover',
2250
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2251
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2252
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2258
... 'tail': 'short',
2259
... 'name': 'pussy',
2260
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2261
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2262
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2266
... 'tail': 'short',
2267
... 'name': 'pussy',
2268
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2269
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2270
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2274
... 'tail': 'short',
2275
... 'name': 'pussy',
2276
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2277
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2278
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2283
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2284
>>> repeater.validate(val)
2288
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2289
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2290
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2293
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2294
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2295
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2299
>>> repeater = ConfigObj(configspec=repeated_4)
2300
>>> repeater['Michael'] = {}
2301
>>> repeater.validate(val)
2304
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2307
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2309
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2310
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2313
>>> repeater.validate(val)
2316
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2320
... 'tail': 'short',
2321
... 'name': 'pussy',
2322
... 'description': {
2324
... 'height': 3.2999999999999998,
2325
... 'coat': {'fur': 'black', 'condition': 5},
2330
... 'tail': 'short',
2331
... 'name': 'pussy',
2332
... 'description': {
2334
... 'height': 3.2999999999999998,
2335
... 'coat': {'fur': 'black', 'condition': 5},
2340
... 'tail': 'short',
2341
... 'name': 'pussy',
2342
... 'description': {
2344
... 'height': 3.2999999999999998,
2345
... 'coat': {'fur': 'black', 'condition': 5},
2352
Test that interpolation is preserved for validated string values.
2353
Also check that interpolation works in configspecs.
2355
>>> t['DEFAULT'] = {}
2356
>>> t['DEFAULT']['test'] = 'a'
2357
>>> t['test'] = '%(test)s'
2361
>>> t.configspec = {'test': 'string'}
2364
>>> t.interpolation = False
2366
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2368
... 'interpolated string = string(default="fuzzy-%(man)s")',
2372
>>> c = ConfigObj(configspec=specs)
2375
>>> c['interpolated string']
2378
FIXME: Above tests will fail if we couldn't import Validator (the ones
2379
that don't raise errors will produce different output and still fail as
2382
2193
if section is None:
2383
2194
if self.configspec is None:
2384
raise ValueError, 'No configspec supplied.'
2195
raise ValueError('No configspec supplied.')
2385
2196
if preserve_errors:
2386
if VdtMissingValue is None:
2387
raise ImportError('Missing validate module.')
2197
# We do this once to remove a top level dependency on the validate module
2198
# Which makes importing configobj faster
2199
from validate import VdtMissingValue
2200
self._vdtMissingValue = VdtMissingValue
2390
2203
spec_section = section.configspec
2204
if copy and getattr(section, '_configspec_initial_comment', None) is not None:
2205
section.initial_comment = section._configspec_initial_comment
2206
section.final_comment = section._configspec_final_comment
2207
section.encoding = section._configspec_encoding
2208
section.BOM = section._configspec_BOM
2209
section.newlines = section._configspec_newlines
2210
section.indent_type = section._configspec_indent_type
2391
2212
if '__many__' in section.configspec:
2392
2213
many = spec_section['__many__']
2393
2214
# dynamically assign the configspecs
2653
# FIXME: test error code for badly built multiline values
2654
# FIXME: test handling of StringIO
2655
# FIXME: test interpolation with writing
2659
Dummy function to hold some of the doctests.
2696
... 'keys11': 'val1',
2697
... 'keys13': 'val3',
2698
... 'keys12': 'val2',
2701
... 'section 2 sub 1': {
2704
... 'keys21': 'val1',
2705
... 'keys22': 'val2',
2706
... 'keys23': 'val3',
2711
... 'a' = b # !"$%^&*(),::;'@~#= 33
2712
... "b" = b #= 6, 33
2713
... ''' .split('\\n')
2714
>>> t2 = ConfigObj(t)
2715
>>> assert t2 == {'a': 'b', 'b': 'b'}
2716
>>> t2.inline_comments['b'] = ''
2718
>>> assert t2.write() == ['','b = b', '']
2720
# Test ``list_values=False`` stuff
2722
... key1 = no quotes
2723
... key2 = 'single quotes'
2724
... key3 = "double quotes"
2725
... key4 = "list", 'with', several, "quotes"
2727
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2728
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2729
... 'key3': '"double quotes"',
2730
... 'key4': '"list", \\'with\\', several, "quotes"'
2733
>>> cfg = ConfigObj(list_values=False)
2734
>>> cfg['key1'] = 'Multiline\\nValue'
2735
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2737
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2738
>>> cfg.list_values = True
2739
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2740
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2743
Test flatten_errors:
2745
>>> from validate import Validator, VdtValueTooSmallError
2761
... '''.split('\\n')
2762
>>> configspec = '''
2763
... test1= integer(30,50)
2766
... test4=float(6.0)
2768
... test1=integer(30,50)
2771
... test4=float(6.0)
2773
... test1=integer(30,50)
2776
... test4=float(6.0)
2777
... '''.split('\\n')
2778
>>> val = Validator()
2779
>>> c1 = ConfigObj(config, configspec=configspec)
2780
>>> res = c1.validate(val)
2781
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2782
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2784
>>> res = c1.validate(val, preserve_errors=True)
2785
>>> check = flatten_errors(c1, res)
2789
(['section', 'sub section'], 'test4')
2791
(['section'], 'test4')
2792
>>> for entry in check:
2793
... isinstance(entry[2], VdtValueTooSmallError)
2794
... print str(entry[2])
2796
the value "5.0" is too small.
2798
the value "5.0" is too small.
2800
the value "5.0" is too small.
2802
Test unicode handling, BOM, write witha file like object and line endings :
2804
... # initial comment
2805
... # inital comment 2
2807
... test1 = some value
2809
... test2 = another value # inline comment
2810
... # section comment
2811
... [section] # inline comment
2812
... test = test # another inline comment
2816
... # final comment2
2818
>>> u = u_base.encode('utf_8').splitlines(True)
2819
>>> u[0] = BOM_UTF8 + u[0]
2820
>>> uc = ConfigObj(u)
2821
>>> uc.encoding = None
2824
>>> uc == {'test1': 'some value', 'test2': 'another value',
2825
... 'section': {'test': 'test', 'test2': 'test2'}}
2827
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2830
>>> isinstance(uc['test1'], unicode)
2836
>>> uc['latin1'] = "This costs lot's of "
2837
>>> a_list = uc.write()
2840
>>> isinstance(a_list[0], str)
2842
>>> a_list[0].startswith(BOM_UTF8)
2844
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2845
>>> uc = ConfigObj(u)
2848
>>> uc.newlines = '\\r'
2849
>>> from cStringIO import StringIO
2850
>>> file_like = StringIO()
2851
>>> uc.write(file_like)
2852
>>> file_like.seek(0)
2853
>>> uc2 = ConfigObj(file_like)
2856
>>> uc2.filename is None
2858
>>> uc2.newlines == '\\r'
2862
if __name__ == '__main__':
2863
# run the code tests in doctest format
2866
key1= val # comment 1
2867
key2= val # comment 2
2870
key1= val # comment 5
2871
key2= val # comment 6
2874
key1= val # comment 9
2875
key2= val # comment 10
2877
[[lev2ba]] # comment 12
2878
key1= val # comment 13
2880
[[lev2bb]] # comment 15
2881
key1= val # comment 16
2883
[lev1c] # comment 18
2885
[[lev2c]] # comment 20
2887
[[[lev3c]]] # comment 22
2888
key1 = val # comment 23"""
2894
["section 1"] # comment
2903
[['section 2 sub 1']]
2908
name1 = """ a single line value """ # comment
2909
name2 = \''' another single line value \''' # comment
2910
name3 = """ a single line value """
2911
name4 = \''' another single line value \'''
2928
\''' # I guess this is a comment too
2932
m = sys.modules.get('__main__')
2933
globs = m.__dict__.copy()
2934
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2935
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2936
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2938
'INTP_VER': INTP_VER,
2943
doctest.testmod(m, globs=globs)
2954
Better support for configuration from multiple files, including tracking
2955
*where* the original file came from and writing changes to the correct
2959
Make ``newline`` an option (as well as an attribute) ?
2961
``UTF16`` encoded files, when returned as a list of lines, will have the
2962
BOM at the start of every line. Should this be removed from all but the
2965
Option to set warning type for unicode decode ? (Defaults to strict).
2967
A method to optionally remove uniform indentation from multiline values.
2968
(do as an example of using ``walk`` - along with string-escape)
2970
Should the results dictionary from validate be an ordered dictionary if
2971
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2973
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2975
Implement some of the sequence methods (which include slicing) from the
2978
INCOMPATIBLE CHANGES
2979
====================
2981
(I have removed a lot of needless complications - this list is probably not
2982
conclusive, many option/attribute/method names have changed)
2986
The only valid divider is '='
2988
We've removed line continuations with '\'
2990
No recursive lists in values
2994
No distinction between flatfiles and non flatfiles
2996
Change in list syntax - use commas to indicate list, not parentheses
2997
(square brackets and parentheses are no longer recognised as lists)
2999
';' is no longer valid for comments and no multiline comments
3003
We don't allow empty values - have to use '' or ""
3005
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
3006
initialise it as an empty section.
3008
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
3009
replaced by triple quote, multiple line values.
3011
The ``newline``, ``force_return``, and ``default`` options have gone
3013
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
3014
with the ``encode`` and ``decode`` methods.
3016
``fileerror`` and ``createempty`` options have become ``file_error`` and
3019
Partial configspecs (for specifying the order members should be written
3020
out and which should be present) have gone. The configspec is no longer
3021
used to specify order for the ``write`` method.
3023
Exceeding the maximum depth of recursion in string interpolation now
3024
raises an error ``InterpolationDepthError``.
3026
Specifying a value for interpolation which doesn't exist now raises an
3027
error ``MissingInterpolationOption`` (instead of merely being ignored).
3029
The ``writein`` method has been removed.
3031
The comments attribute is now a list (``inline_comments`` equates to the
3032
old comments attribute)
3037
``validate`` doesn't report *extra* values or sections.
3039
You can't have a keyword with the same name as a section (in the same
3040
section). They are both dictionary keys - so they would overlap.
3042
ConfigObj doesn't quote and unquote values if ``list_values=False``.
3043
This means that leading or trailing whitespace in values will be lost when
3044
writing. (Unless you manually quote).
3046
Interpolation checks first the 'DEFAULT' subsection of the current
3047
section, next it checks the 'DEFAULT' section of the parent section,
3048
last it checks the 'DEFAULT' section of the main section.
3050
Logically a 'DEFAULT' section should apply to all subsections of the *same
3051
parent* - this means that checking the 'DEFAULT' subsection in the
3052
*current section* is not necessarily logical ?
3054
In order to simplify unicode support (which is possibly of limited value
3055
in a config file) I have removed automatic support and added the
3056
``encode`` and ``decode methods, which can be used to transform keys and
3057
entries. Because the regex looks for specific values on inital parsing
3058
(i.e. the quotes and the equals signs) it can only read ascii compatible
3059
encodings. For unicode use ``UTF8``, which is ASCII compatible.
3061
Does it matter that we don't support the ':' divider, which is supported
3062
by ``ConfigParser`` ?
3064
The regular expression correctly removes the value -
3065
``"'hello', 'goodbye'"`` and then unquote just removes the front and
3066
back quotes (called from ``_handle_value``). What should we do ??
3067
(*ought* to raise exception because it's an invalid value if lists are
3068
off *sigh*. This is not what you want if you want to do your own list
3069
processing - would be *better* in this case not to unquote.)
3071
String interpolation and validation don't play well together. When
3072
validation changes type it sets the value. This will correctly fetch the
3073
value using interpolation - but then overwrite the interpolation reference.
3074
If the value is unchanged by validation (it's a string) - but other types
3081
List values allow you to specify multiple values for a keyword. This
3082
maps to a list as the resulting Python object when parsed.
3084
The syntax for lists is easy. A list is a comma separated set of values.
3085
If these values contain quotes, the hash mark, or commas, then the values
3086
can be surrounded by quotes. e.g. : ::
3088
keyword = value1, 'value 2', "value 3"
3090
If a value needs to be a list, but only has one member, then you indicate
3091
this with a trailing comma. e.g. : ::
3093
keyword = "single value",
3095
If a value needs to be a list, but it has no members, then you indicate
3096
this with a single comma. e.g. : ::
3098
keyword = , # an empty list
3100
Using triple quotes it will be possible for single values to contain
3101
newlines and *both* single quotes and double quotes. Triple quotes aren't
3102
allowed in list values. This means that the members of list values can't
3103
contain carriage returns (or line feeds :-) or both quote values.
3111
Removed ``BOM_UTF8`` from ``__all__``.
3113
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3114
*only* ``True`` for the ``UTF16`` encoding.
3116
File like objects no longer need a ``seek`` attribute.
3118
ConfigObj no longer keeps a reference to file like objects. Instead the
3119
``write`` method takes a file like object as an optional argument. (Which
3120
will be used in preference of the ``filename`` attribute if htat exists as
3123
Full unicode support added. New options/attributes ``encoding``,
3124
``default_encoding``.
3126
utf16 files decoded to unicode.
3128
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3129
written out at the start of the file. (It will normally only be ``True`` if
3130
the utf8 BOM was found when the file was read.)
3132
File paths are *not* converted to absolute paths, relative paths will
3133
remain relative as the ``filename`` attribute.
3135
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3141
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3142
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3143
``enumerate`` is needed for all versions ofPython 2.2)
3145
Deprecated ``istrue``, replaced it with ``as_bool``.
3147
Added ``as_int`` and ``as_float``.
3149
utf8 and utf16 BOM handled in an endian agnostic way.
3154
Validation no longer done on the 'DEFAULT' section (only in the root
3155
level). This allows interpolation in configspecs.
3157
Change in validation syntax implemented in validate 0.2.1
3164
Added ``merge``, a recursive update.
3166
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3169
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3171
Fixed bug where a config file is *all* comment, the comment will now be
3172
``initial_comment`` rather than ``final_comment``.
3177
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3182
Fixed bug in ``Section.walk`` when transforming names as well as values.
3184
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3187
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3188
are multiline values.
3190
List values are written as ``item, item`` rather than ``item,item``.
3197
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3205
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3206
a reference to the new section.
3211
Removed ``PositionError``.
3213
Allowed quotes around keys as documented.
3215
Fixed bug with commas in comments. (matched as a list value)
3222
Fixed bug in initialising ConfigObj from a ConfigObj.
3224
Changed the mailing list address.
3231
Fixed bug in ``Section.__delitem__`` oops.
3236
Interpolation is switched off before writing out files.
3238
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3239
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3241
Moved the doctests from the ``__init__`` method to a separate function.
3242
(For the sake of IDE calltips).
3249
String values unchanged by validation *aren't* reset. This preserves
3250
interpolation in string values.
3255
None from a default is turned to '' if stringify is off - because setting
3256
a value to None raises an error.
3265
Actually added the RepeatSectionError class ;-)
3270
If ``stringify`` is off - list values are preserved by the ``validate``
3278
Fixed ``simpleVal``.
3280
Added ``RepeatSectionError`` error if you have additional sections in a
3281
section with a ``__many__`` (repeated) section.
3285
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
3286
mutated the self._infile, self._index and self._maxline attributes into
3287
local variables and method parameters
3289
Reshaped the ConfigObj._multiline method to better reflect its semantics
3291
Changed the "default_test" test in ConfigObj.validate to check the fix for
3292
the bug in validate.Validator.check
3299
Updated comments at top
3306
Implemented repeated sections.
3310
Added test for interpreter version: raises RuntimeError if earlier than
3318
Implemented default values in configspecs.
3322
Fixed naked except: clause in validate that was silencing the fact
3323
that Python2.2 does not have dict.pop
3330
Bug fix causing error if file didn't exist.
3337
Adjusted doctests for Python 2.2.3 compatibility
3344
Added the inline_comments attribute
3346
We now preserve and rewrite all comments in the config file
3348
configspec is now a section attribute
3350
The validate method changes values in place
3352
Added InterpolationError
3354
The errors now have line number, line, and message attributes. This
3355
simplifies error handling
3364
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3367
Replaced ``basestring`` with ``types.StringTypes``
3369
Removed the ``writein`` method
3378
Indentation in config file is not significant anymore, subsections are
3379
designated by repeating square brackets
3381
Adapted all tests and docs to the new format
3395
Reformatted final docstring in ReST format, indented it for easier folding
3397
Code tests converted to doctest format, and scattered them around
3398
in various docstrings
3400
Walk method rewritten using scalars and sections attributes
3407
Changed Validator and SimpleVal "test" methods to "check"
3414
Changed Section.sequence to Section.scalars and Section.sections
3416
Added Section.configspec
3418
Sections in the root section now have no extra indentation
3420
Comments now better supported in Section and preserved by ConfigObj
3422
Comments also written out
3424
Implemented initial_comment and final_comment
3426
A scalar value after a section will now raise an error
3431
Fixed a couple of bugs
3433
Can now pass a tuple instead of a list
3435
Simplified dict and walk methods
3437
Added __str__ to Section
3449
The stringify option implemented. On by default.
3454
Renamed private attributes with a single underscore prefix.
3456
Changes to interpolation - exceeding recursion depth, or specifying a
3457
missing value, now raise errors.
3459
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3460
``is True`` and ``is False``)
3462
Added test for duplicate section and member (and fixed bug)
3476
Now properly handles values including comments and lists.
3478
Better error handling.
3480
String interpolation.
3482
Some options implemented.
3484
You can pass a Section a dictionary to initialise it.
3486
Setting a Section member to a dictionary will create a Section instance.
3493
Experimental reader.
3495
A reasonably elegant implementation - a basic reader in 160 lines of code.
3497
*A programming language is a medium of expression.* - Paul Graham
2501
"""*A programming language is a medium of expression.* - Paul Graham"""