add consistent support for the vars and default arguments on all

configuration parser classes
(http://bugs.python.org/issue9421)
This commit is contained in:
Fred Drake 2010-09-04 04:35:34 +00:00
parent c934f32e0a
commit cc645b9a59
4 changed files with 256 additions and 82 deletions

View file

@ -135,7 +135,7 @@ keys within each section.
*empty_lines_in_values* were added. *empty_lines_in_values* were added.
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True) .. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
Derived class of :class:`ConfigParser` that implements a sane variant of the Derived class of :class:`ConfigParser` that implements a sane variant of the
magical interpolation feature. This implementation is more predictable as it magical interpolation feature. This implementation is more predictable as it
@ -155,7 +155,7 @@ keys within each section.
*empty_lines_in_values* were added. *empty_lines_in_values* were added.
.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True) .. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
Derived class of :class:`RawConfigParser` that implements the magical Derived class of :class:`RawConfigParser` that implements the magical
interpolation feature and adds optional arguments to the :meth:`get` and interpolation feature and adds optional arguments to the :meth:`get` and
@ -366,39 +366,44 @@ RawConfigParser Objects
Load configuration from a dictionary. Keys are section names, values are Load configuration from a dictionary. Keys are section names, values are
dictionaries with keys and values that should be present in the section. If dictionaries with keys and values that should be present in the section. If
the used dictionary type preserves order, sections and their keys will be the used dictionary type preserves order, sections and their keys will be
added in order. added in order. Values are automatically converted to strings.
Optional argument *source* specifies a context-specific name of the Optional argument *source* specifies a context-specific name of the
dictionary passed. If not given, ``<dict>`` is used. dictionary passed. If not given, ``<dict>`` is used.
.. versionadded:: 3.2 .. versionadded:: 3.2
.. method:: RawConfigParser.get(section, option, [vars, default])
.. method:: RawConfigParser.get(section, option) Get an *option* value for the named *section*. If *vars* is provided, it
must be a dictionary. The *option* is looked up in *vars* (if provided),
Get an *option* value for the named *section*. *section*, and in *DEFAULTSECT* in that order. If the key is not found and
*default* is provided, it is used as a fallback value. ``None`` can be
provided as a *default* value.
.. method:: RawConfigParser.getint(section, option) .. method:: RawConfigParser.getint(section, option, [vars, default])
A convenience method which coerces the *option* in the specified *section* to an A convenience method which coerces the *option* in the specified *section* to
integer. an integer. See :meth:`get` for explanation of *vars* and *default*.
.. method:: RawConfigParser.getfloat(section, option) .. method:: RawConfigParser.getfloat(section, option, [vars, default])
A convenience method which coerces the *option* in the specified *section* to a A convenience method which coerces the *option* in the specified *section* to
floating point number. a floating point number. See :meth:`get` for explanation of *vars* and
*default*.
.. method:: RawConfigParser.getboolean(section, option) .. method:: RawConfigParser.getboolean(section, option, [vars, default])
A convenience method which coerces the *option* in the specified *section* to a A convenience method which coerces the *option* in the specified *section*
Boolean value. Note that the accepted values for the option are ``"1"``, to a Boolean value. Note that the accepted values for the option are
``"yes"``, ``"true"``, and ``"on"``, which cause this method to return ``True``, ``"1"``, ``"yes"``, ``"true"``, and ``"on"``, which cause this method to
and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which cause it to return return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
``False``. These string values are checked in a case-insensitive manner. Any cause it to return ``False``. These string values are checked in
other value will cause it to raise :exc:`ValueError`. a case-insensitive manner. Any other value will cause it to raise
:exc:`ValueError`. See :meth:`get` for explanation of *vars* and *default*.
.. method:: RawConfigParser.items(section) .. method:: RawConfigParser.items(section)
@ -475,17 +480,44 @@ can, consider using :class:`SafeConfigParser` which adds validation and escaping
for the interpolation. for the interpolation.
.. method:: ConfigParser.get(section, option, raw=False, vars=None) .. method:: ConfigParser.get(section, option, raw=False, [vars, default])
Get an *option* value for the named *section*. If *vars* is provided, it Get an *option* value for the named *section*. If *vars* is provided, it
must be a dictionary. The *option* is looked up in *vars* (if provided), must be a dictionary. The *option* is looked up in *vars* (if provided),
*section*, and in *defaults* in that order. *section*, and in *DEFAULTSECT* in that order. If the key is not found and
*default* is provided, it is used as a fallback value. ``None`` can be
provided as a *default* value.
All the ``'%'`` interpolations are expanded in the return values, unless the All the ``'%'`` interpolations are expanded in the return values, unless the
*raw* argument is true. Values for interpolation keys are looked up in the *raw* argument is true. Values for interpolation keys are looked up in the
same manner as the option. same manner as the option.
.. method:: ConfigParser.getint(section, option, raw=False, [vars, default])
A convenience method which coerces the *option* in the specified *section* to
an integer. See :meth:`get` for explanation of *raw*, *vars* and *default*.
.. method:: ConfigParser.getfloat(section, option, raw=False, [vars, default])
A convenience method which coerces the *option* in the specified *section* to
a floating point number. See :meth:`get` for explanation of *raw*, *vars*
and *default*.
.. method:: ConfigParser.getboolean(section, option, raw=False, [vars, default])
A convenience method which coerces the *option* in the specified *section*
to a Boolean value. Note that the accepted values for the option are
``"1"``, ``"yes"``, ``"true"``, and ``"on"``, which cause this method to
return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
cause it to return ``False``. These string values are checked in
a case-insensitive manner. Any other value will cause it to raise
:exc:`ValueError`. See :meth:`get` for explanation of *raw*, *vars* and
*default*.
.. method:: ConfigParser.items(section, raw=False, vars=None) .. method:: ConfigParser.items(section, raw=False, vars=None)
Return a list of ``(name, value)`` pairs for each option in the given Return a list of ``(name, value)`` pairs for each option in the given

