From 26e162e7794339b38e4a69c4ee369f193d9e03e1 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Wed, 11 Sep 2024 18:56:28 +0000 Subject: [PATCH 01/14] update regex for parsing numbers with _ --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 100ef9f55cd2f7..e26a5fcd464838 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1360,7 +1360,7 @@ def __init__(self, self._defaults = {} # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + self._negative_number_matcher = _re.compile(r'^-\d[\d_]*$|^-\d[\d_]*\.\d[\d_]*$') # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index fd111be18aed6e..2dfabb2242adf0 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1506,6 +1506,8 @@ class TestOptionLike(ParserTestCase): ('a -x 1', NS(x=1.0, y=None, z=['a'])), ('-x 1 a', NS(x=1.0, y=None, z=['a'])), ('-3 1 a', NS(x=None, y=1.0, z=['a'])), + ('-x-1_000.0 -3 1_000.0', NS(x=-1000.0, y=1000.0, z=[])), + ('-x-1_000 -3 1_000', NS(x=-1000, y=1000, z=[])), ] From 8b5dd463c7b08aa24507c8949a69fce562219d29 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Wed, 11 Sep 2024 18:59:02 +0000 Subject: [PATCH 02/14] add more test cases for bigger numbers --- Lib/test/test_argparse.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 2dfabb2242adf0..bca7b6051aec39 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1508,6 +1508,9 @@ class TestOptionLike(ParserTestCase): ('-3 1 a', NS(x=None, y=1.0, z=['a'])), ('-x-1_000.0 -3 1_000.0', NS(x=-1000.0, y=1000.0, z=[])), ('-x-1_000 -3 1_000', NS(x=-1000, y=1000, z=[])), + ('-x-1_000_000.0 -3 1_000_000.0', NS(x=-1000000.0, y=1000000.0, z=[])), + ('-x-1_000_000_000.0 -3 1_000_000_000.0', NS(x=-1000000000.0, y=1000000000.0, z=[])), + ] From 43d08f88f3518e9e541a8c3407e5710624de2e0c Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:05:34 +0000 Subject: [PATCH 03/14] =?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-11-19-05-32.gh-issue-123945.jLwybB.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst b/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst new file mode 100644 index 00000000000000..ac5117e33c37e4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst @@ -0,0 +1 @@ +Fix a bug where argparse doesn't recognize negative numbers with underscores From 404b9b163e5699dfbae89d38f06ea8b413f6db1f Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 12 Sep 2024 13:04:34 -0700 Subject: [PATCH 04/14] update regex and add more test cases --- Lib/argparse.py | 3 +-- Lib/test/test_argparse.py | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index e26a5fcd464838..8e54b2d7840d14 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1360,8 +1360,7 @@ def __init__(self, self._defaults = {} # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d[\d_]*$|^-\d[\d_]*\.\d[\d_]*$') - + self._negative_number_matcher = _re.compile(r'^-?\d[\d_]*(\.\d+)?$') # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited self._has_negative_number_optionals = [] diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index bca7b6051aec39..63a9b2fc07de37 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1493,7 +1493,9 @@ class TestOptionLike(ParserTestCase): failures = ['-x', '-y2.5', '-xa', '-x -a', '-x -3', '-x -3.5', '-3 -3.5', '-x -2.5', '-x -2.5 a', '-3 -.5', - 'a x -1', '-x -1 a', '-3 -1 a'] + 'a x -1', '-x -1 a', '-3 -1 a', + '-x -_.45 -3 1_000', '-x -1_000.0_45 -3 1_000', + '-x -1__000_000.0 -3 1_000_000',] successes = [ ('', NS(x=None, y=None, z=[])), ('-x 2.5', NS(x=2.5, y=None, z=[])), From af017edaa40a37d3699e44d570f5c2753e93dcee Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 12 Sep 2024 13:05:11 -0700 Subject: [PATCH 05/14] add newline back --- Lib/argparse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/argparse.py b/Lib/argparse.py index 8e54b2d7840d14..5b83dfd0d7bc84 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1361,6 +1361,7 @@ def __init__(self, # determines whether an "option" looks like a negative number self._negative_number_matcher = _re.compile(r'^-?\d[\d_]*(\.\d+)?$') + # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited self._has_negative_number_optionals = [] From 0af5a1d9b40b8425e05621a4b80191a8a5653908 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 12 Sep 2024 13:26:29 -0700 Subject: [PATCH 06/14] remove indent --- Lib/argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 5b83dfd0d7bc84..984f47246653a4 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1361,7 +1361,7 @@ def __init__(self, # determines whether an "option" looks like a negative number self._negative_number_matcher = _re.compile(r'^-?\d[\d_]*(\.\d+)?$') - + # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited self._has_negative_number_optionals = [] From 43d3c9c61e7b8b643b0375daf15e1845d2332417 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Thu, 12 Sep 2024 13:44:53 -0700 Subject: [PATCH 07/14] Update Lib/argparse.py Co-authored-by: Brandt Bucher --- Lib/argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 984f47246653a4..accd2df47fad97 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1360,7 +1360,7 @@ def __init__(self, self._defaults = {} # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-?\d[\d_]*(\.\d+)?$') + self._negative_number_matcher = _re.compile(r'^-\d[\d_]*(\.\d+)?$') # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited From 7fa550cc72364961bc29c5858fca6e2616719f62 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 14 Sep 2024 18:46:55 -0700 Subject: [PATCH 08/14] update tests --- Lib/test/test_argparse.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 63a9b2fc07de37..c6eb8c4306c878 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1493,9 +1493,7 @@ class TestOptionLike(ParserTestCase): failures = ['-x', '-y2.5', '-xa', '-x -a', '-x -3', '-x -3.5', '-3 -3.5', '-x -2.5', '-x -2.5 a', '-3 -.5', - 'a x -1', '-x -1 a', '-3 -1 a', - '-x -_.45 -3 1_000', '-x -1_000.0_45 -3 1_000', - '-x -1__000_000.0 -3 1_000_000',] + 'a x -1', '-x -1 a', '-3 -1 a',] successes = [ ('', NS(x=None, y=None, z=[])), ('-x 2.5', NS(x=2.5, y=None, z=[])), @@ -1508,11 +1506,6 @@ class TestOptionLike(ParserTestCase): ('a -x 1', NS(x=1.0, y=None, z=['a'])), ('-x 1 a', NS(x=1.0, y=None, z=['a'])), ('-3 1 a', NS(x=None, y=1.0, z=['a'])), - ('-x-1_000.0 -3 1_000.0', NS(x=-1000.0, y=1000.0, z=[])), - ('-x-1_000 -3 1_000', NS(x=-1000, y=1000, z=[])), - ('-x-1_000_000.0 -3 1_000_000.0', NS(x=-1000000.0, y=1000000.0, z=[])), - ('-x-1_000_000_000.0 -3 1_000_000_000.0', NS(x=-1000000000.0, y=1000000000.0, z=[])), - ] @@ -5722,6 +5715,32 @@ def test_double_dash(self): self.assertEqual(NS(foo=[], bar=[]), args) args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd']) self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args) + + def test_negative_number_success(self): + parser = argparse.ArgumentParser() + parser.add_argument('--int', type=int) + parser.add_argument('--float', type=float) + + args = parser.parse_args(['--int', '-1000', '--float', '-1000.0']) + self.assertEqual(NS(int=-1000, float=-1000.0), args) + + args = parser.parse_args(['--int', '-1_000', '--float', '-1_000.0']) + self.assertEqual(NS(int=-1000, float=-1000.0), args) + + args = parser.parse_args(['--int', '-1_000_000', '--float', '-1_000_000.0']) + self.assertEqual(NS(int=-1000000, float=-1000000.0), args) + + def test_negative_float_failure(self): + # Intermixed and remainder are incompatible + parser = ErrorRaisingArgumentParser(prog='PROG') + parser.add_argument('--float', type=float) + + with self.assertRaises(ArgumentParserError) as cm: + parser.parse_args(['--float', '-_.45']) + parser.parse_args(['--float', '-1__000.0']) + parser.parse_args(['--float', '-1_000.0_0']) + + self.assertRegex(str(cm.exception), r'error: argument --float: expected one argument') # =========================== From 87784c8b1630f1bdf35b58e701c0bfde0f55ac4e Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 14 Sep 2024 18:47:52 -0700 Subject: [PATCH 09/14] remove comma --- 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 c6eb8c4306c878..09954f1a3bfa70 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1493,7 +1493,7 @@ class TestOptionLike(ParserTestCase): failures = ['-x', '-y2.5', '-xa', '-x -a', '-x -3', '-x -3.5', '-3 -3.5', '-x -2.5', '-x -2.5 a', '-3 -.5', - 'a x -1', '-x -1 a', '-3 -1 a',] + 'a x -1', '-x -1 a', '-3 -1 a'] successes = [ ('', NS(x=None, y=None, z=[])), ('-x 2.5', NS(x=2.5, y=None, z=[])), From 77888a734c0816719db74210b42cdb11eed04e84 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 14 Sep 2024 18:50:09 -0700 Subject: [PATCH 10/14] appease linter --- 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 09954f1a3bfa70..6ebb36be398782 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5715,12 +5715,12 @@ def test_double_dash(self): self.assertEqual(NS(foo=[], bar=[]), args) args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd']) self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args) - + def test_negative_number_success(self): parser = argparse.ArgumentParser() parser.add_argument('--int', type=int) parser.add_argument('--float', type=float) - + args = parser.parse_args(['--int', '-1000', '--float', '-1000.0']) self.assertEqual(NS(int=-1000, float=-1000.0), args) From 926ecfa8ed927704c4e0fea32e83acd8c81a1ec9 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 14 Sep 2024 18:51:43 -0700 Subject: [PATCH 11/14] remove comment --- Lib/test/test_argparse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 6ebb36be398782..b88838d6a9e9bc 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5731,7 +5731,6 @@ def test_negative_number_success(self): self.assertEqual(NS(int=-1000000, float=-1000000.0), args) def test_negative_float_failure(self): - # Intermixed and remainder are incompatible parser = ErrorRaisingArgumentParser(prog='PROG') parser.add_argument('--float', type=float) From a8cfdee9792178c6d95ab49c01e2e4e8bd007d95 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sun, 15 Sep 2024 08:05:04 -0700 Subject: [PATCH 12/14] refactor to use the ParseTestCase infra --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 44 +++++++++++++++++---------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index accd2df47fad97..a88a8c65c40a1d 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1360,7 +1360,7 @@ def __init__(self, self._defaults = {} # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d[\d_]*(\.\d+)?$') + self._negative_number_matcher = _re.compile(r'^-\d[\d_]*(\.\d[\d_]*)?$') # whether or not there are any optionals that look like negative # numbers -- uses a list so it can be shared and edited diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index b88838d6a9e9bc..f0af517aa5b1c0 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2093,6 +2093,25 @@ class TestActionExtend(ParserTestCase): ] +class TestNegativeNumberAction(ParserTestCase): + """Test parsing negative numbers""" + + argument_signatures = [ + Sig('--int', type=int), + Sig('--float', type=float), + ] + failures = [ + '--float -_.45', + '--int -1__000.0', + ] + successes = [ + ('--int -1000 --float -1000.0', NS(int=-1000, float=-1000.0)), + ('--int -1_000 --float -1_000.0', NS(int=-1000, float=-1000.0)), + ('--int -1_000_000 --float -1_000_000.0', NS(int=-1000000, float=-1000000.0)), + ('--float -1_000.0', NS(int=None, float=-1000.0)), + ('--float -1_000_000.0_0', NS(int=None, float=-1000000.0)), + ] + class TestInvalidAction(TestCase): """Test invalid user defined Action""" @@ -5716,31 +5735,6 @@ def test_double_dash(self): args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd']) self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args) - def test_negative_number_success(self): - parser = argparse.ArgumentParser() - parser.add_argument('--int', type=int) - parser.add_argument('--float', type=float) - - args = parser.parse_args(['--int', '-1000', '--float', '-1000.0']) - self.assertEqual(NS(int=-1000, float=-1000.0), args) - - args = parser.parse_args(['--int', '-1_000', '--float', '-1_000.0']) - self.assertEqual(NS(int=-1000, float=-1000.0), args) - - args = parser.parse_args(['--int', '-1_000_000', '--float', '-1_000_000.0']) - self.assertEqual(NS(int=-1000000, float=-1000000.0), args) - - def test_negative_float_failure(self): - parser = ErrorRaisingArgumentParser(prog='PROG') - parser.add_argument('--float', type=float) - - with self.assertRaises(ArgumentParserError) as cm: - parser.parse_args(['--float', '-_.45']) - parser.parse_args(['--float', '-1__000.0']) - parser.parse_args(['--float', '-1_000.0_0']) - - self.assertRegex(str(cm.exception), r'error: argument --float: expected one argument') - # =========================== # parse_intermixed_args tests From 95bb67a965fb271ef9ba832590524331941ee465 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sun, 15 Sep 2024 08:08:04 -0700 Subject: [PATCH 13/14] add another test case --- 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 f0af517aa5b1c0..2abf41919fa8b1 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2102,7 +2102,8 @@ class TestNegativeNumberAction(ParserTestCase): ] failures = [ '--float -_.45', - '--int -1__000.0', + '--float -1__000.0', + '--int -1__000', ] successes = [ ('--int -1000 --float -1000.0', NS(int=-1000, float=-1000.0)), From 1c4f4cfcef9e690561e9254ee73ecb2bbdd94867 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:23:08 -0700 Subject: [PATCH 14/14] Apply suggestions from code review --- Lib/test/test_argparse.py | 2 +- .../next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 2abf41919fa8b1..584462b741ee99 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2093,7 +2093,7 @@ class TestActionExtend(ParserTestCase): ] -class TestNegativeNumberAction(ParserTestCase): +class TestNegativeNumber(ParserTestCase): """Test parsing negative numbers""" argument_signatures = [ diff --git a/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst b/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst index ac5117e33c37e4..26b0ac80b1b3fd 100644 --- a/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst +++ b/Misc/NEWS.d/next/Library/2024-09-11-19-05-32.gh-issue-123945.jLwybB.rst @@ -1 +1 @@ -Fix a bug where argparse doesn't recognize negative numbers with underscores +Fix a bug where :mod:`argparse` doesn't recognize negative numbers with underscores