summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorFred Drake <fdrake@acm.org>2010-09-04 04:35:34 (GMT)
committerFred Drake <fdrake@acm.org>2010-09-04 04:35:34 (GMT)
commitcc645b9a59ce56aaa9b411c335c819ce31e26e85 (patch)
tree44a6d2ca80ec2d5f76de779a5cd63b8ec8bb5be3 /Lib
parentc934f32e0a2b5f9f2bc0b55573a6860626025d63 (diff)
downloadcpython-cc645b9a59ce56aaa9b411c335c819ce31e26e85.zip
cpython-cc645b9a59ce56aaa9b411c335c819ce31e26e85.tar.gz
cpython-cc645b9a59ce56aaa9b411c335c819ce31e26e85.tar.bz2
add consistent support for the vars and default arguments on all
configuration parser classes (http://bugs.python.org/issue9421)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/configparser.py196
-rw-r--r--Lib/test/test_cfgparser.py57
2 files changed, 196 insertions, 57 deletions
diff --git a/Lib/configparser.py b/Lib/configparser.py
index fb39ac3..7f1514f 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -24,9 +24,9 @@ ConfigParser -- responsible for parsing a list of
methods:
- __init__(defaults=None, dict_type=_default_dict,
- delimiters=('=', ':'), comment_prefixes=('#', ';'),
- strict=False, empty_lines_in_values=True, allow_no_value=False):
+ __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
+ delimiters=('=', ':'), comment_prefixes=_COMPATIBLE,
+ strict=False, empty_lines_in_values=True):
Create the parser. When `defaults' is given, it is initialized into the
dictionary or intrinsic defaults. The keys must be strings, the values
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,
values are 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 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
expanded in the return values, based on the defaults passed into the
constructor and the DEFAULT section. Additional substitutions may be
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.
- getfloat(section, options)
+ getfloat(section, options, raw=False, vars=None, default=_UNSET)
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
insensitively defined as 0, false, no, off for False, and 1, true,
yes, on for True). Returns False or True.
@@ -353,6 +355,17 @@ class MissingSectionHeaderError(ParsingError):
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:
"""ConfigParser that does not do interpolation."""
@@ -389,9 +402,9 @@ class RawConfigParser:
OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
# Compiled regular expression for matching leading whitespace in a line
NONSPACECRE = re.compile(r"\S")
- # Select backwards-compatible inline comment character behavior
- # (; and # are comments at the start of a line, but ; only inline)
- _COMPATIBLE = object()
+ # Possible boolean values in the configuration.
+ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
+ '0': False, 'no': False, 'false': False, 'off': False}
def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False, *, delimiters=('=', ':'),
@@ -414,7 +427,7 @@ class RawConfigParser:
else:
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
re.VERBOSE)
- if comment_prefixes is self._COMPATIBLE:
+ if comment_prefixes is _COMPATIBLE:
self._startonly_comment_prefixes = ('#',)
self._comment_prefixes = (';',)
else:
@@ -528,6 +541,8 @@ class RawConfigParser:
elements_added.add(section)
for key, value in keys.items():
key = self.optionxform(key)
+ if value is not None:
+ value = str(value)
if self._strict and (section, key) in elements_added:
raise DuplicateOptionError(section, key, source)
elements_added.add((section, key))
@@ -542,21 +557,29 @@ class RawConfigParser:
)
self.read_file(fp, source=filename)
- def get(self, section, option):
- opt = self.optionxform(option)
- if section not in self._sections:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- if opt in self._defaults:
- return self._defaults[opt]
+ def get(self, section, option, 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.
+ """
+ try:
+ d = self._unify_values(section, vars)
+ except NoSectionError:
+ if default is _UNSET:
+ raise
else:
+ return default
+ option = self.optionxform(option)
+ try:
+ return d[option]
+ except KeyError:
+ if default is _UNSET:
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:
- raise NoOptionError(option, section)
+ else:
+ return default
def items(self, section):
try:
@@ -571,23 +594,35 @@ class RawConfigParser:
del d["__name__"]
return d.items()
- def _get(self, section, conv, option):
- return conv(self.get(section, option))
+ def _get(self, section, conv, option, *args, **kwargs):
+ return conv(self.get(section, option, *args, **kwargs))
- def getint(self, section, option):
- return self._get(section, int, option)
-
- def getfloat(self, section, option):
- return self._get(section, float, option)
+ def getint(self, section, option, vars=None, default=_UNSET):
+ try:
+ return self._get(section, int, option, vars)
+ except (NoSectionError, NoOptionError):
+ if default is _UNSET:
+ raise
+ else:
+ return default
- _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
- '0': False, 'no': False, 'false': False, 'off': False}
+ def getfloat(self, section, option, vars=None, default=_UNSET):
+ try:
+ return self._get(section, float, option, vars)
+ except (NoSectionError, NoOptionError):
+ if default is _UNSET:
+ raise
+ else:
+ return default
- def getboolean(self, section, option):
- v = self.get(section, option)
- if v.lower() not in self._boolean_states:
- raise ValueError('Not a boolean: %s' % v)
- return self._boolean_states[v.lower()]
+ def getboolean(self, section, option, vars=None, default=_UNSET):
+ try:
+ return self._get(section, self._convert_to_boolean, option, vars)
+ except (NoSectionError, NoOptionError):
+ if default is _UNSET:
+ raise
+ else:
+ return default
def optionxform(self, optionstr):
return optionstr.lower()
@@ -797,15 +832,43 @@ class RawConfigParser:
exc.append(lineno, repr(line))
return exc
+ def _unify_values(self, section, vars):
+ """Create a copy of the DEFAULTSECT with values from a specific
+ `section' and the `vars' dictionary. If provided, values in `vars'
+ take precendence.
+ """
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != DEFAULTSECT:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ if value is not None:
+ value = str(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):
+ 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 `defaults' in that order.
+ 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
@@ -813,27 +876,56 @@ class ConfigParser(RawConfigParser):
The section DEFAULT is special.
"""
- d = self._defaults.copy()
try:
- d.update(self._sections[section])
- except KeyError:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- # Update with the entry specific variables
- if vars:
- for key, value in vars.items():
- d[self.optionxform(key)] = value
+ d = self._unify_values(section, vars)
+ except NoSectionError:
+ if default is _UNSET:
+ raise
+ else:
+ return default
option = self.optionxform(option)
try:
value = d[option]
except KeyError:
- raise NoOptionError(option, section)
+ if default is _UNSET:
+ raise NoOptionError(option, section)
+ else:
+ return default
if raw or value is None:
return value
else:
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):
"""Return a list of (name, value) tuples for each option in a section.
diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py
index a20678d..3079cfb 100644
--- a/Lib/test/test_cfgparser.py
+++ b/Lib/test/test_cfgparser.py
@@ -62,9 +62,10 @@ class BasicTestCase(CfgParserTestCaseClass):
'Spaces',
'Spacey Bar',
'Spacey Bar From The Beginning',
+ 'Types',
]
if self.allow_no_value:
- E.append(r'NoValue')
+ E.append('NoValue')
E.sort()
eq = self.assertEqual
eq(L, E)
@@ -80,9 +81,43 @@ class BasicTestCase(CfgParserTestCaseClass):
eq(cf.get('Commented Bar', 'baz'), 'qwe')
eq(cf.get('Spaces', 'key with spaces'), 'value')
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:
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"),
'__name__ "option" should not be exposed by the API!')
@@ -127,6 +162,10 @@ foo[de]{0[0]}Deutsch
[Spaces]
key with spaces {0[1]} value
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)
if self.allow_no_value:
config_string += (
@@ -194,7 +233,12 @@ another with spaces {0[0]} splat!
"Spaces": {
"key with spaces": "value",
"another with spaces": "splat!",
- }
+ },
+ "Types": {
+ "int": 42,
+ "float": 0.44,
+ "boolean": False,
+ },
}
if self.allow_no_value:
config.update({
@@ -732,8 +776,11 @@ class SafeConfigParserTestCaseTrickyFile(CfgParserTestCaseClass):
'no values here',
'tricky interpolation',
'more interpolation'])
- #self.assertEqual(cf.getint('DEFAULT', 'go', vars={'interpolate': '-1'}),
- # -1)
+ self.assertEqual(cf.getint('DEFAULT', 'go',
+ 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('corruption', 'value').split('\n')), 10)
longname = 'yeah, sections can be indented as well'
@@ -808,7 +855,7 @@ class SortedTestCase(RawConfigParserTestCase):
class CompatibleTestCase(CfgParserTestCaseClass):
config_class = configparser.RawConfigParser
- comment_prefixes = configparser.RawConfigParser._COMPATIBLE
+ comment_prefixes = configparser._COMPATIBLE
def test_comment_handling(self):
config_string = textwrap.dedent("""\