2
# A config file reader/writer that supports nested sections in config files.
3
# Copyright (C) 2005-2006 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
from bzrlib.lazy_import import lazy_import
29
lazy_import(globals(), """
33
from types import StringTypes
34
from warnings import warn
36
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
38
# Python 2.2 does not have these
40
BOM_UTF8 = '\xef\xbb\xbf'
41
# UTF-16, little endian
42
BOM_UTF16_LE = '\xff\xfe'
44
BOM_UTF16_BE = '\xfe\xff'
45
if sys.byteorder == 'little':
46
# UTF-16, native endianness
47
BOM_UTF16 = BOM_UTF16_LE
49
# UTF-16, native endianness
50
BOM_UTF16 = BOM_UTF16_BE
52
# A dictionary mapping BOM to
53
# the encoding to decode with, and what to set the
54
# encoding attribute to.
56
BOM_UTF8: ('utf_8', None),
57
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
58
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
59
BOM_UTF16: ('utf_16', 'utf_16'),
61
# All legal variants of the BOM codecs.
62
# TODO: the list of aliases is not meant to be exhaustive, is there a
69
'utf16_be': 'utf16_be',
70
'utf_16_be': 'utf16_be',
71
'utf-16be': 'utf16_be',
72
'utf16_le': 'utf16_le',
73
'utf_16_le': 'utf16_le',
74
'utf-16le': 'utf16_le',
82
# Map of encodings to the BOM to write.
86
'utf16_be': BOM_UTF16_BE,
87
'utf16_le': BOM_UTF16_LE,
93
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
95
__docformat__ = "restructuredtext en"
99
'DEFAULT_INDENT_TYPE',
100
'DEFAULT_INTERPOLATION',
108
'InterpolationError',
109
'InterpolationLoopError',
110
'MissingInterpolationOption',
111
'RepeatSectionError',
118
DEFAULT_INTERPOLATION = 'configparser'
119
DEFAULT_INDENT_TYPE = ' '
120
MAX_INTERPOL_DEPTH = 10
123
'interpolation': True,
124
'raise_errors': False,
126
'create_empty': False,
130
# option may be set to one of ('', ' ', '\t')
133
'default_encoding': None,
135
'write_empty_values': False,
141
p = compiler.parse(s)
142
return p.getChildren()[1].getChildren()[0].getChildren()[1]
144
class UnknownType(Exception):
150
m = getattr(self, 'build_' + o.__class__.__name__, None)
152
raise UnknownType(o.__class__.__name__)
155
def build_List(self, o):
156
return map(self.build, o.getChildren())
158
def build_Const(self, o):
161
def build_Dict(self, o):
163
i = iter(map(self.build, o.getChildren()))
168
def build_Tuple(self, o):
169
return tuple(self.build_List(o))
171
def build_Name(self, o):
176
if o.name == 'False':
180
raise UnknownType('Undefined Name')
182
def build_Add(self, o):
183
real, imag = map(self.build_Const, o.getChildren())
187
raise UnknownType('Add')
188
if not isinstance(imag, complex) or imag.real != 0.0:
189
raise UnknownType('Add')
192
def build_Getattr(self, o):
193
parent = self.build(o.expr)
194
return getattr(parent, o.attrname)
196
def build_UnarySub(self, o):
197
return -self.build_Const(o.getChildren()[0])
199
def build_UnaryAdd(self, o):
200
return self.build_Const(o.getChildren()[0])
205
return Builder().build(getObj(s))
207
def _splitlines(instring):
208
"""Split a string on lines, without losing line endings or truncating."""
211
class ConfigObjError(SyntaxError):
213
This is the base class for all errors that ConfigObj raises.
214
It is a subclass of SyntaxError.
216
def __init__(self, message='', line_number=None, line=''):
218
self.line_number = line_number
219
self.message = message
220
SyntaxError.__init__(self, message)
222
class NestingError(ConfigObjError):
224
This error indicates a level of nesting that doesn't match.
227
class ParseError(ConfigObjError):
229
This error indicates that a line is badly written.
230
It is neither a valid ``key = value`` line,
231
nor a valid section marker line.
234
class DuplicateError(ConfigObjError):
236
The keyword or section specified already exists.
239
class ConfigspecError(ConfigObjError):
241
An error occured whilst parsing a configspec.
244
class InterpolationError(ConfigObjError):
245
"""Base class for the two interpolation errors."""
247
class InterpolationLoopError(InterpolationError):
248
"""Maximum interpolation depth exceeded in string interpolation."""
250
def __init__(self, option):
251
InterpolationError.__init__(
253
'interpolation loop detected in value "%s".' % option)
255
class RepeatSectionError(ConfigObjError):
257
This error indicates additional sections in a section with a
258
``__many__`` (repeated) section.
261
class MissingInterpolationOption(InterpolationError):
262
"""A value specified for interpolation was missing."""
264
def __init__(self, option):
265
InterpolationError.__init__(
267
'missing option "%s" in interpolation.' % option)
269
class UnreprError(ConfigObjError):
270
"""An error parsing in unrepr mode."""
273
class InterpolationEngine(object):
275
A helper class to help perform string interpolation.
277
This class is an abstract base class; its descendants perform
281
# compiled regexp to use in self.interpolate()
282
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
284
def __init__(self, section):
285
# the Section instance that "owns" this engine
286
self.section = section
288
def interpolate(self, key, value):
289
def recursive_interpolate(key, value, section, backtrail):
290
"""The function that does the actual work.
292
``value``: the string we're trying to interpolate.
293
``section``: the section in which that string was found
294
``backtrail``: a dict to keep track of where we've been,
295
to detect and prevent infinite recursion loops
297
This is similar to a depth-first-search algorithm.
299
# Have we been here already?
300
if backtrail.has_key((key, section.name)):
301
# Yes - infinite loop detected
302
raise InterpolationLoopError(key)
303
# Place a marker on our backtrail so we won't come back here again
304
backtrail[(key, section.name)] = 1
306
# Now start the actual work
307
match = self._KEYCRE.search(value)
309
# The actual parsing of the match is implementation-dependent,
310
# so delegate to our helper function
311
k, v, s = self._parse_match(match)
313
# That's the signal that no further interpolation is needed
316
# Further interpolation may be needed to obtain final value
317
replacement = recursive_interpolate(k, v, s, backtrail)
318
# Replace the matched string with its final value
319
start, end = match.span()
320
value = ''.join((value[:start], replacement, value[end:]))
321
new_search_start = start + len(replacement)
322
# Pick up the next interpolation key, if any, for next time
323
# through the while loop
324
match = self._KEYCRE.search(value, new_search_start)
326
# Now safe to come back here again; remove marker from backtrail
327
del backtrail[(key, section.name)]
331
# Back in interpolate(), all we have to do is kick off the recursive
332
# function with appropriate starting values
333
value = recursive_interpolate(key, value, self.section, {})
336
def _fetch(self, key):
337
"""Helper function to fetch values from owning section.
339
Returns a 2-tuple: the value, and the section where it was found.
341
# switch off interpolation before we try and fetch anything !
342
save_interp = self.section.main.interpolation
343
self.section.main.interpolation = False
345
# Start at section that "owns" this InterpolationEngine
346
current_section = self.section
348
# try the current section first
349
val = current_section.get(key)
353
val = current_section.get('DEFAULT', {}).get(key)
356
# move up to parent and try again
357
# top-level's parent is itself
358
if current_section.parent is current_section:
359
# reached top level, time to give up
361
current_section = current_section.parent
363
# restore interpolation to previous value before returning
364
self.section.main.interpolation = save_interp
366
raise MissingInterpolationOption(key)
367
return val, current_section
369
def _parse_match(self, match):
370
"""Implementation-dependent helper function.
372
Will be passed a match object corresponding to the interpolation
373
key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
374
key in the appropriate config file section (using the ``_fetch()``
375
helper function) and return a 3-tuple: (key, value, section)
377
``key`` is the name of the key we're looking for
378
``value`` is the value found for that key
379
``section`` is a reference to the section where it was found
381
``key`` and ``section`` should be None if no further
382
interpolation should be performed on the resulting value
383
(e.g., if we interpolated "$$" and returned "$").
385
raise NotImplementedError
388
class ConfigParserInterpolation(InterpolationEngine):
389
"""Behaves like ConfigParser."""
390
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
392
def _parse_match(self, match):
394
value, section = self._fetch(key)
395
return key, value, section
398
class TemplateInterpolation(InterpolationEngine):
399
"""Behaves like string.Template."""
401
_KEYCRE = re.compile(r"""
403
(?P<escaped>\$) | # Two $ signs
404
(?P<named>[_a-z][_a-z0-9]*) | # $name format
405
{(?P<braced>[^}]*)} # ${name} format
407
""", re.IGNORECASE | re.VERBOSE)
409
def _parse_match(self, match):
410
# Valid name (in or out of braces): fetch value from section
411
key = match.group('named') or match.group('braced')
413
value, section = self._fetch(key)
414
return key, value, section
415
# Escaped delimiter (e.g., $$): return single delimiter
416
if match.group('escaped') is not None:
417
# Return None for key and section to indicate it's time to stop
418
return None, self._delimiter, None
419
# Anything else: ignore completely, just return it unchanged
420
return None, match.group(), None
422
interpolation_engines = {
423
'configparser': ConfigParserInterpolation,
424
'template': TemplateInterpolation,
429
A dictionary-like object that represents a section in a config file.
431
It does string interpolation if the 'interpolation' attribute
432
of the 'main' object is set to True.
434
Interpolation is tried first from this object, then from the 'DEFAULT'
435
section of this object, next from the parent and its 'DEFAULT' section,
436
and so on until the main object is reached.
438
A Section will behave like an ordered dictionary - following the
439
order of the ``scalars`` and ``sections`` attributes.
440
You can use this to change the order of members.
442
Iteration follows the order: scalars, then sections.
445
def __init__(self, parent, depth, main, indict=None, name=None):
447
* parent is the section above
448
* depth is the depth level of this section
449
* main is the main ConfigObj
450
* indict is a dictionary to initialise the section with
455
# used for nesting level *and* interpolation
457
# used for the interpolation attribute
459
# level of nesting depth of this Section
461
# the sequence of scalar values in this Section
463
# the sequence of sections in this Section
465
# purely for information
469
self.inline_comments = {}
473
self._configspec_comments = {}
474
self._configspec_inline_comments = {}
475
self._cs_section_comments = {}
476
self._cs_section_inline_comments = {}
480
# we do this explicitly so that __setitem__ is used properly
481
# (rather than just passing to ``dict.__init__``)
483
self[entry] = indict[entry]
485
def _interpolate(self, key, value):
487
# do we already have an interpolation engine?
488
engine = self._interpolation_engine
489
except AttributeError:
490
# not yet: first time running _interpolate(), so pick the engine
491
name = self.main.interpolation
492
if name == True: # note that "if name:" would be incorrect here
493
# backwards-compatibility: interpolation=True means use default
494
name = DEFAULT_INTERPOLATION
495
name = name.lower() # so that "Template", "template", etc. all work
496
class_ = interpolation_engines.get(name, None)
498
# invalid value for self.main.interpolation
499
self.main.interpolation = False
502
# save reference to engine so we don't have to do this again
503
engine = self._interpolation_engine = class_(self)
504
# let the engine do the actual work
505
return engine.interpolate(key, value)
507
def __getitem__(self, key):
508
"""Fetch the item and do string interpolation."""
509
val = dict.__getitem__(self, key)
510
if self.main.interpolation and isinstance(val, StringTypes):
511
return self._interpolate(key, val)
514
def __setitem__(self, key, value, unrepr=False):
516
Correctly set a value.
518
Making dictionary values Section instances.
519
(We have to special case 'Section' instances - which are also dicts)
521
Keys must be strings.
522
Values need only be strings (or lists of strings) if
523
``main.stringify`` is set.
525
`unrepr`` must be set when setting a value to a dictionary, without
526
creating a new sub-section.
528
if not isinstance(key, StringTypes):
529
raise ValueError, 'The key "%s" is not a string.' % key
531
if not self.comments.has_key(key):
532
self.comments[key] = []
533
self.inline_comments[key] = ''
534
# remove the entry from defaults
535
if key in self.defaults:
536
self.defaults.remove(key)
538
if isinstance(value, Section):
539
if not self.has_key(key):
540
self.sections.append(key)
541
dict.__setitem__(self, key, value)
542
elif isinstance(value, dict) and not unrepr:
543
# First create the new depth level,
544
# then create the section
545
if not self.has_key(key):
546
self.sections.append(key)
547
new_depth = self.depth + 1
558
if not self.has_key(key):
559
self.scalars.append(key)
560
if not self.main.stringify:
561
if isinstance(value, StringTypes):
563
elif isinstance(value, (list, tuple)):
565
if not isinstance(entry, StringTypes):
567
'Value is not a string "%s".' % entry)
569
raise TypeError, 'Value is not a string "%s".' % value
570
dict.__setitem__(self, key, value)
572
def __delitem__(self, key):
573
"""Remove items from the sequence when deleting."""
574
dict. __delitem__(self, key)
575
if key in self.scalars:
576
self.scalars.remove(key)
578
self.sections.remove(key)
579
del self.comments[key]
580
del self.inline_comments[key]
582
def get(self, key, default=None):
583
"""A version of ``get`` that doesn't bypass string interpolation."""
589
def update(self, indict):
591
A version of update that uses our ``__setitem__``.
594
self[entry] = indict[entry]
596
def pop(self, key, *args):
598
val = dict.pop(self, key, *args)
599
if key in self.scalars:
600
del self.comments[key]
601
del self.inline_comments[key]
602
self.scalars.remove(key)
603
elif key in self.sections:
604
del self.comments[key]
605
del self.inline_comments[key]
606
self.sections.remove(key)
607
if self.main.interpolation and isinstance(val, StringTypes):
608
return self._interpolate(key, val)
612
"""Pops the first (key,val)"""
613
sequence = (self.scalars + self.sections)
615
raise KeyError, ": 'popitem(): dictionary is empty'"
623
A version of clear that also affects scalars/sections
624
Also clears comments and configspec.
626
Leaves other attributes alone :
627
depth/main/parent are not affected
633
self.inline_comments = {}
636
def setdefault(self, key, default=None):
637
"""A version of setdefault that sets sequence if appropriate."""
646
return zip((self.scalars + self.sections), self.values())
650
return (self.scalars + self.sections)
654
return [self[key] for key in (self.scalars + self.sections)]
658
return iter(self.items())
662
return iter((self.scalars + self.sections))
666
def itervalues(self):
668
return iter(self.values())
671
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
672
for key in (self.scalars + self.sections)])
676
# Extra methods - not in a normal dictionary
680
Return a deepcopy of self as a dictionary.
682
All members that are ``Section`` instances are recursively turned to
683
ordinary dictionaries - by calling their ``dict`` method.
693
this_entry = self[entry]
694
if isinstance(this_entry, Section):
695
this_entry = this_entry.dict()
696
elif isinstance(this_entry, list):
697
# create a copy rather than a reference
698
this_entry = list(this_entry)
699
elif isinstance(this_entry, tuple):
700
# create a copy rather than a reference
701
this_entry = tuple(this_entry)
702
newdict[entry] = this_entry
705
def merge(self, indict):
707
A recursive update - useful for merging config files.
709
>>> a = '''[section1]
712
... more_options = False
713
... # end of file'''.splitlines()
714
>>> b = '''# File is user.ini
717
... # end of file'''.splitlines()
718
>>> c1 = ConfigObj(b)
719
>>> c2 = ConfigObj(a)
722
{'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
724
for key, val in indict.items():
725
if (key in self and isinstance(self[key], dict) and
726
isinstance(val, dict)):
731
def rename(self, oldkey, newkey):
733
Change a keyname to another, without changing position in sequence.
735
Implemented so that transformations can be made on keys,
736
as well as on values. (used by encode and decode)
738
Also renames comments.
740
if oldkey in self.scalars:
741
the_list = self.scalars
742
elif oldkey in self.sections:
743
the_list = self.sections
745
raise KeyError, 'Key "%s" not found.' % oldkey
746
pos = the_list.index(oldkey)
749
dict.__delitem__(self, oldkey)
750
dict.__setitem__(self, newkey, val)
751
the_list.remove(oldkey)
752
the_list.insert(pos, newkey)
753
comm = self.comments[oldkey]
754
inline_comment = self.inline_comments[oldkey]
755
del self.comments[oldkey]
756
del self.inline_comments[oldkey]
757
self.comments[newkey] = comm
758
self.inline_comments[newkey] = inline_comment
760
def walk(self, function, raise_errors=True,
761
call_on_sections=False, **keywargs):
763
Walk every member and call a function on the keyword and value.
765
Return a dictionary of the return values
767
If the function raises an exception, raise the errror
768
unless ``raise_errors=False``, in which case set the return value to
771
Any unrecognised keyword arguments you pass to walk, will be pased on
772
to the function you pass in.
774
Note: if ``call_on_sections`` is ``True`` then - on encountering a
775
subsection, *first* the function is called for the *whole* subsection,
776
and then recurses into it's members. This means your function must be
777
able to handle strings, dictionaries and lists. This allows you
778
to change the key of subsections as well as for ordinary members. The
779
return value when called on the whole subsection has to be discarded.
781
See the encode and decode methods for examples, including functions.
785
You can use ``walk`` to transform the names of members of a section
786
but you mustn't add or delete members.
788
>>> config = '''[XXXXsection]
789
... XXXXkey = XXXXvalue'''.splitlines()
790
>>> cfg = ConfigObj(config)
792
{'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
793
>>> def transform(section, key):
794
... val = section[key]
795
... newkey = key.replace('XXXX', 'CLIENT1')
796
... section.rename(key, newkey)
797
... if isinstance(val, (tuple, list, dict)):
800
... val = val.replace('XXXX', 'CLIENT1')
801
... section[newkey] = val
802
>>> cfg.walk(transform, call_on_sections=True)
803
{'CLIENT1section': {'CLIENT1key': None}}
805
{'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
809
for i in range(len(self.scalars)):
810
entry = self.scalars[i]
812
val = function(self, entry, **keywargs)
813
# bound again in case name has changed
814
entry = self.scalars[i]
820
entry = self.scalars[i]
823
for i in range(len(self.sections)):
824
entry = self.sections[i]
827
function(self, entry, **keywargs)
832
entry = self.sections[i]
834
# bound again in case name has changed
835
entry = self.sections[i]
836
# previous result is discarded
837
out[entry] = self[entry].walk(
839
raise_errors=raise_errors,
840
call_on_sections=call_on_sections,
844
def decode(self, encoding):
846
Decode all strings and values to unicode, using the specified encoding.
848
Works with subsections and list values.
850
Uses the ``walk`` method.
852
Testing ``encode`` and ``decode``.
854
>>> m.decode('ascii')
855
>>> def testuni(val):
856
... for entry in val:
857
... if not isinstance(entry, unicode):
858
... print >> sys.stderr, type(entry)
859
... raise AssertionError, 'decode failed.'
860
... if isinstance(val[entry], dict):
861
... testuni(val[entry])
862
... elif not isinstance(val[entry], unicode):
863
... raise AssertionError, 'decode failed.'
865
>>> m.encode('ascii')
869
warn('use of ``decode`` is deprecated.', DeprecationWarning)
870
def decode(section, key, encoding=encoding, warn=True):
873
if isinstance(val, (list, tuple)):
876
newval.append(entry.decode(encoding))
877
elif isinstance(val, dict):
880
newval = val.decode(encoding)
881
newkey = key.decode(encoding)
882
section.rename(key, newkey)
883
section[newkey] = newval
884
# using ``call_on_sections`` allows us to modify section names
885
self.walk(decode, call_on_sections=True)
887
def encode(self, encoding):
889
Encode all strings and values from unicode,
890
using the specified encoding.
892
Works with subsections and list values.
893
Uses the ``walk`` method.
895
warn('use of ``encode`` is deprecated.', DeprecationWarning)
896
def encode(section, key, encoding=encoding):
899
if isinstance(val, (list, tuple)):
902
newval.append(entry.encode(encoding))
903
elif isinstance(val, dict):
906
newval = val.encode(encoding)
907
newkey = key.encode(encoding)
908
section.rename(key, newkey)
909
section[newkey] = newval
910
self.walk(encode, call_on_sections=True)
912
def istrue(self, key):
913
"""A deprecated version of ``as_bool``."""
914
warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
915
'instead.', DeprecationWarning)
916
return self.as_bool(key)
918
def as_bool(self, key):
920
Accepts a key as input. The corresponding value must be a string or
921
the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
922
retain compatibility with Python 2.2.
924
If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
927
If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
930
``as_bool`` is not case sensitive.
932
Any other input will raise a ``ValueError``.
937
Traceback (most recent call last):
938
ValueError: Value "fish" is neither True nor False
953
if not isinstance(val, StringTypes):
956
return self.main._bools[val.lower()]
958
raise ValueError('Value "%s" is neither True nor False' % val)
960
def as_int(self, key):
962
A convenience method which coerces the specified value to an integer.
964
If the value is an invalid literal for ``int``, a ``ValueError`` will
970
Traceback (most recent call last):
971
ValueError: invalid literal for int(): fish
977
Traceback (most recent call last):
978
ValueError: invalid literal for int(): 3.2
980
return int(self[key])
982
def as_float(self, key):
984
A convenience method which coerces the specified value to a float.
986
If the value is an invalid literal for ``float``, a ``ValueError`` will
992
Traceback (most recent call last):
993
ValueError: invalid literal for float(): fish
1001
return float(self[key])
1004
class ConfigObj(Section):
1005
"""An object to read, create, and write config files."""
1007
_keyword = re.compile(r'''^ # line start
1010
(?:".*?")| # double quotes
1011
(?:'.*?')| # single quotes
1012
(?:[^'"=].*?) # no quotes
1015
(.*) # value (including list values and comments)
1020
_sectionmarker = re.compile(r'''^
1021
(\s*) # 1: indentation
1022
((?:\[\s*)+) # 2: section marker open
1023
( # 3: section name open
1024
(?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1025
(?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1026
(?:[^'"\s].*?) # at least one non-space unquoted
1027
) # section name close
1028
((?:\s*\])+) # 4: section marker close
1029
\s*(\#.*)? # 5: optional comment
1033
# this regexp pulls list values out as a single string
1034
# or single values and comments
1035
# FIXME: this regex adds a '' to the end of comma terminated lists
1036
# workaround in ``_handle_value``
1037
_valueexp = re.compile(r'''^
1043
(?:".*?")| # double quotes
1044
(?:'.*?')| # single quotes
1045
(?:[^'",\#][^,\#]*?) # unquoted
1048
)* # match all list items ending in a comma (if any)
1051
(?:".*?")| # double quotes
1052
(?:'.*?')| # single quotes
1053
(?:[^'",\#\s][^,]*?)| # unquoted
1054
(?:(?<!,)) # Empty value
1055
)? # last item in a list - or string value
1057
(,) # alternatively a single comma - empty list
1059
\s*(\#.*)? # optional comment
1063
# use findall to get the members of a list value
1064
_listvalueexp = re.compile(r'''
1066
(?:".*?")| # double quotes
1067
(?:'.*?')| # single quotes
1068
(?:[^'",\#].*?) # unquoted
1074
# this regexp is used for the value
1075
# when lists are switched off
1076
_nolistvalue = re.compile(r'''^
1078
(?:".*?")| # double quotes
1079
(?:'.*?')| # single quotes
1080
(?:[^'"\#].*?)| # unquoted
1083
\s*(\#.*)? # optional comment
1087
# regexes for finding triple quoted values on one line
1088
_single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1089
_single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1090
_multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1091
_multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1094
"'''": (_single_line_single, _multi_line_single),
1095
'"""': (_single_line_double, _multi_line_double),
1098
# Used by the ``istrue`` Section method
1100
'yes': True, 'no': False,
1101
'on': True, 'off': False,
1102
'1': True, '0': False,
1103
'true': True, 'false': False,
1106
def __init__(self, infile=None, options=None, **kwargs):
1108
Parse or create a config file object.
1110
``ConfigObj(infile=None, options=None, **kwargs)``
1117
options = dict(options)
1118
# keyword arguments take precedence over an options dictionary
1119
options.update(kwargs)
1120
# init the superclass
1121
Section.__init__(self, self, 0, self)
1123
defaults = OPTION_DEFAULTS.copy()
1124
for entry in options.keys():
1125
if entry not in defaults.keys():
1126
raise TypeError, 'Unrecognised option "%s".' % entry
1127
# TODO: check the values too.
1129
# Add any explicit options to the defaults
1130
defaults.update(options)
1132
# initialise a few variables
1133
self.filename = None
1135
self.raise_errors = defaults['raise_errors']
1136
self.interpolation = defaults['interpolation']
1137
self.list_values = defaults['list_values']
1138
self.create_empty = defaults['create_empty']
1139
self.file_error = defaults['file_error']
1140
self.stringify = defaults['stringify']
1141
self.indent_type = defaults['indent_type']
1142
self.encoding = defaults['encoding']
1143
self.default_encoding = defaults['default_encoding']
1145
self.newlines = None
1146
self.write_empty_values = defaults['write_empty_values']
1147
self.unrepr = defaults['unrepr']
1149
self.initial_comment = []
1150
self.final_comment = []
1152
self._terminated = False
1154
if isinstance(infile, StringTypes):
1155
self.filename = infile
1156
if os.path.isfile(infile):
1157
infile = open(infile).read() or []
1158
elif self.file_error:
1159
# raise an error if the file doesn't exist
1160
raise IOError, 'Config file not found: "%s".' % self.filename
1162
# file doesn't already exist
1163
if self.create_empty:
1164
# this is a good test that the filename specified
1165
# isn't impossible - like on a non existent device
1166
h = open(infile, 'w')
1170
elif isinstance(infile, (list, tuple)):
1171
infile = list(infile)
1172
elif isinstance(infile, dict):
1174
# the Section class handles creating subsections
1175
if isinstance(infile, ConfigObj):
1176
# get a copy of our ConfigObj
1177
infile = infile.dict()
1178
for entry in infile:
1179
self[entry] = infile[entry]
1181
if defaults['configspec'] is not None:
1182
self._handle_configspec(defaults['configspec'])
1184
self.configspec = None
1186
elif hasattr(infile, 'read'):
1187
# This supports file like objects
1188
infile = infile.read() or []
1189
# needs splitting into lines - but needs doing *after* decoding
1190
# in case it's not an 8 bit encoding
1192
raise TypeError, ('infile must be a filename,'
1193
' file like object, or list of lines.')
1196
# don't do it for the empty ConfigObj
1197
infile = self._handle_bom(infile)
1198
# infile is now *always* a list
1200
# Set the newlines attribute (first line ending it finds)
1201
# and strip trailing '\n' or '\r' from lines
1203
if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1205
for end in ('\r\n', '\n', '\r'):
1206
if line.endswith(end):
1210
if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'):
1211
self._terminated = True
1212
infile = [line.rstrip('\r\n') for line in infile]
1215
# if we had any errors, now is the time to raise them
1217
info = "at line %s." % self._errors[0].line_number
1218
if len(self._errors) > 1:
1219
msg = ("Parsing failed with several errors.\nFirst error %s" %
1221
error = ConfigObjError(msg)
1223
error = self._errors[0]
1224
# set the errors attribute; it's a list of tuples:
1225
# (error_type, message, line_number)
1226
error.errors = self._errors
1227
# set the config attribute
1230
# delete private attributes
1233
if defaults['configspec'] is None:
1234
self.configspec = None
1236
self._handle_configspec(defaults['configspec'])
1239
return 'ConfigObj({%s})' % ', '.join(
1240
[('%s: %s' % (repr(key), repr(self[key]))) for key in
1241
(self.scalars + self.sections)])
1243
def _handle_bom(self, infile):
1245
Handle any BOM, and decode if necessary.
1247
If an encoding is specified, that *must* be used - but the BOM should
1248
still be removed (and the BOM attribute set).
1250
(If the encoding is wrongly specified, then a BOM for an alternative
1251
encoding won't be discovered or removed.)
1253
If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1254
removed. The BOM attribute will be set. UTF16 will be decoded to
1257
NOTE: This method must not be called with an empty ``infile``.
1259
Specifying the *wrong* encoding is likely to cause a
1260
``UnicodeDecodeError``.
1262
``infile`` must always be returned as a list of lines, but may be
1263
passed in as a single string.
1265
if ((self.encoding is not None) and
1266
(self.encoding.lower() not in BOM_LIST)):
1267
# No need to check for a BOM
1268
# the encoding specified doesn't have one
1270
return self._decode(infile, self.encoding)
1272
if isinstance(infile, (list, tuple)):
1276
if self.encoding is not None:
1277
# encoding explicitly supplied
1278
# And it could have an associated BOM
1279
# TODO: if encoding is just UTF16 - we ought to check for both
1280
# TODO: big endian and little endian versions.
1281
enc = BOM_LIST[self.encoding.lower()]
1283
# For UTF16 we try big endian and little endian
1284
for BOM, (encoding, final_encoding) in BOMS.items():
1285
if not final_encoding:
1288
if infile.startswith(BOM):
1291
# Don't need to remove BOM
1292
return self._decode(infile, encoding)
1294
# If we get this far, will *probably* raise a DecodeError
1295
# As it doesn't appear to start with a BOM
1296
return self._decode(infile, self.encoding)
1300
if not line.startswith(BOM):
1301
return self._decode(infile, self.encoding)
1303
newline = line[len(BOM):]
1306
if isinstance(infile, (list, tuple)):
1311
return self._decode(infile, self.encoding)
1313
# No encoding specified - so we need to check for UTF8/UTF16
1314
for BOM, (encoding, final_encoding) in BOMS.items():
1315
if not line.startswith(BOM):
1319
self.encoding = final_encoding
1320
if not final_encoding:
1324
newline = line[len(BOM):]
1325
if isinstance(infile, (list, tuple)):
1329
# UTF8 - don't decode
1330
if isinstance(infile, StringTypes):
1331
return infile.splitlines(True)
1334
# UTF16 - have to decode
1335
return self._decode(infile, encoding)
1337
# No BOM discovered and no encoding specified, just return
1338
if isinstance(infile, StringTypes):
1339
# infile read from a file will be a single string
1340
return infile.splitlines(True)
1344
def _a_to_u(self, aString):
1345
"""Decode ASCII strings to unicode if a self.encoding is specified."""
1347
return aString.decode('ascii')
1351
def _decode(self, infile, encoding):
1353
Decode infile to unicode. Using the specified encoding.
1355
if is a string, it also needs converting to a list.
1357
if isinstance(infile, StringTypes):
1359
# NOTE: Could raise a ``UnicodeDecodeError``
1360
return infile.decode(encoding).splitlines(True)
1361
for i, line in enumerate(infile):
1362
if not isinstance(line, unicode):
1363
# NOTE: The isinstance test here handles mixed lists of unicode/string
1364
# NOTE: But the decode will break on any non-string values
1365
# NOTE: Or could raise a ``UnicodeDecodeError``
1366
infile[i] = line.decode(encoding)
1369
def _decode_element(self, line):
1370
"""Decode element to unicode if necessary."""
1371
if not self.encoding:
1373
if isinstance(line, str) and self.default_encoding:
1374
return line.decode(self.default_encoding)
1377
def _str(self, value):
1379
Used by ``stringify`` within validate, to turn non-string values
1382
if not isinstance(value, StringTypes):
1387
def _parse(self, infile):
1388
"""Actually parse the config file."""
1389
temp_list_values = self.list_values
1391
self.list_values = False
1395
maxline = len(infile) - 1
1397
reset_comment = False
1398
while cur_index < maxline:
1402
line = infile[cur_index]
1403
sline = line.strip()
1404
# do we have anything on the line ?
1405
if not sline or sline.startswith('#'):
1406
reset_comment = False
1407
comment_list.append(line)
1410
# preserve initial comment
1411
self.initial_comment = comment_list
1414
reset_comment = True
1415
# first we check if it's a section marker
1416
mat = self._sectionmarker.match(line)
1419
(indent, sect_open, sect_name, sect_close, comment) = (
1421
if indent and (self.indent_type is None):
1422
self.indent_type = indent
1423
cur_depth = sect_open.count('[')
1424
if cur_depth != sect_close.count(']'):
1426
"Cannot compute the section depth at line %s.",
1427
NestingError, infile, cur_index)
1430
if cur_depth < this_section.depth:
1431
# the new section is dropping back to a previous level
1433
parent = self._match_depth(
1438
"Cannot compute nesting level at line %s.",
1439
NestingError, infile, cur_index)
1441
elif cur_depth == this_section.depth:
1442
# the new section is a sibling of the current section
1443
parent = this_section.parent
1444
elif cur_depth == this_section.depth + 1:
1445
# the new section is a child the current section
1446
parent = this_section
1449
"Section too nested at line %s.",
1450
NestingError, infile, cur_index)
1452
sect_name = self._unquote(sect_name)
1453
if parent.has_key(sect_name):
1455
'Duplicate section name at line %s.',
1456
DuplicateError, infile, cur_index)
1458
# create the new section
1459
this_section = Section(
1464
parent[sect_name] = this_section
1465
parent.inline_comments[sect_name] = comment
1466
parent.comments[sect_name] = comment_list
1469
# it's not a section marker,
1470
# so it should be a valid ``key = value`` line
1471
mat = self._keyword.match(line)
1473
# it neither matched as a keyword
1474
# or a section marker
1476
'Invalid line at line "%s".',
1477
ParseError, infile, cur_index)
1479
# is a keyword value
1480
# value will include any inline comment
1481
(indent, key, value) = mat.groups()
1482
if indent and (self.indent_type is None):
1483
self.indent_type = indent
1484
# check for a multiline value
1485
if value[:3] in ['"""', "'''"]:
1487
(value, comment, cur_index) = self._multiline(
1488
value, infile, cur_index, maxline)
1491
'Parse error in value at line %s.',
1492
ParseError, infile, cur_index)
1498
value = unrepr(value)
1499
except Exception, e:
1500
if type(e) == UnknownType:
1501
msg = 'Unknown name or type in value at line %s.'
1503
msg = 'Parse error in value at line %s.'
1504
self._handle_error(msg, UnreprError, infile,
1511
value = unrepr(value)
1512
except Exception, e:
1513
if isinstance(e, UnknownType):
1514
msg = 'Unknown name or type in value at line %s.'
1516
msg = 'Parse error in value at line %s.'
1517
self._handle_error(msg, UnreprError, infile,
1521
# extract comment and lists
1523
(value, comment) = self._handle_value(value)
1526
'Parse error in value at line %s.',
1527
ParseError, infile, cur_index)
1530
key = self._unquote(key)
1531
if this_section.has_key(key):
1533
'Duplicate keyword name at line %s.',
1534
DuplicateError, infile, cur_index)
1537
# we set unrepr because if we have got this far we will never
1538
# be creating a new section
1539
this_section.__setitem__(key, value, unrepr=True)
1540
this_section.inline_comments[key] = comment
1541
this_section.comments[key] = comment_list
1544
if self.indent_type is None:
1545
# no indentation used, set the type accordingly
1546
self.indent_type = ''
1548
if self._terminated:
1549
comment_list.append('')
1550
# preserve the final comment
1551
if not self and not self.initial_comment:
1552
self.initial_comment = comment_list
1553
elif not reset_comment:
1554
self.final_comment = comment_list
1555
self.list_values = temp_list_values
1557
def _match_depth(self, sect, depth):
1559
Given a section and a depth level, walk back through the sections
1560
parents to see if the depth level matches a previous section.
1562
Return a reference to the right section,
1563
or raise a SyntaxError.
1565
while depth < sect.depth:
1566
if sect is sect.parent:
1567
# we've reached the top level already
1570
if sect.depth == depth:
1572
# shouldn't get here
1575
def _handle_error(self, text, ErrorClass, infile, cur_index):
1577
Handle an error according to the error settings.
1579
Either raise the error or store it.
1580
The error will have occured at ``cur_index``
1582
line = infile[cur_index]
1584
message = text % cur_index
1585
error = ErrorClass(message, cur_index, line)
1586
if self.raise_errors:
1587
# raise the error - parsing stops here
1590
# reraise when parsing has finished
1591
self._errors.append(error)
1593
def _unquote(self, value):
1594
"""Return an unquoted version of a value"""
1595
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1599
def _quote(self, value, multiline=True):
1601
Return a safely quoted version of a value.
1603
Raise a ConfigObjError if the value cannot be safely quoted.
1604
If multiline is ``True`` (default) then use triple quotes
1607
Don't quote values that don't need it.
1608
Recursively quote members of a list and return a comma joined list.
1609
Multiline is ``False`` for lists.
1610
Obey list syntax for empty and single member lists.
1612
If ``list_values=False`` then the value is only quoted if it contains
1613
a ``\n`` (is multiline).
1615
If ``write_empty_values`` is set, and the value is an empty string, it
1618
if multiline and self.write_empty_values and value == '':
1619
# Only if multiline is set, so that it is used for values not
1620
# keys, and not values that are part of a list
1622
if multiline and isinstance(value, (list, tuple)):
1625
elif len(value) == 1:
1626
return self._quote(value[0], multiline=False) + ','
1627
return ', '.join([self._quote(val, multiline=False)
1629
if not isinstance(value, StringTypes):
1633
raise TypeError, 'Value "%s" is not a string.' % value
1637
wspace_plus = ' \r\t\n\v\t\'"'
1642
if (not self.list_values and '\n' not in value) or not (multiline and
1643
((("'" in value) and ('"' in value)) or ('\n' in value))):
1644
if not self.list_values:
1645
# we don't quote if ``list_values=False``
1647
# for normal values either single or double quotes will do
1649
# will only happen if multiline is off - e.g. '\n' in key
1650
raise ConfigObjError, ('Value "%s" cannot be safely quoted.' %
1652
elif ((value[0] not in wspace_plus) and
1653
(value[-1] not in wspace_plus) and
1654
(',' not in value)):
1657
if ("'" in value) and ('"' in value):
1658
raise ConfigObjError, (
1659
'Value "%s" cannot be safely quoted.' % value)
1665
# if value has '\n' or "'" *and* '"', it will need triple quotes
1666
if (value.find('"""') != -1) and (value.find("'''") != -1):
1667
raise ConfigObjError, (
1668
'Value "%s" cannot be safely quoted.' % value)
1669
if value.find('"""') == -1:
1675
def _handle_value(self, value):
1677
Given a value string, unquote, remove comment,
1678
handle lists. (including empty and single member lists)
1680
# do we look for lists in values ?
1681
if not self.list_values:
1682
mat = self._nolistvalue.match(value)
1685
# NOTE: we don't unquote here
1688
mat = self._valueexp.match(value)
1690
# the value is badly constructed, probably badly quoted,
1691
# or an invalid list
1693
(list_values, single, empty_list, comment) = mat.groups()
1694
if (list_values == '') and (single is None):
1695
# change this if you want to accept empty values
1697
# NOTE: note there is no error handling from here if the regex
1698
# is wrong: then incorrect values will slip through
1699
if empty_list is not None:
1700
# the single comma - meaning an empty list
1701
return ([], comment)
1702
if single is not None:
1703
# handle empty values
1704
if list_values and not single:
1705
# FIXME: the '' is a workaround because our regex now matches
1706
# '' at the end of a list if it has a trailing comma
1709
single = single or '""'
1710
single = self._unquote(single)
1711
if list_values == '':
1713
return (single, comment)
1714
the_list = self._listvalueexp.findall(list_values)
1715
the_list = [self._unquote(val) for val in the_list]
1716
if single is not None:
1717
the_list += [single]
1718
return (the_list, comment)
1720
def _multiline(self, value, infile, cur_index, maxline):
1721
"""Extract the value, where we are in a multiline situation."""
1723
newvalue = value[3:]
1724
single_line = self._triple_quote[quot][0]
1725
multi_line = self._triple_quote[quot][1]
1726
mat = single_line.match(value)
1728
retval = list(mat.groups())
1729
retval.append(cur_index)
1731
elif newvalue.find(quot) != -1:
1732
# somehow the triple quote is missing
1735
while cur_index < maxline:
1738
line = infile[cur_index]
1739
if line.find(quot) == -1:
1742
# end of multiline, process it
1745
# we've got to the end of the config, oops...
1747
mat = multi_line.match(line)
1749
# a badly formed line
1751
(value, comment) = mat.groups()
1752
return (newvalue + value, comment, cur_index)
1754
def _handle_configspec(self, configspec):
1755
"""Parse the configspec."""
1756
# FIXME: Should we check that the configspec was created with the
1757
# correct settings ? (i.e. ``list_values=False``)
1758
if not isinstance(configspec, ConfigObj):
1760
configspec = ConfigObj(
1765
except ConfigObjError, e:
1766
# FIXME: Should these errors have a reference
1767
# to the already parsed ConfigObj ?
1768
raise ConfigspecError('Parsing configspec failed: %s' % e)
1770
raise IOError('Reading configspec failed: %s' % e)
1771
self._set_configspec_value(configspec, self)
1773
def _set_configspec_value(self, configspec, section):
1774
"""Used to recursively set configspec values."""
1775
if '__many__' in configspec.sections:
1776
section.configspec['__many__'] = configspec['__many__']
1777
if len(configspec.sections) > 1:
1778
# FIXME: can we supply any useful information here ?
1779
raise RepeatSectionError
1780
if hasattr(configspec, 'initial_comment'):
1781
section._configspec_initial_comment = configspec.initial_comment
1782
section._configspec_final_comment = configspec.final_comment
1783
section._configspec_encoding = configspec.encoding
1784
section._configspec_BOM = configspec.BOM
1785
section._configspec_newlines = configspec.newlines
1786
section._configspec_indent_type = configspec.indent_type
1787
for entry in configspec.scalars:
1788
section._configspec_comments[entry] = configspec.comments[entry]
1789
section._configspec_inline_comments[entry] = (
1790
configspec.inline_comments[entry])
1791
section.configspec[entry] = configspec[entry]
1792
section._order.append(entry)
1793
for entry in configspec.sections:
1794
if entry == '__many__':
1796
section._cs_section_comments[entry] = configspec.comments[entry]
1797
section._cs_section_inline_comments[entry] = (
1798
configspec.inline_comments[entry])
1799
if not section.has_key(entry):
1801
self._set_configspec_value(configspec[entry], section[entry])
1803
def _handle_repeat(self, section, configspec):
1804
"""Dynamically assign configspec for repeated section."""
1806
section_keys = configspec.sections
1807
scalar_keys = configspec.scalars
1808
except AttributeError:
1809
section_keys = [entry for entry in configspec
1810
if isinstance(configspec[entry], dict)]
1811
scalar_keys = [entry for entry in configspec
1812
if not isinstance(configspec[entry], dict)]
1813
if '__many__' in section_keys and len(section_keys) > 1:
1814
# FIXME: can we supply any useful information here ?
1815
raise RepeatSectionError
1818
for entry in scalar_keys:
1819
val = configspec[entry]
1820
scalars[entry] = val
1821
for entry in section_keys:
1822
val = configspec[entry]
1823
if entry == '__many__':
1824
scalars[entry] = val
1826
sections[entry] = val
1828
section.configspec = scalars
1829
for entry in sections:
1830
if not section.has_key(entry):
1832
self._handle_repeat(section[entry], sections[entry])
1834
def _write_line(self, indent_string, entry, this_entry, comment):
1835
"""Write an individual line, for the write method"""
1836
# NOTE: the calls to self._quote here handles non-StringType values.
1838
val = self._decode_element(self._quote(this_entry))
1840
val = repr(this_entry)
1841
return '%s%s%s%s%s' % (
1843
self._decode_element(self._quote(entry, multiline=False)),
1844
self._a_to_u(' = '),
1846
self._decode_element(comment))
1848
def _write_marker(self, indent_string, depth, entry, comment):
1849
"""Write a section marker line"""
1850
return '%s%s%s%s%s' % (
1852
self._a_to_u('[' * depth),
1853
self._quote(self._decode_element(entry), multiline=False),
1854
self._a_to_u(']' * depth),
1855
self._decode_element(comment))
1857
def _handle_comment(self, comment):
1858
"""Deal with a comment."""
1861
start = self.indent_type
1862
if not comment.startswith('#'):
1863
start += self._a_to_u(' # ')
1864
return (start + comment)
1868
def write(self, outfile=None, section=None):
1870
Write the current ConfigObj as a file
1872
tekNico: FIXME: use StringIO instead of real files
1874
>>> filename = a.filename
1875
>>> a.filename = 'test.ini'
1877
>>> a.filename = filename
1878
>>> a == ConfigObj('test.ini', raise_errors=True)
1881
if self.indent_type is None:
1882
# this can be true if initialised from a dictionary
1883
self.indent_type = DEFAULT_INDENT_TYPE
1886
cs = self._a_to_u('#')
1887
csp = self._a_to_u('# ')
1889
int_val = self.interpolation
1890
self.interpolation = False
1892
for line in self.initial_comment:
1893
line = self._decode_element(line)
1894
stripped_line = line.strip()
1895
if stripped_line and not stripped_line.startswith(cs):
1899
indent_string = self.indent_type * section.depth
1900
for entry in (section.scalars + section.sections):
1901
if entry in section.defaults:
1902
# don't write out default values
1904
for comment_line in section.comments[entry]:
1905
comment_line = self._decode_element(comment_line.lstrip())
1906
if comment_line and not comment_line.startswith(cs):
1907
comment_line = csp + comment_line
1908
out.append(indent_string + comment_line)
1909
this_entry = section[entry]
1910
comment = self._handle_comment(section.inline_comments[entry])
1912
if isinstance(this_entry, dict):
1914
out.append(self._write_marker(
1919
out.extend(self.write(section=this_entry))
1921
out.append(self._write_line(
1928
for line in self.final_comment:
1929
line = self._decode_element(line)
1930
stripped_line = line.strip()
1931
if stripped_line and not stripped_line.startswith(cs):
1934
self.interpolation = int_val
1936
if section is not self:
1939
if (self.filename is None) and (outfile is None):
1940
# output a list of lines
1941
# might need to encode
1942
# NOTE: This will *screw* UTF16, each line will start with the BOM
1944
out = [l.encode(self.encoding) for l in out]
1945
if (self.BOM and ((self.encoding is None) or
1946
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1950
out[0] = BOM_UTF8 + out[0]
1953
# Turn the list to a string, joined with correct newlines
1954
output = (self._a_to_u(self.newlines or os.linesep)
1957
output = output.encode(self.encoding)
1958
if (self.BOM and ((self.encoding is None) or
1959
(BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
1961
output = BOM_UTF8 + output
1962
if outfile is not None:
1963
outfile.write(output)
1965
h = open(self.filename, 'wb')
1969
def validate(self, validator, preserve_errors=False, copy=False,
1972
Test the ConfigObj against a configspec.
1974
It uses the ``validator`` object from *validate.py*.
1976
To run ``validate`` on the current ConfigObj, call: ::
1978
test = config.validate(validator)
1980
(Normally having previously passed in the configspec when the ConfigObj
1981
was created - you can dynamically assign a dictionary of checks to the
1982
``configspec`` attribute of a section though).
1984
It returns ``True`` if everything passes, or a dictionary of
1985
pass/fails (True/False). If every member of a subsection passes, it
1986
will just have the value ``True``. (It also returns ``False`` if all
1989
In addition, it converts the values from strings to their native
1990
types if their checks pass (and ``stringify`` is set).
1992
If ``preserve_errors`` is ``True`` (``False`` is default) then instead
1993
of a marking a fail with a ``False``, it will preserve the actual
1994
exception object. This can contain info about the reason for failure.
1995
For example the ``VdtValueTooSmallError`` indeicates that the value
1996
supplied was too small. If a value (or section) is missing it will
1997
still be marked as ``False``.
1999
You must have the validate module to use ``preserve_errors=True``.
2001
You can then use the ``flatten_errors`` function to turn your nested
2002
results dictionary into a flattened list of failures - useful for
2003
displaying meaningful error messages.
2006
if self.configspec is None:
2007
raise ValueError, 'No configspec supplied.'
2010
from validate import VdtMissingValue
2012
VdtMissingValue = None
2013
if VdtMissingValue is None:
2014
raise ImportError('Missing validate module.')
2017
spec_section = section.configspec
2018
if copy and hasattr(section, '_configspec_initial_comment'):
2019
section.initial_comment = section._configspec_initial_comment
2020
section.final_comment = section._configspec_final_comment
2021
section.encoding = section._configspec_encoding
2022
section.BOM = section._configspec_BOM
2023
section.newlines = section._configspec_newlines
2024
section.indent_type = section._configspec_indent_type
2025
if '__many__' in section.configspec:
2026
many = spec_section['__many__']
2027
# dynamically assign the configspecs
2028
# for the sections below
2029
for entry in section.sections:
2030
self._handle_repeat(section[entry], many)
2035
order = [k for k in section._order if k in spec_section]
2036
order += [k for k in spec_section if k not in order]
2038
if entry == '__many__':
2040
if (not entry in section.scalars) or (entry in section.defaults):
2042
# or entries from defaults
2045
if copy and not entry in section.scalars:
2047
section.comments[entry] = (
2048
section._configspec_comments.get(entry, []))
2049
section.inline_comments[entry] = (
2050
section._configspec_inline_comments.get(entry, ''))
2054
val = section[entry]
2056
check = validator.check(spec_section[entry],
2060
except validator.baseErrorClass, e:
2061
if not preserve_errors or isinstance(e, VdtMissingValue):
2064
# preserve the error
2071
if self.stringify or missing:
2072
# if we are doing type conversion
2073
# or the value is a supplied default
2074
if not self.stringify:
2075
if isinstance(check, (list, tuple)):
2077
check = [self._str(item) for item in check]
2078
elif missing and check is None:
2079
# convert the None from a default to a ''
2082
check = self._str(check)
2083
if (check != val) or missing:
2084
section[entry] = check
2085
if not copy and missing and entry not in section.defaults:
2086
section.defaults.append(entry)
2088
# Missing sections will have been created as empty ones when the
2089
# configspec was read.
2090
for entry in section.sections:
2091
# FIXME: this means DEFAULT is not copied in copy mode
2092
if section is self and entry == 'DEFAULT':
2095
section.comments[entry] = section._cs_section_comments[entry]
2096
section.inline_comments[entry] = (
2097
section._cs_section_inline_comments[entry])
2098
check = self.validate(validator, preserve_errors=preserve_errors,
2099
copy=copy, section=section[entry])
2116
class SimpleVal(object):
2119
Can be used to check that all members expected are present.
2121
To use it, provide a configspec with all your members in (the value given
2122
will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2123
method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2124
members are present, or a dictionary with True/False meaning
2125
present/missing. (Whole missing sections will be replaced with ``False``)
2129
self.baseErrorClass = ConfigObjError
2131
def check(self, check, member, missing=False):
2132
"""A dummy check method, always returns the value unchanged."""
2134
raise self.baseErrorClass
2137
# Check / processing functions for options
2138
def flatten_errors(cfg, res, levels=None, results=None):
2140
An example function that will turn a nested dictionary of results
2141
(as returned by ``ConfigObj.validate``) into a flat list.
2143
``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2144
dictionary returned by ``validate``.
2146
(This is a recursive function, so you shouldn't use the ``levels`` or
2147
``results`` arguments - they are used by the function.
2149
Returns a list of keys that failed. Each member of the list is a tuple :
2152
([list of sections...], key, result)
2154
If ``validate`` was called with ``preserve_errors=False`` (the default)
2155
then ``result`` will always be ``False``.
2157
*list of sections* is a flattened list of sections that the key was found
2160
If the section was missing then key will be ``None``.
2162
If the value (or section) was missing then ``result`` will be ``False``.
2164
If ``validate`` was called with ``preserve_errors=True`` and a value
2165
was present, but failed the check, then ``result`` will be the exception
2166
object returned. You can use this as a string that describes the failure.
2168
For example *The value "3" is of the wrong type*.
2171
>>> vtor = validate.Validator()
2177
... another_option = Probably
2179
... another_option = True
2186
... option1 = boolean()
2187
... option2 = boolean()
2188
... option3 = boolean(default=Bad_value)
2190
... option1 = boolean()
2191
... option2 = boolean()
2192
... option3 = boolean(default=Bad_value)
2194
... another_option = boolean()
2196
... another_option = boolean()
2199
... value2 = integer
2200
... value3 = integer(0, 10)
2201
... [[[section3b-sub]]]
2204
... another_option = boolean()
2206
>>> cs = my_cfg.split('\\n')
2207
>>> ini = my_ini.split('\\n')
2208
>>> cfg = ConfigObj(ini, configspec=cs)
2209
>>> res = cfg.validate(vtor, preserve_errors=True)
2211
>>> for entry in flatten_errors(cfg, res):
2212
... section_list, key, error = entry
2213
... section_list.insert(0, '[root]')
2214
... if key is not None:
2215
... section_list.append(key)
2217
... section_list.append('[missing]')
2218
... section_string = ', '.join(section_list)
2219
... errors.append((section_string, ' = ', error))
2221
>>> for entry in errors:
2222
... print entry[0], entry[1], (entry[2] or 0)
2224
[root], option3 = the value "Bad_value" is of the wrong type.
2225
[root], section1, option2 = 0
2226
[root], section1, option3 = the value "Bad_value" is of the wrong type.
2227
[root], section2, another_option = the value "Probably" is of the wrong type.
2228
[root], section3, section3b, section3b-sub, [missing] = 0
2229
[root], section3, section3b, value2 = the value "a" is of the wrong type.
2230
[root], section3, section3b, value3 = the value "11" is too big.
2231
[root], section4, [missing] = 0
2240
results.append((levels[:], None, False))
2244
for (key, val) in res.items():
2247
if isinstance(cfg.get(key), dict):
2250
flatten_errors(cfg[key], val, levels, results)
2252
results.append((levels[:], key, val))
2260
"""*A programming language is a medium of expression.* - Paul Graham"""