mirror of
https://github.com/python/cpython.git
synced 2024-11-23 01:45:25 +08:00
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)
|
||||
|
||||
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
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -0,0 +1,2 @@
|
||||
Add support for :const:`~configparser.UNNAMED_SECTION`
|
||||
in :meth:`configparser.ConfigParser.add_section`.
|
Loading…
Reference in New Issue
Block a user