Upgrade configobj to 4.7.2

This commit is contained in:
Chris Jones 2010-03-02 00:25:44 +00:00
parent 45708d267b
commit afdbaa3fe6
2 changed files with 195 additions and 185 deletions

View File

@ -1,6 +1,6 @@
# configobj.py
# A config file reader/writer that supports nested sections in config files.
# Copyright (C) 2005-2009 Michael Foord, Nicola Larosa
# Copyright (C) 2005-2010 Michael Foord, Nicola Larosa
# E-mail: fuzzyman AT voidspace DOT org DOT uk
# nico AT tekNico DOT net
@ -16,37 +16,17 @@
# http://lists.sourceforge.net/lists/listinfo/configobj-develop
# Comments, suggestions and bug reports welcome.
from __future__ import generators
import sys
import os
import re
import sys
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
# imported lazily to avoid startup performance hit if it isn't used
compiler = None
try:
import compiler
except ImportError:
# for IronPython
pass
try:
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
except ImportError:
# Python 2.2 does not have these
# UTF-8
BOM_UTF8 = '\xef\xbb\xbf'
# UTF-16, little endian
BOM_UTF16_LE = '\xff\xfe'
# UTF-16, big endian
BOM_UTF16_BE = '\xfe\xff'
if sys.byteorder == 'little':
# UTF-16, native endianness
BOM_UTF16 = BOM_UTF16_LE
else:
# UTF-16, native endianness
BOM_UTF16 = BOM_UTF16_BE
# A dictionary mapping BOM to
# the encoding to decode with, and what to set the
@ -100,24 +80,20 @@ wspace_plus = ' \r\n\v\t\'"'
tsquot = '"""%s"""'
tdquot = "'''%s'''"
try:
enumerate
except NameError:
def enumerate(obj):
"""enumerate for Python 2.2."""
i = -1
for item in obj:
i += 1
yield i, item
# Sentinel for use in getattr calls to replace hasattr
MISSING = object()
__version__ = '4.6.0'
__version__ = '4.7.2'
__revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
try:
any
except NameError:
def any(iterable):
for entry in iterable:
if entry:
return True
return False
__docformat__ = "restructuredtext en"
__all__ = (
'__version__',
@ -137,8 +113,8 @@ __all__ = (
'ReloadError',
'UnreprError',
'UnknownType',
'__docformat__',
'flatten_errors',
'get_extra_values'
)
DEFAULT_INTERPOLATION = 'configparser'
@ -164,9 +140,10 @@ OPTION_DEFAULTS = {
def getObj(s):
s = "a=" + s
global compiler
if compiler is None:
raise ImportError('compiler module not available')
import compiler
s = "a=" + s
p = compiler.parse(s)
return p.getChildren()[1].getChildren()[0].getChildren()[1]
@ -309,11 +286,9 @@ class RepeatSectionError(ConfigObjError):
class MissingInterpolationOption(InterpolationError):
"""A value specified for interpolation was missing."""
def __init__(self, option):
InterpolationError.__init__(
self,
'missing option "%s" in interpolation.' % option)
msg = 'missing option "%s" in interpolation.' % option
InterpolationError.__init__(self, msg)
class UnreprError(ConfigObjError):
@ -331,6 +306,7 @@ class InterpolationEngine(object):
# compiled regexp to use in self.interpolate()
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
_cookie = '%'
def __init__(self, section):
# the Section instance that "owns" this engine
@ -338,6 +314,10 @@ class InterpolationEngine(object):
def interpolate(self, key, value):
# short-cut
if not self._cookie in value:
return value
def recursive_interpolate(key, value, section, backtrail):
"""The function that does the actual work.
@ -349,7 +329,7 @@ class InterpolationEngine(object):
This is similar to a depth-first-search algorithm.
"""
# Have we been here already?
if backtrail.has_key((key, section.name)):
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
@ -400,11 +380,11 @@ class InterpolationEngine(object):
while True:
# try the current section first
val = current_section.get(key)
if val is not None:
if val is not None and not isinstance(val, Section):
break
# try "DEFAULT" next
val = current_section.get('DEFAULT', {}).get(key)
if val is not None:
if val is not None and not isinstance(val, Section):
break
# move up to parent and try again
# top-level's parent is itself
@ -442,6 +422,7 @@ class InterpolationEngine(object):
class ConfigParserInterpolation(InterpolationEngine):
"""Behaves like ConfigParser."""
_cookie = '%'
_KEYCRE = re.compile(r"%\(([^)]*)\)s")
def _parse_match(self, match):
@ -453,6 +434,7 @@ class ConfigParserInterpolation(InterpolationEngine):
class TemplateInterpolation(InterpolationEngine):
"""Behaves like string.Template."""
_cookie = '$'
_delimiter = '$'
_KEYCRE = re.compile(r"""
\$(?:
@ -553,6 +535,8 @@ class Section(dict):
# for defaults
self.defaults = []
self.default_values = {}
self.extra_values = []
self._created = False
def _interpolate(self, key, value):
@ -581,8 +565,17 @@ class Section(dict):
def __getitem__(self, key):
"""Fetch the item and do string interpolation."""
val = dict.__getitem__(self, key)
if self.main.interpolation and isinstance(val, basestring):
return self._interpolate(key, val)
if self.main.interpolation:
if isinstance(val, basestring):
return self._interpolate(key, val)
if isinstance(val, list):
def _check(entry):
if isinstance(entry, basestring):
return self._interpolate(key, entry)
return entry
new = [_check(entry) for entry in val]
if new != val:
return new
return val
@ -604,7 +597,7 @@ class Section(dict):
raise ValueError('The key "%s" is not a string.' % key)
# add the comment
if not self.comments.has_key(key):
if key not in self.comments:
self.comments[key] = []
self.inline_comments[key] = ''
# remove the entry from defaults
@ -612,13 +605,13 @@ class Section(dict):
self.defaults.remove(key)
#
if isinstance(value, Section):
if not self.has_key(key):
if key not in self:
self.sections.append(key)
dict.__setitem__(self, key, value)
elif isinstance(value, dict) and not unrepr:
# First create the new depth level,
# then create the section
if not self.has_key(key):
if key not in self:
self.sections.append(key)
new_depth = self.depth + 1
dict.__setitem__(
@ -631,7 +624,7 @@ class Section(dict):
indict=value,
name=key))
else:
if not self.has_key(key):
if key not in self:
self.scalars.append(key)
if not self.main.stringify:
if isinstance(value, basestring):
@ -672,22 +665,19 @@ class Section(dict):
self[entry] = indict[entry]
def pop(self, key, *args):
def pop(self, key, default=MISSING):
"""
'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised'
"""
val = dict.pop(self, key, *args)
if key in self.scalars:
del self.comments[key]
del self.inline_comments[key]
self.scalars.remove(key)
elif key in self.sections:
del self.comments[key]
del self.inline_comments[key]
self.sections.remove(key)
if self.main.interpolation and isinstance(val, basestring):
return self._interpolate(key, val)
try:
val = self[key]
except KeyError:
if default is MISSING:
raise
val = default
else:
del self[key]
return val
@ -716,6 +706,8 @@ class Section(dict):
self.comments = {}
self.inline_comments = {}
self.configspec = None
self.defaults = []
self.extra_values = []
def setdefault(self, key, default=None):
@ -761,7 +753,12 @@ class Section(dict):
def __repr__(self):
"""x.__repr__() <==> repr(x)"""
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
def _getval(key):
try:
return self[key]
except MissingInterpolationOption:
return dict.__getitem__(self, key)
return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
for key in (self.scalars + self.sections)])
__str__ = __repr__
@ -1148,7 +1145,7 @@ class ConfigObj(Section):
(
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\#].*?) # unquoted
(?:[^'",\#]?.*?) # unquoted
)
\s*,\s* # comma
''',
@ -1187,34 +1184,60 @@ class ConfigObj(Section):
}
def __init__(self, infile=None, options=None, _inspec=False, **kwargs):
def __init__(self, infile=None, options=None, configspec=None, encoding=None,
interpolation=True, raise_errors=False, list_values=True,
create_empty=False, file_error=False, stringify=True,
indent_type=None, default_encoding=None, unrepr=False,
write_empty_values=False, _inspec=False):
"""
Parse a config file or create a config file object.
``ConfigObj(infile=None, options=None, **kwargs)``
``ConfigObj(infile=None, configspec=None, encoding=None,
interpolation=True, raise_errors=False, list_values=True,
create_empty=False, file_error=False, stringify=True,
indent_type=None, default_encoding=None, unrepr=False,
write_empty_values=False, _inspec=False)``
"""
self._inspec = _inspec
# init the superclass
Section.__init__(self, self, 0, self)
infile = infile or []
options = dict(options or {})
_options = {'configspec': configspec,
'encoding': encoding, 'interpolation': interpolation,
'raise_errors': raise_errors, 'list_values': list_values,
'create_empty': create_empty, 'file_error': file_error,
'stringify': stringify, 'indent_type': indent_type,
'default_encoding': default_encoding, 'unrepr': unrepr,
'write_empty_values': write_empty_values}
if options is None:
options = _options
else:
import warnings
warnings.warn('Passing in an options dictionary to ConfigObj() is '
'deprecated. Use **options instead.',
DeprecationWarning, stacklevel=2)
# keyword arguments take precedence over an options dictionary
options.update(kwargs)
# TODO: check the values too.
for entry in options:
if entry not in OPTION_DEFAULTS:
raise TypeError('Unrecognised option "%s".' % entry)
for entry, value in OPTION_DEFAULTS.items():
if entry not in options:
options[entry] = value
keyword_value = _options[entry]
if value != keyword_value:
options[entry] = keyword_value
# XXXX this ignores an explicit list_values = True in combination
# with _inspec. The user should *never* do that anyway, but still...
if _inspec:
options['list_values'] = False
defaults = OPTION_DEFAULTS.copy()
# TODO: check the values too.
for entry in options:
if entry not in defaults:
raise TypeError('Unrecognised option "%s".' % entry)
# Add any explicit options to the defaults
defaults.update(options)
self._initialise(defaults)
configspec = defaults['configspec']
self._initialise(options)
configspec = options['configspec']
self._original_configspec = configspec
self._load(infile, configspec)
@ -1247,10 +1270,17 @@ class ConfigObj(Section):
# the Section class handles creating subsections
if isinstance(infile, ConfigObj):
# get a copy of our ConfigObj
infile = infile.dict()
def set_section(in_section, this_section):
for entry in in_section.scalars:
this_section[entry] = in_section[entry]
for section in in_section.sections:
this_section[section] = {}
set_section(in_section[section], this_section[section])
set_section(infile, self)
for entry in infile:
self[entry] = infile[entry]
else:
for entry in infile:
self[entry] = infile[entry]
del self._errors
if configspec is not None:
@ -1342,8 +1372,13 @@ class ConfigObj(Section):
def __repr__(self):
def _getval(key):
try:
return self[key]
except MissingInterpolationOption:
return dict.__getitem__(self, key)
return ('ConfigObj({%s})' %
', '.join([('%s: %s' % (repr(key), repr(self[key])))
', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
for key in (self.scalars + self.sections)]))
@ -1560,7 +1595,7 @@ class ConfigObj(Section):
NestingError, infile, cur_index)
sect_name = self._unquote(sect_name)
if parent.has_key(sect_name):
if sect_name in parent:
self._handle_error('Duplicate section name at line %s.',
DuplicateError, infile, cur_index)
continue
@ -1594,7 +1629,7 @@ class ConfigObj(Section):
# check for a multiline value
if value[:3] in ['"""', "'''"]:
try:
(value, comment, cur_index) = self._multiline(
value, comment, cur_index = self._multiline(
value, infile, cur_index, maxline)
except SyntaxError:
self._handle_error(
@ -1638,7 +1673,7 @@ class ConfigObj(Section):
continue
#
key = self._unquote(key)
if this_section.has_key(key):
if key in this_section:
self._handle_error(
'Duplicate keyword name at line %s.',
DuplicateError, infile, cur_index)
@ -1703,6 +1738,9 @@ class ConfigObj(Section):
def _unquote(self, value):
"""Return an unquoted version of a value"""
if not value:
# should only happen during parsing of lists
raise SyntaxError
if (value[0] == value[-1]) and (value[0] in ('"', "'")):
value = value[1:-1]
return value
@ -1919,6 +1957,7 @@ class ConfigObj(Section):
continue
if entry not in section:
section[entry] = {}
section[entry]._created = True
if copy:
# copy comments
section.comments[entry] = configspec.comments.get(entry, [])
@ -1976,6 +2015,8 @@ class ConfigObj(Section):
>>> a.filename = filename
>>> a == ConfigObj('test.ini', raise_errors=True)
1
>>> import os
>>> os.remove('test.ini')
"""
if self.indent_type is None:
# this can be true if initialised from a dictionary
@ -2051,6 +2092,10 @@ class ConfigObj(Section):
# Turn the list to a string, joined with correct newlines
newline = self.newlines or os.linesep
if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w'
and sys.platform == 'win32' and newline == '\r\n'):
# Windows specific hack to avoid writing '\r\r\n'
newline = '\n'
output = self._a_to_u(newline).join(out)
if self.encoding:
output = output.encode(self.encoding)
@ -2124,10 +2169,21 @@ class ConfigObj(Section):
section.indent_type = section.configspec.indent_type
#
# section.default_values.clear() #??
configspec = section.configspec
self._set_configspec(section, copy)
def validate_entry(entry, spec, val, missing, ret_true, ret_false):
section.default_values.pop(entry, None)
try:
section.default_values[entry] = validator.get_default_value(configspec[entry])
except (KeyError, AttributeError, validator.baseErrorClass):
# No default, bad default or validator has no 'get_default_value'
# (e.g. SimpleVal)
pass
try:
check = validator.check(spec,
val,
@ -2142,21 +2198,6 @@ class ConfigObj(Section):
ret_false = False
ret_true = False
else:
try:
section.default_values.pop(entry, None)
except AttributeError:
# For Python 2.2 compatibility
try:
del section.default_values[entry]
except KeyError:
pass
try:
section.default_values[entry] = validator.get_default_value(configspec[entry])
except (KeyError, AttributeError):
# No default or validator has no 'get_default_value' (e.g. SimpleVal)
pass
ret_false = False
out[entry] = True
if self.stringify or missing:
@ -2190,13 +2231,12 @@ class ConfigObj(Section):
if entry in ('__many__', '___many___'):
# reserved names
continue
if (not entry in section.scalars) or (entry in section.defaults):
# missing entries
# or entries from defaults
missing = True
val = None
if copy and not entry in section.scalars:
if copy and entry not in section.scalars:
# copy comments
section.comments[entry] = (
configspec.comments.get(entry, []))
@ -2206,7 +2246,7 @@ class ConfigObj(Section):
else:
missing = False
val = section[entry]
ret_true, ret_false = validate_entry(entry, configspec[entry], val,
missing, ret_true, ret_false)
@ -2221,6 +2261,7 @@ class ConfigObj(Section):
val = section[entry]
ret_true, ret_false = validate_entry(entry, many, val, False,
ret_true, ret_false)
unvalidated = []
for entry in incorrect_scalars:
ret_true = False
@ -2246,6 +2287,7 @@ class ConfigObj(Section):
if section is self and entry == 'DEFAULT':
continue
if section[entry].configspec is None:
unvalidated.append(entry)
continue
if copy:
section.comments[entry] = configspec.comments.get(entry, [])
@ -2258,8 +2300,19 @@ class ConfigObj(Section):
ret_false = False
else:
ret_true = False
ret_false = False
section.extra_values = unvalidated
if preserve_errors and not section._created:
# If the section wasn't created (i.e. it wasn't missing)
# then we can't return False, we need to preserve errors
ret_false = False
#
if ret_false and preserve_errors and out:
# If we are preserving errors, but all
# the failures are from missing sections / values
# then we can return False. Otherwise there is a
# real failure that we need to preserve.
ret_false = not any(out.values())
if ret_true:
return True
elif ret_false:
@ -2326,7 +2379,6 @@ class SimpleVal(object):
return member
# Check / processing functions for options
def flatten_errors(cfg, res, levels=None, results=None):
"""
An example function that will turn a nested dictionary of results
@ -2338,9 +2390,7 @@ def flatten_errors(cfg, res, levels=None, results=None):
(This is a recursive function, so you shouldn't use the ``levels`` or
``results`` arguments - they are used by the function.)
Returns a list of keys that failed. Each member of the list is a tuple :
::
Returns a list of keys that failed. Each member of the list is a tuple::
([list of sections...], key, result)
@ -2360,77 +2410,14 @@ def flatten_errors(cfg, res, levels=None, results=None):
object returned. You can use this as a string that describes the failure.
For example *The value "3" is of the wrong type*.
>>> import validate
>>> vtor = validate.Validator()
>>> my_ini = '''
... option1 = True
... [section1]
... option1 = True
... [section2]
... another_option = Probably
... [section3]
... another_option = True
... [[section3b]]
... value = 3
... value2 = a
... value3 = 11
... '''
>>> my_cfg = '''
... option1 = boolean()
... option2 = boolean()
... option3 = boolean(default=Bad_value)
... [section1]
... option1 = boolean()
... option2 = boolean()
... option3 = boolean(default=Bad_value)
... [section2]
... another_option = boolean()
... [section3]
... another_option = boolean()
... [[section3b]]
... value = integer
... value2 = integer
... value3 = integer(0, 10)
... [[[section3b-sub]]]
... value = string
... [section4]
... another_option = boolean()
... '''
>>> cs = my_cfg.split('\\n')
>>> ini = my_ini.split('\\n')
>>> cfg = ConfigObj(ini, configspec=cs)
>>> res = cfg.validate(vtor, preserve_errors=True)
>>> errors = []
>>> for entry in flatten_errors(cfg, res):
... section_list, key, error = entry
... section_list.insert(0, '[root]')
... if key is not None:
... section_list.append(key)
... else:
... section_list.append('[missing]')
... section_string = ', '.join(section_list)
... errors.append((section_string, ' = ', error))
>>> errors.sort()
>>> for entry in errors:
... print entry[0], entry[1], (entry[2] or 0)
[root], option2 = 0
[root], option3 = the value "Bad_value" is of the wrong type.
[root], section1, option2 = 0
[root], section1, option3 = the value "Bad_value" is of the wrong type.
[root], section2, another_option = the value "Probably" is of the wrong type.
[root], section3, section3b, section3b-sub, [missing] = 0
[root], section3, section3b, value2 = the value "a" is of the wrong type.
[root], section3, section3b, value3 = the value "11" is too big.
[root], section4, [missing] = 0
"""
if levels is None:
# first time called
levels = []
results = []
if res is True:
if res == True:
return results
if res is False or isinstance(res, Exception):
if res == False or isinstance(res, Exception):
results.append((levels[:], None, res))
if levels:
levels.pop()
@ -2452,4 +2439,30 @@ def flatten_errors(cfg, res, levels=None, results=None):
return results
def get_extra_values(conf, _prepend=()):
"""
Find all the values and sections not in the configspec from a validated
ConfigObj.
``get_extra_values`` returns a list of tuples where each tuple represents
either an extra section, or an extra value.
The tuples contain two values, a tuple representing the section the value
is in and the name of the extra values. For extra values in the top level
section the first member will be an empty tuple. For values in the 'foo'
section the first member will be ``('foo',)``. For members in the 'bar'
subsection of the 'foo' section the first member will be ``('foo', 'bar')``.
NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't
been validated it will return an empty list.
"""
out = []
out.extend([(_prepend, name) for name in conf.extra_values])
for name in conf.sections:
if name not in conf.extra_values:
out.extend(get_extra_values(conf[name], _prepend + (name,)))
return out
"""*A programming language is a medium of expression.* - Paul Graham"""

View File

@ -1,6 +1,6 @@
# validate.py
# A Validator object
# Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
# Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa
# E-mail: fuzzyman AT voidspace DOT org DOT uk
# mark AT la-la DOT com
# nico AT tekNico DOT net
@ -128,11 +128,8 @@
A badly formatted set of arguments will raise a ``VdtParamError``.
"""
__docformat__ = "restructuredtext en"
__version__ = '1.0.1'
__version__ = '1.0.0'
__revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $'
__all__ = (
'__version__',
@ -620,7 +617,7 @@ class Validator(object):
fun_kwargs = dict(fun_kwargs)
else:
fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items())
fun_kwargs = dict([(str(key), value) for (key, value) in fun_kwargs.items()])
self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
return fun_name, fun_args, fun_kwargs, default