From 100afa779bd4f15418c95d6169aff7363b61831b Mon Sep 17 00:00:00 2001 From: NIK-TIGER-BILL Date: Fri, 5 Jun 2026 23:18:43 +0000 Subject: [PATCH 1/2] fix: catch KeyError when accessing __name__ in _idval_from_value (#14560) Custom classes with a __getattr__ that raises KeyError instead of AttributeError would crash pytest during parameterized test id generation. We now catch both AttributeError and KeyError. Fixes #14560 Signed-off-by: NIK-TIGER-BILL --- AUTHORS | 1 + changelog/14560.bugfix.rst | 1 + repro_test.py | 15 +++++++++++++++ src/_pytest/python.py | 7 +++++-- testing/python/metafunc.py | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 changelog/14560.bugfix.rst create mode 100644 repro_test.py diff --git a/AUTHORS b/AUTHORS index 27c0b3ac408..59042885e5e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -352,6 +352,7 @@ Nicolas Delaby Nicolas Simonds Nico Vidal Nikesh Chavhan +Nikita Zamuldinov Nikolay Kondratyev Nipunn Koorapati Oleg Pidsadnyi diff --git a/changelog/14560.bugfix.rst b/changelog/14560.bugfix.rst new file mode 100644 index 00000000000..7adcd6cf6b9 --- /dev/null +++ b/changelog/14560.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when parametrized tests used custom classes with a ``__getattr__`` that raises ``KeyError`` instead of ``AttributeError`` during test id generation. diff --git a/repro_test.py b/repro_test.py new file mode 100644 index 00000000000..f00e45acf60 --- /dev/null +++ b/repro_test.py @@ -0,0 +1,15 @@ +import sys +sys.path.insert(0, '/root/.openclaw/workspace-fork/pytest-work/src') + +from _pytest.python import IdMaker + +class CustomDict: + def __init__(self, data): + self._data = data + def __getattr__(self, item): + return self._data[item] + +val = CustomDict({"a": 1}) +result = IdMaker([], [], None, None, None, None)._idval(val, "a", 6) +assert result == "a6", f"Expected 'a6', got {result!r}" +print("Test passed!") diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ad5a2c6a59b..f09a76c2a9c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1048,9 +1048,12 @@ def _idval_from_value(self, val: object) -> str | None: pass elif isinstance(val, enum.Enum): return str(val) - elif isinstance(getattr(val, "__name__", None), str): + try: + name = getattr(val, "__name__", None) + except (AttributeError, KeyError): + name = None + if isinstance(name, str): # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") return name return None diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 026589d65f5..4cf31fceff1 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -425,6 +425,24 @@ def test_function(): IdMaker([], [], None, None, None, None)._idval(val, "a", 6) == expected ) + def test_idval_custom_class_getattr_keyerror(self) -> None: + """Regression test for #14560. + + Custom classes with a ``__getattr__`` that raises ``KeyError`` + (instead of ``AttributeError``) should not crash id generation. + """ + + class CustomDict: + def __init__(self, data: dict) -> None: + self._data = data + + def __getattr__(self, item: str) -> object: + return self._data[item] + + val = CustomDict({"a": 1}) + result = IdMaker([], [], None, None, None, None)._idval(val, "a", 6) + assert result == "a6" + def test_notset_idval(self) -> None: """Test that a NOTSET value (used by an empty parameterset) generates a proper ID. From d47381a034e1673880f7dd704f806b0a71748106 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:24:08 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- repro_test.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/repro_test.py b/repro_test.py index f00e45acf60..00f73abdc92 100644 --- a/repro_test.py +++ b/repro_test.py @@ -1,14 +1,21 @@ +from __future__ import annotations + import sys -sys.path.insert(0, '/root/.openclaw/workspace-fork/pytest-work/src') + + +sys.path.insert(0, "/root/.openclaw/workspace-fork/pytest-work/src") from _pytest.python import IdMaker + class CustomDict: def __init__(self, data): self._data = data + def __getattr__(self, item): return self._data[item] + val = CustomDict({"a": 1}) result = IdMaker([], [], None, None, None, None)._idval(val, "a", 6) assert result == "a6", f"Expected 'a6', got {result!r}"