Skip to content

Commit 9f87a05

Browse files
savannahostrowskiebonnal
authored andcommitted
pythonGH-127133: Remove ability to nest argument groups & mutually exclusive groups (python#127186)
1 parent d157389 commit 9f87a05

File tree

5 files changed

+41
-95
lines changed

5 files changed

+41
-95
lines changed

Doc/library/argparse.rst

+9-10
Original file line numberDiff line numberDiff line change
@@ -1926,11 +1926,10 @@ Argument groups
19261926
Note that any arguments not in your user-defined groups will end up back
19271927
in the usual "positional arguments" and "optional arguments" sections.
19281928

1929-
.. versionchanged:: 3.11
1930-
Calling :meth:`add_argument_group` on an argument group is deprecated.
1931-
This feature was never supported and does not always work correctly.
1932-
The function exists on the API by accident through inheritance and
1933-
will be removed in the future.
1929+
.. deprecated-removed:: 3.11 3.14
1930+
Calling :meth:`add_argument_group` on an argument group now raises an
1931+
exception. This nesting was never supported, often failed to work
1932+
correctly, and was unintentionally exposed through inheritance.
19341933

19351934
.. deprecated:: 3.14
19361935
Passing prefix_chars_ to :meth:`add_argument_group`
@@ -1993,11 +1992,11 @@ Mutual exclusion
19931992
--foo FOO foo help
19941993
--bar BAR bar help
19951994

1996-
.. versionchanged:: 3.11
1997-
Calling :meth:`add_argument_group` or :meth:`add_mutually_exclusive_group`
1998-
on a mutually exclusive group is deprecated. These features were never
1999-
supported and do not always work correctly. The functions exist on the
2000-
API by accident through inheritance and will be removed in the future.
1995+
.. deprecated-removed:: 3.11 3.14
1996+
Calling :meth:`add_argument_group` or :meth:`add_mutually_exclusive_group`
1997+
on a mutually exclusive group now raises an exception. This nesting was
1998+
never supported, often failed to work correctly, and was unintentionally
1999+
exposed through inheritance.
20012000

20022001

20032002
Parser defaults

Doc/whatsnew/3.14.rst

+8
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,14 @@ argparse
641641
of :class:`!argparse.BooleanOptionalAction`.
642642
They were deprecated since 3.12.
643643

644+
* Calling :meth:`~argparse.ArgumentParser.add_argument_group` on an argument
645+
group, and calling :meth:`~argparse.ArgumentParser.add_argument_group` or
646+
:meth:`~argparse.ArgumentParser.add_mutually_exclusive_group` on a mutually
647+
exclusive group now raise exceptions. This nesting was never supported,
648+
often failed to work correctly, and was unintentionally exposed through
649+
inheritance. This functionality has been deprecated since Python 3.11.
650+
(Contributed by Savannah Ostrowski in :gh:`127186`.)
651+
644652
ast
645653
---
646654

Lib/argparse.py

+3-17
Original file line numberDiff line numberDiff line change
@@ -1709,14 +1709,7 @@ def _remove_action(self, action):
17091709
self._group_actions.remove(action)
17101710

17111711
def add_argument_group(self, *args, **kwargs):
1712-
import warnings
1713-
warnings.warn(
1714-
"Nesting argument groups is deprecated.",
1715-
category=DeprecationWarning,
1716-
stacklevel=2
1717-
)
1718-
return super().add_argument_group(*args, **kwargs)
1719-
1712+
raise ValueError('argument groups cannot be nested')
17201713

17211714
class _MutuallyExclusiveGroup(_ArgumentGroup):
17221715

@@ -1737,15 +1730,8 @@ def _remove_action(self, action):
17371730
self._container._remove_action(action)
17381731
self._group_actions.remove(action)
17391732

1740-
def add_mutually_exclusive_group(self, *args, **kwargs):
1741-
import warnings
1742-
warnings.warn(
1743-
"Nesting mutually exclusive groups is deprecated.",
1744-
category=DeprecationWarning,
1745-
stacklevel=2
1746-
)
1747-
return super().add_mutually_exclusive_group(*args, **kwargs)
1748-
1733+
def add_mutually_exclusive_group(self, **kwargs):
1734+
raise ValueError('mutually exclusive groups cannot be nested')
17491735

17501736
def _prog_name(prog=None):
17511737
if prog is not None:

Lib/test/test_argparse.py

+15-68
Original file line numberDiff line numberDiff line change
@@ -2997,6 +2997,13 @@ def test_group_prefix_chars_default(self):
29972997
self.assertEqual(msg, str(cm.warning))
29982998
self.assertEqual(cm.filename, __file__)
29992999

3000+
def test_nested_argument_group(self):
3001+
parser = argparse.ArgumentParser()
3002+
g = parser.add_argument_group()
3003+
self.assertRaisesRegex(ValueError,
3004+
'argument groups cannot be nested',
3005+
g.add_argument_group)
3006+
30003007
# ===================
30013008
# Parent parser tests
30023009
# ===================
@@ -3297,6 +3304,14 @@ def test_empty_group(self):
32973304
with self.assertRaises(ValueError):
32983305
parser.parse_args(['-h'])
32993306

3307+
def test_nested_mutex_groups(self):
3308+
parser = argparse.ArgumentParser(prog='PROG')
3309+
g = parser.add_mutually_exclusive_group()
3310+
g.add_argument("--spam")
3311+
self.assertRaisesRegex(ValueError,
3312+
'mutually exclusive groups cannot be nested',
3313+
g.add_mutually_exclusive_group)
3314+
33003315
class MEMixin(object):
33013316

33023317
def test_failures_when_not_required(self):
@@ -3664,55 +3679,6 @@ def get_parser(self, required):
36643679
-c c help
36653680
'''
36663681

3667-
class TestMutuallyExclusiveNested(MEMixin, TestCase):
3668-
3669-
# Nesting mutually exclusive groups is an undocumented feature
3670-
# that came about by accident through inheritance and has been
3671-
# the source of many bugs. It is deprecated and this test should
3672-
# eventually be removed along with it.
3673-
3674-
def get_parser(self, required):
3675-
parser = ErrorRaisingArgumentParser(prog='PROG')
3676-
group = parser.add_mutually_exclusive_group(required=required)
3677-
group.add_argument('-a')
3678-
group.add_argument('-b')
3679-
with warnings.catch_warnings():
3680-
warnings.simplefilter('ignore', DeprecationWarning)
3681-
group2 = group.add_mutually_exclusive_group(required=required)
3682-
group2.add_argument('-c')
3683-
group2.add_argument('-d')
3684-
with warnings.catch_warnings():
3685-
warnings.simplefilter('ignore', DeprecationWarning)
3686-
group3 = group2.add_mutually_exclusive_group(required=required)
3687-
group3.add_argument('-e')
3688-
group3.add_argument('-f')
3689-
return parser
3690-
3691-
usage_when_not_required = '''\
3692-
usage: PROG [-h] [-a A | -b B | [-c C | -d D | [-e E | -f F]]]
3693-
'''
3694-
usage_when_required = '''\
3695-
usage: PROG [-h] (-a A | -b B | (-c C | -d D | (-e E | -f F)))
3696-
'''
3697-
3698-
help = '''\
3699-
3700-
options:
3701-
-h, --help show this help message and exit
3702-
-a A
3703-
-b B
3704-
-c C
3705-
-d D
3706-
-e E
3707-
-f F
3708-
'''
3709-
3710-
# We are only interested in testing the behavior of format_usage().
3711-
test_failures_when_not_required = None
3712-
test_failures_when_required = None
3713-
test_successes_when_not_required = None
3714-
test_successes_when_required = None
3715-
37163682

37173683
class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
37183684
def get_parser(self, required=None):
@@ -4883,25 +4849,6 @@ def test_all_suppressed_mutex_with_optional_nargs(self):
48834849
usage = 'usage: PROG [-h]\n'
48844850
self.assertEqual(parser.format_usage(), usage)
48854851

4886-
def test_nested_mutex_groups(self):
4887-
parser = argparse.ArgumentParser(prog='PROG')
4888-
g = parser.add_mutually_exclusive_group()
4889-
g.add_argument("--spam")
4890-
with warnings.catch_warnings():
4891-
warnings.simplefilter('ignore', DeprecationWarning)
4892-
gg = g.add_mutually_exclusive_group()
4893-
gg.add_argument("--hax")
4894-
gg.add_argument("--hox", help=argparse.SUPPRESS)
4895-
gg.add_argument("--hex")
4896-
g.add_argument("--eggs")
4897-
parser.add_argument("--num")
4898-
4899-
usage = textwrap.dedent('''\
4900-
usage: PROG [-h] [--spam SPAM | [--hax HAX | --hex HEX] | --eggs EGGS]
4901-
[--num NUM]
4902-
''')
4903-
self.assertEqual(parser.format_usage(), usage)
4904-
49054852
def test_long_mutex_groups_wrap(self):
49064853
parser = argparse.ArgumentParser(prog='PROG')
49074854
g = parser.add_mutually_exclusive_group()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Calling :meth:`argparse.ArgumentParser.add_argument_group` on an argument group,
2+
and calling :meth:`argparse.ArgumentParser.add_argument_group` or
3+
:meth:`argparse.ArgumentParser.add_mutually_exclusive_group` on a mutually
4+
exclusive group now raise exceptions. This nesting was never supported, often
5+
failed to work correctly, and was unintentionally exposed through inheritance.
6+
This functionality has been deprecated since Python 3.11.

0 commit comments

Comments
 (0)