From cbdacbf1a88c5526476fe2906add61dcd02dacf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 23 Sep 2026 16:34:42 -0700 Subject: [PATCH 1/2] gh-144812: Make argparse group classes public Code that annotates the return values of add_argument_group() and add_mutually_exclusive_group() has to import the private _ArgumentGroup and _MutuallyExclusiveGroup today. Public names give that code a supported type, and the old names stay as aliases so existing imports keep working. --- Doc/library/argparse.rst | 25 +++++++++++++++++++ Doc/whatsnew/3.16.rst | 10 ++++++++ Lib/argparse.py | 22 ++++++++++------ Lib/test/test_argparse.py | 14 +++++++++++ ...9-23-23-34-30.gh-issue-144812.ujptbVOI.rst | 3 +++ 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index fc5302d875fc1f..42355fac7f66d8 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -2068,6 +2068,18 @@ Argument groups is now deprecated. +.. class:: ArgumentGroup + + The type of objects returned by :meth:`ArgumentParser.add_argument_group`. + Its :meth:`~ArgumentParser.add_argument` and + :meth:`~ArgumentParser.add_mutually_exclusive_group` methods work as on + :class:`ArgumentParser`. Use :meth:`~ArgumentParser.add_argument_group` + to create instances; the constructor is an implementation detail. + + .. versionadded:: next + The private ``_ArgumentGroup`` name remains as an alias. + + Mutual exclusion ^^^^^^^^^^^^^^^^ @@ -2131,6 +2143,19 @@ Mutual exclusion exposed through inheritance. +.. class:: MutuallyExclusiveGroup + + The type of objects returned by + :meth:`ArgumentParser.add_mutually_exclusive_group`. + Its :meth:`~ArgumentParser.add_argument` method works as on + :class:`ArgumentParser`. Use + :meth:`~ArgumentParser.add_mutually_exclusive_group` to create instances; + the constructor is an implementation detail. + + .. versionadded:: next + The private ``_MutuallyExclusiveGroup`` name remains as an alias. + + Parser defaults ^^^^^^^^^^^^^^^ diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 6c38beb04e8d27..666da5c4265759 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -108,6 +108,16 @@ New modules Improved modules ================ +argparse +-------- + +* :class:`~argparse.ArgumentGroup` and + :class:`~argparse.MutuallyExclusiveGroup`, the types returned by + :meth:`~argparse.ArgumentParser.add_argument_group` and + :meth:`~argparse.ArgumentParser.add_mutually_exclusive_group`, are now + public. The ``_``-prefixed names remain as aliases. + (Contributed by Bernát Gábor in :gh:`144812`.) + asyncio ------- diff --git a/Lib/argparse.py b/Lib/argparse.py index 38e1c0d0ed78fd..284c97667c7b1e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -67,6 +67,7 @@ __all__ = [ 'ArgumentParser', 'ArgumentError', + 'ArgumentGroup', 'ArgumentTypeError', 'BooleanOptionalAction', 'FileType', @@ -75,6 +76,7 @@ 'RawDescriptionHelpFormatter', 'RawTextHelpFormatter', 'MetavarTypeHelpFormatter', + 'MutuallyExclusiveGroup', 'Namespace', 'Action', 'ONE_OR_MORE', @@ -1699,12 +1701,12 @@ def add_argument(self, *args, **kwargs): return self._add_action(action) def add_argument_group(self, *args, **kwargs): - group = _ArgumentGroup(self, *args, **kwargs) + group = ArgumentGroup(self, *args, **kwargs) self._action_groups.append(group) return group def add_mutually_exclusive_group(self, **kwargs): - group = _MutuallyExclusiveGroup(self, **kwargs) + group = MutuallyExclusiveGroup(self, **kwargs) self._mutually_exclusive_groups.append(group) return group @@ -1892,7 +1894,7 @@ def _check_help(self, action): raise ValueError('badly formed help string') from exc -class _ArgumentGroup(_ActionsContainer): +class ArgumentGroup(_ActionsContainer): def __init__(self, container, title=None, description=None, **kwargs): if 'prefix_chars' in kwargs: @@ -1907,7 +1909,7 @@ def __init__(self, container, title=None, description=None, **kwargs): update('conflict_handler', container.conflict_handler) update('prefix_chars', container.prefix_chars) update('argument_default', container.argument_default) - super_init = super(_ArgumentGroup, self).__init__ + super_init = super(ArgumentGroup, self).__init__ super_init(description=description, **kwargs) # group attributes @@ -1924,21 +1926,21 @@ def __init__(self, container, title=None, description=None, **kwargs): self._mutually_exclusive_groups = container._mutually_exclusive_groups def _add_action(self, action): - action = super(_ArgumentGroup, self)._add_action(action) + action = super(ArgumentGroup, self)._add_action(action) self._group_actions.append(action) return action def _remove_action(self, action): - super(_ArgumentGroup, self)._remove_action(action) + super(ArgumentGroup, self)._remove_action(action) self._group_actions.remove(action) def add_argument_group(self, *args, **kwargs): raise ValueError('argument groups cannot be nested') -class _MutuallyExclusiveGroup(_ArgumentGroup): +class MutuallyExclusiveGroup(ArgumentGroup): def __init__(self, container, required=False): - super(_MutuallyExclusiveGroup, self).__init__(container) + super(MutuallyExclusiveGroup, self).__init__(container) self.required = required self._container = container @@ -1957,6 +1959,10 @@ def _remove_action(self, action): def add_mutually_exclusive_group(self, **kwargs): raise ValueError('mutually exclusive groups cannot be nested') +# Kept for code that imports the pre-3.16 private names. +_ArgumentGroup = ArgumentGroup +_MutuallyExclusiveGroup = MutuallyExclusiveGroup + def _prog_name(prog=None): if prog is not None: return prog diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 75beb5ede13fef..9c829f7cddc634 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -3246,6 +3246,20 @@ def test_nested_argument_group(self): 'argument groups cannot be nested', g.add_argument_group) + def test_group_types(self): + parser = argparse.ArgumentParser() + group = parser.add_argument_group() + self.assertIs(type(group), argparse.ArgumentGroup) + self.assertIs(type(parser.add_mutually_exclusive_group()), + argparse.MutuallyExclusiveGroup) + self.assertIs(type(group.add_mutually_exclusive_group()), + argparse.MutuallyExclusiveGroup) + + def test_private_aliases(self): + self.assertIs(argparse._ArgumentGroup, argparse.ArgumentGroup) + self.assertIs(argparse._MutuallyExclusiveGroup, + argparse.MutuallyExclusiveGroup) + # =================== # Parent parser tests # =================== diff --git a/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst b/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst new file mode 100644 index 00000000000000..d24f028fdbeaae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst @@ -0,0 +1,3 @@ +Make :class:`argparse.ArgumentGroup` and +:class:`argparse.MutuallyExclusiveGroup` public. The private +``_ArgumentGroup`` and ``_MutuallyExclusiveGroup`` names remain as aliases. From c79e92290f30d9da1b76e5413a3977b541139042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Tue, 20 Oct 2026 16:37:50 -0700 Subject: [PATCH 2/2] gh-144812: Clarify public API of argparse group classes --- Doc/library/argparse.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 42355fac7f66d8..27b2af2022bc02 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -2074,7 +2074,8 @@ Argument groups Its :meth:`~ArgumentParser.add_argument` and :meth:`~ArgumentParser.add_mutually_exclusive_group` methods work as on :class:`ArgumentParser`. Use :meth:`~ArgumentParser.add_argument_group` - to create instances; the constructor is an implementation detail. + to create instances. Only the methods documented here are part of the + public API; other attributes and methods are implementation details. .. versionadded:: next The private ``_ArgumentGroup`` name remains as an alias. @@ -2149,8 +2150,9 @@ Mutual exclusion :meth:`ArgumentParser.add_mutually_exclusive_group`. Its :meth:`~ArgumentParser.add_argument` method works as on :class:`ArgumentParser`. Use - :meth:`~ArgumentParser.add_mutually_exclusive_group` to create instances; - the constructor is an implementation detail. + :meth:`~ArgumentParser.add_mutually_exclusive_group` to create instances. + Only the ``add_argument`` method documented here is part of the public API; + other attributes and methods are implementation details. .. versionadded:: next The private ``_MutuallyExclusiveGroup`` name remains as an alias.