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