From 7e2d414a599782773f46eb8a20506db0cfa3dcce Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:43:26 +0200 Subject: [PATCH] [3.12] gh-72795: Make positional arguments with nargs='*' or REMAINDER non-required (GH-124306) (GH-124422) This allows to use positional argument with nargs='*' and without default in mutually exclusive group and improves error message about required arguments. (cherry picked from commit 3c83f9958c14cd62ad8951c53536f7788745b0ba) Co-authored-by: Serhiy Storchaka --- Lib/argparse.py | 5 ++- Lib/test/test_argparse.py | 31 ++++++++++++++++--- ...4-09-21-22-32-21.gh-issue-72795.naLmkX.rst | 4 +++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 1b7c54e56ba..d84be6813ca 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1575,9 +1575,8 @@ class _ActionsContainer(object): # mark positional arguments as required if at least one is # always required - if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: - kwargs['required'] = True - if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + nargs = kwargs.get('nargs') + if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]: kwargs['required'] = True # return the keyword arguments with no option strings diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 37c2238895b..4195bfee9f2 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -3033,7 +3033,7 @@ class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): group = parser.add_mutually_exclusive_group(required=required) group.add_argument('--foo', action='store_true', help='FOO') group.add_argument('--spam', help='SPAM') - group.add_argument('badger', nargs='*', default='X', help='BADGER') + group.add_argument('badger', nargs='*', help='BADGER') return parser failures = [ @@ -3044,13 +3044,13 @@ class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): '--foo X Y', ] successes = [ - ('--foo', NS(foo=True, spam=None, badger='X')), - ('--spam S', NS(foo=False, spam='S', badger='X')), + ('--foo', NS(foo=True, spam=None, badger=[])), + ('--spam S', NS(foo=False, spam='S', badger=[])), ('X', NS(foo=False, spam=None, badger=['X'])), ('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])), ] successes_when_not_required = [ - ('', NS(foo=False, spam=None, badger='X')), + ('', NS(foo=False, spam=None, badger=[])), ] usage_when_not_required = '''\ @@ -6020,7 +6020,28 @@ class TestExitOnError(TestCase): self.parser.add_argument('bar') self.parser.add_argument('baz') self.assertRaisesRegex(argparse.ArgumentError, - 'the following arguments are required: bar, baz', + 'the following arguments are required: bar, baz$', + self.parser.parse_args, []) + + def test_required_args_optional(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs='?') + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar$', + self.parser.parse_args, []) + + def test_required_args_zero_or_more(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs='*') + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar$', + self.parser.parse_args, []) + + def test_required_args_remainder(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs='...') + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar$', self.parser.parse_args, []) def test_required_mutually_exclusive_args(self): diff --git a/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst b/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst new file mode 100644 index 00000000000..15c09180973 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-21-22-32-21.gh-issue-72795.naLmkX.rst @@ -0,0 +1,4 @@ +Positional arguments with :ref:`nargs` equal to ``'*'`` or +:data:`!argparse.REMAINDER` are no longer required. This allows to use +positional argument with ``nargs='*'`` and without ``default`` in mutually +exclusive group and improves error message about required arguments.