Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions cmd2/argparse_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]


############################################################################################################
Expand Down
5 changes: 4 additions & 1 deletion tests/test_argparse_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading