# A config file reader/writer that supports nested sections in config files.
# Copyright (C) 2005-2014:
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk
# Nicola Larosa: nico AT tekNico DOT net
# Rob Dennis: rdennis AT gmail DOT com
# Eli Courtwright: eli AT courtwright DOT org
# This software is licensed under the terms of the BSD license.
# http://opensource.org/licenses/BSD-3-Clause
# ConfigObj 5 - main repository for documentation and issue tracking:
# https://github.com/DiffSK/configobj
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
from _version import __version__
# imported lazily to avoid startup performance hit if it isn't used
# A dictionary mapping BOM to
# the encoding to decode with, and what to set the
BOM_UTF8: ('utf_8', None),
BOM_UTF16_BE: ('utf16_be', 'utf_16'),
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
BOM_UTF16: ('utf_16', 'utf_16'),
# All legal variants of the BOM codecs.
# TODO: the list of aliases is not meant to be exhaustive, is there a
# Map of encodings to the BOM to write.
'utf16_be': BOM_UTF16_BE,
'utf16_le': BOM_UTF16_LE,
def match_utf8(encoding):
return BOM_LIST.get(encoding.lower()) == 'utf_8'
# Quote strings used for writing values
wspace_plus = ' \r\n\v\t\'"'
# Sentinel for use in getattr calls to replace hasattr
'InterpolationLoopError',
'MissingInterpolationOption',
DEFAULT_INTERPOLATION = 'configparser'
DEFAULT_INDENT_TYPE = ' '
# option may be set to one of ('', ' ', '\t')
'default_encoding': None,
'write_empty_values': False,
# this could be replaced if six is used for compatibility, or there are no
# more assertions about items being a string
return p.getChildren()[1].getChildren()[0].getChildren()[1]
class UnknownType(Exception):
raise UnknownType(o.__class__.__name__)
return list(map(self.build, o.getChildren()))
def build_Const(self, o):
i = iter(map(self.build, o.getChildren()))
def build_Tuple(self, o):
return tuple(self.build_List(o))
raise UnknownType('Undefined Name')
real, imag = list(map(self.build_Const, o.getChildren()))
if not isinstance(imag, complex) or imag.real != 0.0:
def build_Getattr(self, o):
parent = self.build(o.expr)
return getattr(parent, o.attrname)
def build_UnarySub(self, o):
return -self.build_Const(o.getChildren()[0])
def build_UnaryAdd(self, o):
return self.build_Const(o.getChildren()[0])
# this is supposed to be safe
return ast.literal_eval(s)
class ConfigObjError(SyntaxError):
This is the base class for all errors that ConfigObj raises.
It is a subclass of SyntaxError.
def __init__(self, message='', line_number=None, line=''):
self.line_number = line_number
SyntaxError.__init__(self, message)
class NestingError(ConfigObjError):
This error indicates a level of nesting that doesn't match.
class ParseError(ConfigObjError):
This error indicates that a line is badly written.
It is neither a valid ``key = value`` line,
nor a valid section marker line.
class ReloadError(IOError):
A 'reload' operation failed.
This exception is a subclass of ``IOError``.
IOError.__init__(self, 'reload failed, filename is not set.')
class DuplicateError(ConfigObjError):
The keyword or section specified already exists.
class ConfigspecError(ConfigObjError):
An error occured whilst parsing a configspec.
class InterpolationError(ConfigObjError):
"""Base class for the two interpolation errors."""
class InterpolationLoopError(InterpolationError):
"""Maximum interpolation depth exceeded in string interpolation."""
def __init__(self, option):
InterpolationError.__init__(
'interpolation loop detected in value "%s".' % option)
class RepeatSectionError(ConfigObjError):
This error indicates additional sections in a section with a
``__many__`` (repeated) section.
class MissingInterpolationOption(InterpolationError):
"""A value specified for interpolation was missing."""
def __init__(self, option):
msg = 'missing option "%s" in interpolation.' % option
InterpolationError.__init__(self, msg)
class UnreprError(ConfigObjError):
"""An error parsing in unrepr mode."""
class InterpolationEngine(object):
A helper class to help perform string interpolation.
This class is an abstract base class; its descendants perform
# compiled regexp to use in self.interpolate()
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
def __init__(self, section):
# the Section instance that "owns" this engine
def interpolate(self, key, value):
if not self._cookie in value:
def recursive_interpolate(key, value, section, backtrail):
"""The function that does the actual work.
``value``: the string we're trying to interpolate.
``section``: the section in which that string was found
``backtrail``: a dict to keep track of where we've been,
to detect and prevent infinite recursion loops
This is similar to a depth-first-search algorithm.
# Have we been here already?
if (key, section.name) in backtrail:
# Yes - infinite loop detected
raise InterpolationLoopError(key)
# Place a marker on our backtrail so we won't come back here again
backtrail[(key, section.name)] = 1
# Now start the actual work
match = self._KEYCRE.search(value)
# The actual parsing of the match is implementation-dependent,
# so delegate to our helper function
k, v, s = self._parse_match(match)
# That's the signal that no further interpolation is needed
# Further interpolation may be needed to obtain final value
replacement = recursive_interpolate(k, v, s, backtrail)
# Replace the matched string with its final value
start, end = match.span()
value = ''.join((value[:start], replacement, value[end:]))
new_search_start = start + len(replacement)
# Pick up the next interpolation key, if any, for next time
match = self._KEYCRE.search(value, new_search_start)
# Now safe to come back here again; remove marker from backtrail
del backtrail[(key, section.name)]
# Back in interpolate(), all we have to do is kick off the recursive
# function with appropriate starting values
value = recursive_interpolate(key, value, self.section, {})
"""Helper function to fetch values from owning section.
Returns a 2-tuple: the value, and the section where it was found.
# switch off interpolation before we try and fetch anything !
save_interp = self.section.main.interpolation
self.section.main.interpolation = False
# Start at section that "owns" this InterpolationEngine
current_section = self.section
# try the current section first
val = current_section.get(key)
if val is not None and not isinstance(val, Section):
val = current_section.get('DEFAULT', {}).get(key)
if val is not None and not isinstance(val, Section):
# move up to parent and try again
# top-level's parent is itself
if current_section.parent is current_section:
# reached top level, time to give up
current_section = current_section.parent
# restore interpolation to previous value before returning
self.section.main.interpolation = save_interp
raise MissingInterpolationOption(key)
return val, current_section
def _parse_match(self, match):
"""Implementation-dependent helper function.
Will be passed a match object corresponding to the interpolation
key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
key in the appropriate config file section (using the ``_fetch()``
helper function) and return a 3-tuple: (key, value, section)
``key`` is the name of the key we're looking for
``value`` is the value found for that key
``section`` is a reference to the section where it was found
``key`` and ``section`` should be None if no further
interpolation should be performed on the resulting value
(e.g., if we interpolated "$$" and returned "$").
raise NotImplementedError()
class ConfigParserInterpolation(InterpolationEngine):
"""Behaves like ConfigParser."""
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
def _parse_match(self, match):
value, section = self._fetch(key)
return key, value, section
class TemplateInterpolation(InterpolationEngine):
"""Behaves like string.Template."""
_KEYCRE = re.compile(r"""
(?P<escaped>\$) | # Two $ signs
(?P<named>[_a-z][_a-z0-9]*) | # $name format
{(?P<braced>[^}]*)} # ${name} format
""", re.IGNORECASE | re.VERBOSE)
def _parse_match(self, match):
# Valid name (in or out of braces): fetch value from section
key = match.group('named') or match.group('braced')
value, section = self._fetch(key)
return key, value, section
# Escaped delimiter (e.g., $$): return single delimiter
if match.group('escaped') is not None:
# Return None for key and section to indicate it's time to stop
return None, self._delimiter, None
# Anything else: ignore completely, just return it unchanged
return None, match.group(), None
interpolation_engines = {
'configparser': ConfigParserInterpolation,
'template': TemplateInterpolation,
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
A dictionary-like object that represents a section in a config file.
It does string interpolation if the 'interpolation' attribute
of the 'main' object is set to True.
Interpolation is tried first from this object, then from the 'DEFAULT'
section of this object, next from the parent and its 'DEFAULT' section,
and so on until the main object is reached.
A Section will behave like an ordered dictionary - following the
order of the ``scalars`` and ``sections`` attributes.
You can use this to change the order of members.
Iteration follows the order: scalars, then sections.
def __setstate__(self, state):
dict.update(self, state[0])
self.__dict__.update(state[1])
state = (dict(self), self.__dict__)
return (__newobj__, (self.__class__,), state)
def __init__(self, parent, depth, main, indict=None, name=None):
* parent is the section above
* depth is the depth level of this section
* main is the main ConfigObj
* indict is a dictionary to initialise the section with
# used for nesting level *and* interpolation
# used for the interpolation attribute
# level of nesting depth of this Section