From b8c57178969f0ae1c49644049646d7764e995841 Mon Sep 17 00:00:00 2001 From: hrideymarwah15 Date: Fri, 16 Jan 2026 17:46:42 +0530 Subject: [PATCH 1/8] Fix: Preserve dict insertion order in assertion output Remove sorted() calls in PrettyPrinter to preserve dict insertion order. Since Python 3.7+, dicts maintain insertion order. Sorting keys alphabetically in assertion failures is confusing and doesn't match how Python naturally displays dicts. Changes: - _pprint_dict: Remove sorted() on object.items() - _safe_repr: Remove sorted() on dict.items() iteration Fixes #13503 --- src/_pytest/_io/pprint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 28f06909206..ec41b449ddf 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -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("}") @@ -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}") From 1d2980d026309ec894e913a0f79b815e88104ad1 Mon Sep 17 00:00:00 2001 From: hrideymarwah15 Date: Fri, 16 Jan 2026 17:46:48 +0530 Subject: [PATCH 2/8] Add test for dict insertion order preservation Add test_dict_preserves_insertion_order to verify that dictionary keys maintain their insertion order in assertion output, not alphabetical order. This test ensures the fix for issue #13503 works correctly and prevents future regressions. Related to #13503 --- testing/io/test_pprint.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 1326ef34b2e..94893ea1b96 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -406,3 +406,36 @@ 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 Date: Fri, 16 Jan 2026 12:34:35 +0000 Subject: [PATCH 3/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/io/test_pprint.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 94893ea1b96..627df4c36d9 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -410,26 +410,28 @@ def test_consistent_pretty_printer(data: Any, expected: str) -> None: 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 - + 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 Date: Fri, 16 Jan 2026 18:05:20 +0530 Subject: [PATCH 4/8] Add changelog entry for issue #13503 --- changelog/13503.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/13503.bugfix.rst diff --git a/changelog/13503.bugfix.rst b/changelog/13503.bugfix.rst new file mode 100644 index 00000000000..1edcf346c34 --- /dev/null +++ b/changelog/13503.bugfix.rst @@ -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+. From 7a40fc10d98fc375c3bc43de49a7ed381b77a769 Mon Sep 17 00:00:00 2001 From: hrideymarwah15 Date: Fri, 16 Jan 2026 18:29:16 +0530 Subject: [PATCH 5/8] Add test for _safe_repr dict insertion order Improve code coverage by adding test that triggers the _safe_repr code path with depth limiting, ensuring both modified lines in pprint.py are fully covered. This addresses Codecov target of 100% diff coverage. --- testing/io/test_pprint.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 627df4c36d9..f190843ab27 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -441,3 +441,25 @@ def test_dict_preserves_insertion_order() -> None: } """).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 Date: Fri, 16 Jan 2026 12:59:37 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/io/test_pprint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index f190843ab27..5507617b855 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -445,17 +445,17 @@ def test_dict_preserves_insertion_order() -> None: 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'") From 6aeca7b8d63f01c3b8293a78f68d4804631c2fb6 Mon Sep 17 00:00:00 2001 From: hrideymarwah15 Date: Fri, 16 Jan 2026 18:34:27 +0530 Subject: [PATCH 7/8] Add direct _safe_repr test for full coverage Call _safe_repr directly to ensure the dict sorting code path in _safe_repr is tested, achieving 100% diff coverage. --- testing/io/test_pprint.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 5507617b855..8f0c17c8866 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -463,3 +463,27 @@ def test_dict_insertion_order_with_depth() -> None: assert z_pos < a_pos < m_pos, ( f"Expected insertion order z 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 Date: Fri, 16 Jan 2026 13:05:05 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/io/test_pprint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 8f0c17c8866..be54fdb1e5e 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -467,18 +467,18 @@ def test_dict_insertion_order_with_depth() -> None: 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'")