From baad8c07045fc3365aef52ed63ca3d4591798fcb Mon Sep 17 00:00:00 2001 From: croc100 Date: Mon, 8 Jun 2026 20:15:27 +0900 Subject: [PATCH 1/3] Fix isset() to include dict.items() and dict.keys() in set comparisons isset() was limited to set/frozenset, so assert expressions like dict.items() >= other.items() fell through without any diff output. dict.items() and dict.keys() both implement collections.abc.Set, so widening the check to isinstance(x, AbstractSet) is enough. Fixes #12860 --- src/_pytest/assertion/_guards.py | 5 +++-- testing/test_assertion.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/_guards.py b/src/_pytest/assertion/_guards.py index bb9fedd6e65..d8d786ece6f 100644 --- a/src/_pytest/assertion/_guards.py +++ b/src/_pytest/assertion/_guards.py @@ -2,6 +2,7 @@ import collections.abc from collections.abc import Mapping +from collections.abc import Set as AbstractSet import dataclasses from typing import TypeGuard @@ -18,8 +19,8 @@ def ismapping(x: object) -> TypeGuard[Mapping[object, object]]: return isinstance(x, Mapping) -def isset(x: object) -> TypeGuard[set[object] | frozenset[object]]: - return isinstance(x, set | frozenset) +def isset(x: object) -> TypeGuard[AbstractSet[object]]: + return isinstance(x, AbstractSet) def isnamedtuple(obj: object) -> bool: diff --git a/testing/test_assertion.py b/testing/test_assertion.py index c25487bdf33..e68a4c6132a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1870,6 +1870,38 @@ def test_hello(): ] ) + @pytest.mark.parametrize("op", [">=", "<="]) + def test_dict_items_view_subset(self, op, pytester: Pytester) -> None: + """dict.items() supports set-like comparisons; assert diff should show the missing items.""" + if op == ">=": + pytester.makepyfile( + """ + def test_hello(): + x = {"a": 1, "b": 2} + y = {"a": 1, "b": 2, "c": 3} + assert x.items() >= y.items() + """ + ) + else: + pytester.makepyfile( + """ + def test_hello(): + x = {"a": 1, "b": 2, "c": 3} + y = {"a": 1, "b": 2} + assert x.items() <= y.items() + """ + ) + result = pytester.runpytest() + side = "right" if op == ">=" else "left" + result.stdout.fnmatch_lines( + [ + "*def test_hello():*", + f"*assert x.items() {op} y.items()*", + f"*E*Extra items in the {side} set:*", + "*E*('c', 3)*", + ] + ) + def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"]) From 97a1d3d809bf4260320800ea566e9ea70da58a34 Mon Sep 17 00:00:00 2001 From: croc100 Date: Mon, 8 Jun 2026 20:17:33 +0900 Subject: [PATCH 2/3] Add changelog fragment for #12860 --- changelog/12860.improvement.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/12860.improvement.rst diff --git a/changelog/12860.improvement.rst b/changelog/12860.improvement.rst new file mode 100644 index 00000000000..2ea7492bc10 --- /dev/null +++ b/changelog/12860.improvement.rst @@ -0,0 +1 @@ +Assertion diffs for ``dict.items()`` and ``dict.keys()`` comparisons (``>=``, ``<=``, ``>``, ``<``, ``==``) now show which items are missing, the same way set comparisons do. From 451cfdcfe2ea07ebf3f9454169ce8c8b0b4198c7 Mon Sep 17 00:00:00 2001 From: croc100 Date: Mon, 8 Jun 2026 20:19:03 +0900 Subject: [PATCH 3/3] Add croc100 to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 27c0b3ac408..9ab7b823945 100644 --- a/AUTHORS +++ b/AUTHORS @@ -80,6 +80,7 @@ Brian Okken Brianna Laugher Bruno Oliveira Cal Jacobson +croc100 Cal Leeming Carl Friedrich Bolz Carlos Jenkins