From 8e07f7fe074bb633bba762a576167b5d15869f12 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Mon, 11 May 2026 16:30:28 -0400 Subject: [PATCH] Updated monkey patch to match how CPython fixed the argparse bug. --- cmd2/argparse_utils.py | 18 ++++++++++++------ tests/test_argparse_utils.py | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cmd2/argparse_utils.py b/cmd2/argparse_utils.py index 5f42993d5..3c6b076a0 100644 --- a/cmd2/argparse_utils.py +++ b/cmd2/argparse_utils.py @@ -371,21 +371,27 @@ def _validate_completion_callable(self: argparse.Action, value: Any) -> Any: ############################################################################################################ # Workaround for Python 3.15.0b1 argparse bug -# _ColorlessTheme.__getattr__ incorrectly returns "" for dunder methods, which breaks +# _ColorlessTheme.__getattr__ incorrectly returns "" for non-public attributes, which breaks # protocols like copy.deepcopy(). ############################################################################################################ if sys.version_info >= (3, 15): - def _ColorlessTheme_getattr(_self: argparse._ColorlessTheme, name: str) -> Any: # noqa: N802 - """Patched __getattr__ that allows dunder lookups to fail correctly.""" - if name.startswith("__") and name.endswith("__"): + def _ColorlessTheme_getattr( # noqa: N802 + _self: argparse._ColorlessTheme, # type: ignore[name-defined] + name: str, + ) -> Any: + """Patched __getattr__ that allows non-public lookups to fail correctly. + + This matches the implementation in CPython for their next release. + """ + if name.startswith("_"): raise AttributeError(name) return "" # If the bug still exists, then install the patch. - if getattr(argparse._ColorlessTheme(), "__deepcopy__", None) == "": - argparse._ColorlessTheme.__getattr__ = _ColorlessTheme_getattr + if getattr(argparse._ColorlessTheme(), "__deepcopy__", None) == "": # type: ignore[attr-defined] + argparse._ColorlessTheme.__getattr__ = _ColorlessTheme_getattr # type: ignore[attr-defined] ############################################################################################################ diff --git a/tests/test_argparse_utils.py b/tests/test_argparse_utils.py index 7d990a057..3f3f3a83c 100644 --- a/tests/test_argparse_utils.py +++ b/tests/test_argparse_utils.py @@ -761,6 +761,9 @@ def test_colorless_theme_monkeypatch() -> None: # We can remove the patch function and this test. assert argparse._ColorlessTheme.__getattr__ == argparse_utils._ColorlessTheme_getattr - # Our patch raises an Attribute error for dunder attributes. + # Our patch raises an Attribute error for non-public. + with pytest.raises(AttributeError): + getattr(argparse._ColorlessTheme(), "_fake") # noqa: B009 + with pytest.raises(AttributeError): getattr(argparse._ColorlessTheme(), "__deepcopy__") # noqa: B009