Skip to content

Commit a5fab17

Browse files
authored
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.
1 parent a6296d3 commit a5fab17

File tree

3 files changed

+34
-12
lines changed

3 files changed

+34
-12
lines changed

Doc/library/configparser.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,8 +1213,10 @@ RawConfigParser Objects
12131213
default_section=configparser.DEFAULTSECT[, \
12141214
interpolation])
12151215
1216-
Legacy variant of the :class:`ConfigParser` with interpolation disabled
1217-
by default and unsafe ``add_section`` and ``set`` methods.
1216+
Legacy variant of the :class:`ConfigParser`. It has interpolation
1217+
disabled by default and allows for non-string section names, option
1218+
names, and values via its unsafe ``add_section`` and ``set`` methods,
1219+
as well as the legacy ``defaults=`` keyword argument handling.
12181220

12191221
.. note::
12201222
Consider using :class:`ConfigParser` instead which checks types of

Lib/configparser.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ def __init__(self, defaults=None, dict_type=_default_dict,
635635
if converters is not _UNSET:
636636
self._converters.update(converters)
637637
if defaults:
638-
self.read_dict({default_section: defaults})
638+
self._read_defaults(defaults)
639639

640640
def defaults(self):
641641
return self._defaults
@@ -1121,6 +1121,12 @@ def _join_multiline_values(self):
11211121
section,
11221122
name, val)
11231123

1124+
def _read_defaults(self, defaults):
1125+
"""Read the defaults passed in the initializer.
1126+
Note: values can be non-string."""
1127+
for key, value in defaults.items():
1128+
self._defaults[self.optionxform(key)] = value
1129+
11241130
def _handle_error(self, exc, fpname, lineno, line):
11251131
if not exc:
11261132
exc = ParsingError(fpname)
@@ -1198,6 +1204,11 @@ def add_section(self, section):
11981204
self._validate_value_types(section=section)
11991205
super().add_section(section)
12001206

1207+
def _read_defaults(self, defaults):
1208+
"""Reads the defaults passed in the initializer, implicitly converting
1209+
values to strings like the rest of the API."""
1210+
self.read_dict({self.default_section: defaults})
1211+
12011212

12021213
class SafeConfigParser(ConfigParser):
12031214
"""ConfigParser alias for backwards compatibility purposes."""

Lib/test/test_configparser.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -855,15 +855,6 @@ def test_invalid_multiline_value(self):
855855
self.assertEqual(cf.get('DEFAULT', 'test'), 'test')
856856
self.assertEqual(cf['DEFAULT']['test'], 'test')
857857

858-
def test_defaults_keyword(self):
859-
# test that bpo-23835 is fixed
860-
cf = self.newconfig(defaults={1: 2.4})
861-
self.assertEqual(cf[self.default_section]['1'], '2.4')
862-
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
863-
cf = self.newconfig(defaults={"A": 5.2})
864-
self.assertEqual(cf[self.default_section]['a'], '5.2')
865-
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
866-
867858

868859
class StrictTestCase(BasicTestCase, unittest.TestCase):
869860
config_class = configparser.RawConfigParser
@@ -959,6 +950,15 @@ def test_add_section_default(self):
959950
cf = self.newconfig()
960951
self.assertRaises(ValueError, cf.add_section, self.default_section)
961952

953+
def test_defaults_keyword(self):
954+
"""bpo-23835 fix for ConfigParser"""
955+
cf = self.newconfig(defaults={1: 2.4})
956+
self.assertEqual(cf[self.default_section]['1'], '2.4')
957+
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
958+
cf = self.newconfig(defaults={"A": 5.2})
959+
self.assertEqual(cf[self.default_section]['a'], '5.2')
960+
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
961+
962962

963963
class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
964964
config_class = configparser.ConfigParser
@@ -1099,6 +1099,15 @@ def test_set_nonstring_types(self):
10991099
cf.set('non-string', 1, 1)
11001100
self.assertEqual(cf.get('non-string', 1), 1)
11011101

1102+
def test_defaults_keyword(self):
1103+
"""bpo-23835 legacy behavior for RawConfigParser"""
1104+
with self.assertRaises(AttributeError) as ctx:
1105+
self.newconfig(defaults={1: 2.4})
1106+
err = ctx.exception
1107+
self.assertEqual(str(err), "'int' object has no attribute 'lower'")
1108+
cf = self.newconfig(defaults={"A": 5.2})
1109+
self.assertAlmostEqual(cf[self.default_section]['a'], 5.2)
1110+
11021111

11031112
class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
11041113
delimiters = (':=', '$')

0 commit comments

Comments
 (0)