From 001ebf1e046c202cb337f010026fb5620cbeba6c Mon Sep 17 00:00:00 2001 From: Vinod Kumar Date: Thu, 14 May 2026 21:09:18 +0530 Subject: [PATCH 1/3] Support generic mappings in assertion comparison --- src/_pytest/assertion/util.py | 4 ++-- testing/test_assertion.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 07918a66284..d4ff86da52d 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -127,8 +127,8 @@ def istext(x: object) -> TypeGuard[str]: return isinstance(x, str) -def isdict(x: object) -> TypeGuard[dict[object, object]]: - return isinstance(x, dict) +def isdict(x: object) -> TypeGuard[Mapping[object, object]]: + return isinstance(x, Mapping) def isset(x: object) -> TypeGuard[set[object] | frozenset[object]]: diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 9a7305a2905..2a5a196dbcb 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1,7 +1,9 @@ # mypy: allow-untyped-defs from __future__ import annotations +from collections.abc import Mapping from collections.abc import MutableSequence + import sys import textwrap from typing import Any @@ -719,6 +721,34 @@ def test_dict_wrap(self) -> None: " }", ] + def test_Mapping(self) -> None: + # Test comparing with a Mapping implementation that is not a dict subclass. + class TestMapping(Mapping[str, int]): + def __init__(self, values: dict[str, int]) -> None: + self.values = values + + def __getitem__(self, key: str) -> int: + return self.values[key] + + def __iter__(self): + return iter(self.values) + + def __len__(self) -> int: + return len(self.values) + + lines = callequal( + TestMapping({"a": 0, "b": 1}), + TestMapping({"a": 1, "b": 1}), + ) + + assert lines is not None + assert any( + line.startswith("Omitting 1 identical item") + for line in lines + ) + assert "Differing items:" in lines + assert "{'a': 0} != {'a': 1}" in lines + def test_dict(self) -> None: expl = callequal({"a": 0}, {"a": 1}) assert expl is not None From eac6d11b6f4bf080ab606a1d4c8f8ae5d85a1401 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 15:40:59 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_assertion.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 2a5a196dbcb..4a487a0b7fe 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -3,7 +3,6 @@ from collections.abc import Mapping from collections.abc import MutableSequence - import sys import textwrap from typing import Any @@ -742,10 +741,7 @@ def __len__(self) -> int: ) assert lines is not None - assert any( - line.startswith("Omitting 1 identical item") - for line in lines - ) + assert any(line.startswith("Omitting 1 identical item") for line in lines) assert "Differing items:" in lines assert "{'a': 0} != {'a': 1}" in lines From 6691904d09cd889aff5e67182a28605bbe558893 Mon Sep 17 00:00:00 2001 From: Vinod Kumar Date: Thu, 14 May 2026 21:17:04 +0530 Subject: [PATCH 3/3] Add changelog entry for generic mapping assertion diffs --- changelog/14461.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/14461.bugfix.rst diff --git a/changelog/14461.bugfix.rst b/changelog/14461.bugfix.rst new file mode 100644 index 00000000000..01c5f2ec964 --- /dev/null +++ b/changelog/14461.bugfix.rst @@ -0,0 +1,2 @@ +Assertion diffs now use mapping-specific output for objects implementing +:class:`collections.abc.Mapping`, not only concrete :class:`dict` instances.