Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
896c8b9
fix: display output failure message in insertion order of dictionary
ritvi-alagusankar Jul 5, 2025
4468c17
chore: add changelog message
ritvi-alagusankar Jul 5, 2025
62f5a34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
12b4233
fix: test case
ritvi-alagusankar Jul 5, 2025
0ce44bb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
9205e51
test: fix test cases
ritvi-alagusankar Jul 5, 2025
bcaa184
test: add test case nested dicts
ritvi-alagusankar Jul 5, 2025
57ecd50
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 20, 2025
0188efc
Merge branch 'main' into fix-output-order-dictionary-keys
ritvi-alagusankar Jul 20, 2025
f898fdd
style: fix fmt
ritvi-alagusankar Jul 20, 2025
29689b2
Merge branch 'fix-output-order-dictionary-keys' of https://github.com…
ritvi-alagusankar Jul 20, 2025
290e21f
test: add test case for pprint
ritvi-alagusankar Jul 20, 2025
04673d4
fix: display output failure message in insertion order of dictionary
ritvi-alagusankar Jul 5, 2025
d25b699
chore: add changelog message
ritvi-alagusankar Jul 5, 2025
d4a58da
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
bb95491
fix: test case
ritvi-alagusankar Jul 5, 2025
ce769db
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2025
969407c
test: fix test cases
ritvi-alagusankar Jul 5, 2025
36733f0
test: add test case nested dicts
ritvi-alagusankar Jul 5, 2025
816e755
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 20, 2025
6619690
style: fix fmt
ritvi-alagusankar Jul 20, 2025
ffb5e46
test: add test case for pprint
ritvi-alagusankar Jul 20, 2025
0048258
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 20, 2025
f1d227d
fix: reviewer suggestion
ritvi-alagusankar Sep 23, 2025
bb7ce38
fix: reviewer suggestions
ritvi-alagusankar Sep 23, 2025
89edc53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2025
72012e2
fix: format
ritvi-alagusankar Sep 23, 2025
cafff20
fix: minor changes
ritvi-alagusankar Sep 23, 2025
acd597d
Merge branch 'main' into fix-output-order-dictionary-keys
ritvi-alagusankar Sep 23, 2025
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
4 changes: 4 additions & 0 deletions changelog/13503.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed the order of dictionary keys in assertion failure messages.
Previously, dictionary diffs were shown in alphabetical order, regardless of how the keys appeared in the original dicts.
Now, common keys are shown in the insertion order of the left-hand dictionary,
and differing keys are shown in the insertion order of their respective dictionaries
16 changes: 14 additions & 2 deletions src/_pytest/_io/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def __init__(
indent: int = 4,
width: int = 80,
depth: int | None = None,
sort_dicts: bool = True,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With your changes, the default behaviour of pytest is that dictionaries are not sorted alphabetically.

So the default behaviour is sort_dicts = False. But, the default value of sort_dicts is True. It seems unintuitive.

) -> None:
"""Handle pretty printing operations onto a stream using a set of
configured parameters.
Expand All @@ -75,6 +76,9 @@ def __init__(
depth
The maximum depth to print out nested structures.

sort_dicts
If true, dict keys are sorted.

"""
if indent < 0:
raise ValueError("indent must be >= 0")
Expand All @@ -85,6 +89,7 @@ def __init__(
self._depth = depth
self._indent_per_level = indent
self._width = width
self._sort_dicts = sort_dicts

def pformat(self, object: Any) -> str:
sio = _StringIO()
Expand Down Expand Up @@ -162,7 +167,10 @@ def _pprint_dict(
) -> None:
write = stream.write
write("{")
items = sorted(object.items(), key=_safe_tuple)
if self._sort_dicts:
items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
self._format_dict_items(items, stream, indent, allowance, context, level)
write("}")

Expand Down Expand Up @@ -608,7 +616,11 @@ def _safe_repr(
components: list[str] = []
append = components.append
level += 1
for k, v in sorted(object.items(), key=_safe_tuple):
if self._sort_dicts:
items = sorted(object.items(), key=_safe_tuple)
else:
items = object.items()
for k, v in items:
krepr = self._safe_repr(k, context, maxlevels, level)
vrepr = self._safe_repr(v, context, maxlevels, level)
append(f"{krepr}: {vrepr}")
Expand Down
35 changes: 23 additions & 12 deletions src/_pytest/assertion/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ def _compare_eq_iterable(
# dynamic import to speedup pytest
import difflib

left_formatting = PrettyPrinter().pformat(left).splitlines()
right_formatting = PrettyPrinter().pformat(right).splitlines()
left_formatting = PrettyPrinter(sort_dicts=False).pformat(left).splitlines()
right_formatting = PrettyPrinter(sort_dicts=False).pformat(right).splitlines()

explanation = ["", "Full diff:"]
# "right" is the expected base against which we compare "left",
Expand Down Expand Up @@ -505,29 +505,36 @@ def _compare_eq_dict(
set_left = set(left)
set_right = set(right)
common = set_left.intersection(set_right)
same = {k: left[k] for k in common if left[k] == right[k]}
same = {k: left[k] for k in left if k in right and left[k] == right[k]}
if same and verbose < 2:
explanation += [f"Omitting {len(same)} identical items, use -vv to show"]
elif same:
explanation += ["Common items:"]
explanation += highlighter(pprint.pformat(same)).splitlines()
# Common items are displayed in the order of the left dict
explanation += highlighter(pprint.pformat(same, sort_dicts=False)).splitlines()
diff = {k for k in common if left[k] != right[k]}
if diff:
explanation += ["Differing items:"]
for k in diff:
explanation += [
highlighter(saferepr({k: left[k]}))
+ " != "
+ highlighter(saferepr({k: right[k]}))
]
# Differing items are displayed in the order of the left dict
for k in left:
if k in diff:
explanation += [
highlighter(saferepr({k: left[k]}))
+ " != "
+ highlighter(saferepr({k: right[k]}))
]
extra_left = set_left - set_right
len_extra_left = len(extra_left)
if len_extra_left:
explanation.append(
f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:"
)
explanation.extend(
highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines()
highlighter(
pprint.pformat(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the caveats of not working for nesting

Copy link
Author

@ritvi-alagusankar ritvi-alagusankar Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test case for nested dictionaries, and it appears to be working as expected. The extra_left set only includes keys from the top level of the dictionary, so nested dicts shouldn't pose an issue. Let me know if this is the concern you were referring to.

{k: left[k] for k in left if k in extra_left}, sort_dicts=False
)
).splitlines()
)
extra_right = set_right - set_left
len_extra_right = len(extra_right)
Expand All @@ -536,7 +543,11 @@ def _compare_eq_dict(
f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:"
)
explanation.extend(
highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines()
highlighter(
pprint.pformat(
{k: right[k] for k in right if k in extra_right}, sort_dicts=False
)
).splitlines()
)
return explanation

Expand Down
35 changes: 35 additions & 0 deletions testing/io/test_pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,38 @@ class DataclassWithTwoItems:
)
def test_consistent_pretty_printer(data: Any, expected: str) -> None:
assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip()


@pytest.mark.parametrize(
("sort_dicts"),
(
pytest.param(True, id="sort_dicts-True"),
pytest.param(False, id="sort_dicts-False"),
),
)
def test_pretty_printer_sort_dicts(sort_dicts: bool) -> None:
data = {
"b": 2,
"a": 1,
"c": 3,
}

if sort_dicts:
expected = textwrap.dedent("""
{
'a': 1,
'b': 2,
'c': 3,
}
""").strip()
else:
expected = textwrap.dedent("""
{
'b': 2,
'a': 1,
'c': 3,
}
""").strip()

actual = PrettyPrinter(sort_dicts=sort_dicts).pformat(data)
assert actual == expected
79 changes: 79 additions & 0 deletions testing/test_error_diffs.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,85 @@ def test_this():
""",
id="Compare dicts with differing items",
),
pytest.param(
"""
def test_this():
result = {'d': 4, 'c': 3, 'b': 2, 'a': 1}
expected = {'d': 4, 'c': 3, 'e': 5}
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert {'d': 4, 'c': 3, 'b': 2, 'a': 1} == {'d': 4, 'c': 3, 'e': 5}
E Common items:
E {'d': 4, 'c': 3}
E Left contains 2 more items:
E {'b': 2, 'a': 1}
E Right contains 1 more item:
E {'e': 5}
E Full diff:
E {
E 'd': 4,
E 'c': 3,
E - 'e': 5,
E ? ^ ^
E + 'b': 2,
E ? ^ ^
E + 'a': 1,
E }
""",
id="Compare dicts and check order of diff",
),
pytest.param(
"""
def test_this():
result = {
"b": {"m": 3, "n": 4},
"a": {"x": 1, "y": 2},
"c": {"k": 5, "l": 6}
}
expected = {
"c": {"l": 6, "k": 5},
"e": {"v": 8},
"d": {"u": 7}
}
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert {'b': {'m': 3, 'n': 4}, 'a': {'x': 1, 'y': 2}, 'c': {'k': 5, 'l': 6}} \
== {'c': {'l': 6, 'k': 5}, 'e': {'v': 8}, 'd': {'u': 7}}
E Common items:
E {'c': {'k': 5, 'l': 6}}
E Left contains 2 more items:
E {'b': {'m': 3, 'n': 4}, 'a': {'x': 1, 'y': 2}}
E Right contains 2 more items:
E {'e': {'v': 8}, 'd': {'u': 7}}
E Full diff:
E {
E + 'b': {
E + 'm': 3,
E + 'n': 4,
E + },
E + 'a': {
E + 'x': 1,
E + 'y': 2,
E + },
E 'c': {
E + 'k': 5,
E 'l': 6,
E - 'k': 5,
E - },
E - 'e': {
E - 'v': 8,
E - },
E - 'd': {
E - 'u': 7,
E },
E }
""",
id="Compare nested dicts and check order of diff",
),
pytest.param(
"""
def test_this():
Expand Down
Loading