39
42
from types import StringTypes
41
# the UTF8 BOM - from codecs module
42
BOM_UTF8 = '\xef\xbb\xbf'
46
__revision__ = '$Id: configobj.py 135 2005-10-12 14:52:28Z fuzzyman $'
43
from warnings import warn
44
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
46
# A dictionary mapping BOM to
47
# the encoding to decode with, and what to set the
48
# encoding attribute to.
50
BOM_UTF8: ('utf_8', None),
51
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
52
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
53
BOM_UTF16: ('utf_16', 'utf_16'),
55
# All legal variants of the BOM codecs.
56
# TODO: the list of aliases is not meant to be exhaustive, is there a
63
'utf16_be': 'utf16_be',
64
'utf_16_be': 'utf16_be',
65
'utf-16be': 'utf16_be',
66
'utf16_le': 'utf16_le',
67
'utf_16_le': 'utf16_le',
68
'utf-16le': 'utf16_le',
76
# Map of encodings to the BOM to write.
80
'utf16_be': BOM_UTF16_BE,
81
'utf16_le': BOM_UTF16_LE,
86
from validate import VdtMissingValue
88
VdtMissingValue = None
94
"""enumerate for Python 2.2."""
106
__version__ = '4.2.0beta2'
108
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
48
110
__docformat__ = "restructuredtext en"
112
# NOTE: Does it make sense to have the following in __all__ ?
113
# NOTE: DEFAULT_INDENT_TYPE, NUM_INDENT_SPACES, MAX_INTERPOL_DEPTH
114
# NOTE: If used as from configobj import...
115
# NOTE: They are effectively read only
53
118
'DEFAULT_INDENT_TYPE',
54
119
'NUM_INDENT_SPACES',
55
120
'MAX_INTERPOL_DEPTH',
507
602
return value when called on the whole subsection has to be discarded.
509
604
See the encode and decode methods for examples, including functions.
608
You can use ``walk`` to transform the names of members of a section
609
but you mustn't add or delete members.
611
>>> config = '''[XXXXsection]
612
... XXXXkey = XXXXvalue'''.splitlines()
613
>>> cfg = ConfigObj(config)
615
{'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
616
>>> def transform(section, key):
617
... val = section[key]
618
... newkey = key.replace('XXXX', 'CLIENT1')
619
... section.rename(key, newkey)
620
... if isinstance(val, (tuple, list, dict)):
623
... val = val.replace('XXXX', 'CLIENT1')
624
... section[newkey] = val
625
>>> cfg.walk(transform, call_on_sections=True)
626
{'CLIENT1section': {'CLIENT1key': None}}
628
{'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
513
for entry in self.scalars[:]:
632
for i in range(len(self.scalars)):
633
entry = self.scalars[i]
515
out[entry] = function(self, entry, **keywargs)
635
val = function(self, entry, **keywargs)
636
# bound again in case name has changed
637
entry = self.scalars[i]
516
639
except Exception:
643
entry = self.scalars[i]
520
644
out[entry] = False
522
for entry in self.sections[:]:
646
for i in range(len(self.sections)):
647
entry = self.sections[i]
523
648
if call_on_sections:
525
650
function(self, entry, **keywargs)
602
731
section[newkey] = newval
603
732
self.walk(encode, call_on_sections=True)
734
def istrue(self, key):
735
"""A deprecated version of ``as_bool``."""
736
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
737
'instead.', DeprecationWarning)
738
return self.as_bool(key)
740
def as_bool(self, key):
742
Accepts a key as input. The corresponding value must be a string or
743
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
744
retain compatibility with Python 2.2.
746
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
749
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
752
``as_bool`` is not case sensitive.
754
Any other input will raise a ``ValueError``.
759
Traceback (most recent call last):
760
ValueError: Value "fish" is neither True nor False
775
if not isinstance(val, StringTypes):
778
return self.main._bools[val.lower()]
780
raise ValueError('Value "%s" is neither True nor False' % val)
782
def as_int(self, key):
784
A convenience method which coerces the specified value to an integer.
786
If the value is an invalid literal for ``int``, a ``ValueError`` will
792
Traceback (most recent call last):
793
ValueError: invalid literal for int(): fish
799
Traceback (most recent call last):
800
ValueError: invalid literal for int(): 3.2
802
return int(self[key])
804
def as_float(self, key):
806
A convenience method which coerces the specified value to a float.
808
If the value is an invalid literal for ``float``, a ``ValueError`` will
814
Traceback (most recent call last):
815
ValueError: invalid literal for float(): fish
823
return float(self[key])
605
826
class ConfigObj(Section):
607
828
An object to read, create, and write config files.
754
985
self.file_error = defaults['file_error']
755
986
self.stringify = defaults['stringify']
756
987
self.indent_type = defaults['indent_type']
757
# used by the write method
988
self.encoding = defaults['encoding']
989
self.default_encoding = defaults['default_encoding']
760
993
self.initial_comment = []
761
994
self.final_comment = []
763
996
if isinstance(infile, StringTypes):
764
self.filename = os.path.abspath(infile)
765
if os.path.isfile(self.filename):
766
infile = open(self.filename).readlines()
997
self.filename = infile
998
if os.path.isfile(infile):
999
infile = open(infile).read() or []
767
1000
elif self.file_error:
768
1001
# raise an error if the file doesn't exist
769
1002
raise IOError, 'Config file not found: "%s".' % self.filename
786
1019
infile = infile.dict()
787
1020
for entry in infile:
788
1021
self[entry] = infile[entry]
790
1022
del self._errors
791
1023
if defaults['configspec'] is not None:
792
1024
self._handle_configspec(defaults['configspec'])
794
1026
self.configspec = None
796
elif hasattr(infile, 'seek'):
797
# this supports StringIO instances and even file objects
798
self.filename = infile
800
infile = infile.readlines()
801
self.filename.seek(0)
1028
elif getattr(infile, 'read', None) is not None:
1029
# This supports file like objects
1030
infile = infile.read() or []
1031
# needs splitting into lines - but needs doing *after* decoding
1032
# in case it's not an 8 bit encoding
803
1034
raise TypeError, ('infile must be a filename,'
804
' StringIO instance, or a file as a list.')
806
# strip trailing '\n' from lines
807
infile = [line.rstrip('\n') for line in infile]
809
# remove the UTF8 BOM if it is there
810
# FIXME: support other BOM
811
if infile and infile[0].startswith(BOM_UTF8):
812
infile[0] = infile[0][3:]
1035
' file like object, or list of lines.')
1038
# don't do it for the empty ConfigObj
1039
infile = self._handle_bom(infile)
1040
# infile is now *always* a list
1042
# Set the newlines attribute (first line ending it finds)
1043
# and strip trailing '\n' or '\r' from lines
1045
if (not line) or (line[-1] not in '\r\n'):
1047
for end in ('\r\n', '\n', '\r'):
1048
if line.endswith(end):
1052
infile = [line.rstrip('\r\n') for line in infile]
817
1054
self._parse(infile)
818
1055
# if we had any errors, now is the time to raise them
833
1070
self._handle_configspec(defaults['configspec'])
1072
def _handle_bom(self, infile):
1074
Handle any BOM, and decode if necessary.
1076
If an encoding is specified, that *must* be used - but the BOM should
1077
still be removed (and the BOM attribute set).
1079
(If the encoding is wrongly specified, then a BOM for an alternative
1080
encoding won't be discovered or removed.)
1082
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1083
removed. The BOM attribute will be set. UTF16 will be decoded to
1086
NOTE: This method must not be called with an empty ``infile``.
1088
Specifying the *wrong* encoding is likely to cause a
1089
``UnicodeDecodeError``.
1091
``infile`` must always be returned as a list of lines, but may be
1092
passed in as a single string.
1094
if ((self.encoding is not None) and
1095
(self.encoding.lower() not in BOM_LIST)):
1096
# No need to check for a BOM
1097
# encoding specified doesn't have one
1099
return self._decode(infile, self.encoding)
1101
if isinstance(infile, (list, tuple)):
1105
if self.encoding is not None:
1106
# encoding explicitly supplied
1107
# And it could have an associated BOM
1108
# TODO: if encoding is just UTF16 - we ought to check for both
1109
# TODO: big endian and little endian versions.
1110
enc = BOM_LIST[self.encoding.lower()]
1112
# For UTF16 we try big endian and little endian
1113
for BOM, (encoding, final_encoding) in BOMS.items():
1114
if not final_encoding:
1117
if infile.startswith(BOM):
1120
# Don't need to remove BOM
1121
return self._decode(infile, encoding)
1123
# If we get this far, will *probably* raise a DecodeError
1124
# As it doesn't appear to start with a BOM
1125
return self._decode(infile, self.encoding)
1129
if not line.startswith(BOM):
1130
return self._decode(infile, self.encoding)
1132
newline = line[len(BOM):]
1135
if isinstance(infile, (list, tuple)):
1140
return self._decode(infile, self.encoding)
1142
# No encoding specified - so we need to check for UTF8/UTF16
1143
for BOM, (encoding, final_encoding) in BOMS.items():
1144
if not line.startswith(BOM):
1148
self.encoding = final_encoding
1149
if not final_encoding:
1153
newline = line[len(BOM):]
1154
if isinstance(infile, (list, tuple)):
1158
# UTF8 - don't decode
1159
if isinstance(infile, StringTypes):
1160
return infile.splitlines(True)
1163
# UTF16 - have to decode
1164
return self._decode(infile, encoding)
1166
# No BOM discovered and no encoding specified, just return
1167
if isinstance(infile, StringTypes):
1168
# infile read from a file will be a single string
1169
return infile.splitlines(True)
1173
def _a_to_u(self, string):
1174
"""Decode ascii strings to unicode if a self.encoding is specified."""
1175
if not self.encoding:
1178
return string.decode('ascii')
1180
def _decode(self, infile, encoding):
1182
Decode infile to unicode. Using the specified encoding.
1184
if is a string, it also needs converting to a list.
1186
if isinstance(infile, StringTypes):
1188
# NOTE: Could raise a ``UnicodeDecodeError``
1189
return infile.decode(encoding).splitlines(True)
1190
for i, line in enumerate(infile):
1191
if not isinstance(line, unicode):
1192
# NOTE: The isinstance test here handles mixed lists of unicode/string
1193
# NOTE: But the decode will break on any non-string values
1194
# NOTE: Or could raise a ``UnicodeDecodeError``
1195
infile[i] = line.decode(encoding)
1198
def _decode_element(self, line):
1199
"""Decode element to unicode if necessary."""
1200
if not self.encoding:
1202
if isinstance(line, str) and self.default_encoding:
1203
return line.decode(self.default_encoding)
1206
def _str(self, value):
1208
Used by ``stringify`` within validate, to turn non-string values
1211
if not isinstance(value, StringTypes):
835
1216
def _parse(self, infile):
837
1218
Actually parse the config file
1012
1395
ParseError, infile, cur_index)
1015
## print >> sys.stderr, sline
1398
## sys.stderr.write(sline)
1399
## sys.stderr.write('\n')
1016
1400
key = self._unquote(key)
1017
if this_section.has_key(key):
1401
if key in this_section:
1018
1402
self._handle_error(
1019
1403
'Duplicate keyword name at line %s.',
1020
1404
DuplicateError, infile, cur_index)
1023
## print >> sys.stderr, this_section.name
1407
## sys.stderr.write(this_section.name + '\n')
1024
1408
this_section[key] = value
1025
1409
this_section.inline_comments[key] = comment
1026
1410
this_section.comments[key] = comment_list
1027
## print >> sys.stderr, key, this_section[key]
1411
## sys.stderr.write('%s %s\n' % (key, this_section[key]))
1028
1412
## if this_section.name is not None:
1029
## print >> sys.stderr, this_section
1030
## print >> sys.stderr, this_section.parent
1031
## print >> sys.stderr, this_section.parent[this_section.name]
1413
## sys.stderr.write(this_section + '\n')
1414
## sys.stderr.write(this_section.parent + '\n')
1415
## sys.stderr.write(this_section.parent[this_section.name])
1416
## sys.stderr.write('\n')
1034
1419
# it neither matched as a keyword
1387
1783
section.configspec = scalars
1388
1784
for entry in sections:
1389
if not section.has_key(entry):
1785
if entry not in section:
1390
1786
section[entry] = {}
1391
1787
self._handle_repeat(section[entry], sections[entry])
1393
1789
def _write_line(self, indent_string, entry, this_entry, comment):
1394
1790
"""Write an individual line, for the write method"""
1395
return '%s%s = %s%s' % (
1791
# NOTE: the calls to self._quote here handles non-StringType values.
1792
return '%s%s%s%s%s' % (
1397
self._quote(entry, multiline=False),
1398
self._quote(this_entry),
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))
1401
1799
def _write_marker(self, indent_string, depth, entry, comment):
1402
1800
"""Write a section marker line"""
1403
1801
return '%s%s%s%s%s' % (
1406
self._quote(entry, multiline=False),
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))
1410
1808
def _handle_comment(self, comment):
1489
1887
['a = %(a)s', '[DEFAULT]', 'a = fish']
1492
1889
if self.indent_type is None:
1493
1890
# this can be true if initialised from a dictionary
1494
1891
self.indent_type = DEFAULT_INDENT_TYPE
1894
cs = self._a_to_u('#')
1895
csp = self._a_to_u('# ')
1498
1896
if section is None:
1499
1897
int_val = self.interpolation
1500
1898
self.interpolation = False
1503
1900
for line in self.initial_comment:
1901
line = self._decode_element(line)
1504
1902
stripped_line = line.strip()
1505
if stripped_line and not stripped_line.startswith('#'):
1903
if stripped_line and not stripped_line.startswith(cs):
1507
1905
out.append(line)
1509
indent_string = self._compute_indent_string(section.depth)
1907
indent_string = self._a_to_u(
1908
self._compute_indent_string(section.depth))
1510
1909
for entry in (section.scalars + section.sections):
1511
1910
if entry in section.defaults:
1512
1911
# don't write out default values
1514
1913
for comment_line in section.comments[entry]:
1515
comment_line = comment_line.lstrip()
1516
if comment_line and not comment_line.startswith('#'):
1517
comment_line = '#' + comment_line
1914
comment_line = self._decode_element(comment_line.lstrip())
1915
if comment_line and not comment_line.startswith(cs):
1916
comment_line = csp + comment_line
1518
1917
out.append(indent_string + comment_line)
1519
1918
this_entry = section[entry]
1520
1919
comment = self._handle_comment(section.inline_comments[entry])
1538
1937
for line in self.final_comment:
1938
line = self._decode_element(line)
1539
1939
stripped_line = line.strip()
1540
if stripped_line and not stripped_line.startswith('#'):
1940
if stripped_line and not stripped_line.startswith(cs):
1542
1942
out.append(line)
1544
if int_val != 'test':
1545
1943
self.interpolation = int_val
1547
if (return_list) or (self.filename is None):
1550
if isinstance(self.filename, StringTypes):
1945
if section is not self:
1948
if (self.filename is None) and (outfile is None):
1949
# output a list of lines
1950
# might need to encode
1951
# NOTE: This will *screw* UTF16, each line will start with the BOM
1953
out = [l.encode(self.encoding) for l in out]
1954
if (self.BOM and ((self.encoding is None) or
1955
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1959
out[0] = BOM_UTF8 + out[0]
1962
# Turn the list to a string, joined with correct newlines
1963
output = (self._a_to_u(self.newlines or os.linesep)
1966
output = output.encode(self.encoding)
1967
if (self.BOM and ((self.encoding is None) or
1968
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1970
output = BOM_UTF8 + output
1971
if outfile is not None:
1972
outfile.write(output)
1551
1974
h = open(self.filename, 'w')
1552
h.write(self.BOM or '')
1553
h.write('\n'.join(out))
1556
self.filename.seek(0)
1557
self.filename.write(self.BOM or '')
1558
self.filename.write('\n'.join(out))
1559
# if we have a stored file object (or StringIO)
1560
# we *don't* close it
1562
def validate(self, validator, section=None):
1978
def validate(self, validator, preserve_errors=False, section=None):
1564
1980
Test the ConfigObj against a configspec.
1581
1997
In addition, it converts the values from strings to their native
1582
1998
types if their checks pass (and ``stringify`` is set).
2000
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2001
of a marking a fail with a ``False``, it will preserve the actual
2002
exception object. This can contain info about the reason for failure.
2003
For example the ``VdtValueTooSmallError`` indeicates that the value
2004
supplied was too small. If a value (or section) is missing it will
2005
still be marked as ``False``.
2007
You must have the validate module to use ``preserve_errors=True``.
2009
You can then use the ``flatten_errors`` function to turn your nested
2010
results dictionary into a flattened list of failures - useful for
2011
displaying meaningful error messages.
1585
2014
... from validate import Validator
1586
2015
... except ImportError:
1587
... print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
2016
... sys.stderr.write('Cannot import the Validator object, skipping the related tests\n')
1589
2018
... config = '''
1670
2099
>>> val_res == {'key2': True, 'section': True, 'key': False}
1672
2101
>>> configspec = '''
1673
... test1='integer(30,50, default=40)'
1674
... test2='string(default="hello")'
1675
... test3='integer(default=3)'
1676
... 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)
1678
... test1='integer(30,50, default=40)'
1679
... test2='string(default="hello")'
1680
... test3='integer(default=3)'
1681
... 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)
1682
2111
... [[sub section]]
1683
... test1='integer(30,50, default=40)'
1684
... test2='string(default="hello")'
1685
... test3='integer(default=3)'
1686
... 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)
1687
2116
... '''.split('\\n')
1688
2117
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
1689
2118
>>> default_test
1990
2436
if not self.stringify:
1991
2437
if isinstance(check, (list, tuple)):
1992
2438
# preserve lists
1993
check = [str(item) for item in check]
2439
check = [self._str(item) for item in check]
1994
2440
elif missing and check is None:
1995
2441
# convert the None from a default to a ''
2444
check = self._str(check)
1999
2445
if (check != val) or missing:
2000
2446
section[entry] = check
2001
2447
if missing and entry not in section.defaults:
2002
2448
section.defaults.append(entry)
2450
# FIXME: Will this miss missing sections ?
2004
2451
for entry in section.sections:
2005
check = self.validate(validator, section[entry])
2452
if section is self and entry == 'DEFAULT':
2454
check = self.validate(validator, preserve_errors=preserve_errors,
2455
section=section[entry])
2006
2456
out[entry] = check
2007
2457
if check == False:
2008
2458
ret_true = False
2080
2530
raise self.baseErrorClass
2533
# Check / processing functions for options
2534
def flatten_errors(cfg, res, levels=None, results=None):
2536
An example function that will turn a nested dictionary of results
2537
(as returned by ``ConfigObj.validate``) into a flat list.
2539
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2540
dictionary returned by ``validate``.
2542
(This is a recursive function, so you shouldn't use the ``levels`` or
2543
``results`` arguments - they are used by the function.
2545
Returns a list of keys that failed. Each member of the list is a tuple :
2548
([list of sections...], key, result)
2550
If ``validate`` was called with ``preserve_errors=False`` (the default)
2551
then ``result`` will always be ``False``.
2553
*list of sections* is a flattened list of sections that the key was found
2556
If the section was missing then key will be ``None``.
2558
If the value (or section) was missing then ``result`` will be ``False``.
2560
If ``validate`` was called with ``preserve_errors=True`` and a value
2561
was present, but failed the check, then ``result`` will be the exception
2562
object returned. You can use this as a string that describes the failure.
2564
For example *The value "3" is of the wrong type*.
2566
# FIXME: is the ordering of the output arbitrary ?
2568
>>> vtor = validate.Validator()
2574
... another_option = Probably
2576
... another_option = True
2583
... option1 = boolean()
2584
... option2 = boolean()
2585
... option3 = boolean(default=Bad_value)
2587
... option1 = boolean()
2588
... option2 = boolean()
2589
... option3 = boolean(default=Bad_value)
2591
... another_option = boolean()
2593
... another_option = boolean()
2596
... value2 = integer
2597
... value3 = integer(0, 10)
2598
... [[[section3b-sub]]]
2601
... another_option = boolean()
2603
>>> cs = my_cfg.split('\\n')
2604
>>> ini = my_ini.split('\\n')
2605
>>> cfg = ConfigObj(ini, configspec=cs)
2606
>>> res = cfg.validate(vtor, preserve_errors=True)
2608
>>> for entry in flatten_errors(cfg, res):
2609
... section_list, key, error = entry
2610
... section_list.insert(0, '[root]')
2611
... if key is not None:
2612
... section_list.append(key)
2614
... section_list.append('[missing]')
2615
... section_string = ', '.join(section_list)
2616
... errors.append((section_string, ' = ', error))
2618
>>> for entry in errors:
2619
... print entry[0], entry[1], (entry[2] or 0)
2621
[root], option3 = the value "Bad_value" is of the wrong type.
2622
[root], section1, option2 = 0
2623
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2624
[root], section2, another_option = the value "Probably" is of the wrong type.
2625
[root], section3, section3b, section3b-sub, [missing] = 0
2626
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2627
[root], section3, section3b, value3 = the value "11" is too big.
2628
[root], section4, [missing] = 0
2637
results.append((levels[:], None, False))
2641
for (key, val) in res.items():
2644
if isinstance(cfg.get(key), dict):
2647
flatten_errors(cfg[key], val, levels, results)
2649
results.append((levels[:], key, val))
2083
2658
# FIXME: test error code for badly built multiline values
2084
2659
# FIXME: test handling of StringIO
2085
2660
# FIXME: test interpolation with writing
2146
2721
>>> t2.inline_comments['b'] = ''
2147
2722
>>> del t2['a']
2148
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'
2151
2867
if __name__ == '__main__':
2238
With list values off, ConfigObj can incorrectly unquote values. (This makes
2239
it impossible to use listquote to handle your list values for you - for
2240
nested lists. Not handling quotes at all would be better for this)
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).
2245
2972
A method to optionally remove uniform indentation from multiline values.
2246
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
2248
2983
INCOMPATIBLE CHANGES
2249
2984
====================
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``.
2381
3202
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
2382
3203
``interpolation``).