https://github.com/python/cpython/commit/2104bde572aa60f547274fd529312c5708599001
commit: 2104bde572aa60f547274fd529312c5708599001
branch: main
author: Savannah Ostrowski <[email protected]>
committer: iritkatriel <[email protected]>
date: 2024-11-24T15:20:37Z
summary:

GH-127133: Remove ability to nest argument groups & mutually exclusive groups 
(#127186)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2024-11-23-04-54-42.gh-issue-127133.WMoJjF.rst
M Doc/library/argparse.rst
M Doc/whatsnew/3.14.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 410b6e11af0dc0..da4071dee34b8c 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -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
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index bc4ab6789e1676..2c1acb832080fc 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -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
 ---
 
diff --git a/Lib/argparse.py b/Lib/argparse.py
index f5a7342c2fc355..d24fa72e573d4f 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1709,14 +1709,7 @@ def _remove_action(self, action):
         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 @@ def _remove_action(self, action):
         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:
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 69243fde5f0f98..488a3a4ed20fac 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2997,6 +2997,13 @@ def test_group_prefix_chars_default(self):
         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 @@ def test_empty_group(self):
         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 @@ def get_parser(self, required):
           -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 @@ def test_all_suppressed_mutex_with_optional_nargs(self):
         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
index 00000000000000..56b496bdf1e310
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-23-04-54-42.gh-issue-127133.WMoJjF.rst
@@ -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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to