256
166
self.message = message
257
167
SyntaxError.__init__(self, message)
260
169
class NestingError(ConfigObjError):
262
171
This error indicates a level of nesting that doesn't match.
173
>>> raise NestingError
174
Traceback (most recent call last):
266
178
class ParseError(ConfigObjError):
268
180
This error indicates that a line is badly written.
269
181
It is neither a valid ``key = value`` line,
270
182
nor a valid section marker line.
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.')
185
Traceback (most recent call last):
283
189
class DuplicateError(ConfigObjError):
285
191
The keyword or section specified already exists.
193
>>> raise DuplicateError
194
Traceback (most recent call last):
289
198
class ConfigspecError(ConfigObjError):
291
200
An error occured whilst parsing a configspec.
202
>>> raise ConfigspecError
203
Traceback (most recent call last):
295
207
class InterpolationError(ConfigObjError):
296
208
"""Base class for the two interpolation errors."""
299
class InterpolationLoopError(InterpolationError):
210
class InterpolationDepthError(InterpolationError):
300
211
"""Maximum interpolation depth exceeded in string interpolation."""
302
213
def __init__(self, option):
215
>>> raise InterpolationDepthError('yoda')
216
Traceback (most recent call last):
217
InterpolationDepthError: max interpolation depth exceeded in value "yoda".
303
219
InterpolationError.__init__(
305
'interpolation loop detected in value "%s".' % option)
221
'max interpolation depth exceeded in value "%s".' % option)
308
223
class RepeatSectionError(ConfigObjError):
310
225
This error indicates additional sections in a section with a
311
226
``__many__`` (repeated) section.
228
>>> raise RepeatSectionError
229
Traceback (most recent call last):
315
233
class MissingInterpolationOption(InterpolationError):
316
234
"""A value specified for interpolation was missing."""
318
236
def __init__(self, option):
238
>>> raise MissingInterpolationOption('yoda')
239
Traceback (most recent call last):
240
MissingInterpolationOption: missing option "yoda" in interpolation.
319
242
InterpolationError.__init__(
321
244
'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,
491
246
class Section(dict):
493
248
A dictionary-like object that represents a section in a config file.
495
It does string interpolation if the 'interpolation' attribute
250
It does string interpolation if the 'interpolate' attribute
496
251
of the 'main' object is set to True.
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.
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.
502
256
A Section will behave like an ordered dictionary - following the
503
257
order of the ``scalars`` and ``sections`` attributes.
523
279
# level of nesting depth of this Section
524
280
self.depth = depth
281
# the sequence of scalar values in this Section
283
# the sequence of sections in this Section
525
285
# 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):
536
# the sequence of scalar values in this Section
538
# the sequence of sections in this Section
540
287
# for comments :-)
541
288
self.comments = {}
542
289
self.inline_comments = {}
543
290
# for the configspec
544
291
self.configspec = {}
546
self._configspec_comments = {}
547
self._configspec_inline_comments = {}
548
self._cs_section_comments = {}
549
self._cs_section_inline_comments = {}
551
293
self.defaults = []
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
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)
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)
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)
578
335
def __getitem__(self, key):
579
336
"""Fetch the item and do string interpolation."""
580
337
val = dict.__getitem__(self, key)
581
338
if self.main.interpolation and isinstance(val, StringTypes):
582
return self._interpolate(key, val)
339
return self._interpolate(val)
586
def __setitem__(self, key, value, unrepr=False):
342
def __setitem__(self, key, value):
588
344
Correctly set a value.
1238
951
'true': True, 'false': False,
1242
954
def __init__(self, infile=None, options=None, **kwargs):
1244
Parse a config file or create a config file object.
956
Parse or create a config file object.
1246
958
``ConfigObj(infile=None, options=None, **kwargs)``
1248
# init the superclass
1249
Section.__init__(self, self, 0, self)
1251
960
if infile is None:
1253
962
if options is None:
1256
options = dict(options)
1258
964
# keyword arguments take precedence over an options dictionary
1259
965
options.update(kwargs)
966
# init the superclass
967
Section.__init__(self, self, 0, self)
1261
969
defaults = OPTION_DEFAULTS.copy()
970
for entry in options.keys():
971
if entry not in defaults.keys():
972
raise TypeError, 'Unrecognised option "%s".' % entry
1262
973
# TODO: check the values too.
1263
for entry in options:
1264
if entry not in defaults:
1265
raise TypeError('Unrecognised option "%s".' % entry)
1267
975
# Add any explicit options to the defaults
1268
976
defaults.update(options)
1269
self._initialise(defaults)
1270
configspec = defaults['configspec']
1271
self._original_configspec = configspec
1272
self._load(infile, configspec)
1275
def _load(self, infile, configspec):
978
# initialise a few variables
981
self.raise_errors = defaults['raise_errors']
982
self.interpolation = defaults['interpolation']
983
self.list_values = defaults['list_values']
984
self.create_empty = defaults['create_empty']
985
self.file_error = defaults['file_error']
986
self.stringify = defaults['stringify']
987
self.indent_type = defaults['indent_type']
988
self.encoding = defaults['encoding']
989
self.default_encoding = defaults['default_encoding']
993
self.initial_comment = []
994
self.final_comment = []
1276
996
if isinstance(infile, StringTypes):
1277
997
self.filename = infile
1278
998
if os.path.isfile(infile):
1279
h = open(infile, 'rb')
1280
infile = h.read() or []
999
infile = open(infile).read() or []
1282
1000
elif self.file_error:
1283
1001
# raise an error if the file doesn't exist
1284
raise IOError('Config file not found: "%s".' % self.filename)
1002
raise IOError, 'Config file not found: "%s".' % self.filename
1286
1004
# file doesn't already exist
1287
1005
if self.create_empty:
1288
1006
# this is a good test that the filename specified
1289
# isn't impossible - like on a non-existent device
1007
# isn't impossible - like on a non existent device
1290
1008
h = open(infile, 'w')
1295
1012
elif isinstance(infile, (list, tuple)):
1296
1013
infile = list(infile)
1298
1014
elif isinstance(infile, dict):
1299
1015
# initialise self
1300
1016
# the Section class handles creating subsections
1301
1017
if isinstance(infile, ConfigObj):
1302
1018
# get a copy of our ConfigObj
1303
1019
infile = infile.dict()
1305
1020
for entry in infile:
1306
1021
self[entry] = infile[entry]
1307
1022
del self._errors
1309
if configspec is not None:
1310
self._handle_configspec(configspec)
1023
if defaults['configspec'] is not None:
1024
self._handle_configspec(defaults['configspec'])
1312
1026
self.configspec = None
1315
1028
elif getattr(infile, 'read', None) is not None:
1316
1029
# This supports file like objects
1317
1030
infile = infile.read() or []
1318
1031
# needs splitting into lines - but needs doing *after* decoding
1319
1032
# in case it's not an 8 bit encoding
1321
raise TypeError('infile must be a filename, file like object, or list of lines.')
1034
raise TypeError, ('infile must be a filename,'
1035
' file like object, or list of lines.')
1324
1038
# don't do it for the empty ConfigObj
1325
1039
infile = self._handle_bom(infile)
1793
1499
if self.stringify:
1794
1500
value = str(value)
1796
raise TypeError('Value "%s" is not a string.' % value)
1502
raise TypeError, 'Value "%s" is not a string.' % value
1506
wspace_plus = ' \r\t\n\v\t\'"'
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:
1511
if (not self.list_values and '\n' not in value) or not (multiline and
1512
((("'" in value) and ('"' in value)) or ('\n' in value))):
1807
1513
if not self.list_values:
1808
1514
# we don't quote if ``list_values=False``
1810
1516
# for normal values either single or double quotes will do
1811
1517
elif '\n' in value:
1812
1518
# will only happen if multiline is off - e.g. '\n' in key
1813
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1519
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1814
1521
elif ((value[0] not in wspace_plus) and
1815
1522
(value[-1] not in wspace_plus) and
1816
1523
(',' not in value)):
1819
quot = self._get_single_quote(value)
1526
if ("'" in value) and ('"' in value):
1527
raise ConfigObjError, (
1528
'Value "%s" cannot be safely quoted.' % value)
1821
1534
# if value has '\n' or "'" *and* '"', it will need triple quotes
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)
1535
if (value.find('"""') != -1) and (value.find("'''") != -1):
1536
raise ConfigObjError, (
1537
'Value "%s" cannot be safely quoted.' % value)
1538
if value.find('"""') == -1:
1827
1542
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:
1850
1544
def _handle_value(self, value):
1852
1546
Given a value string, unquote, remove comment,
1853
1547
handle lists. (including empty and single member lists)
1549
Testing list values.
1551
>>> testconfig3 = '''
1554
... c = test1, test2 , test3
1555
... d = test1, test2, test3,
1557
>>> d = ConfigObj(testconfig3.split('\\n'), raise_errors=True)
1560
>>> d['b'] == ['test']
1562
>>> d['c'] == ['test1', 'test2', 'test3']
1564
>>> d['d'] == ['test1', 'test2', 'test3']
1567
Testing with list values off.
1570
... testconfig3.split('\\n'),
1571
... raise_errors=True,
1572
... list_values=False)
1575
>>> e['b'] == 'test,'
1577
>>> e['c'] == 'test1, test2 , test3'
1579
>>> e['d'] == 'test1, test2, test3,'
1582
Testing creating from a dictionary.
1605
>>> g = ConfigObj(f)
1609
Testing we correctly detect badly built list values (4 of them).
1611
>>> testconfig4 = '''
1615
... dummy = ,,hello, goodbye
1618
... ConfigObj(testconfig4.split('\\n'))
1619
... except ConfigObjError, e:
1623
Testing we correctly detect badly quoted values (4 of them).
1625
>>> testconfig5 = '''
1626
... config = "hello # comment
1628
... fish = 'goodbye # comment
1629
... dummy = "hello again
1632
... ConfigObj(testconfig5.split('\\n'))
1633
... except ConfigObjError, e:
1855
1637
# do we look for lists in values ?
1856
1638
if not self.list_values:
1857
1639
mat = self._nolistvalue.match(value)
1858
1640
if mat is None:
1642
(value, comment) = mat.groups()
1860
1643
# NOTE: we don't unquote here
1644
return (value, comment)
1863
1645
mat = self._valueexp.match(value)
1864
1646
if mat is None:
1865
1647
# the value is badly constructed, probably badly quoted,
1866
1648
# or an invalid list
1868
1650
(list_values, single, empty_list, comment) = mat.groups()
1869
1651
if (list_values == '') and (single is None):
1870
1652
# change this if you want to accept empty values
1872
1654
# NOTE: note there is no error handling from here if the regex
1873
1655
# is wrong: then incorrect values will slip through
1874
1656
if empty_list is not None:
1875
1657
# the single comma - meaning an empty list
1876
1658
return ([], comment)
1877
1659
if single is not None:
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)
1660
single = self._unquote(single)
1886
1661
if list_values == '':
1887
1662
# not a list value
1888
1663
return (single, comment)
2007
1779
scalars[entry] = val
2009
1781
sections[entry] = val
2011
1783
section.configspec = scalars
2012
1784
for entry in sections:
2013
if not section.has_key(entry):
1785
if entry not in section:
2014
1786
section[entry] = {}
2015
1787
self._handle_repeat(section[entry], sections[entry])
2018
1789
def _write_line(self, indent_string, entry, this_entry, comment):
2019
1790
"""Write an individual line, for the write method"""
2020
1791
# NOTE: the calls to self._quote here handles non-StringType values.
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))
1792
return '%s%s%s%s%s' % (
1794
self._decode_element(self._quote(entry, multiline=False)),
1795
self._a_to_u(' = '),
1796
self._decode_element(self._quote(this_entry)),
1797
self._decode_element(comment))
2032
1799
def _write_marker(self, indent_string, depth, entry, comment):
2033
1800
"""Write a section marker line"""
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))
1801
return '%s%s%s%s%s' % (
1803
self._a_to_u('[' * depth),
1804
self._quote(self._decode_element(entry), multiline=False),
1805
self._a_to_u(']' * depth),
1806
self._decode_element(comment))
2041
1808
def _handle_comment(self, comment):
2042
"""Deal with a comment."""
1810
Deal with a comment.
1812
>>> filename = a.filename
1813
>>> a.filename = None
1814
>>> values = a.write()
1816
>>> while index < 23:
1818
... line = values[index-1]
1819
... assert line.endswith('# comment ' + str(index))
1820
>>> a.filename = filename
1822
>>> start_comment = ['# Initial Comment', '', '#']
1823
>>> end_comment = ['', '#', '# Final Comment']
1824
>>> newconfig = start_comment + testconfig1.split('\\n') + end_comment
1825
>>> nc = ConfigObj(newconfig)
1826
>>> nc.initial_comment
1827
['# Initial Comment', '', '#']
1828
>>> nc.final_comment
1829
['', '#', '# Final Comment']
1830
>>> nc.initial_comment == start_comment
1832
>>> nc.final_comment == end_comment
2043
1835
if not comment:
2045
start = self.indent_type
1837
if self.indent_type == '\t':
1838
start = self._a_to_u('\t')
1840
start = self._a_to_u(' ' * NUM_INDENT_SPACES)
2046
1841
if not comment.startswith('#'):
2047
start += self._a_to_u(' # ')
1842
start += _a_to_u('# ')
2048
1843
return (start + comment)
1845
def _compute_indent_string(self, depth):
1847
Compute the indent string, according to current indent_type and depth
1849
if self.indent_type == '':
1850
# no indentation at all
1852
if self.indent_type == '\t':
1854
if self.indent_type == ' ':
1855
return ' ' * NUM_INDENT_SPACES * depth
2051
1858
# Public methods
2189
2009
You can then use the ``flatten_errors`` function to turn your nested
2190
2010
results dictionary into a flattened list of failures - useful for
2191
2011
displaying meaningful error messages.
2014
... from validate import Validator
2015
... except ImportError:
2016
... sys.stderr.write('Cannot import the Validator object, skipping the related tests\n')
2033
... '''.split('\\n')
2034
... configspec = '''
2035
... test1= integer(30,50)
2038
... test4=float(6.0)
2040
... test1=integer(30,50)
2043
... test4=float(6.0)
2045
... test1=integer(30,50)
2048
... test4=float(6.0)
2049
... '''.split('\\n')
2050
... val = Validator()
2051
... c1 = ConfigObj(config, configspec=configspec)
2052
... test = c1.validate(val)
2063
... 'sub section': {
2072
>>> val.check(c1.configspec['test4'], c1['test4'])
2073
Traceback (most recent call last):
2074
VdtValueTooSmallError: the value "5.0" is too small.
2076
>>> val_test_config = '''
2081
... key2 = 1.1, 3.0, 17, 6.8
2084
... key2 = True'''.split('\\n')
2085
>>> val_test_configspec = '''
2090
... key2 = float_list(4)
2092
... key = option(option1, option2)
2093
... key2 = boolean'''.split('\\n')
2094
>>> val_test = ConfigObj(val_test_config, configspec=val_test_configspec)
2095
>>> val_test.validate(val)
2097
>>> val_test['key'] = 'text not a digit'
2098
>>> val_res = val_test.validate(val)
2099
>>> val_res == {'key2': True, 'section': True, 'key': False}
2101
>>> configspec = '''
2102
... test1=integer(30,50, default=40)
2103
... test2=string(default="hello")
2104
... test3=integer(default=3)
2105
... test4=float(6.0, default=6.0)
2107
... test1=integer(30,50, default=40)
2108
... test2=string(default="hello")
2109
... test3=integer(default=3)
2110
... test4=float(6.0, default=6.0)
2112
... test1=integer(30,50, default=40)
2113
... test2=string(default="hello")
2114
... test3=integer(default=3)
2115
... test4=float(6.0, default=6.0)
2116
... '''.split('\\n')
2117
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2119
{'test1': '30', 'section': {'sub section': {}}}
2120
>>> default_test.validate(val)
2122
>>> default_test == {
2124
... 'test2': 'hello',
2129
... 'test2': 'hello',
2132
... 'sub section': {
2135
... 'test2': 'hello',
2142
Now testing with repeated sections : BIG TEST
2144
>>> repeated_1 = '''
2146
... [[__many__]] # spec for a dog
2147
... fleas = boolean(default=True)
2148
... tail = option(long, short, default=long)
2149
... name = string(default=rover)
2150
... [[[__many__]]] # spec for a puppy
2151
... name = string(default="son of rover")
2152
... age = float(default=0.0)
2154
... [[__many__]] # spec for a cat
2155
... fleas = boolean(default=True)
2156
... tail = option(long, short, default=short)
2157
... name = string(default=pussy)
2158
... [[[__many__]]] # spec for a kitten
2159
... name = string(default="son of pussy")
2160
... age = float(default=0.0)
2161
... '''.split('\\n')
2162
>>> repeated_2 = '''
2165
... # blank dogs with puppies
2166
... # should be filled in by the configspec
2181
... # blank cats with kittens
2182
... # should be filled in by the configspec
2195
... '''.split('\\n')
2196
>>> repeated_3 = '''
2207
... '''.split('\\n')
2208
>>> repeated_4 = '''
2211
... name = string(default=Michael)
2212
... age = float(default=0.0)
2213
... sex = option(m, f, default=m)
2214
... '''.split('\\n')
2215
>>> repeated_5 = '''
2218
... fleas = boolean(default=True)
2219
... tail = option(long, short, default=short)
2220
... name = string(default=pussy)
2221
... [[[description]]]
2222
... height = float(default=3.3)
2223
... weight = float(default=6)
2225
... fur = option(black, grey, brown, "tortoise shell", default=black)
2226
... condition = integer(0,10, default=5)
2227
... '''.split('\\n')
2228
>>> from validate import Validator
2229
>>> val= Validator()
2230
>>> repeater = ConfigObj(repeated_2, configspec=repeated_1)
2231
>>> repeater.validate(val)
2238
... 'name': 'rover',
2239
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2240
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2241
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2246
... 'name': 'rover',
2247
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2248
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2249
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2254
... 'name': 'rover',
2255
... 'puppy1': {'name': 'son of rover', 'age': 0.0},
2256
... 'puppy2': {'name': 'son of rover', 'age': 0.0},
2257
... 'puppy3': {'name': 'son of rover', 'age': 0.0},
2263
... 'tail': 'short',
2264
... 'name': 'pussy',
2265
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2266
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2267
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2271
... 'tail': 'short',
2272
... 'name': 'pussy',
2273
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2274
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2275
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2279
... 'tail': 'short',
2280
... 'name': 'pussy',
2281
... 'kitten1': {'name': 'son of pussy', 'age': 0.0},
2282
... 'kitten2': {'name': 'son of pussy', 'age': 0.0},
2283
... 'kitten3': {'name': 'son of pussy', 'age': 0.0},
2288
>>> repeater = ConfigObj(repeated_3, configspec=repeated_1)
2289
>>> repeater.validate(val)
2293
... 'cat1': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2294
... 'cat2': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2295
... 'cat3': {'fleas': True, 'tail': 'short', 'name': 'pussy'},
2298
... 'dog1': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2299
... 'dog2': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2300
... 'dog3': {'fleas': True, 'tail': 'long', 'name': 'rover'},
2304
>>> repeater = ConfigObj(configspec=repeated_4)
2305
>>> repeater['Michael'] = {}
2306
>>> repeater.validate(val)
2309
... 'Michael': {'age': 0.0, 'name': 'Michael', 'sex': 'm'},
2312
>>> repeater = ConfigObj(repeated_3, configspec=repeated_5)
2314
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2315
... 'cats': {'cat1': {}, 'cat2': {}, 'cat3': {}},
2318
>>> repeater.validate(val)
2321
... 'dogs': {'dog1': {}, 'dog2': {}, 'dog3': {}},
2325
... 'tail': 'short',
2326
... 'name': 'pussy',
2327
... 'description': {
2329
... 'height': 3.2999999999999998,
2330
... 'coat': {'fur': 'black', 'condition': 5},
2335
... 'tail': 'short',
2336
... 'name': 'pussy',
2337
... 'description': {
2339
... 'height': 3.2999999999999998,
2340
... 'coat': {'fur': 'black', 'condition': 5},
2345
... 'tail': 'short',
2346
... 'name': 'pussy',
2347
... 'description': {
2349
... 'height': 3.2999999999999998,
2350
... 'coat': {'fur': 'black', 'condition': 5},
2357
Test that interpolation is preserved for validated string values.
2358
Also check that interpolation works in configspecs.
2360
>>> t['DEFAULT'] = {}
2361
>>> t['DEFAULT']['test'] = 'a'
2362
>>> t['test'] = '%(test)s'
2366
>>> t.configspec = {'test': 'string'}
2369
>>> t.interpolation = False
2371
{'test': '%(test)s', 'DEFAULT': {'test': 'a'}}
2373
... 'interpolated string = string(default="fuzzy-%(man)s")',
2377
>>> c = ConfigObj(configspec=specs)
2380
>>> c['interpolated string']
2383
FIXME: Above tests will fail if we couldn't import Validator (the ones
2384
that don't raise errors will produce different output and still fail as
2193
2387
if section is None:
2194
2388
if self.configspec is None:
2195
raise ValueError('No configspec supplied.')
2389
raise ValueError, 'No configspec supplied.'
2196
2390
if preserve_errors:
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
2391
if VdtMissingValue is None:
2392
raise ImportError('Missing validate module.')
2203
2395
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
2212
2396
if '__many__' in section.configspec:
2213
2397
many = spec_section['__many__']
2214
2398
# dynamically assign the configspecs
2501
"""*A programming language is a medium of expression.* - Paul Graham"""
2658
# FIXME: test error code for badly built multiline values
2659
# FIXME: test handling of StringIO
2660
# FIXME: test interpolation with writing
2664
Dummy function to hold some of the doctests.
2701
... 'keys11': 'val1',
2702
... 'keys13': 'val3',
2703
... 'keys12': 'val2',
2706
... 'section 2 sub 1': {
2709
... 'keys21': 'val1',
2710
... 'keys22': 'val2',
2711
... 'keys23': 'val3',
2716
... 'a' = b # !"$%^&*(),::;'@~#= 33
2717
... "b" = b #= 6, 33
2718
... ''' .split('\\n')
2719
>>> t2 = ConfigObj(t)
2720
>>> assert t2 == {'a': 'b', 'b': 'b'}
2721
>>> t2.inline_comments['b'] = ''
2723
>>> assert t2.write() == ['','b = b', '']
2725
# Test ``list_values=False`` stuff
2727
... key1 = no quotes
2728
... key2 = 'single quotes'
2729
... key3 = "double quotes"
2730
... key4 = "list", 'with', several, "quotes"
2732
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2733
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2734
... 'key3': '"double quotes"',
2735
... 'key4': '"list", \\'with\\', several, "quotes"'
2738
>>> cfg = ConfigObj(list_values=False)
2739
>>> cfg['key1'] = 'Multiline\\nValue'
2740
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2742
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2743
>>> cfg.list_values = True
2744
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2745
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2748
Test flatten_errors:
2750
>>> from validate import Validator, VdtValueTooSmallError
2766
... '''.split('\\n')
2767
>>> configspec = '''
2768
... test1= integer(30,50)
2771
... test4=float(6.0)
2773
... test1=integer(30,50)
2776
... test4=float(6.0)
2778
... test1=integer(30,50)
2781
... test4=float(6.0)
2782
... '''.split('\\n')
2783
>>> val = Validator()
2784
>>> c1 = ConfigObj(config, configspec=configspec)
2785
>>> res = c1.validate(val)
2786
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2787
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2789
>>> res = c1.validate(val, preserve_errors=True)
2790
>>> check = flatten_errors(c1, res)
2794
(['section', 'sub section'], 'test4')
2796
(['section'], 'test4')
2797
>>> for entry in check:
2798
... isinstance(entry[2], VdtValueTooSmallError)
2799
... print str(entry[2])
2801
the value "5.0" is too small.
2803
the value "5.0" is too small.
2805
the value "5.0" is too small.
2807
Test unicode handling, BOM, write witha file like object and line endings :
2809
... # initial comment
2810
... # inital comment 2
2812
... test1 = some value
2814
... test2 = another value # inline comment
2815
... # section comment
2816
... [section] # inline comment
2817
... test = test # another inline comment
2821
... # final comment2
2823
>>> u = u_base.encode('utf_8').splitlines(True)
2824
>>> u[0] = BOM_UTF8 + u[0]
2825
>>> uc = ConfigObj(u)
2826
>>> uc.encoding = None
2829
>>> uc == {'test1': 'some value', 'test2': 'another value',
2830
... 'section': {'test': 'test', 'test2': 'test2'}}
2832
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2835
>>> isinstance(uc['test1'], unicode)
2841
>>> uc['latin1'] = "This costs lot's of "
2842
>>> a_list = uc.write()
2845
>>> isinstance(a_list[0], str)
2847
>>> a_list[0].startswith(BOM_UTF8)
2849
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2850
>>> uc = ConfigObj(u)
2853
>>> uc.newlines = '\\r'
2854
>>> from cStringIO import StringIO
2855
>>> file_like = StringIO()
2856
>>> uc.write(file_like)
2857
>>> file_like.seek(0)
2858
>>> uc2 = ConfigObj(file_like)
2861
>>> uc2.filename is None
2863
>>> uc2.newlines == '\\r'
2867
if __name__ == '__main__':
2868
# run the code tests in doctest format
2871
key1= val # comment 1
2872
key2= val # comment 2
2875
key1= val # comment 5
2876
key2= val # comment 6
2879
key1= val # comment 9
2880
key2= val # comment 10
2882
[[lev2ba]] # comment 12
2883
key1= val # comment 13
2885
[[lev2bb]] # comment 15
2886
key1= val # comment 16
2888
[lev1c] # comment 18
2890
[[lev2c]] # comment 20
2892
[[[lev3c]]] # comment 22
2893
key1 = val # comment 23"""
2899
["section 1"] # comment
2908
[['section 2 sub 1']]
2913
name1 = """ a single line value """ # comment
2914
name2 = \''' another single line value \''' # comment
2915
name3 = """ a single line value """
2916
name4 = \''' another single line value \'''
2933
\''' # I guess this is a comment too
2937
m = sys.modules.get('__main__')
2938
globs = m.__dict__.copy()
2939
a = ConfigObj(testconfig1.split('\n'), raise_errors=True)
2940
b = ConfigObj(testconfig2.split('\n'), raise_errors=True)
2941
i = ConfigObj(testconfig6.split('\n'), raise_errors=True)
2943
'INTP_VER': INTP_VER,
2948
doctest.testmod(m, globs=globs)
2959
Better support for configuration from multiple files, including tracking
2960
*where* the original file came from and writing changes to the correct
2964
Make ``newline`` an option (as well as an attribute) ?
2966
``UTF16`` encoded files, when returned as a list of lines, will have the
2967
BOM at the start of every line. Should this be removed from all but the
2970
Option to set warning type for unicode decode ? (Defaults to strict).
2972
A method to optionally remove uniform indentation from multiline values.
2973
(do as an example of using ``walk`` - along with string-escape)
2975
Should the results dictionary from validate be an ordered dictionary if
2976
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2978
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2980
Implement some of the sequence methods (which include slicing) from the
2983
INCOMPATIBLE CHANGES
2984
====================
2986
(I have removed a lot of needless complications - this list is probably not
2987
conclusive, many option/attribute/method names have changed)
2991
The only valid divider is '='
2993
We've removed line continuations with '\'
2995
No recursive lists in values
2999
No distinction between flatfiles and non flatfiles
3001
Change in list syntax - use commas to indicate list, not parentheses
3002
(square brackets and parentheses are no longer recognised as lists)
3004
';' is no longer valid for comments and no multiline comments
3008
We don't allow empty values - have to use '' or ""
3010
In ConfigObj 3 - setting a non-flatfile member to ``None`` would
3011
initialise it as an empty section.
3013
The escape entities '&mjf-lf;' and '&mjf-quot;' have gone
3014
replaced by triple quote, multiple line values.
3016
The ``newline``, ``force_return``, and ``default`` options have gone
3018
The ``encoding`` and ``backup_encoding`` methods have gone - replaced
3019
with the ``encode`` and ``decode`` methods.
3021
``fileerror`` and ``createempty`` options have become ``file_error`` and
3024
Partial configspecs (for specifying the order members should be written
3025
out and which should be present) have gone. The configspec is no longer
3026
used to specify order for the ``write`` method.
3028
Exceeding the maximum depth of recursion in string interpolation now
3029
raises an error ``InterpolationDepthError``.
3031
Specifying a value for interpolation which doesn't exist now raises an
3032
error ``MissingInterpolationOption`` (instead of merely being ignored).
3034
The ``writein`` method has been removed.
3036
The comments attribute is now a list (``inline_comments`` equates to the
3037
old comments attribute)
3042
``validate`` doesn't report *extra* values or sections.
3044
You can't have a keyword with the same name as a section (in the same
3045
section). They are both dictionary keys - so they would overlap.
3047
ConfigObj doesn't quote and unquote values if ``list_values=False``.
3048
This means that leading or trailing whitespace in values will be lost when
3049
writing. (Unless you manually quote).
3051
Interpolation checks first the 'DEFAULT' subsection of the current
3052
section, next it checks the 'DEFAULT' section of the parent section,
3053
last it checks the 'DEFAULT' section of the main section.
3055
Logically a 'DEFAULT' section should apply to all subsections of the *same
3056
parent* - this means that checking the 'DEFAULT' subsection in the
3057
*current section* is not necessarily logical ?
3059
In order to simplify unicode support (which is possibly of limited value
3060
in a config file) I have removed automatic support and added the
3061
``encode`` and ``decode methods, which can be used to transform keys and
3062
entries. Because the regex looks for specific values on inital parsing
3063
(i.e. the quotes and the equals signs) it can only read ascii compatible
3064
encodings. For unicode use ``UTF8``, which is ASCII compatible.
3066
Does it matter that we don't support the ':' divider, which is supported
3067
by ``ConfigParser`` ?
3069
The regular expression correctly removes the value -
3070
``"'hello', 'goodbye'"`` and then unquote just removes the front and
3071
back quotes (called from ``_handle_value``). What should we do ??
3072
(*ought* to raise exception because it's an invalid value if lists are
3073
off *sigh*. This is not what you want if you want to do your own list
3074
processing - would be *better* in this case not to unquote.)
3076
String interpolation and validation don't play well together. When
3077
validation changes type it sets the value. This will correctly fetch the
3078
value using interpolation - but then overwrite the interpolation reference.
3079
If the value is unchanged by validation (it's a string) - but other types
3086
List values allow you to specify multiple values for a keyword. This
3087
maps to a list as the resulting Python object when parsed.
3089
The syntax for lists is easy. A list is a comma separated set of values.
3090
If these values contain quotes, the hash mark, or commas, then the values
3091
can be surrounded by quotes. e.g. : ::
3093
keyword = value1, 'value 2', "value 3"
3095
If a value needs to be a list, but only has one member, then you indicate
3096
this with a trailing comma. e.g. : ::
3098
keyword = "single value",
3100
If a value needs to be a list, but it has no members, then you indicate
3101
this with a single comma. e.g. : ::
3103
keyword = , # an empty list
3105
Using triple quotes it will be possible for single values to contain
3106
newlines and *both* single quotes and double quotes. Triple quotes aren't
3107
allowed in list values. This means that the members of list values can't
3108
contain carriage returns (or line feeds :-) or both quote values.
3116
Removed ``BOM_UTF8`` from ``__all__``.
3118
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3119
*only* ``True`` for the ``UTF16`` encoding.
3121
File like objects no longer need a ``seek`` attribute.
3123
ConfigObj no longer keeps a reference to file like objects. Instead the
3124
``write`` method takes a file like object as an optional argument. (Which
3125
will be used in preference of the ``filename`` attribute if htat exists as
3128
Full unicode support added. New options/attributes ``encoding``,
3129
``default_encoding``.
3131
utf16 files decoded to unicode.
3133
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3134
written out at the start of the file. (It will normally only be ``True`` if
3135
the utf8 BOM was found when the file was read.)
3137
File paths are *not* converted to absolute paths, relative paths will
3138
remain relative as the ``filename`` attribute.
3140
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3146
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3147
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3148
``enumerate`` is needed for all versions ofPython 2.2)
3150
Deprecated ``istrue``, replaced it with ``as_bool``.
3152
Added ``as_int`` and ``as_float``.
3154
utf8 and utf16 BOM handled in an endian agnostic way.
3159
Validation no longer done on the 'DEFAULT' section (only in the root
3160
level). This allows interpolation in configspecs.
3162
Change in validation syntax implemented in validate 0.2.1
3169
Added ``merge``, a recursive update.
3171
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3174
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3176
Fixed bug where a config file is *all* comment, the comment will now be
3177
``initial_comment`` rather than ``final_comment``.
3182
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3187
Fixed bug in ``Section.walk`` when transforming names as well as values.
3189
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3192
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3193
are multiline values.
3195
List values are written as ``item, item`` rather than ``item,item``.
3202
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3210
Fixed bug in ``setdefault`` - creating a new section *wouldn't* return
3211
a reference to the new section.
3216
Removed ``PositionError``.
3218
Allowed quotes around keys as documented.
3220
Fixed bug with commas in comments. (matched as a list value)
3227
Fixed bug in initialising ConfigObj from a ConfigObj.
3229
Changed the mailing list address.
3236
Fixed bug in ``Section.__delitem__`` oops.
3241
Interpolation is switched off before writing out files.
3243
Fixed bug in handling ``StringIO`` instances. (Thanks to report from
3244
"Gustavo Niemeyer" <gustavo@niemeyer.net>)
3246
Moved the doctests from the ``__init__`` method to a separate function.
3247
(For the sake of IDE calltips).
3254
String values unchanged by validation *aren't* reset. This preserves
3255
interpolation in string values.
3260
None from a default is turned to '' if stringify is off - because setting
3261
a value to None raises an error.
3270
Actually added the RepeatSectionError class ;-)
3275
If ``stringify`` is off - list values are preserved by the ``validate``
3283
Fixed ``simpleVal``.
3285
Added ``RepeatSectionError`` error if you have additional sections in a
3286
section with a ``__many__`` (repeated) section.
3290
Reworked the ConfigObj._parse, _handle_error and _multiline methods:
3291
mutated the self._infile, self._index and self._maxline attributes into
3292
local variables and method parameters
3294
Reshaped the ConfigObj._multiline method to better reflect its semantics
3296
Changed the "default_test" test in ConfigObj.validate to check the fix for
3297
the bug in validate.Validator.check
3304
Updated comments at top
3311
Implemented repeated sections.
3315
Added test for interpreter version: raises RuntimeError if earlier than
3323
Implemented default values in configspecs.
3327
Fixed naked except: clause in validate that was silencing the fact
3328
that Python2.2 does not have dict.pop
3335
Bug fix causing error if file didn't exist.
3342
Adjusted doctests for Python 2.2.3 compatibility
3349
Added the inline_comments attribute
3351
We now preserve and rewrite all comments in the config file
3353
configspec is now a section attribute
3355
The validate method changes values in place
3357
Added InterpolationError
3359
The errors now have line number, line, and message attributes. This
3360
simplifies error handling
3369
Fixed bug in Section.pop (now doesn't raise KeyError if a default value
3372
Replaced ``basestring`` with ``types.StringTypes``
3374
Removed the ``writein`` method
3383
Indentation in config file is not significant anymore, subsections are
3384
designated by repeating square brackets
3386
Adapted all tests and docs to the new format
3400
Reformatted final docstring in ReST format, indented it for easier folding
3402
Code tests converted to doctest format, and scattered them around
3403
in various docstrings
3405
Walk method rewritten using scalars and sections attributes
3412
Changed Validator and SimpleVal "test" methods to "check"
3419
Changed Section.sequence to Section.scalars and Section.sections
3421
Added Section.configspec
3423
Sections in the root section now have no extra indentation
3425
Comments now better supported in Section and preserved by ConfigObj
3427
Comments also written out
3429
Implemented initial_comment and final_comment
3431
A scalar value after a section will now raise an error
3436
Fixed a couple of bugs
3438
Can now pass a tuple instead of a list
3440
Simplified dict and walk methods
3442
Added __str__ to Section
3454
The stringify option implemented. On by default.
3459
Renamed private attributes with a single underscore prefix.
3461
Changes to interpolation - exceeding recursion depth, or specifying a
3462
missing value, now raise errors.
3464
Changes for Python 2.2 compatibility. (changed boolean tests - removed
3465
``is True`` and ``is False``)
3467
Added test for duplicate section and member (and fixed bug)
3481
Now properly handles values including comments and lists.
3483
Better error handling.
3485
String interpolation.
3487
Some options implemented.
3489
You can pass a Section a dictionary to initialise it.
3491
Setting a Section member to a dictionary will create a Section instance.
3498
Experimental reader.
3500
A reasonably elegant implementation - a basic reader in 160 lines of code.
3502
*A programming language is a medium of expression.* - Paul Graham