From ef8e4fc483f28890bbc52235c5b08499bee93d39 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 17:07:43 +0500 Subject: [PATCH 01/45] Add closet choice if exists in argparser if wrong choice picked --- Lib/argparse.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 240625ff01084e..5fec46730745a6 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -85,6 +85,7 @@ ] +import difflib as _difflib import os as _os import re as _re import sys as _sys @@ -2543,9 +2544,17 @@ def _get_value(self, action, arg_string): def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: + closet_choice = _difflib.get_close_matches(value, action.choices) args = {'value': value, 'choices': ', '.join(map(repr, action.choices))} - msg = _('invalid choice: %(value)r (choose from %(choices)s)') + + if closet_choice := closet_choice and closet_choice[0] or '': + args['closet'] = closet_choice + msg = _('invalid choice: %(value)r, maybe you meant %(closet)r? ' + '(choose from %(choices)s)') + else: + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + raise ArgumentError(action, msg % args) # ======================= From 7e8dcf87908e012a64cc56ff3275893d87cf9307 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 25 Nov 2022 12:23:57 +0000 Subject: [PATCH 02/45] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...022-11-25-12-23-56.gh-issue-99749.-S_guS.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst new file mode 100644 index 00000000000000..fc6aeb72223aa1 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst @@ -0,0 +1,17 @@ +Add closet choice if exists in Argparser if wrong choice picked. + + +``` +import argparser + +arg_parser = argparse.ArgumentParser() +arg_parser.add_argument('family', type=str.casefold, help='Font family', choices=['apple', 'mango']) + +args = arg_parser.parse_args(['mangeo']) +``` + +Current behaviour of the above statement: +error: argument family: invalid choice: 'mangeo' (choose from 'apple', 'mango') + +After this PR behaviour would be: +error: argument family: invalid choice: 'mangeo', maybe you meant 'mango'? (choose from 'apple', 'mango') From 8c754cd701eadd65ac7a92d4d917644acd42563d Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 17:33:02 +0500 Subject: [PATCH 03/45] Fix documentation --- ...22-11-25-12-23-56.gh-issue-99749.-S_guS.rst | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst index fc6aeb72223aa1..762dc4cc45d99c 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst @@ -1,17 +1 @@ -Add closet choice if exists in Argparser if wrong choice picked. - - -``` -import argparser - -arg_parser = argparse.ArgumentParser() -arg_parser.add_argument('family', type=str.casefold, help='Font family', choices=['apple', 'mango']) - -args = arg_parser.parse_args(['mangeo']) -``` - -Current behaviour of the above statement: -error: argument family: invalid choice: 'mangeo' (choose from 'apple', 'mango') - -After this PR behaviour would be: -error: argument family: invalid choice: 'mangeo', maybe you meant 'mango'? (choose from 'apple', 'mango') +Add closet choice if exists in Argparser if wrong choice picked. \ No newline at end of file From 4fb8ce670e23962abf459dbfef554ee2aa3d7460 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 17:34:57 +0500 Subject: [PATCH 04/45] Added EOL :hammer: --- .../2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst index 762dc4cc45d99c..327fbbb82356a6 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst @@ -1 +1 @@ -Add closet choice if exists in Argparser if wrong choice picked. \ No newline at end of file +Add closet choice if exists in Argparser if wrong choice picked. From 2f197b37c5b57942a78fa74eb1c659d11b7b9974 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 18:15:15 +0500 Subject: [PATCH 05/45] Test cases updated for argparser --- Lib/test/test_argparse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index cabb2f837693ff..4632c0ab566c05 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2195,7 +2195,8 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" + r"error: argument family: invalid choice: 'baz', maybe you meant 'bar'?\ + \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From 940a66ed25b22cec7a281e86e09a7690fc7d60bd Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 18:20:58 +0500 Subject: [PATCH 06/45] Fixed typo errors :hammer: --- Lib/argparse.py | 8 ++++---- .../2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 5fec46730745a6..4d4c4a5d72c6f6 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2544,13 +2544,13 @@ def _get_value(self, action, arg_string): def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - closet_choice = _difflib.get_close_matches(value, action.choices) + closest_choice = _difflib.get_close_matches(value, action.choices) args = {'value': value, 'choices': ', '.join(map(repr, action.choices))} - if closet_choice := closet_choice and closet_choice[0] or '': - args['closet'] = closet_choice - msg = _('invalid choice: %(value)r, maybe you meant %(closet)r? ' + if closest_choice := closest_choice and closest_choice[0] or '': + args['closest'] = closest_choice + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' '(choose from %(choices)s)') else: msg = _('invalid choice: %(value)r (choose from %(choices)s)') diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst index 327fbbb82356a6..479d5e45f56fc1 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst @@ -1 +1 @@ -Add closet choice if exists in Argparser if wrong choice picked. +Add closest choice if exists in Argparser if wrong choice picked. From badc5ed65e5d2aac27676968f54efa53c32c26db Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 19:16:37 +0500 Subject: [PATCH 07/45] Test case fix :hammer: --- Lib/test/test_argparse.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 4632c0ab566c05..9d97113ac71d03 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -13,6 +13,7 @@ import argparse import warnings +from gettext import gettext as _ from test.support import os_helper from unittest import mock @@ -2195,8 +2196,8 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument family: invalid choice: 'baz', maybe you meant 'bar'?\ - \(choose from 'foo', 'bar'\)\n$" + _("error: argument family: invalid choice: 'baz', maybe you meant" + r" 'bar'? \(choose from 'foo', 'bar'\)\n$") ) def test_optional_subparsers(self): From 2588ef10fc193f28b19b584e011daaefc92203ab Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 19:40:13 +0500 Subject: [PATCH 08/45] Fixed test cse error msg :hammer: --- Lib/test/test_argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 9d97113ac71d03..2b1568ab11dd52 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2196,8 +2196,8 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - _("error: argument family: invalid choice: 'baz', maybe you meant" - r" 'bar'? \(choose from 'foo', 'bar'\)\n$") + "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" + r" 'bar'? \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From ee05c1eae9b20b99a6d3940aee932f25b0c778b5 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 20:03:34 +0500 Subject: [PATCH 09/45] Fixed error test case assert msg :hammer: --- Lib/test/test_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 2b1568ab11dd52..8bb0594b725c18 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2197,7 +2197,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): self.assertRegex( excinfo.exception.stderr, "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" - r" 'bar'? \(choose from 'foo', 'bar'\)\n$" + " 'bar'? (choose from 'foo', 'bar'\)\\n" ) def test_optional_subparsers(self): From 4a364068c21f84e4629b24b65a0e0f2163d27f5f Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 20:38:01 +0500 Subject: [PATCH 10/45] assertion fix :hammer: --- Lib/test/test_argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 8bb0594b725c18..232dc204a2ac1c 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2196,8 +2196,8 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" - " 'bar'? (choose from 'foo', 'bar'\)\\n" + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" + " 'bar'? (choose from 'foo', 'bar')" ) def test_optional_subparsers(self): From fe169bc396481ac318d40b2d95fa0e7b4108e577 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 21:04:49 +0500 Subject: [PATCH 11/45] assertion test case fix --- Lib/test/test_argparse.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 232dc204a2ac1c..fb3bcae3b9f01b 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2194,10 +2194,11 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) - self.assertRegex( + self.assertEqual( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" - " 'bar'? (choose from 'foo', 'bar')" + "usage: __main__.py [-h] {foo,bar} ...\n__main__.py: error:" + "argument {foo,bar}: invalid choice: 'baz', maybe you meant" + " 'bar'? (choose from 'foo', 'bar')\n" ) def test_optional_subparsers(self): From 9fe0d95ff5014ed84cdf354a5367f5b3fd23b2c1 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 21:23:57 +0500 Subject: [PATCH 12/45] Test Case fix --- Lib/test/test_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index fb3bcae3b9f01b..767e14201abb71 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2197,7 +2197,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): self.assertEqual( excinfo.exception.stderr, "usage: __main__.py [-h] {foo,bar} ...\n__main__.py: error:" - "argument {foo,bar}: invalid choice: 'baz', maybe you meant" + " argument {foo,bar}: invalid choice: 'baz', maybe you meant" " 'bar'? (choose from 'foo', 'bar')\n" ) From d12e1c4fe64d3ea2ebb2b32a14652b78de4f8edd Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 22:06:48 +0500 Subject: [PATCH 13/45] Reveet to assertRegex from assertEqual --- Lib/test/test_argparse.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 767e14201abb71..beb17a27ec0f50 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2194,11 +2194,9 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) - self.assertEqual( + self.assertRegex( excinfo.exception.stderr, - "usage: __main__.py [-h] {foo,bar} ...\n__main__.py: error:" - " argument {foo,bar}: invalid choice: 'baz', maybe you meant" - " 'bar'? (choose from 'foo', 'bar')\n" + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From 35f09613333de90602eb1e91034a2ef5799bfa4d Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 22:25:05 +0500 Subject: [PATCH 14/45] Remove unused imports :hammer: --- Lib/test/test_argparse.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index beb17a27ec0f50..43ebe25f573e06 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -13,7 +13,6 @@ import argparse import warnings -from gettext import gettext as _ from test.support import os_helper from unittest import mock @@ -2196,7 +2195,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$" + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')" ) def test_optional_subparsers(self): From 087895c645157587d53f1069fa9658008f65af3a Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 22:58:36 +0500 Subject: [PATCH 15/45] assertion msg fixed :hammer: --- Lib/test/test_argparse.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 43ebe25f573e06..100a2da0164fdf 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2193,9 +2193,11 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) - self.assertRegex( + self.assertEqual( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')" + "usage: __main__.py [-h] {foo,bar} ...\\n__main__.py: error: argument" + " {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'?" + " (choose from 'foo', 'bar')\\n" ) def test_optional_subparsers(self): From 6eeae5cd964da52025e0ad95c726c9a60bef67bb Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Fri, 25 Nov 2022 23:37:06 +0500 Subject: [PATCH 16/45] test cases fixed :hammer: --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 4d4c4a5d72c6f6..7437faa4fb4547 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2548,7 +2548,7 @@ def _check_value(self, action, value): args = {'value': value, 'choices': ', '.join(map(repr, action.choices))} - if closest_choice := closest_choice and closest_choice[0] or '': + if closest_choice := closest_choice and closest_choice[0] or '': args['closest'] = closest_choice msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' '(choose from %(choices)s)') diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 100a2da0164fdf..97d48d151b43be 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2192,12 +2192,10 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('foo') subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: - parser.parse_args(('baz',)) - self.assertEqual( + parser.parse_args(('python',)) + self.assertRegex( excinfo.exception.stderr, - "usage: __main__.py [-h] {foo,bar} ...\\n__main__.py: error: argument" - " {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'?" - " (choose from 'foo', 'bar')\\n" + r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From 9965694e969de8fbf9fe1279ddf5fed2207170a7 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 00:09:45 +0500 Subject: [PATCH 17/45] assert fix --- Lib/test/test_argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 97d48d151b43be..9fc94fdc00642b 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2192,10 +2192,10 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('foo') subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: - parser.parse_args(('python',)) + parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From 4ef218a17fd0c6614be04735235195cb3a9697ce Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 00:34:17 +0500 Subject: [PATCH 18/45] assertion fix --- Lib/test/test_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 9fc94fdc00642b..d9de6cfbab8031 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2195,7 +2195,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$" + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')\\n$" ) def test_optional_subparsers(self): From e63a01c1fe01813770119ad826f44d8579805ddf Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 00:56:29 +0500 Subject: [PATCH 19/45] revert testing --- Lib/argparse.py | 11 +---------- Lib/test/test_argparse.py | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 7437faa4fb4547..240625ff01084e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -85,7 +85,6 @@ ] -import difflib as _difflib import os as _os import re as _re import sys as _sys @@ -2544,17 +2543,9 @@ def _get_value(self, action, arg_string): def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - closest_choice = _difflib.get_close_matches(value, action.choices) args = {'value': value, 'choices': ', '.join(map(repr, action.choices))} - - if closest_choice := closest_choice and closest_choice[0] or '': - args['closest'] = closest_choice - msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' - '(choose from %(choices)s)') - else: - msg = _('invalid choice: %(value)r (choose from %(choices)s)') - + msg = _('invalid choice: %(value)r (choose from %(choices)s)') raise ArgumentError(action, msg % args) # ======================= diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index d9de6cfbab8031..cabb2f837693ff 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2195,7 +2195,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')\\n$" + r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" ) def test_optional_subparsers(self): From bfc926239a0d48999bbbf2a0fc5d1df128366d9d Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 01:01:11 +0500 Subject: [PATCH 20/45] test: hammer: --- Lib/test/test_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index cabb2f837693ff..c6dab42290a685 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2195,7 +2195,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" + r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$", ) def test_optional_subparsers(self): From b3b4a9eb460160d262517d967e6d3e7c5734910c Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 01:46:41 +0500 Subject: [PATCH 21/45] fixed --- Lib/argparse.py | 17 +++++++++++++---- Lib/test/test_argparse.py | 14 +++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 240625ff01084e..e8a85d65865cdc 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -84,7 +84,7 @@ 'ZERO_OR_MORE', ] - +import difflib as _difflib import os as _os import re as _re import sys as _sys @@ -2543,9 +2543,18 @@ def _get_value(self, action, arg_string): def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - args = {'value': value, - 'choices': ', '.join(map(repr, action.choices))} - msg = _('invalid choice: %(value)r (choose from %(choices)s)') + closest_choice = _difflib.get_close_matches(value, action.choices) + args = { + 'value': value, + 'choices': ', '.join(map(repr, action.choices)), + } + if closest_choice := closest_choice and closest_choice[0] or None: + args['closest'] = closest_choice + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' + '(choose from %(choices)s)') + else: + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + raise ArgumentError(action, msg % args) # ======================= diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index c6dab42290a685..65d469f308783a 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2187,6 +2187,18 @@ def test_required_subparsers_no_destination_error(self): ) def test_wrong_argument_subparsers_no_destination_error(self): + parser = ErrorRaisingArgumentParser() + subparsers = parser.add_subparsers(required=True) + subparsers.add_parser('foo') + subparsers.add_parser('bar') + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('test',)) + self.assertRegex( + excinfo.exception.stderr, + r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" + ) + + def test_wrong_argument_subparsers_no_destination_error_with_closest_choice_input(self): parser = ErrorRaisingArgumentParser() subparsers = parser.add_subparsers(required=True) subparsers.add_parser('foo') @@ -2195,7 +2207,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$", + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant bar? \(choose from 'foo', 'bar'\)\n$", ) def test_optional_subparsers(self): From b8bc465b07d5bce0361c6d62dd427e07fce7ab22 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 13:25:19 +0500 Subject: [PATCH 22/45] PR review changes :hammer: --- Lib/argparse.py | 13 +++++++++++-- Lib/test/test_argparse.py | 12 ------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index e8a85d65865cdc..3e1c62714a2a78 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2541,14 +2541,23 @@ def _get_value(self, action, arg_string): return result def _check_value(self, action, value): + if not action.choices and isinstance(action.choices, list): + msg = 'Either add options in choices array or remove it' + raise ArgumentError(action, msg) + # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - closest_choice = _difflib.get_close_matches(value, action.choices) + try: + closest_choice = _difflib.get_close_matches(value, action.choices, 1) + except TypeError: + closest_choice = [] + args = { 'value': value, 'choices': ', '.join(map(repr, action.choices)), } - if closest_choice := closest_choice and closest_choice[0] or None: + if closest_choice: + closest_choice = closest_choice[0] args['closest'] = closest_choice msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' '(choose from %(choices)s)') diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 65d469f308783a..9d878dc4f80691 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2187,18 +2187,6 @@ def test_required_subparsers_no_destination_error(self): ) def test_wrong_argument_subparsers_no_destination_error(self): - parser = ErrorRaisingArgumentParser() - subparsers = parser.add_subparsers(required=True) - subparsers.add_parser('foo') - subparsers.add_parser('bar') - with self.assertRaises(ArgumentParserError) as excinfo: - parser.parse_args(('test',)) - self.assertRegex( - excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" - ) - - def test_wrong_argument_subparsers_no_destination_error_with_closest_choice_input(self): parser = ErrorRaisingArgumentParser() subparsers = parser.add_subparsers(required=True) subparsers.add_parser('foo') From ade070fc575613d6d2a9eb7a7b75f954026c0800 Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 17:25:02 +0500 Subject: [PATCH 23/45] test case fix :hammer: --- Lib/test/test_argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 9d878dc4f80691..2efd49ca408539 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2195,7 +2195,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant bar? \(choose from 'foo', 'bar'\)\n$", + r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$", ) def test_optional_subparsers(self): From 3753a0d524e951a7bb30d25f8599698ae176c46d Mon Sep 17 00:00:00 2001 From: Abdul Rafey Date: Sat, 26 Nov 2022 18:00:57 +0500 Subject: [PATCH 24/45] test case fixation assertIn --- Lib/test/test_argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 2efd49ca408539..ec289310367db3 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2193,9 +2193,9 @@ def test_wrong_argument_subparsers_no_destination_error(self): subparsers.add_parser('bar') with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) - self.assertRegex( + self.assertIn( + "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')", excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? \(choose from 'foo', 'bar'\)\n$", ) def test_optional_subparsers(self): From f516bb499272cf6a8951d2881ea469ca3e70f184 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 11:25:48 -0700 Subject: [PATCH 25/45] Rework to add tests and make optional --- Lib/argparse.py | 32 +++++----- Lib/test/test_argparse.py | 61 +++++++++++++++---- ...2-11-25-12-23-56.gh-issue-99749.-S_guS.rst | 1 - 3 files changed, 66 insertions(+), 28 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 5fa795d333ab14..ad2cc281bdb353 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -84,7 +84,6 @@ 'ZERO_OR_MORE', ] -import difflib as _difflib import os as _os import re as _re import sys as _sys @@ -1716,6 +1715,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): - allow_abbrev -- Allow long options to be abbreviated unambiguously - exit_on_error -- Determines whether or not ArgumentParser exits with error info when an error occurs + - suggest_on_error - Enables argument choice and subparser name + suggestions on user typo """ def __init__(self, @@ -1731,7 +1732,8 @@ def __init__(self, conflict_handler='error', add_help=True, allow_abbrev=True, - exit_on_error=True): + exit_on_error=True, + suggest_on_error=False): superinit = super(ArgumentParser, self).__init__ superinit(description=description, @@ -1751,6 +1753,7 @@ def __init__(self, self.add_help = add_help self.allow_abbrev = allow_abbrev self.exit_on_error = exit_on_error + self.suggest_on_error = suggest_on_error add_group = self.add_argument_group self._positionals = add_group(_('positional arguments')) @@ -2559,22 +2562,23 @@ def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - try: - closest_choice = _difflib.get_close_matches(value, action.choices, 1) - except TypeError: - closest_choice = [] - args = { 'value': value, 'choices': ', '.join(map(repr, action.choices)), } - if closest_choice: - closest_choice = closest_choice[0] - args['closest'] = closest_choice - msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' - '(choose from %(choices)s)') - else: - msg = _('invalid choice: %(value)r (choose from %(choices)s)') + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + + if self.suggest_on_error: + try: + import difflib as _difflib + closest_choice = _difflib.get_close_matches(value, action.choices, 1) + if closest_choice: + closest_choice = closest_choice[0] + args['closest'] = closest_choice + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' + '(choose from %(choices)s)') + except TypeError: + closest_choice = [] raise ArgumentError(action, msg % args) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index e67248a927d5c9..e14b5db51a8bd2 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2172,6 +2172,53 @@ class TestNegativeNumber(ParserTestCase): ('--float -.5_000', NS(int=None, float=-0.5)), ] +class TestArgumentAndSubparserSuggestions(TestCase): + """Test error handling and suggestion when a user makes a typo""" + + def test_wrong_argument_error_with_suggestions(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=True) + parser.add_argument('foo', choices=['bar', 'baz']) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('bazz',)) + self.assertIn( + "maybe you meant 'baz'? (choose from 'bar', 'baz')", + excinfo.exception.stderr, + ) + + def test_wrong_argument_error_no_suggestions(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=False) + parser.add_argument('foo', choices=['bar', 'baz']) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('bazz',)) + self.assertIn( + "invalid choice: 'bazz' (choose from 'bar', 'baz')", + excinfo.exception.stderr, + ) + + def test_wrong_argument_subparsers_with_suggestions(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=True) + subparsers = parser.add_subparsers(required=True) + subparsers.add_parser('foo') + subparsers.add_parser('bar') + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('baz',)) + self.assertIn( + "maybe you meant 'bar'? (choose from 'foo', 'bar')", + excinfo.exception.stderr, + ) + + def test_wrong_argument_subparsers_no_suggestions(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=False) + subparsers = parser.add_subparsers(required=True) + subparsers.add_parser('foo') + subparsers.add_parser('bar') + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('baz',)) + self.assertIn( + "invalid choice: 'baz' (choose from 'foo', 'bar')", + excinfo.exception.stderr, + ) + class TestInvalidAction(TestCase): """Test invalid user defined Action""" @@ -2390,18 +2437,6 @@ def test_required_subparsers_no_destination_error(self): 'error: the following arguments are required: {foo,bar}\n$' ) - def test_wrong_argument_subparsers_no_destination_error(self): - parser = ErrorRaisingArgumentParser() - subparsers = parser.add_subparsers(required=True) - subparsers.add_parser('foo') - subparsers.add_parser('bar') - with self.assertRaises(ArgumentParserError) as excinfo: - parser.parse_args(('baz',)) - self.assertIn( - "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from 'foo', 'bar')", - excinfo.exception.stderr, - ) - def test_optional_subparsers(self): parser = ErrorRaisingArgumentParser() subparsers = parser.add_subparsers(dest='command', required=False) @@ -2726,7 +2761,7 @@ def test_single_parent_mutex(self): parser = ErrorRaisingArgumentParser(parents=[self.ab_mutex_parent]) self._test_mutex_ab(parser.parse_args) - def test_single_granparent_mutex(self): + def test_single_grandparent_mutex(self): parents = [self.ab_mutex_parent] parser = ErrorRaisingArgumentParser(add_help=False, parents=parents) parser = ErrorRaisingArgumentParser(parents=[parser]) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst deleted file mode 100644 index 479d5e45f56fc1..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2022-11-25-12-23-56.gh-issue-99749.-S_guS.rst +++ /dev/null @@ -1 +0,0 @@ -Add closest choice if exists in Argparser if wrong choice picked. From c9b40f57d3cc9f177247a8202c3a8fad6966c2e2 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 11:32:19 -0700 Subject: [PATCH 26/45] Add to docs --- Doc/library/argparse.rst | 7 ++++++- Lib/argparse.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 53ecc97d5659f4..9b664820584a94 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -189,7 +189,8 @@ ArgumentParser objects formatter_class=argparse.HelpFormatter, \ prefix_chars='-', fromfile_prefix_chars=None, \ argument_default=None, conflict_handler='error', \ - add_help=True, allow_abbrev=True, exit_on_error=True) + add_help=True, allow_abbrev=True, exit_on_error=True,\ + suggest_on_error=False) Create a new :class:`ArgumentParser` object. All parameters should be passed as keyword arguments. Each parameter has its own more detailed description @@ -230,6 +231,10 @@ ArgumentParser objects * exit_on_error_ - Determines whether or not ArgumentParser exits with error info when an error occurs. (default: ``True``) + + * suggest_on_error_ - Enables suggestions for mistyped argument choices + and subparser names. (default: ``False``) + .. versionchanged:: 3.5 *allow_abbrev* parameter was added. diff --git a/Lib/argparse.py b/Lib/argparse.py index ad2cc281bdb353..2b4f86bb5e4895 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1715,8 +1715,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): - allow_abbrev -- Allow long options to be abbreviated unambiguously - exit_on_error -- Determines whether or not ArgumentParser exits with error info when an error occurs - - suggest_on_error - Enables argument choice and subparser name - suggestions on user typo + - suggest_on_error - Enables suggestions for mistyped argument choices + and subparser names. (default: ``False``) """ def __init__(self, From 981bacdc72c4d4da4636b09a4c60268ad0507e65 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 11:41:55 -0700 Subject: [PATCH 27/45] Add implicit test case --- Lib/test/test_argparse.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index e14b5db51a8bd2..66d46bf139daef 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2218,6 +2218,16 @@ def test_wrong_argument_subparsers_no_suggestions(self): "invalid choice: 'baz' (choose from 'foo', 'bar')", excinfo.exception.stderr, ) + + def test_wrong_argument_no_suggestion_implicit(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('foo', choices=['bar', 'baz']) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('bazz',)) + self.assertIn( + "invalid choice: 'bazz' (choose from 'bar', 'baz')", + excinfo.exception.stderr, + ) class TestInvalidAction(TestCase): """Test invalid user defined Action""" From f9a6cc1974ff5edae63ed7ad0233b27126592606 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 11:45:40 -0700 Subject: [PATCH 28/45] Appease linter --- Doc/library/argparse.rst | 2 +- Lib/test/test_argparse.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 9b664820584a94..89fb6c261c27de 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -231,7 +231,7 @@ ArgumentParser objects * exit_on_error_ - Determines whether or not ArgumentParser exits with error info when an error occurs. (default: ``True``) - + * suggest_on_error_ - Enables suggestions for mistyped argument choices and subparser names. (default: ``False``) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 66d46bf139daef..f6ade65fdd2e5e 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2218,7 +2218,7 @@ def test_wrong_argument_subparsers_no_suggestions(self): "invalid choice: 'baz' (choose from 'foo', 'bar')", excinfo.exception.stderr, ) - + def test_wrong_argument_no_suggestion_implicit(self): parser = ErrorRaisingArgumentParser() parser.add_argument('foo', choices=['bar', 'baz']) From 6bdb1310214594876094f935a0fb5a50eab3df51 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:49:18 +0000 Subject: [PATCH 29/45] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst b/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst new file mode 100644 index 00000000000000..ad39573214e04e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst @@ -0,0 +1 @@ +Adds a feature to optionally enable suggestions for argument choices and subparser names if mistyped by the user From fb3769c0afa4756d4c10a9daf8a516ebae5c2f1a Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 11:59:53 -0700 Subject: [PATCH 30/45] Undo import rename --- Lib/argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 2b4f86bb5e4895..c1bea5212a6991 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2570,8 +2570,8 @@ def _check_value(self, action, value): if self.suggest_on_error: try: - import difflib as _difflib - closest_choice = _difflib.get_close_matches(value, action.choices, 1) + import difflib + closest_choice = difflib.get_close_matches(value, action.choices, 1) if closest_choice: closest_choice = closest_choice[0] args['closest'] = closest_choice From 0eeb54368d01e2733ea615a5d00b5a82da63c10e Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 12:02:12 -0700 Subject: [PATCH 31/45] update docs --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 89fb6c261c27de..41e1a7313a5d0e 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -233,7 +233,7 @@ ArgumentParser objects error info when an error occurs. (default: ``True``) * suggest_on_error_ - Enables suggestions for mistyped argument choices - and subparser names. (default: ``False``) + and subparser names. (default: ``False``) .. versionchanged:: 3.5 From 972cc1ed74fcda22e8d3e31aeb11c0eff03e05b5 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 12:03:15 -0700 Subject: [PATCH 32/45] update spacing --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 41e1a7313a5d0e..3b51dbea26ee2a 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -189,7 +189,7 @@ ArgumentParser objects formatter_class=argparse.HelpFormatter, \ prefix_chars='-', fromfile_prefix_chars=None, \ argument_default=None, conflict_handler='error', \ - add_help=True, allow_abbrev=True, exit_on_error=True,\ + add_help=True, allow_abbrev=True, exit_on_error=True, \ suggest_on_error=False) Create a new :class:`ArgumentParser` object. All parameters should be passed From 22cdcf2a6b867961c0e1d6e0c5ae4280d1131d03 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 12:04:56 -0700 Subject: [PATCH 33/45] restore newline --- Lib/argparse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/argparse.py b/Lib/argparse.py index c1bea5212a6991..eb4024aaf29a31 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -84,6 +84,7 @@ 'ZERO_OR_MORE', ] + import os as _os import re as _re import sys as _sys From 257c030d19d86a7c06dbe31ac738d40061413102 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 13:37:27 -0700 Subject: [PATCH 34/45] add section on suggest_on_error --- Doc/library/argparse.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 3b51dbea26ee2a..dc86ef18c022e3 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -745,6 +745,26 @@ If the user would like to catch errors manually, the feature can be enabled by s .. versionadded:: 3.9 +suggest_on_error +^^^^^^^^^^^^^^^^ + +By default, when a user passes an invalid argument choice or subparser name, +:class:`ArgumentParser` will exit with error info and list the permissible +argument choices (if specified) or subparser names as part of the error message. + +If the user would like to enable suggestions for mistyped argument choices and +subparser names, the feature can be enabled by setting ``suggest_on_error`` to +``True``:: + + >>> parser = argparse.ArgumentParser(description='Process some integers.', suggest_on_error=True) + >>> parser.add_argument('--action', choices=['sum', 'max']) + >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', + ... help='an integer for the accumulator') + >>> parser.parse_args(['--action', 'sumn', 1, 2, 3]) +tester.py: error: argument --action: invalid choice: 'sumn', maybe you meant 'sum'? (choose from 'sum', 'max') + +.. versionadded:: 3.14 + The add_argument() method ------------------------- From f97de33bbe1a13defb697684321d730edc9015c5 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 24 Sep 2024 13:41:18 -0700 Subject: [PATCH 35/45] fix indent --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index dc86ef18c022e3..41e4cb6785134d 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -761,7 +761,7 @@ subparser names, the feature can be enabled by setting ``suggest_on_error`` to >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', ... help='an integer for the accumulator') >>> parser.parse_args(['--action', 'sumn', 1, 2, 3]) -tester.py: error: argument --action: invalid choice: 'sumn', maybe you meant 'sum'? (choose from 'sum', 'max') + tester.py: error: argument --action: invalid choice: 'sumn', maybe you meant 'sum'? (choose from 'sum', 'max') .. versionadded:: 3.14 From a55483e6c2620b5d4b9b37fb39d3589fbd068d1c Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 26 Sep 2024 10:14:24 -0700 Subject: [PATCH 36/45] Address PR comments --- Doc/library/argparse.rst | 5 +++-- Lib/argparse.py | 23 ++++++++--------------- Lib/test/test_argparse.py | 1 + 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 41e4cb6785134d..951082dc35c117 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -233,7 +233,7 @@ ArgumentParser objects error info when an error occurs. (default: ``True``) * suggest_on_error_ - Enables suggestions for mistyped argument choices - and subparser names. (default: ``False``) + and subparser names (default: ``False``) .. versionchanged:: 3.5 @@ -754,7 +754,8 @@ argument choices (if specified) or subparser names as part of the error message. If the user would like to enable suggestions for mistyped argument choices and subparser names, the feature can be enabled by setting ``suggest_on_error`` to -``True``:: +``True``. Note that this only applies for arguments when the choices specified +are strings:: >>> parser = argparse.ArgumentParser(description='Process some integers.', suggest_on_error=True) >>> parser.add_argument('--action', choices=['sum', 'max']) diff --git a/Lib/argparse.py b/Lib/argparse.py index 7e62c854ab50b4..c65d1453a78ceb 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2557,10 +2557,6 @@ def _get_value(self, action, arg_string): return result def _check_value(self, action, value): - if not action.choices and isinstance(action.choices, list): - msg = 'Either add options in choices array or remove it' - raise ArgumentError(action, msg) - # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: args = { @@ -2569,17 +2565,14 @@ def _check_value(self, action, value): } msg = _('invalid choice: %(value)r (choose from %(choices)s)') - if self.suggest_on_error: - try: - import difflib - closest_choice = difflib.get_close_matches(value, action.choices, 1) - if closest_choice: - closest_choice = closest_choice[0] - args['closest'] = closest_choice - msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' - '(choose from %(choices)s)') - except TypeError: - closest_choice = [] + if self.suggest_on_error and isinstance(value, str): + import difflib + suggestions = difflib.get_close_matches(value, action.choices, 1) + if suggestions: + suggestions = suggestions[0] + args['closest'] = suggestions + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' + '(choose from %(choices)s)') raise ArgumentError(action, msg % args) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index f6ade65fdd2e5e..4e2b547957bd89 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2228,6 +2228,7 @@ def test_wrong_argument_no_suggestion_implicit(self): "invalid choice: 'bazz' (choose from 'bar', 'baz')", excinfo.exception.stderr, ) + class TestInvalidAction(TestCase): """Test invalid user defined Action""" From 1df84e4b14ac1de4c55783fc2aa3f309a2fabd4f Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 26 Sep 2024 10:48:02 -0700 Subject: [PATCH 37/45] add guard for types --- Lib/argparse.py | 5 ++++- Lib/test/test_argparse.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index c65d1453a78ceb..56e000139d50df 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2565,7 +2565,10 @@ def _check_value(self, action, value): } msg = _('invalid choice: %(value)r (choose from %(choices)s)') - if self.suggest_on_error and isinstance(value, str): + #check that choices is a list of strings + all_strings = all(isinstance(choice, str) for choice in action.choices) + + if self.suggest_on_error and isinstance(value, str) and all_strings: import difflib suggestions = difflib.get_close_matches(value, action.choices, 1) if suggestions: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 4e2b547957bd89..a05c87f5237a09 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2229,6 +2229,36 @@ def test_wrong_argument_no_suggestion_implicit(self): excinfo.exception.stderr, ) + def test_suggestions_choices_empty(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=True) + parser.add_argument('foo', choices=[]) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('bazz',)) + self.assertIn( + "argument foo: invalid choice: 'bazz' (choose from )", + excinfo.exception.stderr, + ) + + def test_suggestions_choices_int(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=True) + parser.add_argument('foo', choices=[1, 2]) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('3',)) + self.assertIn( + "invalid choice: '3' (choose from 1, 2)", + excinfo.exception.stderr, + ) + + def test_suggestions_choices_mixed_types(self): + parser = ErrorRaisingArgumentParser(suggest_on_error=True) + parser.add_argument('foo', choices=[1, '2']) + with self.assertRaises(ArgumentParserError) as excinfo: + parser.parse_args(('3',)) + self.assertIn( + "invalid choice: '3' (choose from 1, '2')", + excinfo.exception.stderr, + ) + class TestInvalidAction(TestCase): """Test invalid user defined Action""" From b5cb94ba5ce8b570b24a86f9b98079b8acb0ce1f Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 26 Sep 2024 10:48:39 -0700 Subject: [PATCH 38/45] remove comment --- Lib/argparse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 56e000139d50df..ffb7186aabbf66 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2565,7 +2565,6 @@ def _check_value(self, action, value): } msg = _('invalid choice: %(value)r (choose from %(choices)s)') - #check that choices is a list of strings all_strings = all(isinstance(choice, str) for choice in action.choices) if self.suggest_on_error and isinstance(value, str) and all_strings: From 4179a6f3777b98b86ad7ba95150319bffaad5e81 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 26 Sep 2024 10:51:30 -0700 Subject: [PATCH 39/45] Linting --- Doc/library/argparse.rst | 2 +- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 951082dc35c117..7aace8eebf11c7 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -754,7 +754,7 @@ argument choices (if specified) or subparser names as part of the error message. If the user would like to enable suggestions for mistyped argument choices and subparser names, the feature can be enabled by setting ``suggest_on_error`` to -``True``. Note that this only applies for arguments when the choices specified +``True``. Note that this only applies for arguments when the choices specified are strings:: >>> parser = argparse.ArgumentParser(description='Process some integers.', suggest_on_error=True) diff --git a/Lib/argparse.py b/Lib/argparse.py index ffb7186aabbf66..5327d3167a8893 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2567,7 +2567,7 @@ def _check_value(self, action, value): all_strings = all(isinstance(choice, str) for choice in action.choices) - if self.suggest_on_error and isinstance(value, str) and all_strings: + if self.suggest_on_error and isinstance(value, str) and all_strings: import difflib suggestions = difflib.get_close_matches(value, action.choices, 1) if suggestions: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index a05c87f5237a09..811083787a3418 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2228,7 +2228,7 @@ def test_wrong_argument_no_suggestion_implicit(self): "invalid choice: 'bazz' (choose from 'bar', 'baz')", excinfo.exception.stderr, ) - + def test_suggestions_choices_empty(self): parser = ErrorRaisingArgumentParser(suggest_on_error=True) parser.add_argument('foo', choices=[]) @@ -2238,7 +2238,7 @@ def test_suggestions_choices_empty(self): "argument foo: invalid choice: 'bazz' (choose from )", excinfo.exception.stderr, ) - + def test_suggestions_choices_int(self): parser = ErrorRaisingArgumentParser(suggest_on_error=True) parser.add_argument('foo', choices=[1, 2]) @@ -2248,7 +2248,7 @@ def test_suggestions_choices_int(self): "invalid choice: '3' (choose from 1, 2)", excinfo.exception.stderr, ) - + def test_suggestions_choices_mixed_types(self): parser = ErrorRaisingArgumentParser(suggest_on_error=True) parser.add_argument('foo', choices=[1, '2']) @@ -2258,7 +2258,7 @@ def test_suggestions_choices_mixed_types(self): "invalid choice: '3' (choose from 1, '2')", excinfo.exception.stderr, ) - + class TestInvalidAction(TestCase): """Test invalid user defined Action""" From 8e4793c3c0b73de105b109775e08238e2b17b13c Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 26 Sep 2024 11:06:13 -0700 Subject: [PATCH 40/45] add pr comment --- Lib/argparse.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 5327d3167a8893..dbcc64defe7dd1 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2565,16 +2565,15 @@ def _check_value(self, action, value): } msg = _('invalid choice: %(value)r (choose from %(choices)s)') - all_strings = all(isinstance(choice, str) for choice in action.choices) - - if self.suggest_on_error and isinstance(value, str) and all_strings: - import difflib - suggestions = difflib.get_close_matches(value, action.choices, 1) - if suggestions: - suggestions = suggestions[0] - args['closest'] = suggestions - msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' - '(choose from %(choices)s)') + if self.suggest_on_error and isinstance(value, str): + if all(isinstance(choice, str) for choice in action.choices): + import difflib + suggestions = difflib.get_close_matches(value, action.choices, 1) + if suggestions: + suggestions = suggestions[0] + args['closest'] = suggestions + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' + '(choose from %(choices)s)') raise ArgumentError(action, msg % args) From 73c4bd31027f8cec011ce594821c16c4d6f0e6a0 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Fri, 11 Oct 2024 17:08:50 -0700 Subject: [PATCH 41/45] fix merge conflict --- Lib/argparse.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 779282ebb2faaa..88f24bab197344 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2581,25 +2581,25 @@ def _get_value(self, action, arg_string): def _check_value(self, action, value): # converted value must be one of the choices (if specified) choices = action.choices - if choices is not None: - if isinstance(choices, str): - choices = iter(choices) - if value not in choices: - args = {'value': value, - 'choices': ', '.join(map(repr, action.choices))} - msg = _('invalid choice: %(value)r (choose from %(choices)s)') - raise ArgumentError(action, msg % args) - - if self.suggest_on_error and isinstance(value, str): - if all(isinstance(choice, str) for choice in action.choices): - import difflib - suggestions = difflib.get_close_matches(value, action.choices, 1) - if suggestions: - suggestions = suggestions[0] - args['closest'] = suggestions - msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' - '(choose from %(choices)s)') + if choices is None: + return + + if isinstance(choices, str): + choices = iter(choices) + + if value not in choices: + args = {'value': value, 'choices': ', '.join(map(repr, action.choices))} + msg = _('invalid choice: %(value)r (choose from %(choices)s)') + if self.suggest_on_error and isinstance(value, str): + if all(isinstance(choice, str) for choice in action.choices): + import difflib + suggestions = difflib.get_close_matches(value, action.choices, 1) + if suggestions: + args['closest'] = suggestions[0] + msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' + '(choose from %(choices)s)') + raise ArgumentError(action, msg % args) # ======================= From d1daff155d72c0106526d1a6da9df7ca147ba17a Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 12 Oct 2024 13:47:05 -0700 Subject: [PATCH 42/45] Appease linter --- Lib/argparse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 88f24bab197344..9e213a72df9a35 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2583,7 +2583,7 @@ def _check_value(self, action, value): choices = action.choices if choices is None: return - + if isinstance(choices, str): choices = iter(choices) @@ -2599,7 +2599,7 @@ def _check_value(self, action, value): args['closest'] = suggestions[0] msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? ' '(choose from %(choices)s)') - + raise ArgumentError(action, msg % args) # ======================= From 3d9bbb4c5f45296b6c3b1f443a58ed004a4cefa6 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Wed, 16 Oct 2024 20:03:10 -0700 Subject: [PATCH 43/45] Update Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst Co-authored-by: Serhiy Storchaka --- .../next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst b/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst index ad39573214e04e..3ecd75c5b551b6 100644 --- a/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst +++ b/Misc/NEWS.d/next/Library/2024-09-24-18-49-16.gh-issue-99749.gBDJX7.rst @@ -1 +1 @@ -Adds a feature to optionally enable suggestions for argument choices and subparser names if mistyped by the user +Adds a feature to optionally enable suggestions for argument choices and subparser names if mistyped by the user. From 05dc55c2149c1599e84ae8b01a7f3e87974980db Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Wed, 16 Oct 2024 20:22:12 -0700 Subject: [PATCH 44/45] Test whole line --- Lib/test/test_argparse.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 544157bc09ba9c..24e5dc752a9c23 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2262,8 +2262,8 @@ def test_wrong_argument_error_with_suggestions(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('bazz',)) self.assertIn( - "maybe you meant 'baz'? (choose from bar, baz)", - excinfo.exception.stderr, + "error: argument foo: invalid choice: 'bazz', maybe you meant 'baz'? (choose from bar, baz)", + excinfo.exception.stderr ) def test_wrong_argument_error_no_suggestions(self): @@ -2272,7 +2272,7 @@ def test_wrong_argument_error_no_suggestions(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('bazz',)) self.assertIn( - "invalid choice: 'bazz' (choose from bar, baz)", + "error: argument foo: invalid choice: 'bazz' (choose from bar, baz)", excinfo.exception.stderr, ) @@ -2284,7 +2284,7 @@ def test_wrong_argument_subparsers_with_suggestions(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) self.assertIn( - "maybe you meant 'bar'? (choose from foo, bar)", + "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from foo, bar)", excinfo.exception.stderr, ) @@ -2296,7 +2296,7 @@ def test_wrong_argument_subparsers_no_suggestions(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) self.assertIn( - "invalid choice: 'baz' (choose from foo, bar)", + "error: argument {foo,bar}: invalid choice: 'baz' (choose from foo, bar)", excinfo.exception.stderr, ) @@ -2306,7 +2306,7 @@ def test_wrong_argument_no_suggestion_implicit(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('bazz',)) self.assertIn( - "invalid choice: 'bazz' (choose from bar, baz)", + "error: argument foo: invalid choice: 'bazz' (choose from bar, baz)", excinfo.exception.stderr, ) @@ -2316,7 +2316,7 @@ def test_suggestions_choices_empty(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('bazz',)) self.assertIn( - "argument foo: invalid choice: 'bazz' (choose from )", + "error: argument foo: invalid choice: 'bazz' (choose from )", excinfo.exception.stderr, ) @@ -2326,7 +2326,7 @@ def test_suggestions_choices_int(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('3',)) self.assertIn( - "invalid choice: '3' (choose from 1, 2)", + "error: argument foo: invalid choice: '3' (choose from 1, 2)", excinfo.exception.stderr, ) @@ -2336,7 +2336,7 @@ def test_suggestions_choices_mixed_types(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('3',)) self.assertIn( - "invalid choice: '3' (choose from 1, 2)", + "error: argument foo: invalid choice: '3' (choose from 1, 2)", excinfo.exception.stderr, ) From f2781cce05d2d89f4f0d28d921ce00a426e7190d Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Wed, 16 Oct 2024 20:24:52 -0700 Subject: [PATCH 45/45] Fix line length --- Lib/test/test_argparse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 24e5dc752a9c23..a3c096ef3199c8 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2284,7 +2284,8 @@ def test_wrong_argument_subparsers_with_suggestions(self): with self.assertRaises(ArgumentParserError) as excinfo: parser.parse_args(('baz',)) self.assertIn( - "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant 'bar'? (choose from foo, bar)", + "error: argument {foo,bar}: invalid choice: 'baz', maybe you meant" + " 'bar'? (choose from foo, bar)", excinfo.exception.stderr, )