From a5fab17fc11433b2418f626dc51e8a3d07b198ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Thu, 24 Aug 2017 09:43:53 -0700 Subject: [PATCH] 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. --- Doc/library/configparser.rst | 6 ++++-- Lib/configparser.py | 13 ++++++++++++- Lib/test/test_configparser.py | 27 ++++++++++++++++++--------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index c31a7e94651..acca6470111 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -1213,8 +1213,10 @@ RawConfigParser Objects default_section=configparser.DEFAULTSECT[, \ interpolation]) - Legacy variant of the :class:`ConfigParser` with interpolation disabled - by default and unsafe ``add_section`` and ``set`` methods. + Legacy variant of the :class:`ConfigParser`. It has interpolation + 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:: Consider using :class:`ConfigParser` instead which checks types of diff --git a/Lib/configparser.py b/Lib/configparser.py index 360924008dc..e172ac8b36f 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -635,7 +635,7 @@ class RawConfigParser(MutableMapping): if converters is not _UNSET: self._converters.update(converters) if defaults: - self.read_dict({default_section: defaults}) + self._read_defaults(defaults) def defaults(self): return self._defaults @@ -1121,6 +1121,12 @@ class RawConfigParser(MutableMapping): section, 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): if not exc: exc = ParsingError(fpname) @@ -1198,6 +1204,11 @@ class ConfigParser(RawConfigParser): self._validate_value_types(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): """ConfigParser alias for backwards compatibility purposes.""" diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index be22fa40310..d8969efc4db 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -855,15 +855,6 @@ boolean {0[0]} NO self.assertEqual(cf.get('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): config_class = configparser.RawConfigParser @@ -959,6 +950,15 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase): cf = self.newconfig() 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): config_class = configparser.ConfigParser @@ -1099,6 +1099,15 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): cf.set('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): delimiters = (':=', '$')