2
# A config file reader/writer that supports nested sections in config files.
3
# Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
4
# E-mail: fuzzyman AT voidspace DOT org DOT uk
5
# nico AT tekNico DOT net
8
# http://www.voidspace.org.uk/python/configobj.html
10
# Released subject to the BSD License
11
# Please see http://www.voidspace.org.uk/python/license.shtml
13
# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14
# For information about bugfixes, updates and support, please join the
15
# ConfigObj mailing list:
16
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
17
# Comments, suggestions and bug reports welcome.
19
from __future__ import generators
22
INTP_VER = sys.version_info[:2]
24
raise RuntimeError("Python v.2.2 or later needed")
28
# Bzr modification: Disabled import of 'compiler' module
29
# bzr doesn't use the 'unrepr' feature of configobj, so importing compiler just
30
# wastes several milliseconds on every single bzr invocation.
31
# -- Andrew Bennetts, 2008-10-14
37
from types import StringTypes
38
from warnings import warn
40
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
42
# Python 2.2 does not have these
44
BOM_UTF8 = '\xef\xbb\xbf'
45
# UTF-16, little endian
46
BOM_UTF16_LE = '\xff\xfe'
48
BOM_UTF16_BE = '\xfe\xff'
49
if sys.byteorder == 'little':
50
# UTF-16, native endianness
51
BOM_UTF16 = BOM_UTF16_LE
53
# UTF-16, native endianness
54
BOM_UTF16 = BOM_UTF16_BE
56
# A dictionary mapping BOM to
57
# the encoding to decode with, and what to set the
58
# encoding attribute to.
60
BOM_UTF8: ('utf_8', None),
61
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
62
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
63
BOM_UTF16: ('utf_16', 'utf_16'),
65
# All legal variants of the BOM codecs.
66
# TODO: the list of aliases is not meant to be exhaustive, is there a
73
'utf16_be': 'utf16_be',
74
'utf_16_be': 'utf16_be',
75
'utf-16be': 'utf16_be',
76
'utf16_le': 'utf16_le',
77
'utf_16_le': 'utf16_le',
78
'utf-16le': 'utf16_le',
86
# Map of encodings to the BOM to write.
90
'utf16_be': BOM_UTF16_BE,
91
'utf16_le': BOM_UTF16_LE,
96
def match_utf8(encoding):
97
return BOM_LIST.get(encoding.lower()) == 'utf_8'
100
# Quote strings used for writing values
104
wspace_plus = ' \r\t\n\v\t\'"'
112
"""enumerate for Python 2.2."""
124
__version__ = '4.5.2'
126
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
128
__docformat__ = "restructuredtext en"
132
'DEFAULT_INDENT_TYPE',
133
'DEFAULT_INTERPOLATION',
141
'InterpolationError',
142
'InterpolationLoopError',
143
'MissingInterpolationOption',
144
'RepeatSectionError',
152
DEFAULT_INTERPOLATION = 'configparser'
153
DEFAULT_INDENT_TYPE = ' '
154
MAX_INTERPOL_DEPTH = 10
157
'interpolation': True,
158
'raise_errors': False,
160
'create_empty': False,
164
# option may be set to one of ('', ' ', '\t')
167
'default_encoding': None,
169
'write_empty_values': False,
177
raise ImportError('compiler module not available')
178
p = compiler.parse(s)
179
return p.getChildren()[1].getChildren()[0].getChildren()[1]
182
class UnknownType(Exception):
186
class Builder(object):
189
m = getattr(self, 'build_' + o.__class__.__name__, None)
191
raise UnknownType(o.__class__.__name__)
194
def build_List(self, o):
195
return map(self.build, o.getChildren())
197
def build_Const(self, o):
200
def build_Dict(self, o):
202
i = iter(map(self.build, o.getChildren()))
207
def build_Tuple(self, o):
208
return tuple(self.build_List(o))
210
def build_Name(self, o):
215
if o.name == 'False':
219
raise UnknownType('Undefined Name')
221
def build_Add(self, o):
222
real, imag = map(self.build_Const, o.getChildren())
226
raise UnknownType('Add')
227
if not isinstance(imag, complex) or imag.real != 0.0:
228
raise UnknownType('Add')
231
def build_Getattr(self, o):
232
parent = self.build(o.expr)
233
return getattr(parent, o.attrname)
235
def build_UnarySub(self, o):
236
return -self.build_Const(o.getChildren()[0])
238
def build_UnaryAdd(self, o):
239
return self.build_Const(o.getChildren()[0])
248
return _builder.build(getObj(s))
252
class ConfigObjError(SyntaxError):
254
This is the base class for all errors that ConfigObj raises.
255
It is a subclass of SyntaxError.
257
def __init__(self, msg='', line_number=None, line=''):
259
self.line_number = line_number
261
SyntaxError.__init__(self, msg)
264
class NestingError(ConfigObjError):
266
This error indicates a level of nesting that doesn't match.
270
class ParseError(ConfigObjError):
272
This error indicates that a line is badly written.
273
It is neither a valid ``key = value`` line,
274
nor a valid section marker line.
278
class ReloadError(IOError):
280
A 'reload' operation failed.
281
This exception is a subclass of ``IOError``.
284
IOError.__init__(self, 'reload failed, filename is not set.')
287
class DuplicateError(ConfigObjError):
289
The keyword or section specified already exists.
293
class ConfigspecError(ConfigObjError):
295
An error occured whilst parsing a configspec.
299
class InterpolationError(ConfigObjError):
300
"""Base class for the two interpolation errors."""
303
class InterpolationLoopError(InterpolationError):
304
"""Maximum interpolation depth exceeded in string interpolation."""
306
def __init__(self, option):
307
InterpolationError.__init__(
309
'interpolation loop detected in value "%s".' % option)
312
class RepeatSectionError(ConfigObjError):
314
This error indicates additional sections in a section with a
315
``__many__`` (repeated) section.
319
class MissingInterpolationOption(InterpolationError):
320
"""A value specified for interpolation was missing."""
322
def __init__(self, option):
323
InterpolationError.__init__(
325
'missing option "%s" in interpolation.' % option)
328
class UnreprError(ConfigObjError):
329
"""An error parsing in unrepr mode."""
333
class InterpolationEngine(object):
335
A helper class to help perform string interpolation.
337
This class is an abstract base class; its descendants perform
341
# compiled regexp to use in self.interpolate()
342
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
344
def __init__(self, section):
345
# the Section instance that "owns" this engine
346
self.section = section
349
def interpolate(self, key, value):
350
def recursive_interpolate(key, value, section, backtrail):
351
"""The function that does the actual work.
353
``value``: the string we're trying to interpolate.
354
``section``: the section in which that string was found
355
``backtrail``: a dict to keep track of where we've been,
356
to detect and prevent infinite recursion loops
358
This is similar to a depth-first-search algorithm.
360
# Have we been here already?
361
if backtrail.has_key((key, section.name)):
362
# Yes - infinite loop detected
363
raise InterpolationLoopError(key)
364
# Place a marker on our backtrail so we won't come back here again
365
backtrail[(key, section.name)] = 1
367
# Now start the actual work
368
match = self._KEYCRE.search(value)
370
# The actual parsing of the match is implementation-dependent,
371
# so delegate to our helper function
372
k, v, s = self._parse_match(match)
374
# That's the signal that no further interpolation is needed
377
# Further interpolation may be needed to obtain final value
378
replacement = recursive_interpolate(k, v, s, backtrail)
379
# Replace the matched string with its final value
380
start, end = match.span()
381
value = ''.join((value[:start], replacement, value[end:]))
382
new_search_start = start + len(replacement)
383
# Pick up the next interpolation key, if any, for next time
384
# through the while loop
385
match = self._KEYCRE.search(value, new_search_start)
387
# Now safe to come back here again; remove marker from backtrail
388
del backtrail[(key, section.name)]
392
# Back in interpolate(), all we have to do is kick off the recursive
393
# function with appropriate starting values
394
value = recursive_interpolate(key, value, self.section, {})
398
def _fetch(self, key):
399
"""Helper function to fetch values from owning section.
401
Returns a 2-tuple: the value, and the section where it was found.
403
# switch off interpolation before we try and fetch anything !
404
save_interp = self.section.main.interpolation
405
self.section.main.interpolation = False
407
# Start at section that "owns" this InterpolationEngine
408
current_section = self.section
410
# try the current section first
411
val = current_section.get(key)
415
val = current_section.get('DEFAULT', {}).get(key)
418
# move up to parent and try again
419
# top-level's parent is itself
420
if current_section.parent is current_section:
421
# reached top level, time to give up
423
current_section = current_section.parent
425
# restore interpolation to previous value before returning
426
self.section.main.interpolation = save_interp
428
raise MissingInterpolationOption(key)
429
return val, current_section
432
def _parse_match(self, match):
433
"""Implementation-dependent helper function.
435
Will be passed a match object corresponding to the interpolation
436
key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
437
key in the appropriate config file section (using the ``_fetch()``
438
helper function) and return a 3-tuple: (key, value, section)
440
``key`` is the name of the key we're looking for
441
``value`` is the value found for that key
442
``section`` is a reference to the section where it was found
444
``key`` and ``section`` should be None if no further
445
interpolation should be performed on the resulting value
446
(e.g., if we interpolated "$$" and returned "$").
448
raise NotImplementedError()
452
class ConfigParserInterpolation(InterpolationEngine):
453
"""Behaves like ConfigParser."""
454
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
456
def _parse_match(self, match):
458
value, section = self._fetch(key)
459
return key, value, section
463
class TemplateInterpolation(InterpolationEngine):
464
"""Behaves like string.Template."""
466
_KEYCRE = re.compile(r"""
468
(?P<escaped>\$) | # Two $ signs
469
(?P<named>[_a-z][_a-z0-9]*) | # $name format
470
{(?P<braced>[^}]*)} # ${name} format
472
""", re.IGNORECASE | re.VERBOSE)
474
def _parse_match(self, match):
475
# Valid name (in or out of braces): fetch value from section
476
key = match.group('named') or match.group('braced')
478
value, section = self._fetch(key)
479
return key, value, section
480
# Escaped delimiter (e.g., $$): return single delimiter
481
if match.group('escaped') is not None:
482
# Return None for key and section to indicate it's time to stop
483
return None, self._delimiter, None
484
# Anything else: ignore completely, just return it unchanged
485
return None, match.group(), None
488
interpolation_engines = {
489
'configparser': ConfigParserInterpolation,
490
'template': TemplateInterpolation,
497
A dictionary-like object that represents a section in a config file.
499
It does string interpolation if the 'interpolation' attribute
500
of the 'main' object is set to True.
502
Interpolation is tried first from this object, then from the 'DEFAULT'
503
section of this object, next from the parent and its 'DEFAULT' section,
504
and so on until the main object is reached.
506
A Section will behave like an ordered dictionary - following the
507
order of the ``scalars`` and ``sections`` attributes.
508
You can use this to change the order of members.
510
Iteration follows the order: scalars, then sections.
513
def __init__(self, parent, depth, main, indict=None, name=None):
515
* parent is the section above
516
* depth is the depth level of this section
517
* main is the main ConfigObj
518
* indict is a dictionary to initialise the section with
523
# used for nesting level *and* interpolation
525
# used for the interpolation attribute
527
# level of nesting depth of this Section
529
# purely for information
533
# we do this explicitly so that __setitem__ is used properly
534
# (rather than just passing to ``dict.__init__``)
535
for entry, value in indict.iteritems():
539
def _initialise(self):
540
# the sequence of scalar values in this Section
542
# the sequence of sections in this Section
546
self.inline_comments = {}
550
self._configspec_comments = {}
551
self._configspec_inline_comments = {}
552
self._cs_section_comments = {}
553
self._cs_section_inline_comments = {}
556
self.default_values = {}
559
def _interpolate(self, key, value):
561
# do we already have an interpolation engine?
562
engine = self._interpolation_engine
563
except AttributeError:
564
# not yet: first time running _interpolate(), so pick the engine
565
name = self.main.interpolation
566
if name == True: # note that "if name:" would be incorrect here
567
# backwards-compatibility: interpolation=True means use default
568
name = DEFAULT_INTERPOLATION
569
name = name.lower() # so that "Template", "template", etc. all work
570
class_ = interpolation_engines.get(name, None)
572
# invalid value for self.main.interpolation
573
self.main.interpolation = False
576
# save reference to engine so we don't have to do this again
577
engine = self._interpolation_engine = class_(self)
578
# let the engine do the actual work
579
return engine.interpolate(key, value)
582
def __getitem__(self, key):
583
"""Fetch the item and do string interpolation."""
584
val = dict.__getitem__(self, key)
585
if self.main.interpolation and isinstance(val, StringTypes):
586
return self._interpolate(key, val)
590
def __setitem__(self, key, value, unrepr=False):
592
Correctly set a value.
594
Making dictionary values Section instances.
595
(We have to special case 'Section' instances - which are also dicts)
597
Keys must be strings.
598
Values need only be strings (or lists of strings) if
599
``main.stringify`` is set.
601
`unrepr`` must be set when setting a value to a dictionary, without
602
creating a new sub-section.
604
if not isinstance(key, StringTypes):
605
raise ValueError('The key "%s" is not a string.' % key)
608
if not self.comments.has_key(key):
609
self.comments[key] = []
610
self.inline_comments[key] = ''
611
# remove the entry from defaults
612
if key in self.defaults:
613
self.defaults.remove(key)
615
if isinstance(value, Section):
616
if not self.has_key(key):
617
self.sections.append(key)
618
dict.__setitem__(self, key, value)
619
elif isinstance(value, dict) and not unrepr:
620
# First create the new depth level,
621
# then create the section
622
if not self.has_key(key):
623
self.sections.append(key)
624
new_depth = self.depth + 1
635
if not self.has_key(key):
636
self.scalars.append(key)
637
if not self.main.stringify:
638
if isinstance(value, StringTypes):
640
elif isinstance(value, (list, tuple)):
642
if not isinstance(entry, StringTypes):
643
raise TypeError('Value is not a string "%s".' % entry)
645
raise TypeError('Value is not a string "%s".' % value)
646
dict.__setitem__(self, key, value)
649
def __delitem__(self, key):
650
"""Remove items from the sequence when deleting."""
651
dict. __delitem__(self, key)
652
if key in self.scalars:
653
self.scalars.remove(key)
655
self.sections.remove(key)
656
del self.comments[key]
657
del self.inline_comments[key]
660
def get(self, key, default=None):
661
"""A version of ``get`` that doesn't bypass string interpolation."""
668
def update(self, indict):
670
A version of update that uses our ``__setitem__``.
673
self[entry] = indict[entry]
676
def pop(self, key, *args):
678
'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
679
If key is not found, d is returned if given, otherwise KeyError is raised'
681
val = dict.pop(self, key, *args)
682
if key in self.scalars:
683
del self.comments[key]
684
del self.inline_comments[key]
685
self.scalars.remove(key)
686
elif key in self.sections:
687
del self.comments[key]
688
del self.inline_comments[key]
689
self.sections.remove(key)
690
if self.main.interpolation and isinstance(val, StringTypes):
691
return self._interpolate(key, val)
696
"""Pops the first (key,val)"""
697
sequence = (self.scalars + self.sections)
699
raise KeyError(": 'popitem(): dictionary is empty'")
708
A version of clear that also affects scalars/sections
709
Also clears comments and configspec.
711
Leaves other attributes alone :
712
depth/main/parent are not affected
718
self.inline_comments = {}
722
def setdefault(self, key, default=None):
723
"""A version of setdefault that sets sequence if appropriate."""
732
"""D.items() -> list of D's (key, value) pairs, as 2-tuples"""
733
return zip((self.scalars + self.sections), self.values())
737
"""D.keys() -> list of D's keys"""
738
return (self.scalars + self.sections)
742
"""D.values() -> list of D's values"""
743
return [self[key] for key in (self.scalars + self.sections)]
747
"""D.iteritems() -> an iterator over the (key, value) items of D"""
748
return iter(self.items())
752
"""D.iterkeys() -> an iterator over the keys of D"""
753
return iter((self.scalars + self.sections))
758
def itervalues(self):
759
"""D.itervalues() -> an iterator over the values of D"""
760
return iter(self.values())
764
"""x.__repr__() <==> repr(x)"""
765
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
766
for key in (self.scalars + self.sections)])
769
__str__.__doc__ = "x.__str__() <==> str(x)"
772
# Extra methods - not in a normal dictionary
776
Return a deepcopy of self as a dictionary.
778
All members that are ``Section`` instances are recursively turned to
779
ordinary dictionaries - by calling their ``dict`` method.
789
this_entry = self[entry]
790
if isinstance(this_entry, Section):
791
this_entry = this_entry.dict()
792
elif isinstance(this_entry, list):
793
# create a copy rather than a reference
794
this_entry = list(this_entry)
795
elif isinstance(this_entry, tuple):
796
# create a copy rather than a reference
797
this_entry = tuple(this_entry)
798
newdict[entry] = this_entry
802
def merge(self, indict):
804
A recursive update - useful for merging config files.
806
>>> a = '''[section1]
809
... more_options = False
810
... # end of file'''.splitlines()
811
>>> b = '''# File is user.ini
814
... # end of file'''.splitlines()
815
>>> c1 = ConfigObj(b)
816
>>> c2 = ConfigObj(a)
819
{'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
821
for key, val in indict.items():
822
if (key in self and isinstance(self[key], dict) and
823
isinstance(val, dict)):
829
def rename(self, oldkey, newkey):
831
Change a keyname to another, without changing position in sequence.
833
Implemented so that transformations can be made on keys,
834
as well as on values. (used by encode and decode)
836
Also renames comments.
838
if oldkey in self.scalars:
839
the_list = self.scalars
840
elif oldkey in self.sections:
841
the_list = self.sections
843
raise KeyError('Key "%s" not found.' % oldkey)
844
pos = the_list.index(oldkey)
847
dict.__delitem__(self, oldkey)
848
dict.__setitem__(self, newkey, val)
849
the_list.remove(oldkey)
850
the_list.insert(pos, newkey)
851
comm = self.comments[oldkey]
852
inline_comment = self.inline_comments[oldkey]
853
del self.comments[oldkey]
854
del self.inline_comments[oldkey]
855
self.comments[newkey] = comm
856
self.inline_comments[newkey] = inline_comment
859
def walk(self, function, raise_errors=True,
860
call_on_sections=False, **keywargs):
862
Walk every member and call a function on the keyword and value.
864
Return a dictionary of the return values
866
If the function raises an exception, raise the errror
867
unless ``raise_errors=False``, in which case set the return value to
870
Any unrecognised keyword arguments you pass to walk, will be pased on
871
to the function you pass in.
873
Note: if ``call_on_sections`` is ``True`` then - on encountering a
874
subsection, *first* the function is called for the *whole* subsection,
875
and then recurses into it's members. This means your function must be
876
able to handle strings, dictionaries and lists. This allows you
877
to change the key of subsections as well as for ordinary members. The
878
return value when called on the whole subsection has to be discarded.
880
See the encode and decode methods for examples, including functions.
884
You can use ``walk`` to transform the names of members of a section
885
but you mustn't add or delete members.
887
>>> config = '''[XXXXsection]
888
... XXXXkey = XXXXvalue'''.splitlines()
889
>>> cfg = ConfigObj(config)
891
{'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
892
>>> def transform(section, key):
893
... val = section[key]
894
... newkey = key.replace('XXXX', 'CLIENT1')
895
... section.rename(key, newkey)
896
... if isinstance(val, (tuple, list, dict)):
899
... val = val.replace('XXXX', 'CLIENT1')
900
... section[newkey] = val
901
>>> cfg.walk(transform, call_on_sections=True)
902
{'CLIENT1section': {'CLIENT1key': None}}
904
{'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
908
for i in range(len(self.scalars)):
909
entry = self.scalars[i]
911
val = function(self, entry, **keywargs)
912
# bound again in case name has changed
913
entry = self.scalars[i]
919
entry = self.scalars[i]
922
for i in range(len(self.sections)):
923
entry = self.sections[i]
926
function(self, entry, **keywargs)
931
entry = self.sections[i]
933
# bound again in case name has changed
934
entry = self.sections[i]
935
# previous result is discarded
936
out[entry] = self[entry].walk(
938
raise_errors=raise_errors,
939
call_on_sections=call_on_sections,
944
def decode(self, encoding):
946
Decode all strings and values to unicode, using the specified encoding.
948
Works with subsections and list values.
950
Uses the ``walk`` method.
952
Testing ``encode`` and ``decode``.
954
>>> m.decode('ascii')
955
>>> def testuni(val):
956
... for entry in val:
957
... if not isinstance(entry, unicode):
958
... print >> sys.stderr, type(entry)
959
... raise AssertionError, 'decode failed.'
960
... if isinstance(val[entry], dict):
961
... testuni(val[entry])
962
... elif not isinstance(val[entry], unicode):
963
... raise AssertionError, 'decode failed.'
965
>>> m.encode('ascii')
969
warn('use of ``decode`` is deprecated.', DeprecationWarning)
970
def decode(section, key, encoding=encoding, warn=True):
973
if isinstance(val, (list, tuple)):
976
newval.append(entry.decode(encoding))
977
elif isinstance(val, dict):
980
newval = val.decode(encoding)
981
newkey = key.decode(encoding)
982
section.rename(key, newkey)
983
section[newkey] = newval
984
# using ``call_on_sections`` allows us to modify section names
985
self.walk(decode, call_on_sections=True)
988
def encode(self, encoding):
990
Encode all strings and values from unicode,
991
using the specified encoding.
993
Works with subsections and list values.
994
Uses the ``walk`` method.
996
warn('use of ``encode`` is deprecated.', DeprecationWarning)
997
def encode(section, key, encoding=encoding):
1000
if isinstance(val, (list, tuple)):
1003
newval.append(entry.encode(encoding))
1004
elif isinstance(val, dict):
1007
newval = val.encode(encoding)
1008
newkey = key.encode(encoding)
1009
section.rename(key, newkey)
1010
section[newkey] = newval
1011
self.walk(encode, call_on_sections=True)
1014
def istrue(self, key):
1015
"""A deprecated version of ``as_bool``."""
1016
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
1017
'instead.', DeprecationWarning)
1018
return self.as_bool(key)
1021
def as_bool(self, key):
1023
Accepts a key as input. The corresponding value must be a string or
1024
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
1025
retain compatibility with Python 2.2.
1027
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
1030
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
1033
``as_bool`` is not case sensitive.
1035
Any other input will raise a ``ValueError``.
1040
Traceback (most recent call last):
1041
ValueError: Value "fish" is neither True nor False
1056
if not isinstance(val, StringTypes):
1057
# TODO: Why do we raise a KeyError here?
1060
return self.main._bools[val.lower()]
1062
raise ValueError('Value "%s" is neither True nor False' % val)
1065
def as_int(self, key):
1067
A convenience method which coerces the specified value to an integer.
1069
If the value is an invalid literal for ``int``, a ``ValueError`` will
1075
Traceback (most recent call last):
1076
ValueError: invalid literal for int(): fish
1082
Traceback (most recent call last):
1083
ValueError: invalid literal for int(): 3.2
1085
return int(self[key])
1088
def as_float(self, key):
1090
A convenience method which coerces the specified value to a float.
1092
If the value is an invalid literal for ``float``, a ``ValueError`` will
1098
Traceback (most recent call last):
1099
ValueError: invalid literal for float(): fish
1107
return float(self[key])
1110
def restore_default(self, key):
1112
Restore (and return) default value for the specified key.
1114
This method will only work for a ConfigObj that was created
1115
with a configspec and has been validated.
1117
If there is no default value for this key, ``KeyError`` is raised.
1119
default = self.default_values[key]
1120
dict.__setitem__(self, key, default)
1121
if key not in self.defaults:
1122
self.defaults.append(key)
1126
def restore_defaults(self):
1128
Recursively restore default values to all members
1131
This method will only work for a ConfigObj that was created
1132
with a configspec and has been validated.
1134
It doesn't delete or modify entries without default values.
1136
for key in self.default_values:
1137
self.restore_default(key)
1139
for section in self.sections:
1140
self[section].restore_defaults()
1143
class ConfigObj(Section):
1144
"""An object to read, create, and write config files."""
1146
_keyword = re.compile(r'''^ # line start
1149
(?:".*?")| # double quotes
1150
(?:'.*?')| # single quotes
1151
(?:[^'"=].*?) # no quotes
1154
(.*) # value (including list values and comments)
1159
_sectionmarker = re.compile(r'''^
1160
(\s*) # 1: indentation
1161
((?:\[\s*)+) # 2: section marker open
1162
( # 3: section name open
1163
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1164
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1165
(?:[^'"\s].*?) # at least one non-space unquoted
1166
) # section name close
1167
((?:\s*\])+) # 4: section marker close
1168
\s*(\#.*)? # 5: optional comment
1172
# this regexp pulls list values out as a single string
1173
# or single values and comments
1174
# FIXME: this regex adds a '' to the end of comma terminated lists
1175
# workaround in ``_handle_value``
1176
_valueexp = re.compile(r'''^
1182
(?:".*?")| # double quotes
1183
(?:'.*?')| # single quotes
1184
(?:[^'",\#][^,\#]*?) # unquoted
1187
)* # match all list items ending in a comma (if any)
1190
(?:".*?")| # double quotes
1191
(?:'.*?')| # single quotes
1192
(?:[^'",\#\s][^,]*?)| # unquoted
1193
(?:(?<!,)) # Empty value
1194
)? # last item in a list - or string value
1196
(,) # alternatively a single comma - empty list
1198
\s*(\#.*)? # optional comment
1202
# use findall to get the members of a list value
1203
_listvalueexp = re.compile(r'''
1205
(?:".*?")| # double quotes
1206
(?:'.*?')| # single quotes
1207
(?:[^'",\#].*?) # unquoted
1213
# this regexp is used for the value
1214
# when lists are switched off
1215
_nolistvalue = re.compile(r'''^
1217
(?:".*?")| # double quotes
1218
(?:'.*?')| # single quotes
1219
(?:[^'"\#].*?)| # unquoted
1222
\s*(\#.*)? # optional comment
1226
# regexes for finding triple quoted values on one line
1227
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1228
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1229
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1230
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1233
"'''": (_single_line_single, _multi_line_single),
1234
'"""': (_single_line_double, _multi_line_double),
1237
# Used by the ``istrue`` Section method
1239
'yes': True, 'no': False,
1240
'on': True, 'off': False,
1241
'1': True, '0': False,
1242
'true': True, 'false': False,
1246
def __init__(self, infile=None, options=None, **kwargs):
1248
Parse a config file or create a config file object.
1250
``ConfigObj(infile=None, options=None, **kwargs)``
1252
# init the superclass
1253
Section.__init__(self, self, 0, self)
1260
options = dict(options)
1262
# keyword arguments take precedence over an options dictionary
1263
options.update(kwargs)
1265
defaults = OPTION_DEFAULTS.copy()
1266
# TODO: check the values too.
1267
for entry in options:
1268
if entry not in defaults:
1269
raise TypeError('Unrecognised option "%s".' % entry)
1271
# Add any explicit options to the defaults
1272
defaults.update(options)
1273
self._initialise(defaults)
1274
configspec = defaults['configspec']
1275
self._original_configspec = configspec
1276
self._load(infile, configspec)
1279
def _load(self, infile, configspec):
1280
if isinstance(infile, StringTypes):
1281
self.filename = infile
1282
if os.path.isfile(infile):
1283
h = open(infile, 'rb')
1284
infile = h.read() or []
1286
elif self.file_error:
1287
# raise an error if the file doesn't exist
1288
raise IOError('Config file not found: "%s".' % self.filename)
1290
# file doesn't already exist
1291
if self.create_empty:
1292
# this is a good test that the filename specified
1293
# isn't impossible - like on a non-existent device
1294
h = open(infile, 'w')
1299
elif isinstance(infile, (list, tuple)):
1300
infile = list(infile)
1302
elif isinstance(infile, dict):
1304
# the Section class handles creating subsections
1305
if isinstance(infile, ConfigObj):
1306
# get a copy of our ConfigObj
1307
infile = infile.dict()
1309
for entry in infile:
1310
self[entry] = infile[entry]
1313
if configspec is not None:
1314
self._handle_configspec(configspec)
1316
self.configspec = None
1319
elif getattr(infile, 'read', None) is not None:
1320
# This supports file like objects
1321
infile = infile.read() or []
1322
# needs splitting into lines - but needs doing *after* decoding
1323
# in case it's not an 8 bit encoding
1325
raise TypeError('infile must be a filename, file like object, or list of lines.')
1328
# don't do it for the empty ConfigObj
1329
infile = self._handle_bom(infile)
1330
# infile is now *always* a list
1332
# Set the newlines attribute (first line ending it finds)
1333
# and strip trailing '\n' or '\r' from lines
1335
if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1337
for end in ('\r\n', '\n', '\r'):
1338
if line.endswith(end):
1343
infile = [line.rstrip('\r\n') for line in infile]
1346
# if we had any errors, now is the time to raise them
1348
info = "at line %s." % self._errors[0].line_number
1349
if len(self._errors) > 1:
1350
msg = "Parsing failed with several errors.\nFirst error %s" % info
1351
error = ConfigObjError(msg)
1353
error = self._errors[0]
1354
# set the errors attribute; it's a list of tuples:
1355
# (error_type, message, line_number)
1356
error.errors = self._errors
1357
# set the config attribute
1360
# delete private attributes
1363
if configspec is None:
1364
self.configspec = None
1366
self._handle_configspec(configspec)
1369
def _initialise(self, options=None):
1371
options = OPTION_DEFAULTS
1373
# initialise a few variables
1374
self.filename = None
1376
self.raise_errors = options['raise_errors']
1377
self.interpolation = options['interpolation']
1378
self.list_values = options['list_values']
1379
self.create_empty = options['create_empty']
1380
self.file_error = options['file_error']
1381
self.stringify = options['stringify']
1382
self.indent_type = options['indent_type']
1383
self.encoding = options['encoding']
1384
self.default_encoding = options['default_encoding']
1386
self.newlines = None
1387
self.write_empty_values = options['write_empty_values']
1388
self.unrepr = options['unrepr']
1390
self.initial_comment = []
1391
self.final_comment = []
1392
self.configspec = {}
1394
# Clear section attributes as well
1395
Section._initialise(self)
1399
return ('ConfigObj({%s})' %
1400
', '.join([('%s: %s' % (repr(key), repr(self[key])))
1401
for key in (self.scalars + self.sections)]))
1404
def _handle_bom(self, infile):
1406
Handle any BOM, and decode if necessary.
1408
If an encoding is specified, that *must* be used - but the BOM should
1409
still be removed (and the BOM attribute set).
1411
(If the encoding is wrongly specified, then a BOM for an alternative
1412
encoding won't be discovered or removed.)
1414
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1415
removed. The BOM attribute will be set. UTF16 will be decoded to
1418
NOTE: This method must not be called with an empty ``infile``.
1420
Specifying the *wrong* encoding is likely to cause a
1421
``UnicodeDecodeError``.
1423
``infile`` must always be returned as a list of lines, but may be
1424
passed in as a single string.
1426
if ((self.encoding is not None) and
1427
(self.encoding.lower() not in BOM_LIST)):
1428
# No need to check for a BOM
1429
# the encoding specified doesn't have one
1431
return self._decode(infile, self.encoding)
1433
if isinstance(infile, (list, tuple)):
1437
if self.encoding is not None:
1438
# encoding explicitly supplied
1439
# And it could have an associated BOM
1440
# TODO: if encoding is just UTF16 - we ought to check for both
1441
# TODO: big endian and little endian versions.
1442
enc = BOM_LIST[self.encoding.lower()]
1444
# For UTF16 we try big endian and little endian
1445
for BOM, (encoding, final_encoding) in BOMS.items():
1446
if not final_encoding:
1449
if infile.startswith(BOM):
1452
# Don't need to remove BOM
1453
return self._decode(infile, encoding)
1455
# If we get this far, will *probably* raise a DecodeError
1456
# As it doesn't appear to start with a BOM
1457
return self._decode(infile, self.encoding)
1461
if not line.startswith(BOM):
1462
return self._decode(infile, self.encoding)
1464
newline = line[len(BOM):]
1467
if isinstance(infile, (list, tuple)):
1472
return self._decode(infile, self.encoding)
1474
# No encoding specified - so we need to check for UTF8/UTF16
1475
for BOM, (encoding, final_encoding) in BOMS.items():
1476
if not line.startswith(BOM):
1480
self.encoding = final_encoding
1481
if not final_encoding:
1485
newline = line[len(BOM):]
1486
if isinstance(infile, (list, tuple)):
1490
# UTF8 - don't decode
1491
if isinstance(infile, StringTypes):
1492
return infile.splitlines(True)
1495
# UTF16 - have to decode
1496
return self._decode(infile, encoding)
1498
# No BOM discovered and no encoding specified, just return
1499
if isinstance(infile, StringTypes):
1500
# infile read from a file will be a single string
1501
return infile.splitlines(True)
1505
def _a_to_u(self, aString):
1506
"""Decode ASCII strings to unicode if a self.encoding is specified."""
1508
return aString.decode('ascii')
1513
def _decode(self, infile, encoding):
1515
Decode infile to unicode. Using the specified encoding.
1517
if is a string, it also needs converting to a list.
1519
if isinstance(infile, StringTypes):
1521
# NOTE: Could raise a ``UnicodeDecodeError``
1522
return infile.decode(encoding).splitlines(True)
1523
for i, line in enumerate(infile):
1524
if not isinstance(line, unicode):
1525
# NOTE: The isinstance test here handles mixed lists of unicode/string
1526
# NOTE: But the decode will break on any non-string values
1527
# NOTE: Or could raise a ``UnicodeDecodeError``
1528
infile[i] = line.decode(encoding)
1532
def _decode_element(self, line):
1533
"""Decode element to unicode if necessary."""
1534
if not self.encoding:
1536
if isinstance(line, str) and self.default_encoding:
1537
return line.decode(self.default_encoding)
1541
def _str(self, value):
1543
Used by ``stringify`` within validate, to turn non-string values
1546
if not isinstance(value, StringTypes):
1552
def _parse(self, infile):
1553
"""Actually parse the config file."""
1554
temp_list_values = self.list_values
1556
self.list_values = False
1561
maxline = len(infile) - 1
1563
reset_comment = False
1565
while cur_index < maxline:
1569
line = infile[cur_index]
1570
sline = line.strip()
1571
# do we have anything on the line ?
1572
if not sline or sline.startswith('#'):
1573
reset_comment = False
1574
comment_list.append(line)
1578
# preserve initial comment
1579
self.initial_comment = comment_list
1583
reset_comment = True
1584
# first we check if it's a section marker
1585
mat = self._sectionmarker.match(line)
1588
(indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1589
if indent and (self.indent_type is None):
1590
self.indent_type = indent
1591
cur_depth = sect_open.count('[')
1592
if cur_depth != sect_close.count(']'):
1593
self._handle_error("Cannot compute the section depth at line %s.",
1594
NestingError, infile, cur_index)
1597
if cur_depth < this_section.depth:
1598
# the new section is dropping back to a previous level
1600
parent = self._match_depth(this_section,
1603
self._handle_error("Cannot compute nesting level at line %s.",
1604
NestingError, infile, cur_index)
1606
elif cur_depth == this_section.depth:
1607
# the new section is a sibling of the current section
1608
parent = this_section.parent
1609
elif cur_depth == this_section.depth + 1:
1610
# the new section is a child the current section
1611
parent = this_section
1613
self._handle_error("Section too nested at line %s.",
1614
NestingError, infile, cur_index)
1616
sect_name = self._unquote(sect_name)
1617
if parent.has_key(sect_name):
1618
self._handle_error('Duplicate section name at line %s.',
1619
DuplicateError, infile, cur_index)
1622
# create the new section
1623
this_section = Section(
1628
parent[sect_name] = this_section
1629
parent.inline_comments[sect_name] = comment
1630
parent.comments[sect_name] = comment_list
1633
# it's not a section marker,
1634
# so it should be a valid ``key = value`` line
1635
mat = self._keyword.match(line)
1637
# it neither matched as a keyword
1638
# or a section marker
1640
'Invalid line at line "%s".',
1641
ParseError, infile, cur_index)
1643
# is a keyword value
1644
# value will include any inline comment
1645
(indent, key, value) = mat.groups()
1646
if indent and (self.indent_type is None):
1647
self.indent_type = indent
1648
# check for a multiline value
1649
if value[:3] in ['"""', "'''"]:
1651
(value, comment, cur_index) = self._multiline(
1652
value, infile, cur_index, maxline)
1655
'Parse error in value at line %s.',
1656
ParseError, infile, cur_index)
1662
value = unrepr(value)
1663
except Exception, e:
1664
if type(e) == UnknownType:
1665
msg = 'Unknown name or type in value at line %s.'
1667
msg = 'Parse error in value at line %s.'
1668
self._handle_error(msg, UnreprError, infile,
1675
value = unrepr(value)
1676
except Exception, e:
1677
if isinstance(e, UnknownType):
1678
msg = 'Unknown name or type in value at line %s.'
1680
msg = 'Parse error in value at line %s.'
1681
self._handle_error(msg, UnreprError, infile,
1685
# extract comment and lists
1687
(value, comment) = self._handle_value(value)
1690
'Parse error in value at line %s.',
1691
ParseError, infile, cur_index)
1694
key = self._unquote(key)
1695
if this_section.has_key(key):
1697
'Duplicate keyword name at line %s.',
1698
DuplicateError, infile, cur_index)
1701
# we set unrepr because if we have got this far we will never
1702
# be creating a new section
1703
this_section.__setitem__(key, value, unrepr=True)
1704
this_section.inline_comments[key] = comment
1705
this_section.comments[key] = comment_list
1708
if self.indent_type is None:
1709
# no indentation used, set the type accordingly
1710
self.indent_type = ''
1712
# preserve the final comment
1713
if not self and not self.initial_comment:
1714
self.initial_comment = comment_list
1715
elif not reset_comment:
1716
self.final_comment = comment_list
1717
self.list_values = temp_list_values
1720
def _match_depth(self, sect, depth):
1722
Given a section and a depth level, walk back through the sections
1723
parents to see if the depth level matches a previous section.
1725
Return a reference to the right section,
1726
or raise a SyntaxError.
1728
while depth < sect.depth:
1729
if sect is sect.parent:
1730
# we've reached the top level already
1733
if sect.depth == depth:
1735
# shouldn't get here
1739
def _handle_error(self, text, ErrorClass, infile, cur_index):
1741
Handle an error according to the error settings.
1743
Either raise the error or store it.
1744
The error will have occured at ``cur_index``
1746
line = infile[cur_index]
1748
message = text % cur_index
1749
error = ErrorClass(message, cur_index, line)
1750
if self.raise_errors:
1751
# raise the error - parsing stops here
1754
# reraise when parsing has finished
1755
self._errors.append(error)
1758
def _unquote(self, value):
1759
"""Return an unquoted version of a value"""
1760
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1765
def _quote(self, value, multiline=True):
1767
Return a safely quoted version of a value.
1769
Raise a ConfigObjError if the value cannot be safely quoted.
1770
If multiline is ``True`` (default) then use triple quotes
1773
Don't quote values that don't need it.
1774
Recursively quote members of a list and return a comma joined list.
1775
Multiline is ``False`` for lists.
1776
Obey list syntax for empty and single member lists.
1778
If ``list_values=False`` then the value is only quoted if it contains
1779
a ``\n`` (is multiline) or '#'.
1781
If ``write_empty_values`` is set, and the value is an empty string, it
1784
if multiline and self.write_empty_values and value == '':
1785
# Only if multiline is set, so that it is used for values not
1786
# keys, and not values that are part of a list
1789
if multiline and isinstance(value, (list, tuple)):
1792
elif len(value) == 1:
1793
return self._quote(value[0], multiline=False) + ','
1794
return ', '.join([self._quote(val, multiline=False)
1796
if not isinstance(value, StringTypes):
1800
raise TypeError('Value "%s" is not a string.' % value)
1805
no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1806
need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1807
hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1808
check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1810
if check_for_single:
1811
if not self.list_values:
1812
# we don't quote if ``list_values=False``
1814
# for normal values either single or double quotes will do
1816
# will only happen if multiline is off - e.g. '\n' in key
1817
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1818
elif ((value[0] not in wspace_plus) and
1819
(value[-1] not in wspace_plus) and
1820
(',' not in value)):
1823
quot = self._get_single_quote(value)
1825
# if value has '\n' or "'" *and* '"', it will need triple quotes
1826
quot = self._get_triple_quote(value)
1828
if quot == noquot and '#' in value and self.list_values:
1829
quot = self._get_single_quote(value)
1834
def _get_single_quote(self, value):
1835
if ("'" in value) and ('"' in value):
1836
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1844
def _get_triple_quote(self, value):
1845
if (value.find('"""') != -1) and (value.find("'''") != -1):
1846
raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1847
if value.find('"""') == -1:
1854
def _handle_value(self, value):
1856
Given a value string, unquote, remove comment,
1857
handle lists. (including empty and single member lists)
1859
# do we look for lists in values ?
1860
if not self.list_values:
1861
mat = self._nolistvalue.match(value)
1864
# NOTE: we don't unquote here
1867
mat = self._valueexp.match(value)
1869
# the value is badly constructed, probably badly quoted,
1870
# or an invalid list
1872
(list_values, single, empty_list, comment) = mat.groups()
1873
if (list_values == '') and (single is None):
1874
# change this if you want to accept empty values
1876
# NOTE: note there is no error handling from here if the regex
1877
# is wrong: then incorrect values will slip through
1878
if empty_list is not None:
1879
# the single comma - meaning an empty list
1880
return ([], comment)
1881
if single is not None:
1882
# handle empty values
1883
if list_values and not single:
1884
# FIXME: the '' is a workaround because our regex now matches
1885
# '' at the end of a list if it has a trailing comma
1888
single = single or '""'
1889
single = self._unquote(single)
1890
if list_values == '':
1892
return (single, comment)
1893
the_list = self._listvalueexp.findall(list_values)
1894
the_list = [self._unquote(val) for val in the_list]
1895
if single is not None:
1896
the_list += [single]
1897
return (the_list, comment)
1900
def _multiline(self, value, infile, cur_index, maxline):
1901
"""Extract the value, where we are in a multiline situation."""
1903
newvalue = value[3:]
1904
single_line = self._triple_quote[quot][0]
1905
multi_line = self._triple_quote[quot][1]
1906
mat = single_line.match(value)
1908
retval = list(mat.groups())
1909
retval.append(cur_index)
1911
elif newvalue.find(quot) != -1:
1912
# somehow the triple quote is missing
1915
while cur_index < maxline:
1918
line = infile[cur_index]
1919
if line.find(quot) == -1:
1922
# end of multiline, process it
1925
# we've got to the end of the config, oops...
1927
mat = multi_line.match(line)
1929
# a badly formed line
1931
(value, comment) = mat.groups()
1932
return (newvalue + value, comment, cur_index)
1935
def _handle_configspec(self, configspec):
1936
"""Parse the configspec."""
1937
# FIXME: Should we check that the configspec was created with the
1938
# correct settings ? (i.e. ``list_values=False``)
1939
if not isinstance(configspec, ConfigObj):
1941
configspec = ConfigObj(configspec,
1945
except ConfigObjError, e:
1946
# FIXME: Should these errors have a reference
1947
# to the already parsed ConfigObj ?
1948
raise ConfigspecError('Parsing configspec failed: %s' % e)
1950
raise IOError('Reading configspec failed: %s' % e)
1952
self._set_configspec_value(configspec, self)
1955
def _set_configspec_value(self, configspec, section):
1956
"""Used to recursively set configspec values."""
1957
if '__many__' in configspec.sections:
1958
section.configspec['__many__'] = configspec['__many__']
1959
if len(configspec.sections) > 1:
1960
# FIXME: can we supply any useful information here ?
1961
raise RepeatSectionError()
1963
if getattr(configspec, 'initial_comment', None) is not None:
1964
section._configspec_initial_comment = configspec.initial_comment
1965
section._configspec_final_comment = configspec.final_comment
1966
section._configspec_encoding = configspec.encoding
1967
section._configspec_BOM = configspec.BOM
1968
section._configspec_newlines = configspec.newlines
1969
section._configspec_indent_type = configspec.indent_type
1971
for entry in configspec.scalars:
1972
section._configspec_comments[entry] = configspec.comments[entry]
1973
section._configspec_inline_comments[entry] = configspec.inline_comments[entry]
1974
section.configspec[entry] = configspec[entry]
1975
section._order.append(entry)
1977
for entry in configspec.sections:
1978
if entry == '__many__':
1981
section._cs_section_comments[entry] = configspec.comments[entry]
1982
section._cs_section_inline_comments[entry] = configspec.inline_comments[entry]
1983
if not section.has_key(entry):
1985
self._set_configspec_value(configspec[entry], section[entry])
1988
def _handle_repeat(self, section, configspec):
1989
"""Dynamically assign configspec for repeated section."""
1991
section_keys = configspec.sections
1992
scalar_keys = configspec.scalars
1993
except AttributeError:
1994
section_keys = [entry for entry in configspec
1995
if isinstance(configspec[entry], dict)]
1996
scalar_keys = [entry for entry in configspec
1997
if not isinstance(configspec[entry], dict)]
1999
if '__many__' in section_keys and len(section_keys) > 1:
2000
# FIXME: can we supply any useful information here ?
2001
raise RepeatSectionError()
2005
for entry in scalar_keys:
2006
val = configspec[entry]
2007
scalars[entry] = val
2008
for entry in section_keys:
2009
val = configspec[entry]
2010
if entry == '__many__':
2011
scalars[entry] = val
2013
sections[entry] = val
2015
section.configspec = scalars
2016
for entry in sections:
2017
if not section.has_key(entry):
2019
self._handle_repeat(section[entry], sections[entry])
2022
def _write_line(self, indent_string, entry, this_entry, comment):
2023
"""Write an individual line, for the write method"""
2024
# NOTE: the calls to self._quote here handles non-StringType values.
2026
val = self._decode_element(self._quote(this_entry))
2028
val = repr(this_entry)
2029
return '%s%s%s%s%s' % (indent_string,
2030
self._decode_element(self._quote(entry, multiline=False)),
2031
self._a_to_u(' = '),
2033
self._decode_element(comment))
2036
def _write_marker(self, indent_string, depth, entry, comment):
2037
"""Write a section marker line"""
2038
return '%s%s%s%s%s' % (indent_string,
2039
self._a_to_u('[' * depth),
2040
self._quote(self._decode_element(entry), multiline=False),
2041
self._a_to_u(']' * depth),
2042
self._decode_element(comment))
2045
def _handle_comment(self, comment):
2046
"""Deal with a comment."""
2049
start = self.indent_type
2050
if not comment.startswith('#'):
2051
start += self._a_to_u(' # ')
2052
return (start + comment)
2057
def write(self, outfile=None, section=None):
2059
Write the current ConfigObj as a file
2061
tekNico: FIXME: use StringIO instead of real files
2063
>>> filename = a.filename
2064
>>> a.filename = 'test.ini'
2066
>>> a.filename = filename
2067
>>> a == ConfigObj('test.ini', raise_errors=True)
2070
if self.indent_type is None:
2071
# this can be true if initialised from a dictionary
2072
self.indent_type = DEFAULT_INDENT_TYPE
2075
cs = self._a_to_u('#')
2076
csp = self._a_to_u('# ')
2078
int_val = self.interpolation
2079
self.interpolation = False
2081
for line in self.initial_comment:
2082
line = self._decode_element(line)
2083
stripped_line = line.strip()
2084
if stripped_line and not stripped_line.startswith(cs):
2088
indent_string = self.indent_type * section.depth
2089
for entry in (section.scalars + section.sections):
2090
if entry in section.defaults:
2091
# don't write out default values
2093
for comment_line in section.comments[entry]:
2094
comment_line = self._decode_element(comment_line.lstrip())
2095
if comment_line and not comment_line.startswith(cs):
2096
comment_line = csp + comment_line
2097
out.append(indent_string + comment_line)
2098
this_entry = section[entry]
2099
comment = self._handle_comment(section.inline_comments[entry])
2101
if isinstance(this_entry, dict):
2103
out.append(self._write_marker(
2108
out.extend(self.write(section=this_entry))
2110
out.append(self._write_line(
2117
for line in self.final_comment:
2118
line = self._decode_element(line)
2119
stripped_line = line.strip()
2120
if stripped_line and not stripped_line.startswith(cs):
2123
self.interpolation = int_val
2125
if section is not self:
2128
if (self.filename is None) and (outfile is None):
2129
# output a list of lines
2130
# might need to encode
2131
# NOTE: This will *screw* UTF16, each line will start with the BOM
2133
out = [l.encode(self.encoding) for l in out]
2134
if (self.BOM and ((self.encoding is None) or
2135
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2139
out[0] = BOM_UTF8 + out[0]
2142
# Turn the list to a string, joined with correct newlines
2143
newline = self.newlines or os.linesep
2144
output = self._a_to_u(newline).join(out)
2146
output = output.encode(self.encoding)
2147
if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2149
output = BOM_UTF8 + output
2151
if not output.endswith(newline):
2153
if outfile is not None:
2154
outfile.write(output)
2156
h = open(self.filename, 'wb')
2161
def validate(self, validator, preserve_errors=False, copy=False,
2164
Test the ConfigObj against a configspec.
2166
It uses the ``validator`` object from *validate.py*.
2168
To run ``validate`` on the current ConfigObj, call: ::
2170
test = config.validate(validator)
2172
(Normally having previously passed in the configspec when the ConfigObj
2173
was created - you can dynamically assign a dictionary of checks to the
2174
``configspec`` attribute of a section though).
2176
It returns ``True`` if everything passes, or a dictionary of
2177
pass/fails (True/False). If every member of a subsection passes, it
2178
will just have the value ``True``. (It also returns ``False`` if all
2181
In addition, it converts the values from strings to their native
2182
types if their checks pass (and ``stringify`` is set).
2184
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2185
of a marking a fail with a ``False``, it will preserve the actual
2186
exception object. This can contain info about the reason for failure.
2187
For example the ``VdtValueTooSmallError`` indicates that the value
2188
supplied was too small. If a value (or section) is missing it will
2189
still be marked as ``False``.
2191
You must have the validate module to use ``preserve_errors=True``.
2193
You can then use the ``flatten_errors`` function to turn your nested
2194
results dictionary into a flattened list of failures - useful for
2195
displaying meaningful error messages.
2198
if self.configspec is None:
2199
raise ValueError('No configspec supplied.')
2201
# We do this once to remove a top level dependency on the validate module
2202
# Which makes importing configobj faster
2203
from validate import VdtMissingValue
2204
self._vdtMissingValue = VdtMissingValue
2207
spec_section = section.configspec
2208
if copy and getattr(section, '_configspec_initial_comment', None) is not None:
2209
section.initial_comment = section._configspec_initial_comment
2210
section.final_comment = section._configspec_final_comment
2211
section.encoding = section._configspec_encoding
2212
section.BOM = section._configspec_BOM
2213
section.newlines = section._configspec_newlines
2214
section.indent_type = section._configspec_indent_type
2216
if '__many__' in section.configspec:
2217
many = spec_section['__many__']
2218
# dynamically assign the configspecs
2219
# for the sections below
2220
for entry in section.sections:
2221
self._handle_repeat(section[entry], many)
2226
order = [k for k in section._order if k in spec_section]
2227
order += [k for k in spec_section if k not in order]
2229
if entry == '__many__':
2231
if (not entry in section.scalars) or (entry in section.defaults):
2233
# or entries from defaults
2236
if copy and not entry in section.scalars:
2238
section.comments[entry] = (
2239
section._configspec_comments.get(entry, []))
2240
section.inline_comments[entry] = (
2241
section._configspec_inline_comments.get(entry, ''))
2245
val = section[entry]
2247
check = validator.check(spec_section[entry],
2251
except validator.baseErrorClass, e:
2252
if not preserve_errors or isinstance(e, self._vdtMissingValue):
2255
# preserve the error
2261
section.default_values.pop(entry, None)
2262
except AttributeError:
2263
# For Python 2.2 compatibility
2265
del section.default_values[entry]
2269
if getattr(validator, 'get_default_value', None) is not None:
2271
section.default_values[entry] = validator.get_default_value(spec_section[entry])
2278
if self.stringify or missing:
2279
# if we are doing type conversion
2280
# or the value is a supplied default
2281
if not self.stringify:
2282
if isinstance(check, (list, tuple)):
2284
check = [self._str(item) for item in check]
2285
elif missing and check is None:
2286
# convert the None from a default to a ''
2289
check = self._str(check)
2290
if (check != val) or missing:
2291
section[entry] = check
2292
if not copy and missing and entry not in section.defaults:
2293
section.defaults.append(entry)
2294
# Missing sections will have been created as empty ones when the
2295
# configspec was read.
2296
for entry in section.sections:
2297
# FIXME: this means DEFAULT is not copied in copy mode
2298
if section is self and entry == 'DEFAULT':
2301
section.comments[entry] = section._cs_section_comments[entry]
2302
section.inline_comments[entry] = (
2303
section._cs_section_inline_comments[entry])
2304
check = self.validate(validator, preserve_errors=preserve_errors,
2305
copy=copy, section=section[entry])
2323
"""Clear ConfigObj instance and restore to 'freshly created' state."""
2326
# FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2327
# requires an empty dictionary
2328
self.configspec = None
2329
# Just to be sure ;-)
2330
self._original_configspec = None
2335
Reload a ConfigObj from file.
2337
This method raises a ``ReloadError`` if the ConfigObj doesn't have
2338
a filename attribute pointing to a file.
2340
if not isinstance(self.filename, StringTypes):
2343
filename = self.filename
2344
current_options = {}
2345
for entry in OPTION_DEFAULTS:
2346
if entry == 'configspec':
2348
current_options[entry] = getattr(self, entry)
2350
configspec = self._original_configspec
2351
current_options['configspec'] = configspec
2354
self._initialise(current_options)
2355
self._load(filename, configspec)
2359
class SimpleVal(object):
2362
Can be used to check that all members expected are present.
2364
To use it, provide a configspec with all your members in (the value given
2365
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2366
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2367
members are present, or a dictionary with True/False meaning
2368
present/missing. (Whole missing sections will be replaced with ``False``)
2372
self.baseErrorClass = ConfigObjError
2374
def check(self, check, member, missing=False):
2375
"""A dummy check method, always returns the value unchanged."""
2377
raise self.baseErrorClass()
2381
# Check / processing functions for options
2382
def flatten_errors(cfg, res, levels=None, results=None):
2384
An example function that will turn a nested dictionary of results
2385
(as returned by ``ConfigObj.validate``) into a flat list.
2387
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2388
dictionary returned by ``validate``.
2390
(This is a recursive function, so you shouldn't use the ``levels`` or
2391
``results`` arguments - they are used by the function.
2393
Returns a list of keys that failed. Each member of the list is a tuple :
2396
([list of sections...], key, result)
2398
If ``validate`` was called with ``preserve_errors=False`` (the default)
2399
then ``result`` will always be ``False``.
2401
*list of sections* is a flattened list of sections that the key was found
2404
If the section was missing then key will be ``None``.
2406
If the value (or section) was missing then ``result`` will be ``False``.
2408
If ``validate`` was called with ``preserve_errors=True`` and a value
2409
was present, but failed the check, then ``result`` will be the exception
2410
object returned. You can use this as a string that describes the failure.
2412
For example *The value "3" is of the wrong type*.
2415
>>> vtor = validate.Validator()
2421
... another_option = Probably
2423
... another_option = True
2430
... option1 = boolean()
2431
... option2 = boolean()
2432
... option3 = boolean(default=Bad_value)
2434
... option1 = boolean()
2435
... option2 = boolean()
2436
... option3 = boolean(default=Bad_value)
2438
... another_option = boolean()
2440
... another_option = boolean()
2443
... value2 = integer
2444
... value3 = integer(0, 10)
2445
... [[[section3b-sub]]]
2448
... another_option = boolean()
2450
>>> cs = my_cfg.split('\\n')
2451
>>> ini = my_ini.split('\\n')
2452
>>> cfg = ConfigObj(ini, configspec=cs)
2453
>>> res = cfg.validate(vtor, preserve_errors=True)
2455
>>> for entry in flatten_errors(cfg, res):
2456
... section_list, key, error = entry
2457
... section_list.insert(0, '[root]')
2458
... if key is not None:
2459
... section_list.append(key)
2461
... section_list.append('[missing]')
2462
... section_string = ', '.join(section_list)
2463
... errors.append((section_string, ' = ', error))
2465
>>> for entry in errors:
2466
... print entry[0], entry[1], (entry[2] or 0)
2468
[root], option3 = the value "Bad_value" is of the wrong type.
2469
[root], section1, option2 = 0
2470
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2471
[root], section2, another_option = the value "Probably" is of the wrong type.
2472
[root], section3, section3b, section3b-sub, [missing] = 0
2473
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2474
[root], section3, section3b, value3 = the value "11" is too big.
2475
[root], section4, [missing] = 0
2484
results.append((levels[:], None, False))
2488
for (key, val) in res.items():
2491
if isinstance(cfg.get(key), dict):
2494
flatten_errors(cfg[key], val, levels, results)
2496
results.append((levels[:], key, val))
2505
"""*A programming language is a medium of expression.* - Paul Graham"""