]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-123049: configparser: Allow to create the unnamed section from scratch. (#123077)
authorPedro Lacerda <pslacerda@users.noreply.github.com>
Sun, 18 Aug 2024 19:52:25 +0000 (16:52 -0300)
committerGitHub <noreply@github.com>
Sun, 18 Aug 2024 19:52:25 +0000 (15:52 -0400)
---------

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Doc/library/configparser.rst
Lib/configparser.py
Lib/test/test_configparser.py
Misc/NEWS.d/next/Library/2024-08-16-16-53-52.gh-issue-123049.izx_fH.rst [new file with mode: 0644]

index 7aaad932c0104a1ffaa855c35312764f63036585..587fe50fd5116464d9a8692caa21cde579883f9f 100644 (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
index 4344a9e8baca4497d3535d0b1feea5ab876e5b2a..420dce77c234e1c7a13a52835cdea7921c228f0d 100644 (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:
index a934e493a76391cf9dea03389758ee9ceaacdba5..e3c5d08dd1e7d124ce26e1dd573a16e99e0c2031 100644 (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):
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 (file)
index 0000000..2faf850
--- /dev/null
@@ -0,0 +1,2 @@
+Add support for :const:`~configparser.UNNAMED_SECTION`
+in :meth:`configparser.ConfigParser.add_section`.