]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-127133: Remove ability to nest argument groups & mutually exclusive groups (#127186)
authorSavannah Ostrowski <savannahostrowski@gmail.com>
Sun, 24 Nov 2024 15:20:37 +0000 (07:20 -0800)
committerGitHub <noreply@github.com>
Sun, 24 Nov 2024 15:20:37 +0000 (15:20 +0000)
Doc/library/argparse.rst
Doc/whatsnew/3.14.rst
Lib/argparse.py
Lib/test/test_argparse.py
Misc/NEWS.d/next/Core_and_Builtins/2024-11-23-04-54-42.gh-issue-127133.WMoJjF.rst [new file with mode: 0644]

index 410b6e11af0dc09cd14bcf53de7b10091a2efa53..da4071dee34b8c414238cb2f3deebcaec7fbd3eb 100644 (file)
@@ -1926,11 +1926,10 @@ Argument groups
    Note that any arguments not in your user-defined groups will end up back
    in the usual "positional arguments" and "optional arguments" sections.
 
-   .. versionchanged:: 3.11
-    Calling :meth:`add_argument_group` on an argument group is deprecated.
-    This feature was never supported and does not always work correctly.
-    The function exists on the API by accident through inheritance and
-    will be removed in the future.
+   .. deprecated-removed:: 3.11 3.14
+      Calling :meth:`add_argument_group` on an argument group now raises an
+      exception. This nesting was never supported, often failed to work
+      correctly, and was unintentionally exposed through inheritance.
 
    .. deprecated:: 3.14
       Passing prefix_chars_ to :meth:`add_argument_group`
@@ -1993,11 +1992,11 @@ Mutual exclusion
        --foo FOO   foo help
        --bar BAR   bar help
 
-   .. versionchanged:: 3.11
-    Calling :meth:`add_argument_group` or :meth:`add_mutually_exclusive_group`
-    on a mutually exclusive group is deprecated. These features were never
-    supported and do not always work correctly. The functions exist on the
-    API by accident through inheritance and will be removed in the future.
+   .. deprecated-removed:: 3.11 3.14
+      Calling :meth:`add_argument_group` or :meth:`add_mutually_exclusive_group`
+      on a mutually exclusive group now raises an exception. This nesting was
+      never supported, often failed to work correctly, and was unintentionally
+      exposed through inheritance.
 
 
 Parser defaults
index bc4ab6789e16762836875dd20ed7020fb6d7bf04..2c1acb832080fcadb08cfc925625d26a1bd8ed53 100644 (file)
@@ -641,6 +641,14 @@ argparse
   of :class:`!argparse.BooleanOptionalAction`.
   They were deprecated since 3.12.
 
+* Calling :meth:`~argparse.ArgumentParser.add_argument_group` on an argument
+  group, and calling :meth:`~argparse.ArgumentParser.add_argument_group` or
+  :meth:`~argparse.ArgumentParser.add_mutually_exclusive_group` on a mutually
+  exclusive group now raise exceptions. This nesting was never supported,
+  often failed to work correctly, and was unintentionally exposed through
+  inheritance. This functionality has been deprecated since Python 3.11.
+  (Contributed by Savannah Ostrowski in :gh:`127186`.)
+
 ast
 ---
 
index f5a7342c2fc355c34f466ec625c5e7c3c6ed03f6..d24fa72e573d4f24b14305ca2a6e51e52b3559f7 100644 (file)
@@ -1709,14 +1709,7 @@ class _ArgumentGroup(_ActionsContainer):
         self._group_actions.remove(action)
 
     def add_argument_group(self, *args, **kwargs):
-        import warnings
-        warnings.warn(
-            "Nesting argument groups is deprecated.",
-            category=DeprecationWarning,
-            stacklevel=2
-        )
-        return super().add_argument_group(*args, **kwargs)
-
+        raise ValueError('argument groups cannot be nested')
 
 class _MutuallyExclusiveGroup(_ArgumentGroup):
 
@@ -1737,15 +1730,8 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
         self._container._remove_action(action)
         self._group_actions.remove(action)
 
-    def add_mutually_exclusive_group(self, *args, **kwargs):
-        import warnings
-        warnings.warn(
-            "Nesting mutually exclusive groups is deprecated.",
-            category=DeprecationWarning,
-            stacklevel=2
-        )
-        return super().add_mutually_exclusive_group(*args, **kwargs)
-
+    def add_mutually_exclusive_group(self, **kwargs):
+        raise ValueError('mutually exclusive groups cannot be nested')
 
 def _prog_name(prog=None):
     if prog is not None:
index 69243fde5f0f9810538cc38877ac520cfce81b5e..488a3a4ed20facc4f6ce8ce4d64d7dfffa5324c8 100644 (file)
@@ -2997,6 +2997,13 @@ class TestGroupConstructor(TestCase):
         self.assertEqual(msg, str(cm.warning))
         self.assertEqual(cm.filename, __file__)
 
+    def test_nested_argument_group(self):
+        parser = argparse.ArgumentParser()
+        g = parser.add_argument_group()
+        self.assertRaisesRegex(ValueError,
+                                 'argument groups cannot be nested',
+                                 g.add_argument_group)
+
 # ===================
 # Parent parser tests
 # ===================
@@ -3297,6 +3304,14 @@ class TestMutuallyExclusiveGroupErrors(TestCase):
         with self.assertRaises(ValueError):
             parser.parse_args(['-h'])
 
+    def test_nested_mutex_groups(self):
+        parser = argparse.ArgumentParser(prog='PROG')
+        g = parser.add_mutually_exclusive_group()
+        g.add_argument("--spam")
+        self.assertRaisesRegex(ValueError,
+                               'mutually exclusive groups cannot be nested',
+                               g.add_mutually_exclusive_group)
+
 class MEMixin(object):
 
     def test_failures_when_not_required(self):
@@ -3664,55 +3679,6 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase):
           -c          c help
         '''
 
-class TestMutuallyExclusiveNested(MEMixin, TestCase):
-
-    # Nesting mutually exclusive groups is an undocumented feature
-    # that came about by accident through inheritance and has been
-    # the source of many bugs. It is deprecated and this test should
-    # eventually be removed along with it.
-
-    def get_parser(self, required):
-        parser = ErrorRaisingArgumentParser(prog='PROG')
-        group = parser.add_mutually_exclusive_group(required=required)
-        group.add_argument('-a')
-        group.add_argument('-b')
-        with warnings.catch_warnings():
-            warnings.simplefilter('ignore', DeprecationWarning)
-            group2 = group.add_mutually_exclusive_group(required=required)
-        group2.add_argument('-c')
-        group2.add_argument('-d')
-        with warnings.catch_warnings():
-            warnings.simplefilter('ignore', DeprecationWarning)
-            group3 = group2.add_mutually_exclusive_group(required=required)
-        group3.add_argument('-e')
-        group3.add_argument('-f')
-        return parser
-
-    usage_when_not_required = '''\
-        usage: PROG [-h] [-a A | -b B | [-c C | -d D | [-e E | -f F]]]
-        '''
-    usage_when_required = '''\
-        usage: PROG [-h] (-a A | -b B | (-c C | -d D | (-e E | -f F)))
-        '''
-
-    help = '''\
-
-        options:
-          -h, --help  show this help message and exit
-          -a A
-          -b B
-          -c C
-          -d D
-          -e E
-          -f F
-        '''
-
-    # We are only interested in testing the behavior of format_usage().
-    test_failures_when_not_required = None
-    test_failures_when_required = None
-    test_successes_when_not_required = None
-    test_successes_when_required = None
-
 
 class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
     def get_parser(self, required=None):
@@ -4883,25 +4849,6 @@ class TestHelpUsageNoWhitespaceCrash(TestCase):
         usage = 'usage: PROG [-h]\n'
         self.assertEqual(parser.format_usage(), usage)
 
-    def test_nested_mutex_groups(self):
-        parser = argparse.ArgumentParser(prog='PROG')
-        g = parser.add_mutually_exclusive_group()
-        g.add_argument("--spam")
-        with warnings.catch_warnings():
-            warnings.simplefilter('ignore', DeprecationWarning)
-            gg = g.add_mutually_exclusive_group()
-        gg.add_argument("--hax")
-        gg.add_argument("--hox", help=argparse.SUPPRESS)
-        gg.add_argument("--hex")
-        g.add_argument("--eggs")
-        parser.add_argument("--num")
-
-        usage = textwrap.dedent('''\
-        usage: PROG [-h] [--spam SPAM | [--hax HAX | --hex HEX] | --eggs EGGS]
-                    [--num NUM]
-        ''')
-        self.assertEqual(parser.format_usage(), usage)
-
     def test_long_mutex_groups_wrap(self):
         parser = argparse.ArgumentParser(prog='PROG')
         g = parser.add_mutually_exclusive_group()
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-23-04-54-42.gh-issue-127133.WMoJjF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-23-04-54-42.gh-issue-127133.WMoJjF.rst
new file mode 100644 (file)
index 0000000..56b496b
--- /dev/null
@@ -0,0 +1,6 @@
+Calling :meth:`argparse.ArgumentParser.add_argument_group` on an argument group,
+and calling :meth:`argparse.ArgumentParser.add_argument_group` or
+:meth:`argparse.ArgumentParser.add_mutually_exclusive_group` on a mutually
+exclusive group now raise exceptions. This nesting was never supported, often
+failed to work correctly, and was unintentionally exposed through inheritance.
+This functionality has been deprecated since Python 3.11.