summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/configparser.rst20
-rw-r--r--Lib/configparser.py30
-rw-r--r--Lib/test/test_configparser.py13
-rw-r--r--Misc/NEWS.d/next/Library/2024-08-16-16-53-52.gh-issue-123049.izx_fH.rst2
4 files changed, 52 insertions, 13 deletions
diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst
index 7aaad93..587fe50 100644
--- a/Doc/library/configparser.rst
+++ b/Doc/library/configparser.rst
@@ -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
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 4344a9e..420dce7 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -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:
diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py
index a934e49..e3c5d08 100644
--- a/Lib/test/test_configparser.py
+++ b/Lib/test/test_configparser.py
@@ -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):
diff --git a/Misc/NEWS.d/next/Library/2024-08-16-16-53-52.gh-issue-123049.izx_fH.rst b/Misc/NEWS.d/next/Library/2024-08-16-16-53-52.gh-issue-123049.izx_fH.rst
new file mode 100644
index 0000000..2faf850
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-16-16-53-52.gh-issue-123049.izx_fH.rst
@@ -0,0 +1,2 @@
+Add support for :const:`~configparser.UNNAMED_SECTION`
+in :meth:`configparser.ConfigParser.add_section`.