View file

@ -24,9 +24,9 @@ ConfigParser -- responsible for parsing a list of
methods: methods:
__init__(defaults=None, dict_type=_default_dict, __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
delimiters=('=', ':'), comment_prefixes=('#', ';'), delimiters=('=', ':'), comment_prefixes=_COMPATIBLE,
strict=False, empty_lines_in_values=True, allow_no_value=False): strict=False, empty_lines_in_values=True):
Create the parser. When `defaults' is given, it is initialized into the Create the parser. When `defaults' is given, it is initialized into the
dictionary or intrinsic defaults. The keys must be strings, the values dictionary or intrinsic defaults. The keys must be strings, the values
must be appropriate for %()s string interpolation. Note that `__name__' must be appropriate for %()s string interpolation. Note that `__name__'
@ -82,22 +82,24 @@ ConfigParser -- responsible for parsing a list of
Read configuration from a dictionary. Keys are section names, Read configuration from a dictionary. Keys are section names,
values are dictionaries with keys and values that should be present values are dictionaries with keys and values that should be present
in the section. If the used dictionary type preserves order, sections in the section. If the used dictionary type preserves order, sections
and their keys will be added in order. and their keys will be added in order. Values are automatically
converted to strings.
get(section, option, raw=False, vars=None) get(section, option, raw=False, vars=None, default=_UNSET)
Return a string value for the named option. All % interpolations are Return a string value for the named option. All % interpolations are
expanded in the return values, based on the defaults passed into the expanded in the return values, based on the defaults passed into the
constructor and the DEFAULT section. Additional substitutions may be constructor and the DEFAULT section. Additional substitutions may be
provided using the `vars' argument, which must be a dictionary whose provided using the `vars' argument, which must be a dictionary whose
contents override any pre-existing defaults. contents override any pre-existing defaults. If `option' is a key in
`vars', the value from `vars' is used.
getint(section, options) getint(section, options, raw=False, vars=None, default=_UNSET)
Like get(), but convert value to an integer. Like get(), but convert value to an integer.
getfloat(section, options) getfloat(section, options, raw=False, vars=None, default=_UNSET)
Like get(), but convert value to a float. Like get(), but convert value to a float.
getboolean(section, options) getboolean(section, options, raw=False, vars=None, default=_UNSET)
Like get(), but convert value to a boolean (currently case Like get(), but convert value to a boolean (currently case
insensitively defined as 0, false, no, off for False, and 1, true, insensitively defined as 0, false, no, off for False, and 1, true,
yes, on for True). Returns False or True. yes, on for True). Returns False or True.
@ -353,6 +355,17 @@ class MissingSectionHeaderError(ParsingError):
self.args = (filename, lineno, line) self.args = (filename, lineno, line)
# Used in parsers to denote selecting a backwards-compatible inline comment
# character behavior (; and # are comments at the start of a line, but ; only
# inline)
_COMPATIBLE = object()
# Used in parser getters to indicate the default behaviour when a specific
# option is not found it to raise an exception. Created to enable `None' as
# a valid fallback value.
_UNSET = object()
class RawConfigParser: class RawConfigParser:
"""ConfigParser that does not do interpolation.""" """ConfigParser that does not do interpolation."""
@ -389,9 +402,9 @@ class RawConfigParser:
OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE) OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
# Compiled regular expression for matching leading whitespace in a line # Compiled regular expression for matching leading whitespace in a line
NONSPACECRE = re.compile(r"\S") NONSPACECRE = re.compile(r"\S")
# Select backwards-compatible inline comment character behavior # Possible boolean values in the configuration.
# (; and # are comments at the start of a line, but ; only inline) BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
_COMPATIBLE = object() '0': False, 'no': False, 'false': False, 'off': False}
def __init__(self, defaults=None, dict_type=_default_dict, def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False, *, delimiters=('=', ':'), allow_no_value=False, *, delimiters=('=', ':'),
@ -414,7 +427,7 @@ class RawConfigParser:
else: else:
self._optcre = re.compile(self._OPT_TMPL.format(delim=d), self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
re.VERBOSE) re.VERBOSE)
if comment_prefixes is self._COMPATIBLE: if comment_prefixes is _COMPATIBLE:
self._startonly_comment_prefixes = ('#',) self._startonly_comment_prefixes = ('#',)
self._comment_prefixes = (';',) self._comment_prefixes = (';',)
else: else:
@ -528,6 +541,8 @@ class RawConfigParser:
elements_added.add(section) elements_added.add(section)
for key, value in keys.items(): for key, value in keys.items():
key = self.optionxform(key) key = self.optionxform(key)
if value is not None:
value = str(value)
if self._strict and (section, key) in elements_added: if self._strict and (section, key) in elements_added:
raise DuplicateOptionError(section, key, source) raise DuplicateOptionError(section, key, source)
elements_added.add((section, key)) elements_added.add((section, key))
@ -542,21 +557,29 @@ class RawConfigParser:
) )
self.read_file(fp, source=filename) self.read_file(fp, source=filename)
def get(self, section, option): def get(self, section, option, vars=None, default=_UNSET):
opt = self.optionxform(option) """Get an option value for a given section.
if section not in self._sections:
if section != DEFAULTSECT: If `vars' is provided, it must be a dictionary. The option is looked up
raise NoSectionError(section) in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
if opt in self._defaults: If the key is not found and `default' is provided, it is used as
return self._defaults[opt] a fallback value. `None' can be provided as a `default' value.
"""
try:
d = self._unify_values(section, vars)
except NoSectionError:
if default is _UNSET:
raise
else: else:
return default
option = self.optionxform(option)
try:
return d[option]
except KeyError:
if default is _UNSET:
raise NoOptionError(option, section) raise NoOptionError(option, section)
elif opt in self._sections[section]:
return self._sections[section][opt]
elif opt in self._defaults:
return self._defaults[opt]
else: else:
raise NoOptionError(option, section) return default
def items(self, section): def items(self, section):
try: try:
@ -571,23 +594,35 @@ class RawConfigParser:
del d["__name__"] del d["__name__"]
return d.items() return d.items()
def _get(self, section, conv, option): def _get(self, section, conv, option, *args, **kwargs):
return conv(self.get(section, option)) return conv(self.get(section, option, *args, **kwargs))
def getint(self, section, option): def getint(self, section, option, vars=None, default=_UNSET):
return self._get(section, int, option) try:
return self._get(section, int, option, vars)
except (NoSectionError, NoOptionError):
if default is _UNSET:
raise
else:
return default
def getfloat(self, section, option): def getfloat(self, section, option, vars=None, default=_UNSET):
return self._get(section, float, option) try:
return self._get(section, float, option, vars)
except (NoSectionError, NoOptionError):
if default is _UNSET:
raise
else:
return default
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True, def getboolean(self, section, option, vars=None, default=_UNSET):
'0': False, 'no': False, 'false': False, 'off': False} try:
return self._get(section, self._convert_to_boolean, option, vars)
def getboolean(self, section, option): except (NoSectionError, NoOptionError):
v = self.get(section, option) if default is _UNSET:
if v.lower() not in self._boolean_states: raise
raise ValueError('Not a boolean: %s' % v) else:
return self._boolean_states[v.lower()] return default
def optionxform(self, optionstr): def optionxform(self, optionstr):
return optionstr.lower() return optionstr.lower()
@ -797,21 +832,10 @@ class RawConfigParser:
exc.append(lineno, repr(line)) exc.append(lineno, repr(line))
return exc return exc
def _unify_values(self, section, vars):
class ConfigParser(RawConfigParser): """Create a copy of the DEFAULTSECT with values from a specific
"""ConfigParser implementing interpolation.""" `section' and the `vars' dictionary. If provided, values in `vars'
take precendence.
def get(self, section, option, raw=False, vars=None):
"""Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up
in `vars' (if provided), `section', and in `defaults' in that order.
All % interpolations are expanded in the return values, unless the
optional argument `raw' is true. Values for interpolation keys are
looked up in the same manner as the option.
The section DEFAULT is special.
""" """
d = self._defaults.copy() d = self._defaults.copy()
try: try:
@ -822,18 +846,86 @@ class ConfigParser(RawConfigParser):
# Update with the entry specific variables # Update with the entry specific variables
if vars: if vars:
for key, value in vars.items(): for key, value in vars.items():
if value is not None:
value = str(value)
d[self.optionxform(key)] = value d[self.optionxform(key)] = value
return d
def _convert_to_boolean(self, value):
"""Return a boolean value translating from other types if necessary.
"""
if value.lower() not in self.BOOLEAN_STATES:
raise ValueError('Not a boolean: %s' % value)
return self.BOOLEAN_STATES[value.lower()]
class ConfigParser(RawConfigParser):
"""ConfigParser implementing interpolation."""
def get(self, section, option, raw=False, vars=None, default=_UNSET):
"""Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
If the key is not found and `default' is provided, it is used as
a fallback value. `None' can be provided as a `default' value.
All % interpolations are expanded in the return values, unless the
optional argument `raw' is true. Values for interpolation keys are
looked up in the same manner as the option.
The section DEFAULT is special.
"""
try:
d = self._unify_values(section, vars)
except NoSectionError:
if default is _UNSET:
raise
else:
return default
option = self.optionxform(option) option = self.optionxform(option)
try: try:
value = d[option] value = d[option]
except KeyError: except KeyError:
if default is _UNSET:
raise NoOptionError(option, section) raise NoOptionError(option, section)
else:
return default
if raw or value is None: if raw or value is None:
return value return value
else: else:
return self._interpolate(section, option, value, d) return self._interpolate(section, option, value, d)
def getint(self, section, option, raw=False, vars=None, default=_UNSET):
try:
return self._get(section, int, option, raw, vars)
except (NoSectionError, NoOptionError):
if default is _UNSET:
raise
else:
return default
def getfloat(self, section, option, raw=False, vars=None, default=_UNSET):
try:
return self._get(section, float, option, raw, vars)
except (NoSectionError, NoOptionError):
if default is _UNSET:
raise
else:
return default
def getboolean(self, section, option, raw=False, vars=None,
default=_UNSET):
try:
return self._get(section, self._convert_to_boolean, option, raw,
vars)
except (NoSectionError, NoOptionError):
if default is _UNSET:
raise
else:
return default
def items(self, section, raw=False, vars=None): def items(self, section, raw=False, vars=None):
"""Return a list of (name, value) tuples for each option in a section. """Return a list of (name, value) tuples for each option in a section.

View file

@ -62,9 +62,10 @@ class BasicTestCase(CfgParserTestCaseClass):
'Spaces', 'Spaces',
'Spacey Bar', 'Spacey Bar',
'Spacey Bar From The Beginning', 'Spacey Bar From The Beginning',
'Types',
] ]
if self.allow_no_value: if self.allow_no_value:
E.append(r'NoValue') E.append('NoValue')
E.sort() E.sort()
eq = self.assertEqual eq = self.assertEqual
eq(L, E) eq(L, E)
@ -80,9 +81,43 @@ class BasicTestCase(CfgParserTestCaseClass):
eq(cf.get('Commented Bar', 'baz'), 'qwe') eq(cf.get('Commented Bar', 'baz'), 'qwe')
eq(cf.get('Spaces', 'key with spaces'), 'value') eq(cf.get('Spaces', 'key with spaces'), 'value')
eq(cf.get('Spaces', 'another with spaces'), 'splat!') eq(cf.get('Spaces', 'another with spaces'), 'splat!')
eq(cf.getint('Types', 'int'), 42)
eq(cf.get('Types', 'int'), "42")
self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44)
eq(cf.get('Types', 'float'), "0.44")
eq(cf.getboolean('Types', 'boolean'), False)
if self.allow_no_value: if self.allow_no_value:
eq(cf.get('NoValue', 'option-without-value'), None) eq(cf.get('NoValue', 'option-without-value'), None)
# test vars= and default=
eq(cf.get('Foo Bar', 'foo', default='baz'), 'bar')
eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
with self.assertRaises(configparser.NoSectionError):
cf.get('No Such Foo Bar', 'foo')
with self.assertRaises(configparser.NoOptionError):
cf.get('Foo Bar', 'no-such-foo')
eq(cf.get('No Such Foo Bar', 'foo', default='baz'), 'baz')
eq(cf.get('Foo Bar', 'no-such-foo', default='baz'), 'baz')
eq(cf.get('Spacey Bar', 'foo', default=None), 'bar')
eq(cf.get('No Such Spacey Bar', 'foo', default=None), None)
eq(cf.getint('Types', 'int', default=18), 42)
eq(cf.getint('Types', 'no-such-int', default=18), 18)
eq(cf.getint('Types', 'no-such-int', default="18"), "18") # sic!
self.assertAlmostEqual(cf.getfloat('Types', 'float',
default=0.0), 0.44)
self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
default=0.0), 0.0)
eq(cf.getfloat('Types', 'no-such-float', default="0.0"), "0.0") # sic!
eq(cf.getboolean('Types', 'boolean', default=True), False)
eq(cf.getboolean('Types', 'no-such-boolean', default="yes"),
"yes") # sic!
eq(cf.getboolean('Types', 'no-such-boolean', default=True), True)
eq(cf.getboolean('No Such Types', 'boolean', default=True), True)
if self.allow_no_value:
eq(cf.get('NoValue', 'option-without-value', default=False), None)
eq(cf.get('NoValue', 'no-such-option-without-value',
default=False), False)
self.assertNotIn('__name__', cf.options("Foo Bar"), self.assertNotIn('__name__', cf.options("Foo Bar"),
'__name__ "option" should not be exposed by the API!') '__name__ "option" should not be exposed by the API!')
@ -127,6 +162,10 @@ foo[de]{0[0]}Deutsch
[Spaces] [Spaces]
key with spaces {0[1]} value key with spaces {0[1]} value
another with spaces {0[0]} splat! another with spaces {0[0]} splat!
[Types]
int {0[1]} 42
float {0[0]} 0.44
boolean {0[0]} NO
""".format(self.delimiters, self.comment_prefixes) """.format(self.delimiters, self.comment_prefixes)
if self.allow_no_value: if self.allow_no_value:
config_string += ( config_string += (
@ -194,7 +233,12 @@ another with spaces {0[0]} splat!
"Spaces": { "Spaces": {
"key with spaces": "value", "key with spaces": "value",
"another with spaces": "splat!", "another with spaces": "splat!",
} },
"Types": {
"int": 42,
"float": 0.44,
"boolean": False,
},
} }
if self.allow_no_value: if self.allow_no_value:
config.update({ config.update({
@ -732,8 +776,11 @@ class SafeConfigParserTestCaseTrickyFile(CfgParserTestCaseClass):
'no values here', 'no values here',
'tricky interpolation', 'tricky interpolation',
'more interpolation']) 'more interpolation'])
#self.assertEqual(cf.getint('DEFAULT', 'go', vars={'interpolate': '-1'}), self.assertEqual(cf.getint('DEFAULT', 'go',
# -1) vars={'interpolate': '-1'}), -1)
with self.assertRaises(ValueError):
# no interpolation will happen
cf.getint('DEFAULT', 'go', raw=True, vars={'interpolate': '-1'})
self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4) self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10) self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
longname = 'yeah, sections can be indented as well' longname = 'yeah, sections can be indented as well'
@ -808,7 +855,7 @@ class SortedTestCase(RawConfigParserTestCase):
class CompatibleTestCase(CfgParserTestCaseClass): class CompatibleTestCase(CfgParserTestCaseClass):
config_class = configparser.RawConfigParser config_class = configparser.RawConfigParser
comment_prefixes = configparser.RawConfigParser._COMPATIBLE comment_prefixes = configparser._COMPATIBLE
def test_comment_handling(self): def test_comment_handling(self):
config_string = textwrap.dedent("""\ config_string = textwrap.dedent("""\

View file

@ -158,6 +158,9 @@ Library
- Issue #9753: Fixed socket.dup, which did not always work correctly - Issue #9753: Fixed socket.dup, which did not always work correctly
on Windows. on Windows.
- Issue #9421: Made the get<type> methods consistently accept the vars
and default arguments on all parser classes.
- Issue #7005: Fixed output of None values for RawConfigParser.write and - Issue #7005: Fixed output of None values for RawConfigParser.write and
ConfigParser.write. ConfigParser.write.