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)
Add a section named *section* to the instance. If a section by the given
name already exists, :exc:`DuplicateSectionError` is raised. If the
*default section* name is passed, :exc:`ValueError` is raised.
Add a section named *section* or :const:`UNNAMED_SECTION` to the instance.
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
sections. This behaviour is unsupported and may cause internal errors.
.. versionchanged:: 3.14
Added support for :const:`UNNAMED_SECTION`.
.. method:: set(section, option, value)
@ -1405,7 +1411,6 @@ Exceptions
Exception raised when attempting to parse a file which has no section
headers.
.. exception:: ParsingError
Exception raised when errors occur attempting to parse a file.
@ -1421,6 +1426,13 @@ Exceptions
.. versionadded:: 3.13
.. exception:: UnnamedSectionDisabledError
Exception raised when attempting to use the
:const:`UNNAMED_SECTION` without enabling it.
.. versionadded:: 3.14
.. rubric:: Footnotes
.. [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",
"InterpolationMissingOptionError", "InterpolationSyntaxError",
"ParsingError", "MissingSectionHeaderError",
"MultilineContinuationError",
"MultilineContinuationError", "UnnamedSectionDisabledError",
"ConfigParser", "RawConfigParser",
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
"SectionProxy", "ConverterMapping",
@ -362,6 +362,14 @@ class MultilineContinuationError(ParsingError):
self.line = 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:
def __repr__(self):
@ -692,6 +700,10 @@ class RawConfigParser(MutableMapping):
if section == self.default_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:
raise DuplicateSectionError(section)
self._sections[section] = self._dict()
@ -1203,20 +1215,20 @@ class RawConfigParser(MutableMapping):
return self.BOOLEAN_STATES[value.lower()]
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
options is None, so we need to check if the value is a
string if:
- we do not allow valueless options, or
- we allow valueless options but the value is not None
Legal non-string values are UNNAMED_SECTION and falsey values if
they are allowed.
For compatibility reasons this method is not used in classic set()
for RawConfigParsers. It is invoked in every case for mapping protocol
access and in ConfigParser.set().
"""
if not isinstance(section, str):
raise TypeError("section names must be strings")
if section is UNNAMED_SECTION:
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):
raise TypeError("option keys must be strings")
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('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):
def test__all__(self):

View File

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