Skip to content
Open
2 changes: 2 additions & 0 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("_"):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason not to check that this starts and ends with __ ?

We use this pattern elsewhere in the repo as well.

raise AttributeError(name)
return ""

_colorless_theme = _ColorlessTheme()
Expand Down
55 changes: 55 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,48 @@ def test_parse_args(self):
)


class TestArgumentParserCopiable(unittest.TestCase):
Comment thread
DavidCEllis marked this conversation as resolved.
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):
Comment thread
savannahostrowski marked this conversation as resolved.
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'])
Comment thread
savannahostrowski marked this conversation as resolved.
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
Expand Down Expand Up @@ -7863,12 +7905,25 @@ def fake_can_colorize(*, file=None):

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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a regression that broke the ability to deepcopy ``argparse.ArgumentParser`` instances.
Loading