Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ Kojo Idrissa
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
Langning Zhang
Lawrence Mitchell
Lee Kamentsky
Leonardus Chen
Expand Down
1 change: 1 addition & 0 deletions changelog/14859.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Used Python 3.15's expanded pretty-printing for clearer multiline output.
5 changes: 3 additions & 2 deletions src/_pytest/_io/saferepr.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

from itertools import islice
import pprint
import reprlib

from _pytest.compat import pformat


def _try_repr_or_str(obj: object) -> str:
try:
Expand Down Expand Up @@ -112,7 +113,7 @@ def safeformat(obj: object) -> str:
with a short exception info.
"""
try:
return pprint.pformat(obj)
return pformat(obj)
except Exception as exc:
return _format_repr_exception(exc, obj)

Expand Down
7 changes: 4 additions & 3 deletions src/_pytest/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from decimal import Decimal
import math
from numbers import Complex
import pprint
import sys
from typing import Any
from typing import Generic
Expand All @@ -23,6 +22,8 @@
from typing import TypeGuard
from typing import TypeVar

from _pytest.compat import pformat


if TYPE_CHECKING:
from numpy import ndarray
Expand Down Expand Up @@ -262,7 +263,7 @@ def __init__(
for key, value in expected.items():
if isinstance(value, type(expected)):
msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}"
raise TypeError(msg.format(key, value, pprint.pformat(expected)))
raise TypeError(msg.format(key, value, pformat(expected)))

super().__init__(expected, rel=rel, abs=abs, nan_ok=nan_ok)

Expand Down Expand Up @@ -360,7 +361,7 @@ def __init__(
for index, x in enumerate(expected):
if isinstance(x, type(expected)):
msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}"
raise TypeError(msg.format(x, index, pprint.pformat(expected)))
raise TypeError(msg.format(x, index, pformat(expected)))

super().__init__(expected, rel=rel, abs=abs, nan_ok=nan_ok)

Expand Down
6 changes: 3 additions & 3 deletions src/_pytest/assertion/_compare_any.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from collections.abc import Iterator
import dataclasses
import pprint

from _pytest.assertion._compare_mapping import _compare_eq_mapping
from _pytest.assertion._compare_sequence import _compare_eq_iterable
Expand All @@ -22,6 +21,7 @@
from _pytest.assertion._typing import NO_TRUNCATION_BUDGET
from _pytest.assertion._typing import TruncationBudget
from _pytest.assertion.compare_text import _compare_eq_text
from _pytest.compat import pformat


def _compare_eq_any(
Expand Down Expand Up @@ -119,10 +119,10 @@ def _compare_eq_cls(
yield f"Omitting {len(same)} identical items, use -vv to show"
elif same:
yield "Matching attributes:"
yield from highlighter(pprint.pformat(same)).splitlines()
yield from highlighter(pformat(same)).splitlines()
if diff:
yield "Differing attributes:"
yield from highlighter(pprint.pformat(diff)).splitlines()
yield from highlighter(pformat(diff)).splitlines()
for field in diff:
field_left = getattr(left, field)
field_right = getattr(right, field)
Expand Down
8 changes: 3 additions & 5 deletions src/_pytest/assertion/_compare_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from collections.abc import Iterator
from collections.abc import Mapping
import heapq
import pprint

from _pytest._io.pprint import _safe_key
from _pytest._io.saferepr import saferepr
from _pytest.assertion._typing import _HighlightFunc
from _pytest.assertion._typing import NO_TRUNCATION_BUDGET
from _pytest.assertion._typing import TruncationBudget
from _pytest.compat import pformat


def _compare_eq_mapping(
Expand All @@ -28,7 +28,7 @@ def _compare_eq_mapping(
yield f"Omitting {len(same)} identical items, use -vv to show"
elif same:
yield "Common items:"
yield from highlighter(pprint.pformat(same)).splitlines()
yield from highlighter(pformat(same)).splitlines()
diff = {k for k in common if left[k] != right[k]}
if diff:
yield "Differing items:"
Expand Down Expand Up @@ -62,9 +62,7 @@ def _format_extra_items(
max_lines = truncation_budget.max_lines
if max_lines == 0 or len(keys) <= max_lines:
# If no need to truncate, let pprint handle it.
yield from highlighter(
pprint.pformat({k: mapping[k] for k in keys})
).splitlines()
yield from highlighter(pformat({k: mapping[k] for k in keys})).splitlines()
else:
# To avoid spending effort on formatting entries that would be truncated,
# only format the needed entries, keeping the sorting that pprint would use.
Expand Down
3 changes: 1 addition & 2 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .reports import CollectReport
from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.compat import pformat
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
Expand Down Expand Up @@ -599,8 +600,6 @@ def cacheshow(config: Config, session: Session) -> int:
:param session: pytest session object.
:returns: Exit code (0 for success).
"""
from pprint import pformat

assert config.cache is not None

tw = TerminalWriter()
Expand Down
7 changes: 7 additions & 0 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from inspect import Signature
import os
from pathlib import Path
import pprint
import sys
from typing import Any
from typing import Final
Expand Down Expand Up @@ -327,3 +328,9 @@ def decorator(func):
return func

return decorator


if sys.version_info >= (3, 15):
pformat = functools.partial(pprint.pformat, expand=True)
else:
pformat = pprint.pformat
2 changes: 1 addition & 1 deletion src/_pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from collections.abc import Callable
from collections.abc import Generator
from collections.abc import Iterator
from pprint import pformat
import re
from types import TracebackType
from typing import Any
Expand All @@ -24,6 +23,7 @@

import warnings

from _pytest.compat import pformat
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture
from _pytest.outcomes import Exit
Expand Down
43 changes: 43 additions & 0 deletions testing/test_cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
from pathlib import Path
import shutil
import sys
from typing import Any

from _pytest.compat import assert_never
Expand Down Expand Up @@ -281,6 +282,48 @@ def pytest_configure(config):
assert result.ret == 0


@pytest.mark.skipif(sys.version_info < (3, 15), reason="requires Python 3.15+")
def test_cache_show_uses_expanded_pformat(pytester: Pytester) -> None:
pytester.makeconftest(
"""
def pytest_configure(config):
config.cache.set(
"nested",
{
"a" * 12: 1,
"b" * 20: 2,
"c" * 30: {
"d" * 5: 3,
"e" * 20: 4,
"f" * 10: 5,
"g" * 20: 6,
},
},
)
"""
)
pytester.runpytest()

result = pytester.runpytest("--cache-show", "nested")

result.stdout.fnmatch_lines(
[
"nested contains:",
" {",
" 'aaaaaaaaaaaa': 1,",
" 'bbbbbbbbbbbbbbbbbbbb': 2,",
" 'cccccccccccccccccccccccccccccc': {",
" 'ddddd': 3,",
" 'eeeeeeeeeeeeeeeeeeee': 4,",
" 'ffffffffff': 5,",
" 'gggggggggggggggggggg': 6,",
" },",
" }",
]
)
assert result.ret == 0


class TestLastFailed:
def test_lastfailed_usecase(
self, pytester: Pytester, monkeypatch: MonkeyPatch
Expand Down