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)
730
602
section[newkey] = newval
731
603
self.walk(encode, call_on_sections=True)
733
def istrue(self, key):
734
"""A deprecated version of ``as_bool``."""
735
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
736
'instead.', DeprecationWarning)
737
return self.as_bool(key)
739
def as_bool(self, key):
741
Accepts a key as input. The corresponding value must be a string or
742
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
743
retain compatibility with Python 2.2.
745
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
748
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
751
``as_bool`` is not case sensitive.
753
Any other input will raise a ``ValueError``.
758
Traceback (most recent call last):
759
ValueError: Value "fish" is neither True nor False
774
if not isinstance(val, StringTypes):
777
return self.main._bools[val.lower()]
779
raise ValueError('Value "%s" is neither True nor False' % val)
781
def as_int(self, key):
783
A convenience method which coerces the specified value to an integer.
785
If the value is an invalid literal for ``int``, a ``ValueError`` will
791
Traceback (most recent call last):
792
ValueError: invalid literal for int(): fish
798
Traceback (most recent call last):
799
ValueError: invalid literal for int(): 3.2
801
return int(self[key])
803
def as_float(self, key):
805
A convenience method which coerces the specified value to a float.
807
If the value is an invalid literal for ``float``, a ``ValueError`` will
813
Traceback (most recent call last):
814
ValueError: invalid literal for float(): fish
822
return float(self[key])
825
605
class ConfigObj(Section):
827
607
An object to read, create, and write config files.
984
754
self.file_error = defaults['file_error']
985
755
self.stringify = defaults['stringify']
986
756
self.indent_type = defaults['indent_type']
987
self.encoding = defaults['encoding']
988
self.default_encoding = defaults['default_encoding']
757
# used by the write method
992
760
self.initial_comment = []
993
761
self.final_comment = []
995
763
if isinstance(infile, StringTypes):
996
self.filename = infile
997
if os.path.isfile(infile):
998
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()
999
767
elif self.file_error:
1000
768
# raise an error if the file doesn't exist
1001
769
raise IOError, 'Config file not found: "%s".' % self.filename
1018
786
infile = infile.dict()
1019
787
for entry in infile:
1020
788
self[entry] = infile[entry]
1021
790
del self._errors
1022
791
if defaults['configspec'] is not None:
1023
792
self._handle_configspec(defaults['configspec'])
1025
794
self.configspec = None
1027
elif getattr(infile, 'read', None) is not None:
1028
# This supports file like objects
1029
infile = infile.read() or []
1030
# needs splitting into lines - but needs doing *after* decoding
1031
# in case it's not an 8 bit encoding
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)
1033
803
raise TypeError, ('infile must be a filename,'
1034
' file like object, or list of lines.')
1037
# don't do it for the empty ConfigObj
1038
infile = self._handle_bom(infile)
1039
# infile is now *always* a list
1041
# Set the newlines attribute (first line ending it finds)
1042
# and strip trailing '\n' or '\r' from lines
1044
if (not line) or (line[-1] not in '\r\n'):
1046
for end in ('\r\n', '\n', '\r'):
1047
if line.endswith(end):
1051
infile = [line.rstrip('\r\n') for line in infile]
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:]
1053
817
self._parse(infile)
1054
818
# if we had any errors, now is the time to raise them
1069
833
self._handle_configspec(defaults['configspec'])
1071
def _handle_bom(self, infile):
1073
Handle any BOM, and decode if necessary.
1075
If an encoding is specified, that *must* be used - but the BOM should
1076
still be removed (and the BOM attribute set).
1078
(If the encoding is wrongly specified, then a BOM for an alternative
1079
encoding won't be discovered or removed.)
1081
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1082
removed. The BOM attribute will be set. UTF16 will be decoded to
1085
NOTE: This method must not be called with an empty ``infile``.
1087
Specifying the *wrong* encoding is likely to cause a
1088
``UnicodeDecodeError``.
1090
``infile`` must always be returned as a list of lines, but may be
1091
passed in as a single string.
1093
if ((self.encoding is not None) and
1094
(self.encoding.lower() not in BOM_LIST)):
1095
# No need to check for a BOM
1096
# encoding specified doesn't have one
1098
return self._decode(infile, self.encoding)
1100
if isinstance(infile, (list, tuple)):
1104
if self.encoding is not None:
1105
# encoding explicitly supplied
1106
# And it could have an associated BOM
1107
# TODO: if encoding is just UTF16 - we ought to check for both
1108
# TODO: big endian and little endian versions.
1109
enc = BOM_LIST[self.encoding.lower()]
1111
# For UTF16 we try big endian and little endian
1112
for BOM, (encoding, final_encoding) in BOMS.items():
1113
if not final_encoding:
1116
if infile.startswith(BOM):
1119
# Don't need to remove BOM
1120
return self._decode(infile, encoding)
1122
# If we get this far, will *probably* raise a DecodeError
1123
# As it doesn't appear to start with a BOM
1124
return self._decode(infile, self.encoding)
1128
if not line.startswith(BOM):
1129
return self._decode(infile, self.encoding)
1131
newline = line[len(BOM):]
1134
if isinstance(infile, (list, tuple)):
1139
return self._decode(infile, self.encoding)
1141
# No encoding specified - so we need to check for UTF8/UTF16
1142
for BOM, (encoding, final_encoding) in BOMS.items():
1143
if not line.startswith(BOM):
1147
self.encoding = final_encoding
1148
if not final_encoding:
1152
newline = line[len(BOM):]
1153
if isinstance(infile, (list, tuple)):
1157
# UTF8 - don't decode
1158
if isinstance(infile, StringTypes):
1159
return infile.splitlines(True)
1162
# UTF16 - have to decode
1163
return self._decode(infile, encoding)
1165
# No BOM discovered and no encoding specified, just return
1166
if isinstance(infile, StringTypes):
1167
# infile read from a file will be a single string
1168
return infile.splitlines(True)
1172
def _a_to_u(self, string):
1173
"""Decode ascii strings to unicode if a self.encoding is specified."""
1174
if not self.encoding:
1177
return string.decode('ascii')
1179
def _decode(self, infile, encoding):
1181
Decode infile to unicode. Using the specified encoding.
1183
if is a string, it also needs converting to a list.
1185
if isinstance(infile, StringTypes):
1187
# NOTE: Could raise a ``UnicodeDecodeError``
1188
return infile.decode(encoding).splitlines(True)
1189
for i, line in enumerate(infile):
1190
if not isinstance(line, unicode):
1191
# NOTE: The isinstance test here handles mixed lists of unicode/string
1192
# NOTE: But the decode will break on any non-string values
1193
# NOTE: Or could raise a ``UnicodeDecodeError``
1194
infile[i] = line.decode(encoding)
1197
def _decode_element(self, line):
1198
"""Decode element to unicode if necessary."""
1199
if not self.encoding:
1201
if isinstance(line, str) and self.default_encoding:
1202
return line.decode(self.default_encoding)
1205
def _str(self, value):
1207
Used by ``stringify`` within validate, to turn non-string values
1210
if not isinstance(value, StringTypes):
1215
835
def _parse(self, infile):
1217
837
Actually parse the config file
1777
1387
section.configspec = scalars
1778
1388
for entry in sections:
1779
if entry not in section:
1389
if not section.has_key(entry):
1780
1390
section[entry] = {}
1781
1391
self._handle_repeat(section[entry], sections[entry])
1783
1393
def _write_line(self, indent_string, entry, this_entry, comment):
1784
1394
"""Write an individual line, for the write method"""
1785
# NOTE: the calls to self._quote here handles non-StringType values.
1786
return '%s%s%s%s%s' % (
1395
return '%s%s = %s%s' % (
1788
self._decode_element(self._quote(entry, multiline=False)),
1789
self._a_to_u(' = '),
1790
self._decode_element(self._quote(this_entry)),
1791
self._decode_element(comment))
1397
self._quote(entry, multiline=False),
1398
self._quote(this_entry),
1793
1401
def _write_marker(self, indent_string, depth, entry, comment):
1794
1402
"""Write a section marker line"""
1795
1403
return '%s%s%s%s%s' % (
1797
self._a_to_u('[' * depth),
1798
self._quote(self._decode_element(entry), multiline=False),
1799
self._a_to_u(']' * depth),
1800
self._decode_element(comment))
1406
self._quote(entry, multiline=False),
1802
1410
def _handle_comment(self, comment):
1881
1489
['a = %(a)s', '[DEFAULT]', 'a = fish']
1883
1492
if self.indent_type is None:
1884
1493
# this can be true if initialised from a dictionary
1885
1494
self.indent_type = DEFAULT_INDENT_TYPE
1888
cs = self._a_to_u('#')
1889
csp = self._a_to_u('# ')
1890
1498
if section is None:
1891
1499
int_val = self.interpolation
1892
1500
self.interpolation = False
1894
1503
for line in self.initial_comment:
1895
line = self._decode_element(line)
1896
1504
stripped_line = line.strip()
1897
if stripped_line and not stripped_line.startswith(cs):
1505
if stripped_line and not stripped_line.startswith('#'):
1899
1507
out.append(line)
1901
indent_string = self._a_to_u(
1902
self._compute_indent_string(section.depth))
1509
indent_string = self._compute_indent_string(section.depth)
1903
1510
for entry in (section.scalars + section.sections):
1904
1511
if entry in section.defaults:
1905
1512
# don't write out default values
1907
1514
for comment_line in section.comments[entry]:
1908
comment_line = self._decode_element(comment_line.lstrip())
1909
if comment_line and not comment_line.startswith(cs):
1910
comment_line = csp + comment_line
1515
comment_line = comment_line.lstrip()
1516
if comment_line and not comment_line.startswith('#'):
1517
comment_line = '#' + comment_line
1911
1518
out.append(indent_string + comment_line)
1912
1519
this_entry = section[entry]
1913
1520
comment = self._handle_comment(section.inline_comments[entry])
1931
1538
for line in self.final_comment:
1932
line = self._decode_element(line)
1933
1539
stripped_line = line.strip()
1934
if stripped_line and not stripped_line.startswith(cs):
1540
if stripped_line and not stripped_line.startswith('#'):
1936
1542
out.append(line)
1544
if int_val != 'test':
1937
1545
self.interpolation = int_val
1939
if section is not self:
1942
if (self.filename is None) and (outfile is None):
1943
# output a list of lines
1944
# might need to encode
1945
# NOTE: This will *screw* UTF16, each line will start with the BOM
1947
out = [l.encode(self.encoding) for l in out]
1948
if (self.BOM and ((self.encoding is None) or
1949
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1953
out[0] = BOM_UTF8 + out[0]
1956
# Turn the list to a string, joined with correct newlines
1957
output = (self._a_to_u(self.newlines or os.linesep)
1960
output = output.encode(self.encoding)
1961
if (self.BOM and ((self.encoding is None) or
1962
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1964
output = BOM_UTF8 + output
1965
if outfile is not None:
1966
outfile.write(output)
1547
if (return_list) or (self.filename is None):
1550
if isinstance(self.filename, StringTypes):
1968
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
1972
def validate(self, validator, preserve_errors=False, section=None):
1562
def validate(self, validator, section=None):
1974
1564
Test the ConfigObj against a configspec.
1991
1581
In addition, it converts the values from strings to their native
1992
1582
types if their checks pass (and ``stringify`` is set).
1994
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
1995
of a marking a fail with a ``False``, it will preserve the actual
1996
exception object. This can contain info about the reason for failure.
1997
For example the ``VdtValueTooSmallError`` indeicates that the value
1998
supplied was too small. If a value (or section) is missing it will
1999
still be marked as ``False``.
2001
You must have the validate module to use ``preserve_errors=True``.
2003
You can then use the ``flatten_errors`` function to turn your nested
2004
results dictionary into a flattened list of failures - useful for
2005
displaying meaningful error messages.
2008
1585
... from validate import Validator
2009
1586
... except ImportError:
2010
... print >> sys.stderr, 'Cannot import the Validator object, skipping the related tests'
1587
... print >> sys.stderr, 'Cannot import the Validator object, skipping the realted tests'
2012
1589
... config = '''
2093
1670
>>> val_res == {'key2': True, 'section': True, 'key': False}
2095
1672
>>> configspec = '''
2096
... test1=integer(30,50, default=40)
2097
... test2=string(default="hello")
2098
... test3=integer(default=3)
2099
... test4=float(6.0, default=6.0)
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)'
2101
... test1=integer(30,50, default=40)
2102
... test2=string(default="hello")
2103
... test3=integer(default=3)
2104
... test4=float(6.0, default=6.0)
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)'
2105
1682
... [[sub section]]
2106
... test1=integer(30,50, default=40)
2107
... test2=string(default="hello")
2108
... test3=integer(default=3)
2109
... test4=float(6.0, default=6.0)
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)'
2110
1687
... '''.split('\\n')
2111
1688
>>> default_test = ConfigObj(['test1=30'], configspec=configspec)
2112
1689
>>> default_test
2430
1990
if not self.stringify:
2431
1991
if isinstance(check, (list, tuple)):
2432
1992
# preserve lists
2433
check = [self._str(item) for item in check]
1993
check = [str(item) for item in check]
2434
1994
elif missing and check is None:
2435
1995
# convert the None from a default to a ''
2438
check = self._str(check)
2439
1999
if (check != val) or missing:
2440
2000
section[entry] = check
2441
2001
if missing and entry not in section.defaults:
2442
2002
section.defaults.append(entry)
2444
# FIXME: Will this miss missing sections ?
2445
2004
for entry in section.sections:
2446
if section is self and entry == 'DEFAULT':
2448
check = self.validate(validator, preserve_errors=preserve_errors,
2449
section=section[entry])
2005
check = self.validate(validator, section[entry])
2450
2006
out[entry] = check
2451
2007
if check == False:
2452
2008
ret_true = False
2524
2080
raise self.baseErrorClass
2527
# Check / processing functions for options
2528
def flatten_errors(cfg, res, levels=None, results=None):
2530
An example function that will turn a nested dictionary of results
2531
(as returned by ``ConfigObj.validate``) into a flat list.
2533
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2534
dictionary returned by ``validate``.
2536
(This is a recursive function, so you shouldn't use the ``levels`` or
2537
``results`` arguments - they are used by the function.
2539
Returns a list of keys that failed. Each member of the list is a tuple :
2542
([list of sections...], key, result)
2544
If ``validate`` was called with ``preserve_errors=False`` (the default)
2545
then ``result`` will always be ``False``.
2547
*list of sections* is a flattened list of sections that the key was found
2550
If the section was missing then key will be ``None``.
2552
If the value (or section) was missing then ``result`` will be ``False``.
2554
If ``validate`` was called with ``preserve_errors=True`` and a value
2555
was present, but failed the check, then ``result`` will be the exception
2556
object returned. You can use this as a string that describes the failure.
2558
For example *The value "3" is of the wrong type*.
2560
# FIXME: is the ordering of the output arbitrary ?
2562
>>> vtor = validate.Validator()
2568
... another_option = Probably
2570
... another_option = True
2577
... option1 = boolean()
2578
... option2 = boolean()
2579
... option3 = boolean(default=Bad_value)
2581
... option1 = boolean()
2582
... option2 = boolean()
2583
... option3 = boolean(default=Bad_value)
2585
... another_option = boolean()
2587
... another_option = boolean()
2590
... value2 = integer
2591
... value3 = integer(0, 10)
2592
... [[[section3b-sub]]]
2595
... another_option = boolean()
2597
>>> cs = my_cfg.split('\\n')
2598
>>> ini = my_ini.split('\\n')
2599
>>> cfg = ConfigObj(ini, configspec=cs)
2600
>>> res = cfg.validate(vtor, preserve_errors=True)
2602
>>> for entry in flatten_errors(cfg, res):
2603
... section_list, key, error = entry
2604
... section_list.insert(0, '[root]')
2605
... if key is not None:
2606
... section_list.append(key)
2608
... section_list.append('[missing]')
2609
... section_string = ', '.join(section_list)
2610
... errors.append((section_string, ' = ', error))
2612
>>> for entry in errors:
2613
... print entry[0], entry[1], (entry[2] or 0)
2615
[root], option3 = the value "Bad_value" is of the wrong type.
2616
[root], section1, option2 = 0
2617
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2618
[root], section2, another_option = the value "Probably" is of the wrong type.
2619
[root], section3, section3b, section3b-sub, [missing] = 0
2620
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2621
[root], section3, section3b, value3 = the value "11" is too big.
2622
[root], section4, [missing] = 0
2631
results.append((levels[:], None, False))
2635
for (key, val) in res.items():
2638
if isinstance(cfg.get(key), dict):
2641
flatten_errors(cfg[key], val, levels, results)
2643
results.append((levels[:], key, val))
2652
2083
# FIXME: test error code for badly built multiline values
2653
2084
# FIXME: test handling of StringIO
2654
2085
# FIXME: test interpolation with writing
2715
2146
>>> t2.inline_comments['b'] = ''
2716
2147
>>> del t2['a']
2717
2148
>>> assert t2.write() == ['','b = b', '']
2719
# Test ``list_values=False`` stuff
2721
... key1 = no quotes
2722
... key2 = 'single quotes'
2723
... key3 = "double quotes"
2724
... key4 = "list", 'with', several, "quotes"
2726
>>> cfg = ConfigObj(c.splitlines(), list_values=False)
2727
>>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
2728
... 'key3': '"double quotes"',
2729
... 'key4': '"list", \\'with\\', several, "quotes"'
2732
>>> cfg = ConfigObj(list_values=False)
2733
>>> cfg['key1'] = 'Multiline\\nValue'
2734
>>> cfg['key2'] = '''"Value" with 'quotes' !'''
2736
["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
2737
>>> cfg.list_values = True
2738
>>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
2739
... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
2742
Test flatten_errors:
2744
>>> from validate import Validator, VdtValueTooSmallError
2760
... '''.split('\\n')
2761
>>> configspec = '''
2762
... test1= integer(30,50)
2765
... test4=float(6.0)
2767
... test1=integer(30,50)
2770
... test4=float(6.0)
2772
... test1=integer(30,50)
2775
... test4=float(6.0)
2776
... '''.split('\\n')
2777
>>> val = Validator()
2778
>>> c1 = ConfigObj(config, configspec=configspec)
2779
>>> res = c1.validate(val)
2780
>>> flatten_errors(c1, res) == [([], 'test4', False), (['section',
2781
... 'sub section'], 'test4', False), (['section'], 'test4', False)]
2783
>>> res = c1.validate(val, preserve_errors=True)
2784
>>> check = flatten_errors(c1, res)
2788
(['section', 'sub section'], 'test4')
2790
(['section'], 'test4')
2791
>>> for entry in check:
2792
... isinstance(entry[2], VdtValueTooSmallError)
2793
... print str(entry[2])
2795
the value "5.0" is too small.
2797
the value "5.0" is too small.
2799
the value "5.0" is too small.
2801
Test unicode handling, BOM, write witha file like object and line endings :
2803
... # initial comment
2804
... # inital comment 2
2806
... test1 = some value
2808
... test2 = another value # inline comment
2809
... # section comment
2810
... [section] # inline comment
2811
... test = test # another inline comment
2815
... # final comment2
2817
>>> u = u_base.encode('utf_8').splitlines(True)
2818
>>> u[0] = BOM_UTF8 + u[0]
2819
>>> uc = ConfigObj(u)
2820
>>> uc.encoding = None
2823
>>> uc == {'test1': 'some value', 'test2': 'another value',
2824
... 'section': {'test': 'test', 'test2': 'test2'}}
2826
>>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
2829
>>> isinstance(uc['test1'], unicode)
2835
>>> uc['latin1'] = "This costs lot's of "
2836
>>> a_list = uc.write()
2839
>>> isinstance(a_list[0], str)
2841
>>> a_list[0].startswith(BOM_UTF8)
2843
>>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
2844
>>> uc = ConfigObj(u)
2847
>>> uc.newlines = '\\r'
2848
>>> from cStringIO import StringIO
2849
>>> file_like = StringIO()
2850
>>> uc.write(file_like)
2851
>>> file_like.seek(0)
2852
>>> uc2 = ConfigObj(file_like)
2855
>>> uc2.filename is None
2857
>>> uc2.newlines == '\\r'
2861
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)
2953
Better support for configuration from multiple files, including tracking
2954
*where* the original file came from and writing changes to the correct
2958
Make ``newline`` an option (as well as an attribute) ?
2960
``UTF16`` encoded files, when returned as a list of lines, will have the
2961
BOM at the start of every line. Should this be removed from all but the
2964
Option to set warning type for unicode decode ? (Defaults to strict).
2966
2245
A method to optionally remove uniform indentation from multiline values.
2967
2246
(do as an example of using ``walk`` - along with string-escape)
2969
Should the results dictionary from validate be an ordered dictionary if
2970
`odict <http://www.voidspace.org.uk/python/odict.html>`_ is available ?
2972
Implement a better ``__repr__`` ? (``ConfigObj({})``)
2974
Implement some of the sequence methods (which include slicing) from the
2977
2248
INCOMPATIBLE CHANGES
2978
2249
====================
3110
Removed ``BOM_UTF8`` from ``__all__``.
3112
The ``BOM`` attribute has become a boolean. (Defaults to ``False``.) It is
3113
*only* ``True`` for the ``UTF16`` encoding.
3115
File like objects no longer need a ``seek`` attribute.
3117
ConfigObj no longer keeps a reference to file like objects. Instead the
3118
``write`` method takes a file like object as an optional argument. (Which
3119
will be used in preference of the ``filename`` attribute if htat exists as
3122
Full unicode support added. New options/attributes ``encoding``,
3123
``default_encoding``.
3125
utf16 files decoded to unicode.
3127
If ``BOM`` is ``True``, but no encoding specified, then the utf8 BOM is
3128
written out at the start of the file. (It will normally only be ``True`` if
3129
the utf8 BOM was found when the file was read.)
3131
File paths are *not* converted to absolute paths, relative paths will
3132
remain relative as the ``filename`` attribute.
3134
Fixed bug where ``final_comment`` wasn't returned if ``write`` is returning
3140
Added ``True``, ``False``, and ``enumerate`` if they are not defined.
3141
(``True`` and ``False`` are needed for *early* versions of Python 2.2,
3142
``enumerate`` is needed for all versions ofPython 2.2)
3144
Deprecated ``istrue``, replaced it with ``as_bool``.
3146
Added ``as_int`` and ``as_float``.
3148
utf8 and utf16 BOM handled in an endian agnostic way.
3153
Validation no longer done on the 'DEFAULT' section (only in the root
3154
level). This allows interpolation in configspecs.
3156
Change in validation syntax implemented in validate 0.2.1
3163
Added ``merge``, a recursive update.
3165
Added ``preserve_errors`` to ``validate`` and the ``flatten_errors``
3168
Thanks to Matthew Brett for suggestions and helping me iron out bugs.
3170
Fixed bug where a config file is *all* comment, the comment will now be
3171
``initial_comment`` rather than ``final_comment``.
3176
Fixed bug in ``create_empty``. Thanks to Paul Jimenez for the report.
3181
Fixed bug in ``Section.walk`` when transforming names as well as values.
3183
Added the ``istrue`` method. (Fetches the boolean equivalent of a string
3186
Fixed ``list_values=False`` - they are now only quoted/unquoted if they
3187
are multiline values.
3189
List values are written as ``item, item`` rather than ``item,item``.
3196
2381
Fixed typo in ``write`` method. (Testing for the wrong value when resetting
3197
2382
``interpolation``).