Skip to content

Commit 9fd1fcc

Browse files
authored
Added monkey patch to handle argparse deepcopy issue introduced in Python 3.15.0b1. (#1660)
1 parent 06b4915 commit 9fd1fcc

3 files changed

Lines changed: 37 additions & 5 deletions

File tree

cmd2/argparse_utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,24 @@ def _validate_completion_callable(self: argparse.Action, value: Any) -> Any:
369369
register_argparse_argument_parameter("nargs_range")
370370
register_argparse_argument_parameter("suppress_tab_hint")
371371

372+
############################################################################################################
373+
# Workaround for Python 3.15.0b1 argparse bug
374+
# _ColorlessTheme.__getattr__ incorrectly returns "" for dunder methods, which breaks
375+
# protocols like copy.deepcopy().
376+
############################################################################################################
377+
378+
if sys.version_info >= (3, 15):
379+
380+
def _ColorlessTheme_getattr(_self: argparse._ColorlessTheme, name: str) -> Any: # noqa: N802
381+
"""Patched __getattr__ that allows dunder lookups to fail correctly."""
382+
if name.startswith("__") and name.endswith("__"):
383+
raise AttributeError(name)
384+
return ""
385+
386+
# If the bug still exists, then install the patch.
387+
if getattr(argparse._ColorlessTheme(), "__deepcopy__", None) == "":
388+
argparse._ColorlessTheme.__getattr__ = _ColorlessTheme_getattr
389+
372390

373391
############################################################################################################
374392
# Patch _ActionsContainer.add_argument to support more arguments

cmd2/utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,11 +480,9 @@ def line_buffering(self) -> bool:
480480
except AttributeError:
481481
return False
482482

483-
def __getattr__(self, item: str) -> Any:
484-
"""When an attribute lookup fails to find the attribute in the usual places, this special method is called."""
485-
if item in self.__dict__:
486-
return self.__dict__[item]
487-
return getattr(self.inner_stream, item)
483+
def __getattr__(self, name: str) -> Any:
484+
"""Forward attribute lookups to the inner stream for attributes not defined on this class."""
485+
return getattr(self.inner_stream, name)
488486

489487

490488
class ByteBuf:

tests/test_argparse_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,3 +748,19 @@ def test_argparse_output_capture(base_app: cmd2.Cmd) -> None:
748748
# Prove that the console style settings were used
749749
assert styled_help_out != unstyled_help_out
750750
assert su.strip_style("\n".join(styled_help_out)) == "\n".join(unstyled_help_out)
751+
752+
753+
@pytest.mark.skipif(
754+
sys.version_info < (3, 15),
755+
reason="_ColorlessTheme only exists in 3.15+",
756+
)
757+
def test_colorless_theme_monkeypatch() -> None:
758+
"""Test the _ColorlessTheme.__getattr__ monkey patch."""
759+
760+
# If this assertion fails, then the bug no longer exists and our patch wasn't installed.
761+
# We can remove the patch function and this test.
762+
assert argparse._ColorlessTheme.__getattr__ == argparse_utils._ColorlessTheme_getattr
763+
764+
# Our patch raises an Attribute error for dunder attributes.
765+
with pytest.raises(AttributeError):
766+
getattr(argparse._ColorlessTheme(), "__deepcopy__") # noqa: B009

0 commit comments

Comments
 (0)