Skip to content
1 change: 1 addition & 0 deletions changelog/13503.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dictionary keys in assertion failure output now preserve insertion order instead of being sorted alphabetically, matching Python's dict behavior since 3.7+.
4 changes: 2 additions & 2 deletions src/_pytest/_io/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def _pprint_dict(
) -> None:
write = stream.write
write("{")
items = sorted(object.items(), key=_safe_tuple)
items = object.items()
self._format_dict_items(items, stream, indent, allowance, context, level)
write("}")

Expand Down Expand Up @@ -608,7 +608,7 @@ def _safe_repr(
components: list[str] = []
append = components.append
level += 1
for k, v in sorted(object.items(), key=_safe_tuple):
for k, v in object.items():
krepr = self._safe_repr(k, context, maxlevels, level)
vrepr = self._safe_repr(v, context, maxlevels, level)
append(f"{krepr}: {vrepr}")
Expand Down
81 changes: 81 additions & 0 deletions testing/io/test_pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,84 @@ class DataclassWithTwoItems:
)
def test_consistent_pretty_printer(data: Any, expected: str) -> None:
assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip()


def test_dict_preserves_insertion_order() -> None:
"""Test that dictionary keys maintain insertion order, not alphabetical.

Relates to issue #13503 - dicts should preserve insertion order
since Python 3.7+, not sort alphabetically.
"""
# Create dict with non-alphabetical insertion order
d = {}
d["z"] = 1
d["a"] = 2
d["m"] = 3

result = PrettyPrinter().pformat(d)

# Verify the keys appear in insertion order (z, a, m)
z_pos = result.index("'z'")
a_pos = result.index("'a'")
m_pos = result.index("'m'")

# z should appear before a, and a before m
assert z_pos < a_pos < m_pos, (
f"Expected insertion order z<a<m, got positions: z={z_pos}, a={a_pos}, m={m_pos}"
)

# Also verify expected format
expected = textwrap.dedent("""
{
'z': 1,
'a': 2,
'm': 3,
}
""").strip()
assert result == expected


def test_dict_insertion_order_with_depth() -> None:
"""Test dict insertion order in _safe_repr (used with maxlevels/depth).

This test ensures the _safe_repr method also preserves insertion order
when it formats dicts at maximum depth levels.
"""
# Create nested dict structure with non-alphabetical keys
d = {"z": {"inner": 1}, "a": {"inner": 2}, "m": {"inner": 3}}

# Use depth parameter to trigger _safe_repr path
pp = PrettyPrinter(depth=1)
result = pp.pformat(d)

# Verify insertion order is preserved (z before a before m)
z_pos = result.index("'z'")
a_pos = result.index("'a'")
m_pos = result.index("'m'")
assert z_pos < a_pos < m_pos, (
f"Expected insertion order z<a<m in _safe_repr, got: z={z_pos}, a={a_pos}, m={m_pos}"
)


def test_dict_insertion_order_in_repr() -> None:
"""Test dict insertion order in _safe_repr inline representation.

This covers the _safe_repr dict formatting code path used for
inline/fallback representations.
"""
pp = PrettyPrinter()

# Create a dict that will be rendered inline via _safe_repr
d = {"z": 1, "a": 2, "m": 3}

# Call _safe_repr directly to ensure that code path is covered
result = pp._safe_repr(d, set(), None, 0)

# Verify insertion order is preserved in the inline repr
z_pos = result.index("'z'")
a_pos = result.index("'a'")
m_pos = result.index("'m'")
assert z_pos < a_pos < m_pos, (
f"Expected insertion order z<a<m in inline repr, got: {result}"
)
assert result == "{'z': 1, 'a': 2, 'm': 3}"