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 # configobj.py
# A config file reader/writer that supports nested sections in config files. # 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 # E-mail: fuzzyman AT voidspace DOT org DOT uk
# nico AT tekNico DOT net # nico AT tekNico DOT net
@ -16,37 +16,17 @@
# http://lists.sourceforge.net/lists/listinfo/configobj-develop # http://lists.sourceforge.net/lists/listinfo/configobj-develop
# Comments, suggestions and bug reports welcome. # Comments, suggestions and bug reports welcome.
from __future__ import generators from __future__ import generators
import sys
import os import os
import re 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 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 # A dictionary mapping BOM to
# the encoding to decode with, and what to set the # the encoding to decode with, and what to set the
@ -100,24 +80,20 @@ wspace_plus = ' \r\n\v\t\'"'
tsquot = '"""%s"""' tsquot = '"""%s"""'
tdquot = "'''%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 # Sentinel for use in getattr calls to replace hasattr
MISSING = object() 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__ = ( __all__ = (
'__version__', '__version__',
@ -137,8 +113,8 @@ __all__ = (
'ReloadError', 'ReloadError',
'UnreprError', 'UnreprError',
'UnknownType', 'UnknownType',
'__docformat__',
'flatten_errors', 'flatten_errors',
'get_extra_values'
) )
DEFAULT_INTERPOLATION = 'configparser' DEFAULT_INTERPOLATION = 'configparser'
@ -164,9 +140,10 @@ OPTION_DEFAULTS = {
def getObj(s): def getObj(s):
s = "a=" + s global compiler
if compiler is None: if compiler is None:
raise ImportError('compiler module not available') import compiler
s = "a=" + s
p = compiler.parse(s) p = compiler.parse(s)
return p.getChildren()[1].getChildren()[0].getChildren()[1] return p.getChildren()[1].getChildren()[0].getChildren()[1]
@ -309,11 +286,9 @@ class RepeatSectionError(ConfigObjError):
class MissingInterpolationOption(InterpolationError): class MissingInterpolationOption(InterpolationError):
"""A value specified for interpolation was missing.""" """A value specified for interpolation was missing."""
def __init__(self, option): def __init__(self, option):
InterpolationError.__init__( msg = 'missing option "%s" in interpolation.' % option
self, InterpolationError.__init__(self, msg)
'missing option "%s" in interpolation.' % option)
class UnreprError(ConfigObjError): class UnreprError(ConfigObjError):
@ -331,6 +306,7 @@ class InterpolationEngine(object):
# compiled regexp to use in self.interpolate() # compiled regexp to use in self.interpolate()
_KEYCRE = re.compile(r"%\(([^)]*)\)s") _KEYCRE = re.compile(r"%\(([^)]*)\)s")
_cookie = '%'
def __init__(self, section): def __init__(self, section):
# the Section instance that "owns" this engine # the Section instance that "owns" this engine
@ -338,6 +314,10 @@ class InterpolationEngine(object):
def interpolate(self, key, value): def interpolate(self, key, value):
# short-cut
if not self._cookie in value:
return value
def recursive_interpolate(key, value, section, backtrail): def recursive_interpolate(key, value, section, backtrail):
"""The function that does the actual work. """The function that does the actual work.
@ -349,7 +329,7 @@ class InterpolationEngine(object):
This is similar to a depth-first-search algorithm. This is similar to a depth-first-search algorithm.
""" """
# Have we been here already? # Have we been here already?
if backtrail.has_key((key, section.name)): if (key, section.name) in backtrail:
# Yes - infinite loop detected # Yes - infinite loop detected
raise InterpolationLoopError(key) raise InterpolationLoopError(key)
# Place a marker on our backtrail so we won't come back here again # Place a marker on our backtrail so we won't come back here again
@ -400,11 +380,11 @@ class InterpolationEngine(object):
while True: while True:
# try the current section first # try the current section first
val = current_section.get(key) val = current_section.get(key)
if val is not None: if val is not None and not isinstance(val, Section):
break break
# try "DEFAULT" next # try "DEFAULT" next
val = current_section.get('DEFAULT', {}).get(key) val = current_section.get('DEFAULT', {}).get(key)
if val is not None: if val is not None and not isinstance(val, Section):
break break
# move up to parent and try again # move up to parent and try again
# top-level's parent is itself # top-level's parent is itself
@ -442,6 +422,7 @@ class InterpolationEngine(object):
class ConfigParserInterpolation(InterpolationEngine): class ConfigParserInterpolation(InterpolationEngine):
"""Behaves like ConfigParser.""" """Behaves like ConfigParser."""
_cookie = '%'
_KEYCRE = re.compile(r"%\(([^)]*)\)s") _KEYCRE = re.compile(r"%\(([^)]*)\)s")
def _parse_match(self, match): def _parse_match(self, match):
@ -453,6 +434,7 @@ class ConfigParserInterpolation(InterpolationEngine):
class TemplateInterpolation(InterpolationEngine): class TemplateInterpolation(InterpolationEngine):
"""Behaves like string.Template.""" """Behaves like string.Template."""
_cookie = '$'
_delimiter = '$' _delimiter = '$'
_KEYCRE = re.compile(r""" _KEYCRE = re.compile(r"""
\$(?: \$(?:
@ -553,6 +535,8 @@ class Section(dict):
# for defaults # for defaults
self.defaults = [] self.defaults = []
self.default_values = {} self.default_values = {}
self.extra_values = []
self._created = False
def _interpolate(self, key, value): def _interpolate(self, key, value):
@ -581,8 +565,17 @@ class Section(dict):
def __getitem__(self, key): def __getitem__(self, key):
"""Fetch the item and do string interpolation.""" """Fetch the item and do string interpolation."""
val = dict.__getitem__(self, key) val = dict.__getitem__(self, key)
if self.main.interpolation and isinstance(val, basestring): if self.main.interpolation:
return self._interpolate(key, val) 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 return val
@ -604,7 +597,7 @@ class Section(dict):
raise ValueError('The key "%s" is not a string.' % key) raise ValueError('The key "%s" is not a string.' % key)
# add the comment # add the comment
if not self.comments.has_key(key): if key not in self.comments:
self.comments[key] = [] self.comments[key] = []
self.inline_comments[key] = '' self.inline_comments[key] = ''
# remove the entry from defaults # remove the entry from defaults
@ -612,13 +605,13 @@ class Section(dict):
self.defaults.remove(key) self.defaults.remove(key)
# #
if isinstance(value, Section): if isinstance(value, Section):
if not self.has_key(key): if key not in self:
self.sections.append(key) self.sections.append(key)
dict.__setitem__(self, key, value) dict.__setitem__(self, key, value)
elif isinstance(value, dict) and not unrepr: elif isinstance(value, dict) and not unrepr:
# First create the new depth level, # First create the new depth level,
# then create the section # then create the section
if not self.has_key(key): if key not in self:
self.sections.append(key) self.sections.append(key)
new_depth = self.depth + 1 new_depth = self.depth + 1
dict.__setitem__( dict.__setitem__(
@ -631,7 +624,7 @@ class Section(dict):
indict=value, indict=value,
name=key)) name=key))
else: else:
if not self.has_key(key): if key not in self:
self.scalars.append(key) self.scalars.append(key)
if not self.main.stringify: if not self.main.stringify:
if isinstance(value, basestring): if isinstance(value, basestring):
@ -672,22 +665,19 @@ class Section(dict):
self[entry] = indict[entry] 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. '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' If key is not found, d is returned if given, otherwise KeyError is raised'
""" """
val = dict.pop(self, key, *args) try:
if key in self.scalars: val = self[key]
del self.comments[key] except KeyError:
del self.inline_comments[key] if default is MISSING:
self.scalars.remove(key) raise
elif key in self.sections: val = default
del self.comments[key] else:
del self.inline_comments[key] del self[key]
self.sections.remove(key)
if self.main.interpolation and isinstance(val, basestring):
return self._interpolate(key, val)
return val return val
@ -716,6 +706,8 @@ class Section(dict):
self.comments = {} self.comments = {}
self.inline_comments = {} self.inline_comments = {}
self.configspec = None self.configspec = None
self.defaults = []
self.extra_values = []
def setdefault(self, key, default=None): def setdefault(self, key, default=None):
@ -761,7 +753,12 @@ class Section(dict):
def __repr__(self): def __repr__(self):
"""x.__repr__() <==> repr(x)""" """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)]) for key in (self.scalars + self.sections)])
__str__ = __repr__ __str__ = __repr__
@ -1148,7 +1145,7 @@ class ConfigObj(Section):
( (
(?:".*?")| # double quotes (?:".*?")| # double quotes
(?:'.*?')| # single quotes (?:'.*?')| # single quotes
(?:[^'",\#].*?) # unquoted (?:[^'",\#]?.*?) # unquoted
) )
\s*,\s* # comma \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. 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 self._inspec = _inspec
# init the superclass # init the superclass
Section.__init__(self, self, 0, self) Section.__init__(self, self, 0, self)
infile = infile or [] 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 # TODO: check the values too.
options.update(kwargs) 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: if _inspec:
options['list_values'] = False options['list_values'] = False
defaults = OPTION_DEFAULTS.copy() self._initialise(options)
# TODO: check the values too. configspec = options['configspec']
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._original_configspec = configspec self._original_configspec = configspec
self._load(infile, configspec) self._load(infile, configspec)
@ -1247,10 +1270,17 @@ class ConfigObj(Section):
# the Section class handles creating subsections # the Section class handles creating subsections
if isinstance(infile, ConfigObj): if isinstance(infile, ConfigObj):
# get a copy of our 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: else:
self[entry] = infile[entry] for entry in infile:
self[entry] = infile[entry]
del self._errors del self._errors
if configspec is not None: if configspec is not None:
@ -1342,8 +1372,13 @@ class ConfigObj(Section):
def __repr__(self): def __repr__(self):
def _getval(key):
try:
return self[key]
except MissingInterpolationOption:
return dict.__getitem__(self, key)
return ('ConfigObj({%s})' % 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)])) for key in (self.scalars + self.sections)]))
@ -1560,7 +1595,7 @@ class ConfigObj(Section):
NestingError, infile, cur_index) NestingError, infile, cur_index)
sect_name = self._unquote(sect_name) 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.', self._handle_error('Duplicate section name at line %s.',
DuplicateError, infile, cur_index) DuplicateError, infile, cur_index)
continue continue
@ -1594,7 +1629,7 @@ class ConfigObj(Section):
# check for a multiline value # check for a multiline value
if value[:3] in ['"""', "'''"]: if value[:3] in ['"""', "'''"]:
try: try:
(value, comment, cur_index) = self._multiline( value, comment, cur_index = self._multiline(
value, infile, cur_index, maxline) value, infile, cur_index, maxline)
except SyntaxError: except SyntaxError:
self._handle_error( self._handle_error(
@ -1638,7 +1673,7 @@ class ConfigObj(Section):
continue continue
# #
key = self._unquote(key) key = self._unquote(key)
if this_section.has_key(key): if key in this_section:
self._handle_error( self._handle_error(
'Duplicate keyword name at line %s.', 'Duplicate keyword name at line %s.',
DuplicateError, infile, cur_index) DuplicateError, infile, cur_index)
@ -1703,6 +1738,9 @@ class ConfigObj(Section):
def _unquote(self, value): def _unquote(self, value):
"""Return an unquoted version of a 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 ('"', "'")): if (value[0] == value[-1]) and (value[0] in ('"', "'")):
value = value[1:-1] value = value[1:-1]
return value return value
@ -1919,6 +1957,7 @@ class ConfigObj(Section):
continue continue
if entry not in section: if entry not in section:
section[entry] = {} section[entry] = {}
section[entry]._created = True
if copy: if copy:
# copy comments # copy comments
section.comments[entry] = configspec.comments.get(entry, []) section.comments[entry] = configspec.comments.get(entry, [])
@ -1976,6 +2015,8 @@ class ConfigObj(Section):
>>> a.filename = filename >>> a.filename = filename
>>> a == ConfigObj('test.ini', raise_errors=True) >>> a == ConfigObj('test.ini', raise_errors=True)
1 1
>>> import os
>>> os.remove('test.ini')
""" """
if self.indent_type is None: if self.indent_type is None:
# this can be true if initialised from a dictionary # 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 # Turn the list to a string, joined with correct newlines
newline = self.newlines or os.linesep 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) output = self._a_to_u(newline).join(out)
if self.encoding: if self.encoding:
output = output.encode(self.encoding) output = output.encode(self.encoding)
@ -2124,10 +2169,21 @@ class ConfigObj(Section):
section.indent_type = section.configspec.indent_type section.indent_type = section.configspec.indent_type
# #
# section.default_values.clear() #??
configspec = section.configspec configspec = section.configspec
self._set_configspec(section, copy) self._set_configspec(section, copy)
def validate_entry(entry, spec, val, missing, ret_true, ret_false): 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: try:
check = validator.check(spec, check = validator.check(spec,
val, val,
@ -2142,21 +2198,6 @@ class ConfigObj(Section):
ret_false = False ret_false = False
ret_true = False ret_true = False
else: 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 ret_false = False
out[entry] = True out[entry] = True
if self.stringify or missing: if self.stringify or missing:
@ -2190,13 +2231,12 @@ class ConfigObj(Section):
if entry in ('__many__', '___many___'): if entry in ('__many__', '___many___'):
# reserved names # reserved names
continue continue
if (not entry in section.scalars) or (entry in section.defaults): if (not entry in section.scalars) or (entry in section.defaults):
# missing entries # missing entries
# or entries from defaults # or entries from defaults
missing = True missing = True
val = None val = None
if copy and not entry in section.scalars: if copy and entry not in section.scalars:
# copy comments # copy comments
section.comments[entry] = ( section.comments[entry] = (
configspec.comments.get(entry, [])) configspec.comments.get(entry, []))
@ -2206,7 +2246,7 @@ class ConfigObj(Section):
else: else:
missing = False missing = False
val = section[entry] val = section[entry]
ret_true, ret_false = validate_entry(entry, configspec[entry], val, ret_true, ret_false = validate_entry(entry, configspec[entry], val,
missing, ret_true, ret_false) missing, ret_true, ret_false)
@ -2221,6 +2261,7 @@ class ConfigObj(Section):
val = section[entry] val = section[entry]
ret_true, ret_false = validate_entry(entry, many, val, False, ret_true, ret_false = validate_entry(entry, many, val, False,
ret_true, ret_false) ret_true, ret_false)
unvalidated = []
for entry in incorrect_scalars: for entry in incorrect_scalars:
ret_true = False ret_true = False
@ -2246,6 +2287,7 @@ class ConfigObj(Section):
if section is self and entry == 'DEFAULT': if section is self and entry == 'DEFAULT':
continue continue
if section[entry].configspec is None: if section[entry].configspec is None:
unvalidated.append(entry)
continue continue
if copy: if copy:
section.comments[entry] = configspec.comments.get(entry, []) section.comments[entry] = configspec.comments.get(entry, [])
@ -2258,8 +2300,19 @@ class ConfigObj(Section):
ret_false = False ret_false = False
else: else:
ret_true = False 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: if ret_true:
return True return True
elif ret_false: elif ret_false:
@ -2326,7 +2379,6 @@ class SimpleVal(object):
return member return member
# Check / processing functions for options
def flatten_errors(cfg, res, levels=None, results=None): def flatten_errors(cfg, res, levels=None, results=None):
""" """
An example function that will turn a nested dictionary of results 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 (This is a recursive function, so you shouldn't use the ``levels`` or
``results`` arguments - they are used by the function.) ``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) ([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. object returned. You can use this as a string that describes the failure.
For example *The value "3" is of the wrong type*. 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: if levels is None:
# first time called # first time called
levels = [] levels = []
results = [] results = []
if res is True: if res == True:
return results return results
if res is False or isinstance(res, Exception): if res == False or isinstance(res, Exception):
results.append((levels[:], None, res)) results.append((levels[:], None, res))
if levels: if levels:
levels.pop() levels.pop()
@ -2452,4 +2439,30 @@ def flatten_errors(cfg, res, levels=None, results=None):
return results 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""" """*A programming language is a medium of expression.* - Paul Graham"""

View File

@ -1,6 +1,6 @@
# validate.py # validate.py
# A Validator object # 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 # E-mail: fuzzyman AT voidspace DOT org DOT uk
# mark AT la-la DOT com # mark AT la-la DOT com
# nico AT tekNico DOT net # nico AT tekNico DOT net
@ -128,11 +128,8 @@
A badly formatted set of arguments will raise a ``VdtParamError``. 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__ = ( __all__ = (
'__version__', '__version__',
@ -620,7 +617,7 @@ class Validator(object):
fun_kwargs = dict(fun_kwargs) fun_kwargs = dict(fun_kwargs)
else: else:
fun_name, fun_args, fun_kwargs, default = self._parse_check(check) 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 self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
return fun_name, fun_args, fun_kwargs, default return fun_name, fun_args, fun_kwargs, default