diff --git a/Lib/configparser.py b/Lib/configparser.py index a53ac87276445a..00625ebe408048 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -908,8 +908,10 @@ def items(self, section=_UNSET, raw=False, vars=None): if vars: for key, value in vars.items(): d[self.optionxform(key)] = value - value_getter = lambda option: self._interpolation.before_get(self, - section, option, d[option], d) + value_getter = lambda option: ( + d[option] if d[option] is None + else self._interpolation.before_get(self, section, option, + d[option], d)) if raw: value_getter = lambda option: d[option] return [(option, value_getter(option)) for option in orig_keys] diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 8d8dd2a2bf27fb..c164fec6bd535d 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1327,6 +1327,17 @@ def test_other_errors(self): class ConfigParserTestCaseNoValue(ConfigParserTestCase): allow_no_value = True + def test_items_reports_none_for_no_value(self): + # gh-82113: items() must report None (not '') for value-less options, + # consistent with get(), mapping access and dict(section). + cf = self.fromstring("[s]\nno_value\nspam = ham\n") + self.assertIsNone(cf.get("s", "no_value")) + self.assertIsNone(cf["s"]["no_value"]) + self.assertEqual(sorted(cf.items("s")), + [("no_value", None), ("spam", "ham")]) + self.assertEqual(sorted(cf.items("s", raw=True)), + [("no_value", None), ("spam", "ham")]) + class NoValueAndExtendedInterpolation(CfgParserTestCaseClass): interpolation = configparser.ExtendedInterpolation() diff --git a/Misc/NEWS.d/next/Library/2026-06-22-13-30-00.gh-issue-82113.Rb8xKt.rst b/Misc/NEWS.d/next/Library/2026-06-22-13-30-00.gh-issue-82113.Rb8xKt.rst new file mode 100644 index 00000000000000..773fe701edae89 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-22-13-30-00.gh-issue-82113.Rb8xKt.rst @@ -0,0 +1,4 @@ +:meth:`configparser.ConfigParser.items` now reports ``None`` rather than an +empty string for value-less options when ``allow_no_value`` is enabled, making +it consistent with :meth:`~configparser.ConfigParser.get`, mapping access and +``dict()`` conversion of a section.