]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-149614 - Restore deepcopiability of argparse.ArgumentParser… (#149693)
authorSavannah Ostrowski <savannah@python.org>
Mon, 11 May 2026 18:25:40 +0000 (11:25 -0700)
committerGitHub <noreply@github.com>
Mon, 11 May 2026 18:25:40 +0000 (18:25 +0000)
[3.15] gh-149614 - Restore deepcopiability of argparse.ArgumentParser instances (GH-149617)
(cherry picked from commit fadd9bc14e43041c84bb8d06824990264fe1434a)

Co-authored-by: David Ellis <ducksual@gmail.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Lib/argparse.py
Lib/test/test_argparse.py
Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst [new file with mode: 0644]

index 6d21823e65242933377411afd5fbb0efab621ca3..29e6ebb9634261a4c63cb8d21103ccd5306755db 100644 (file)
@@ -163,6 +163,8 @@ class _ColorlessTheme:
     def __getattr__(self, name):
         # _colorize's no_color themes are just all empty strings
         # by directly using empty strings the import is avoided
+        if name.startswith("_"):
+            raise AttributeError(name)
         return ""
 
 _colorless_theme = _ColorlessTheme()
index 4ea5b6f53a042655d8d8b8203fbfde918e387035..1dc3f538f4ad8ba1852a624a30fb7e1faabc1a9b 100644 (file)
@@ -140,6 +140,48 @@ class TestLazyImports(unittest.TestCase):
         )
 
 
+class TestArgumentParserCopiable(unittest.TestCase):
+    def _get_parser(self):
+        parser = argparse.ArgumentParser(exit_on_error=False)
+        parser.add_argument('--foo', type=int, default=42)
+        parser.add_argument('bar', nargs='?', default='baz')
+        return parser
+
+    @force_not_colorized
+    def test_copiable(self):
+        import copy
+        parser = self._get_parser()
+        parser2 = copy.copy(parser)
+        ns = parser2.parse_args(['--foo', '123', 'quux'])
+        self.assertEqual(ns.foo, 123)
+        self.assertEqual(ns.bar, 'quux')
+        ns2 = parser2.parse_args([])
+        self.assertEqual(ns2.foo, 42)
+        self.assertEqual(ns2.bar, 'baz')
+
+        # Test shallow copy also gets new arguments
+        parser.add_argument("--extra")
+        ns3 = parser2.parse_args(["--extra", "bar"])
+        self.assertEqual(ns3.extra, "bar")
+
+    @force_not_colorized
+    def test_deepcopiable(self):
+        import copy
+        parser = self._get_parser()
+        parser2 = copy.deepcopy(parser)
+        ns = parser2.parse_args(['--foo', '123', 'quux'])
+        self.assertEqual(ns.foo, 123)
+        self.assertEqual(ns.bar, 'quux')
+        ns2 = parser2.parse_args([])
+        self.assertEqual(ns2.foo, 42)
+        self.assertEqual(ns2.bar, 'baz')
+
+        # Test deep copy does not get new arguments
+        parser.add_argument("--extra")
+        with self.assertRaises(argparse.ArgumentError):
+            parser2.parse_args(["--extra", "bar"])
+
+
 class TestArgumentParserPickleable(unittest.TestCase):
 
     @force_not_colorized
@@ -7863,12 +7905,25 @@ class TestColorized(TestCase):
 
     def test_fake_color_theme_matches_real(self):
         from argparse import _colorless_theme
+
+        # Check the attributes match those of the 'real' theme
         _colorize_nocolor = _colorize.get_theme(force_no_color=True).argparse
         for k in _colorize_nocolor:
             self.assertEqual(
                 getattr(_colorless_theme, k), getattr(_colorize_nocolor, k)
             )
 
+    def test_fake_color_theme_raises(self):
+        from argparse import _colorless_theme
+
+        # Make sure the _colorless_theme doesn't return empty strings
+        # for magic methods or private attributes
+        with self.assertRaises(AttributeError):
+            _colorless_theme.__unknown_dunder__
+
+        with self.assertRaises(AttributeError):
+            _colorless_theme._private_attribute
+
 
 class TestModule(unittest.TestCase):
     def test_deprecated__version__(self):
diff --git a/Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst b/Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst
new file mode 100644 (file)
index 0000000..5169c6c
--- /dev/null
@@ -0,0 +1 @@
+Fix a regression that broke the ability to deepcopy :class:`argparse.ArgumentParser` instances.