mirror of https://github.com/python/cpython
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:
parent
c15bfa9a71
commit
be257c5815
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add support for :const:`~configparser.UNNAMED_SECTION`
|
||||||
|
in :meth:`configparser.ConfigParser.add_section`.
|
Loading…
Reference in New Issue