bpo-23835: Restore legacy defaults= behavior for RawConfigParser (#3191)

The fix for bpo-23835 fixed ConfigParser behavior in defaults= handling.
Unfortunately, it caused a backwards compatibility regression with
RawConfigParser objects which allow for non-string values.

This commit restores the legacy behavior for RawConfigParser only.
This commit is contained in:
Łukasz Langa 2017-08-24 09:43:53 -07:00 committed by GitHub
parent a6296d34a4
commit a5fab17fc1
3 changed files with 34 additions and 12 deletions

View File

@ -1213,8 +1213,10 @@ RawConfigParser Objects
default_section=configparser.DEFAULTSECT[, \ default_section=configparser.DEFAULTSECT[, \
interpolation]) interpolation])
Legacy variant of the :class:`ConfigParser` with interpolation disabled Legacy variant of the :class:`ConfigParser`. It has interpolation
by default and unsafe ``add_section`` and ``set`` methods. disabled by default and allows for non-string section names, option
names, and values via its unsafe ``add_section`` and ``set`` methods,
as well as the legacy ``defaults=`` keyword argument handling.
.. note:: .. note::
Consider using :class:`ConfigParser` instead which checks types of Consider using :class:`ConfigParser` instead which checks types of

View File

@ -635,7 +635,7 @@ class RawConfigParser(MutableMapping):
if converters is not _UNSET: if converters is not _UNSET:
self._converters.update(converters) self._converters.update(converters)
if defaults: if defaults:
self.read_dict({default_section: defaults}) self._read_defaults(defaults)
def defaults(self): def defaults(self):
return self._defaults return self._defaults
@ -1121,6 +1121,12 @@ class RawConfigParser(MutableMapping):
section, section,
name, val) name, val)
def _read_defaults(self, defaults):
"""Read the defaults passed in the initializer.
Note: values can be non-string."""
for key, value in defaults.items():
self._defaults[self.optionxform(key)] = value
def _handle_error(self, exc, fpname, lineno, line): def _handle_error(self, exc, fpname, lineno, line):
if not exc: if not exc:
exc = ParsingError(fpname) exc = ParsingError(fpname)
@ -1198,6 +1204,11 @@ class ConfigParser(RawConfigParser):
self._validate_value_types(section=section) self._validate_value_types(section=section)
super().add_section(section) super().add_section(section)
def _read_defaults(self, defaults):
"""Reads the defaults passed in the initializer, implicitly converting
values to strings like the rest of the API."""
self.read_dict({self.default_section: defaults})
class SafeConfigParser(ConfigParser): class SafeConfigParser(ConfigParser):
"""ConfigParser alias for backwards compatibility purposes.""" """ConfigParser alias for backwards compatibility purposes."""

View File

@ -855,15 +855,6 @@ boolean {0[0]} NO
self.assertEqual(cf.get('DEFAULT', 'test'), 'test') self.assertEqual(cf.get('DEFAULT', 'test'), 'test')
self.assertEqual(cf['DEFAULT']['test'], 'test') self.assertEqual(cf['DEFAULT']['test'], 'test')
def test_defaults_keyword(self):
# test that bpo-23835 is fixed
cf = self.newconfig(defaults={1: 2.4})
self.assertEqual(cf[self.default_section]['1'], '2.4')
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
cf = self.newconfig(defaults={"A": 5.2})
self.assertEqual(cf[self.default_section]['a'], '5.2')
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
class StrictTestCase(BasicTestCase, unittest.TestCase): class StrictTestCase(BasicTestCase, unittest.TestCase):
config_class = configparser.RawConfigParser config_class = configparser.RawConfigParser
@ -959,6 +950,15 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
cf = self.newconfig() cf = self.newconfig()
self.assertRaises(ValueError, cf.add_section, self.default_section) self.assertRaises(ValueError, cf.add_section, self.default_section)
def test_defaults_keyword(self):
"""bpo-23835 fix for ConfigParser"""
cf = self.newconfig(defaults={1: 2.4})
self.assertEqual(cf[self.default_section]['1'], '2.4')
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
cf = self.newconfig(defaults={"A": 5.2})
self.assertEqual(cf[self.default_section]['a'], '5.2')
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase): class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
config_class = configparser.ConfigParser config_class = configparser.ConfigParser
@ -1099,6 +1099,15 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase):
cf.set('non-string', 1, 1) cf.set('non-string', 1, 1)
self.assertEqual(cf.get('non-string', 1), 1) self.assertEqual(cf.get('non-string', 1), 1)
def test_defaults_keyword(self):
"""bpo-23835 legacy behavior for RawConfigParser"""
with self.assertRaises(AttributeError) as ctx:
self.newconfig(defaults={1: 2.4})
err = ctx.exception
self.assertEqual(str(err), "'int' object has no attribute 'lower'")
cf = self.newconfig(defaults={"A": 5.2})
self.assertAlmostEqual(cf[self.default_section]['a'], 5.2)
class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')