gh-123049: configparser: Allow to create the unnamed section from scratch. (#123077)

---------

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Pedro Lacerda 2024-08-18 16:52:25 -03:00 committed by GitHub
parent c15bfa9a71
commit be257c5815
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 52 additions and 13 deletions

View File

@ -1314,13 +1314,19 @@ RawConfigParser Objects
.. method:: add_section(section) .. method:: add_section(section)
Add a section named *section* to the instance. If a section by the given Add a section named *section* or :const:`UNNAMED_SECTION` to the instance.
name already exists, :exc:`DuplicateSectionError` is raised. If the
*default section* name is passed, :exc:`ValueError` is raised. If the given section already exists, :exc:`DuplicateSectionError` is
raised. If the *default section* name is passed, :exc:`ValueError` is
raised. If :const:`UNNAMED_SECTION` is passed and support is disabled,
:exc:`UnnamedSectionDisabledError` is raised.
Type of *section* is not checked which lets users create non-string named Type of *section* is not checked which lets users create non-string named
sections. This behaviour is unsupported and may cause internal errors. sections. This behaviour is unsupported and may cause internal errors.
.. versionchanged:: 3.14
Added support for :const:`UNNAMED_SECTION`.
.. method:: set(section, option, value) .. method:: set(section, option, value)
@ -1405,7 +1411,6 @@ Exceptions
Exception raised when attempting to parse a file which has no section Exception raised when attempting to parse a file which has no section
headers. headers.
.. exception:: ParsingError .. exception:: ParsingError
Exception raised when errors occur attempting to parse a file. Exception raised when errors occur attempting to parse a file.
@ -1421,6 +1426,13 @@ Exceptions
.. versionadded:: 3.13 .. versionadded:: 3.13
.. exception:: UnnamedSectionDisabledError
Exception raised when attempting to use the
:const:`UNNAMED_SECTION` without enabling it.
.. versionadded:: 3.14
.. rubric:: Footnotes .. rubric:: Footnotes
.. [1] Config parsers allow for heavy customization. If you are interested in .. [1] Config parsers allow for heavy customization. If you are interested in

View File

@ -160,7 +160,7 @@ __all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
"NoOptionError", "InterpolationError", "InterpolationDepthError", "NoOptionError", "InterpolationError", "InterpolationDepthError",
"InterpolationMissingOptionError", "InterpolationSyntaxError", "InterpolationMissingOptionError", "InterpolationSyntaxError",
"ParsingError", "MissingSectionHeaderError", "ParsingError", "MissingSectionHeaderError",
"MultilineContinuationError", "MultilineContinuationError", "UnnamedSectionDisabledError",
"ConfigParser", "RawConfigParser", "ConfigParser", "RawConfigParser",
"Interpolation", "BasicInterpolation", "ExtendedInterpolation", "Interpolation", "BasicInterpolation", "ExtendedInterpolation",
"SectionProxy", "ConverterMapping", "SectionProxy", "ConverterMapping",
@ -362,6 +362,14 @@ class MultilineContinuationError(ParsingError):
self.line = line self.line = line
self.args = (filename, lineno, line) self.args = (filename, lineno, line)
class UnnamedSectionDisabledError(Error):
"""Raised when an attempt to use UNNAMED_SECTION is made with the
feature disabled."""
def __init__(self):
Error.__init__(self, "Support for UNNAMED_SECTION is disabled.")
class _UnnamedSection: class _UnnamedSection:
def __repr__(self): def __repr__(self):
@ -692,6 +700,10 @@ class RawConfigParser(MutableMapping):
if section == self.default_section: if section == self.default_section:
raise ValueError('Invalid section name: %r' % section) raise ValueError('Invalid section name: %r' % section)
if section is UNNAMED_SECTION:
if not self._allow_unnamed_section:
raise UnnamedSectionDisabledError
if section in self._sections: if section in self._sections:
raise DuplicateSectionError(section) raise DuplicateSectionError(section)
self._sections[section] = self._dict() self._sections[section] = self._dict()
@ -1203,20 +1215,20 @@ class RawConfigParser(MutableMapping):
return self.BOOLEAN_STATES[value.lower()] return self.BOOLEAN_STATES[value.lower()]
def _validate_value_types(self, *, section="", option="", value=""): def _validate_value_types(self, *, section="", option="", value=""):
"""Raises a TypeError for non-string values. """Raises a TypeError for illegal non-string values.
The only legal non-string value if we allow valueless Legal non-string values are UNNAMED_SECTION and falsey values if
options is None, so we need to check if the value is a they are allowed.
string if:
- we do not allow valueless options, or
- we allow valueless options but the value is not None
For compatibility reasons this method is not used in classic set() For compatibility reasons this method is not used in classic set()
for RawConfigParsers. It is invoked in every case for mapping protocol for RawConfigParsers. It is invoked in every case for mapping protocol
access and in ConfigParser.set(). access and in ConfigParser.set().
""" """
if not isinstance(section, str): if section is UNNAMED_SECTION:
raise TypeError("section names must be strings") if not self._allow_unnamed_section:
raise UnnamedSectionDisabledError
elif not isinstance(section, str):
raise TypeError("section names must be strings or UNNAMED_SECTION")
if not isinstance(option, str): if not isinstance(option, str):
raise TypeError("option keys must be strings") raise TypeError("option keys must be strings")
if not self._allow_no_value or value: if not self._allow_no_value or value:

View File

@ -2161,6 +2161,19 @@ class SectionlessTestCase(unittest.TestCase):
self.assertEqual('1', cfg2[configparser.UNNAMED_SECTION]['a']) self.assertEqual('1', cfg2[configparser.UNNAMED_SECTION]['a'])
self.assertEqual('2', cfg2[configparser.UNNAMED_SECTION]['b']) self.assertEqual('2', cfg2[configparser.UNNAMED_SECTION]['b'])
def test_add_section(self):
cfg = configparser.ConfigParser(allow_unnamed_section=True)
cfg.add_section(configparser.UNNAMED_SECTION)
cfg.set(configparser.UNNAMED_SECTION, 'a', '1')
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
def test_disabled_error(self):
with self.assertRaises(configparser.MissingSectionHeaderError):
configparser.ConfigParser().read_string("a = 1")
with self.assertRaises(configparser.UnnamedSectionDisabledError):
configparser.ConfigParser().add_section(configparser.UNNAMED_SECTION)
class MiscTestCase(unittest.TestCase): class MiscTestCase(unittest.TestCase):
def test__all__(self): def test__all__(self):

View File

@ -0,0 +1,2 @@
Add support for :const:`~configparser.UNNAMED_SECTION`
in :meth:`configparser.ConfigParser.add_section`.