From eb6ad08e5de1b63c1a46e2fce36be3ded899bead Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Fri, 17 Nov 2023 18:40:29 +0000
Subject: [PATCH 1/7] Vendor in the pprint module to allow further
 modifications

We already have the AlwaysDispatchingPrettyPrinter override of the
default pretty printer. In order to make more in depth changes, we
need to copy the upstream version in, as it doesn't lend itself well to
being extended.

This does a verbatime copy, adding provenance information at the top.
---
 src/_pytest/_io/pprint.py   | 664 ++++++++++++++++++++++++++++++++++++
 src/_pytest/_io/saferepr.py |   6 +-
 2 files changed, 668 insertions(+), 2 deletions(-)
 create mode 100644 src/_pytest/_io/pprint.py

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
new file mode 100644
index 000000000..3875f7131
--- /dev/null
+++ b/src/_pytest/_io/pprint.py
@@ -0,0 +1,664 @@
+# This module was imported from the cpython standard library
+# (https://github.com/python/cpython/) at commit
+# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12).
+#
+# fmt: off
+# flake8: noqa
+# type: ignore
+
+#
+#  Author:      Fred L. Drake, Jr.
+#               fdrake@acm.org
+#
+#  This is a simple little module I wrote to make life easier.  I didn't
+#  see anything quite like it in the library, though I may have overlooked
+#  something.  I wrote this when I was trying to read some heavily nested
+#  tuples with fairly non-descriptive content.  This is modeled very much
+#  after Lisp/Scheme - style pretty-printing of lists.  If you find it
+#  useful, thank small children who sleep at night.
+
+"""Support to pretty-print lists, tuples, & dictionaries recursively.
+
+Very simple, but useful, especially in debugging data structures.
+
+Classes
+-------
+
+PrettyPrinter()
+    Handle pretty-printing operations onto a stream using a configured
+    set of formatting parameters.htop
+
+Functions
+---------
+
+pformat()
+    Format a Python object into a pretty-printed representation.
+
+pprint()
+    Pretty-print a Python object to a stream [default is sys.stdout].
+
+saferepr()
+    Generate a 'standard' repr()-like value, but protect against recursive
+    data structures.
+
+"""
+
+import collections as _collections
+import dataclasses as _dataclasses
+import re
+import sys as _sys
+import types as _types
+from io import StringIO as _StringIO
+
+__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
+           "PrettyPrinter", "pp"]
+
+
+def pprint(object, stream=None, indent=1, width=80, depth=None, *,
+           compact=False, sort_dicts=True, underscore_numbers=False):
+    """Pretty-print a Python object to a stream [default is sys.stdout]."""
+    printer = PrettyPrinter(
+        stream=stream, indent=indent, width=width, depth=depth,
+        compact=compact, sort_dicts=sort_dicts,
+        underscore_numbers=underscore_numbers)
+    printer.pprint(object)
+
+def pformat(object, indent=1, width=80, depth=None, *,
+            compact=False, sort_dicts=True, underscore_numbers=False):
+    """Format a Python object into a pretty-printed representation."""
+    return PrettyPrinter(indent=indent, width=width, depth=depth,
+                         compact=compact, sort_dicts=sort_dicts,
+                         underscore_numbers=underscore_numbers).pformat(object)
+
+def pp(object, *args, sort_dicts=False, **kwargs):
+    """Pretty-print a Python object"""
+    pprint(object, *args, sort_dicts=sort_dicts, **kwargs)
+
+def saferepr(object):
+    """Version of repr() which can handle recursive data structures."""
+    return PrettyPrinter()._safe_repr(object, {}, None, 0)[0]
+
+def isreadable(object):
+    """Determine if saferepr(object) is readable by eval()."""
+    return PrettyPrinter()._safe_repr(object, {}, None, 0)[1]
+
+def isrecursive(object):
+    """Determine if object requires a recursive representation."""
+    return PrettyPrinter()._safe_repr(object, {}, None, 0)[2]
+
+class _safe_key:
+    """Helper function for key functions when sorting unorderable objects.
+
+    The wrapped-object will fallback to a Py2.x style comparison for
+    unorderable types (sorting first comparing the type name and then by
+    the obj ids).  Does not work recursively, so dict.items() must have
+    _safe_key applied to both the key and the value.
+
+    """
+
+    __slots__ = ['obj']
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __lt__(self, other):
+        try:
+            return self.obj < other.obj
+        except TypeError:
+            return ((str(type(self.obj)), id(self.obj)) < \
+                    (str(type(other.obj)), id(other.obj)))
+
+def _safe_tuple(t):
+    "Helper function for comparing 2-tuples"
+    return _safe_key(t[0]), _safe_key(t[1])
+
+class PrettyPrinter:
+    def __init__(self, indent=1, width=80, depth=None, stream=None, *,
+                 compact=False, sort_dicts=True, underscore_numbers=False):
+        """Handle pretty printing operations onto a stream using a set of
+        configured parameters.
+
+        indent
+            Number of spaces to indent for each level of nesting.
+
+        width
+            Attempted maximum number of columns in the output.
+
+        depth
+            The maximum depth to print out nested structures.
+
+        stream
+            The desired output stream.  If omitted (or false), the standard
+            output stream available at construction will be used.
+
+        compact
+            If true, several items will be combined in one line.
+
+        sort_dicts
+            If true, dict keys are sorted.
+
+        """
+        indent = int(indent)
+        width = int(width)
+        if indent < 0:
+            raise ValueError('indent must be >= 0')
+        if depth is not None and depth <= 0:
+            raise ValueError('depth must be > 0')
+        if not width:
+            raise ValueError('width must be != 0')
+        self._depth = depth
+        self._indent_per_level = indent
+        self._width = width
+        if stream is not None:
+            self._stream = stream
+        else:
+            self._stream = _sys.stdout
+        self._compact = bool(compact)
+        self._sort_dicts = sort_dicts
+        self._underscore_numbers = underscore_numbers
+
+    def pprint(self, object):
+        if self._stream is not None:
+            self._format(object, self._stream, 0, 0, {}, 0)
+            self._stream.write("\n")
+
+    def pformat(self, object):
+        sio = _StringIO()
+        self._format(object, sio, 0, 0, {}, 0)
+        return sio.getvalue()
+
+    def isrecursive(self, object):
+        return self.format(object, {}, 0, 0)[2]
+
+    def isreadable(self, object):
+        s, readable, recursive = self.format(object, {}, 0, 0)
+        return readable and not recursive
+
+    def _format(self, object, stream, indent, allowance, context, level):
+        objid = id(object)
+        if objid in context:
+            stream.write(_recursion(object))
+            self._recursive = True
+            self._readable = False
+            return
+        rep = self._repr(object, context, level)
+        max_width = self._width - indent - allowance
+        if len(rep) > max_width:
+            p = self._dispatch.get(type(object).__repr__, None)
+            if p is not None:
+                context[objid] = 1
+                p(self, object, stream, indent, allowance, context, level + 1)
+                del context[objid]
+                return
+            elif (_dataclasses.is_dataclass(object) and
+                  not isinstance(object, type) and
+                  object.__dataclass_params__.repr and
+                  # Check dataclass has generated repr method.
+                  hasattr(object.__repr__, "__wrapped__") and
+                  "__create_fn__" in object.__repr__.__wrapped__.__qualname__):
+                context[objid] = 1
+                self._pprint_dataclass(object, stream, indent, allowance, context, level + 1)
+                del context[objid]
+                return
+        stream.write(rep)
+
+    def _pprint_dataclass(self, object, stream, indent, allowance, context, level):
+        cls_name = object.__class__.__name__
+        indent += len(cls_name) + 1
+        items = [(f.name, getattr(object, f.name)) for f in _dataclasses.fields(object) if f.repr]
+        stream.write(cls_name + '(')
+        self._format_namespace_items(items, stream, indent, allowance, context, level)
+        stream.write(')')
+
+    _dispatch = {}
+
+    def _pprint_dict(self, object, stream, indent, allowance, context, level):
+        write = stream.write
+        write('{')
+        if self._indent_per_level > 1:
+            write((self._indent_per_level - 1) * ' ')
+        length = len(object)
+        if length:
+            if self._sort_dicts:
+                items = sorted(object.items(), key=_safe_tuple)
+            else:
+                items = object.items()
+            self._format_dict_items(items, stream, indent, allowance + 1,
+                                    context, level)
+        write('}')
+
+    _dispatch[dict.__repr__] = _pprint_dict
+
+    def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level):
+        if not len(object):
+            stream.write(repr(object))
+            return
+        cls = object.__class__
+        stream.write(cls.__name__ + '(')
+        self._format(list(object.items()), stream,
+                     indent + len(cls.__name__) + 1, allowance + 1,
+                     context, level)
+        stream.write(')')
+
+    _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
+
+    def _pprint_list(self, object, stream, indent, allowance, context, level):
+        stream.write('[')
+        self._format_items(object, stream, indent, allowance + 1,
+                           context, level)
+        stream.write(']')
+
+    _dispatch[list.__repr__] = _pprint_list
+
+    def _pprint_tuple(self, object, stream, indent, allowance, context, level):
+        stream.write('(')
+        endchar = ',)' if len(object) == 1 else ')'
+        self._format_items(object, stream, indent, allowance + len(endchar),
+                           context, level)
+        stream.write(endchar)
+
+    _dispatch[tuple.__repr__] = _pprint_tuple
+
+    def _pprint_set(self, object, stream, indent, allowance, context, level):
+        if not len(object):
+            stream.write(repr(object))
+            return
+        typ = object.__class__
+        if typ is set:
+            stream.write('{')
+            endchar = '}'
+        else:
+            stream.write(typ.__name__ + '({')
+            endchar = '})'
+            indent += len(typ.__name__) + 1
+        object = sorted(object, key=_safe_key)
+        self._format_items(object, stream, indent, allowance + len(endchar),
+                           context, level)
+        stream.write(endchar)
+
+    _dispatch[set.__repr__] = _pprint_set
+    _dispatch[frozenset.__repr__] = _pprint_set
+
+    def _pprint_str(self, object, stream, indent, allowance, context, level):
+        write = stream.write
+        if not len(object):
+            write(repr(object))
+            return
+        chunks = []
+        lines = object.splitlines(True)
+        if level == 1:
+            indent += 1
+            allowance += 1
+        max_width1 = max_width = self._width - indent
+        for i, line in enumerate(lines):
+            rep = repr(line)
+            if i == len(lines) - 1:
+                max_width1 -= allowance
+            if len(rep) <= max_width1:
+                chunks.append(rep)
+            else:
+                # A list of alternating (non-space, space) strings
+                parts = re.findall(r'\S*\s*', line)
+                assert parts
+                assert not parts[-1]
+                parts.pop()  # drop empty last part
+                max_width2 = max_width
+                current = ''
+                for j, part in enumerate(parts):
+                    candidate = current + part
+                    if j == len(parts) - 1 and i == len(lines) - 1:
+                        max_width2 -= allowance
+                    if len(repr(candidate)) > max_width2:
+                        if current:
+                            chunks.append(repr(current))
+                        current = part
+                    else:
+                        current = candidate
+                if current:
+                    chunks.append(repr(current))
+        if len(chunks) == 1:
+            write(rep)
+            return
+        if level == 1:
+            write('(')
+        for i, rep in enumerate(chunks):
+            if i > 0:
+                write('\n' + ' '*indent)
+            write(rep)
+        if level == 1:
+            write(')')
+
+    _dispatch[str.__repr__] = _pprint_str
+
+    def _pprint_bytes(self, object, stream, indent, allowance, context, level):
+        write = stream.write
+        if len(object) <= 4:
+            write(repr(object))
+            return
+        parens = level == 1
+        if parens:
+            indent += 1
+            allowance += 1
+            write('(')
+        delim = ''
+        for rep in _wrap_bytes_repr(object, self._width - indent, allowance):
+            write(delim)
+            write(rep)
+            if not delim:
+                delim = '\n' + ' '*indent
+        if parens:
+            write(')')
+
+    _dispatch[bytes.__repr__] = _pprint_bytes
+
+    def _pprint_bytearray(self, object, stream, indent, allowance, context, level):
+        write = stream.write
+        write('bytearray(')
+        self._pprint_bytes(bytes(object), stream, indent + 10,
+                           allowance + 1, context, level + 1)
+        write(')')
+
+    _dispatch[bytearray.__repr__] = _pprint_bytearray
+
+    def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level):
+        stream.write('mappingproxy(')
+        self._format(object.copy(), stream, indent + 13, allowance + 1,
+                     context, level)
+        stream.write(')')
+
+    _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy
+
+    def _pprint_simplenamespace(self, object, stream, indent, allowance, context, level):
+        if type(object) is _types.SimpleNamespace:
+            # The SimpleNamespace repr is "namespace" instead of the class
+            # name, so we do the same here. For subclasses; use the class name.
+            cls_name = 'namespace'
+        else:
+            cls_name = object.__class__.__name__
+        indent += len(cls_name) + 1
+        items = object.__dict__.items()
+        stream.write(cls_name + '(')
+        self._format_namespace_items(items, stream, indent, allowance, context, level)
+        stream.write(')')
+
+    _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace
+
+    def _format_dict_items(self, items, stream, indent, allowance, context,
+                           level):
+        write = stream.write
+        indent += self._indent_per_level
+        delimnl = ',\n' + ' ' * indent
+        last_index = len(items) - 1
+        for i, (key, ent) in enumerate(items):
+            last = i == last_index
+            rep = self._repr(key, context, level)
+            write(rep)
+            write(': ')
+            self._format(ent, stream, indent + len(rep) + 2,
+                         allowance if last else 1,
+                         context, level)
+            if not last:
+                write(delimnl)
+
+    def _format_namespace_items(self, items, stream, indent, allowance, context, level):
+        write = stream.write
+        delimnl = ',\n' + ' ' * indent
+        last_index = len(items) - 1
+        for i, (key, ent) in enumerate(items):
+            last = i == last_index
+            write(key)
+            write('=')
+            if id(ent) in context:
+                # Special-case representation of recursion to match standard
+                # recursive dataclass repr.
+                write("...")
+            else:
+                self._format(ent, stream, indent + len(key) + 1,
+                             allowance if last else 1,
+                             context, level)
+            if not last:
+                write(delimnl)
+
+    def _format_items(self, items, stream, indent, allowance, context, level):
+        write = stream.write
+        indent += self._indent_per_level
+        if self._indent_per_level > 1:
+            write((self._indent_per_level - 1) * ' ')
+        delimnl = ',\n' + ' ' * indent
+        delim = ''
+        width = max_width = self._width - indent + 1
+        it = iter(items)
+        try:
+            next_ent = next(it)
+        except StopIteration:
+            return
+        last = False
+        while not last:
+            ent = next_ent
+            try:
+                next_ent = next(it)
+            except StopIteration:
+                last = True
+                max_width -= allowance
+                width -= allowance
+            if self._compact:
+                rep = self._repr(ent, context, level)
+                w = len(rep) + 2
+                if width < w:
+                    width = max_width
+                    if delim:
+                        delim = delimnl
+                if width >= w:
+                    width -= w
+                    write(delim)
+                    delim = ', '
+                    write(rep)
+                    continue
+            write(delim)
+            delim = delimnl
+            self._format(ent, stream, indent,
+                         allowance if last else 1,
+                         context, level)
+
+    def _repr(self, object, context, level):
+        repr, readable, recursive = self.format(object, context.copy(),
+                                                self._depth, level)
+        if not readable:
+            self._readable = False
+        if recursive:
+            self._recursive = True
+        return repr
+
+    def format(self, object, context, maxlevels, level):
+        """Format object for a specific context, returning a string
+        and flags indicating whether the representation is 'readable'
+        and whether the object represents a recursive construct.
+        """
+        return self._safe_repr(object, context, maxlevels, level)
+
+    def _pprint_default_dict(self, object, stream, indent, allowance, context, level):
+        if not len(object):
+            stream.write(repr(object))
+            return
+        rdf = self._repr(object.default_factory, context, level)
+        cls = object.__class__
+        indent += len(cls.__name__) + 1
+        stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent))
+        self._pprint_dict(object, stream, indent, allowance + 1, context, level)
+        stream.write(')')
+
+    _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict
+
+    def _pprint_counter(self, object, stream, indent, allowance, context, level):
+        if not len(object):
+            stream.write(repr(object))
+            return
+        cls = object.__class__
+        stream.write(cls.__name__ + '({')
+        if self._indent_per_level > 1:
+            stream.write((self._indent_per_level - 1) * ' ')
+        items = object.most_common()
+        self._format_dict_items(items, stream,
+                                indent + len(cls.__name__) + 1, allowance + 2,
+                                context, level)
+        stream.write('})')
+
+    _dispatch[_collections.Counter.__repr__] = _pprint_counter
+
+    def _pprint_chain_map(self, object, stream, indent, allowance, context, level):
+        if not len(object.maps):
+            stream.write(repr(object))
+            return
+        cls = object.__class__
+        stream.write(cls.__name__ + '(')
+        indent += len(cls.__name__) + 1
+        for i, m in enumerate(object.maps):
+            if i == len(object.maps) - 1:
+                self._format(m, stream, indent, allowance + 1, context, level)
+                stream.write(')')
+            else:
+                self._format(m, stream, indent, 1, context, level)
+                stream.write(',\n' + ' ' * indent)
+
+    _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map
+
+    def _pprint_deque(self, object, stream, indent, allowance, context, level):
+        if not len(object):
+            stream.write(repr(object))
+            return
+        cls = object.__class__
+        stream.write(cls.__name__ + '(')
+        indent += len(cls.__name__) + 1
+        stream.write('[')
+        if object.maxlen is None:
+            self._format_items(object, stream, indent, allowance + 2,
+                               context, level)
+            stream.write('])')
+        else:
+            self._format_items(object, stream, indent, 2,
+                               context, level)
+            rml = self._repr(object.maxlen, context, level)
+            stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml))
+
+    _dispatch[_collections.deque.__repr__] = _pprint_deque
+
+    def _pprint_user_dict(self, object, stream, indent, allowance, context, level):
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict
+
+    def _pprint_user_list(self, object, stream, indent, allowance, context, level):
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserList.__repr__] = _pprint_user_list
+
+    def _pprint_user_string(self, object, stream, indent, allowance, context, level):
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserString.__repr__] = _pprint_user_string
+
+    def _safe_repr(self, object, context, maxlevels, level):
+        # Return triple (repr_string, isreadable, isrecursive).
+        typ = type(object)
+        if typ in _builtin_scalars:
+            return repr(object), True, False
+
+        r = getattr(typ, "__repr__", None)
+
+        if issubclass(typ, int) and r is int.__repr__:
+            if self._underscore_numbers:
+                return f"{object:_d}", True, False
+            else:
+                return repr(object), True, False
+
+        if issubclass(typ, dict) and r is dict.__repr__:
+            if not object:
+                return "{}", True, False
+            objid = id(object)
+            if maxlevels and level >= maxlevels:
+                return "{...}", False, objid in context
+            if objid in context:
+                return _recursion(object), False, True
+            context[objid] = 1
+            readable = True
+            recursive = False
+            components = []
+            append = components.append
+            level += 1
+            if self._sort_dicts:
+                items = sorted(object.items(), key=_safe_tuple)
+            else:
+                items = object.items()
+            for k, v in items:
+                krepr, kreadable, krecur = self.format(
+                    k, context, maxlevels, level)
+                vrepr, vreadable, vrecur = self.format(
+                    v, context, maxlevels, level)
+                append("%s: %s" % (krepr, vrepr))
+                readable = readable and kreadable and vreadable
+                if krecur or vrecur:
+                    recursive = True
+            del context[objid]
+            return "{%s}" % ", ".join(components), readable, recursive
+
+        if (issubclass(typ, list) and r is list.__repr__) or \
+           (issubclass(typ, tuple) and r is tuple.__repr__):
+            if issubclass(typ, list):
+                if not object:
+                    return "[]", True, False
+                format = "[%s]"
+            elif len(object) == 1:
+                format = "(%s,)"
+            else:
+                if not object:
+                    return "()", True, False
+                format = "(%s)"
+            objid = id(object)
+            if maxlevels and level >= maxlevels:
+                return format % "...", False, objid in context
+            if objid in context:
+                return _recursion(object), False, True
+            context[objid] = 1
+            readable = True
+            recursive = False
+            components = []
+            append = components.append
+            level += 1
+            for o in object:
+                orepr, oreadable, orecur = self.format(
+                    o, context, maxlevels, level)
+                append(orepr)
+                if not oreadable:
+                    readable = False
+                if orecur:
+                    recursive = True
+            del context[objid]
+            return format % ", ".join(components), readable, recursive
+
+        rep = repr(object)
+        return rep, (rep and not rep.startswith('<')), False
+
+_builtin_scalars = frozenset({str, bytes, bytearray, float, complex,
+                              bool, type(None)})
+
+def _recursion(object):
+    return ("<Recursion on %s with id=%s>"
+            % (type(object).__name__, id(object)))
+
+
+def _wrap_bytes_repr(object, width, allowance):
+    current = b''
+    last = len(object) // 4 * 4
+    for i in range(0, len(object), 4):
+        part = object[i: i+4]
+        candidate = current + part
+        if i == last:
+            width -= allowance
+        if len(repr(candidate)) > width:
+            if current:
+                yield repr(current)
+            current = part
+        else:
+            current = candidate
+    if current:
+        yield repr(current)
diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py
index c70187223..50d6a303a 100644
--- a/src/_pytest/_io/saferepr.py
+++ b/src/_pytest/_io/saferepr.py
@@ -5,6 +5,8 @@ from typing import Dict
 from typing import IO
 from typing import Optional
 
+from .pprint import PrettyPrinter  # type: ignore
+
 
 def _try_repr_or_str(obj: object) -> str:
     try:
@@ -134,7 +136,7 @@ def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:
         return _format_repr_exception(exc, obj)
 
 
-class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
+class AlwaysDispatchingPrettyPrinter(PrettyPrinter):
     """PrettyPrinter that always dispatches (regardless of width)."""
 
     def _format(
@@ -175,6 +177,6 @@ def _pformat_dispatch(
     *,
     compact: bool = False,
 ) -> str:
-    return AlwaysDispatchingPrettyPrinter(
+    return AlwaysDispatchingPrettyPrinter(  # type: ignore
         indent=indent, width=width, depth=depth, compact=compact
     ).pformat(object)

From 23226683449ef07931e9aa5caa380ef03192db09 Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Fri, 17 Nov 2023 18:54:47 +0000
Subject: [PATCH 2/7] Remove unneeded pprint interfaces

There are parts of the original pprint module that we won't need, let's
limit the surface and remove the unnecessary code
---
 src/_pytest/_io/pprint.py | 79 +--------------------------------------
 1 file changed, 2 insertions(+), 77 deletions(-)

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
index 3875f7131..c13ae99ae 100644
--- a/src/_pytest/_io/pprint.py
+++ b/src/_pytest/_io/pprint.py
@@ -6,9 +6,8 @@
 # flake8: noqa
 # type: ignore
 
-#
-#  Author:      Fred L. Drake, Jr.
-#               fdrake@acm.org
+#  Original Author:      Fred L. Drake, Jr.
+#                        fdrake@acm.org
 #
 #  This is a simple little module I wrote to make life easier.  I didn't
 #  see anything quite like it in the library, though I may have overlooked
@@ -16,33 +15,6 @@
 #  tuples with fairly non-descriptive content.  This is modeled very much
 #  after Lisp/Scheme - style pretty-printing of lists.  If you find it
 #  useful, thank small children who sleep at night.
-
-"""Support to pretty-print lists, tuples, & dictionaries recursively.
-
-Very simple, but useful, especially in debugging data structures.
-
-Classes
--------
-
-PrettyPrinter()
-    Handle pretty-printing operations onto a stream using a configured
-    set of formatting parameters.htop
-
-Functions
----------
-
-pformat()
-    Format a Python object into a pretty-printed representation.
-
-pprint()
-    Pretty-print a Python object to a stream [default is sys.stdout].
-
-saferepr()
-    Generate a 'standard' repr()-like value, but protect against recursive
-    data structures.
-
-"""
-
 import collections as _collections
 import dataclasses as _dataclasses
 import re
@@ -50,41 +22,6 @@ import sys as _sys
 import types as _types
 from io import StringIO as _StringIO
 
-__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
-           "PrettyPrinter", "pp"]
-
-
-def pprint(object, stream=None, indent=1, width=80, depth=None, *,
-           compact=False, sort_dicts=True, underscore_numbers=False):
-    """Pretty-print a Python object to a stream [default is sys.stdout]."""
-    printer = PrettyPrinter(
-        stream=stream, indent=indent, width=width, depth=depth,
-        compact=compact, sort_dicts=sort_dicts,
-        underscore_numbers=underscore_numbers)
-    printer.pprint(object)
-
-def pformat(object, indent=1, width=80, depth=None, *,
-            compact=False, sort_dicts=True, underscore_numbers=False):
-    """Format a Python object into a pretty-printed representation."""
-    return PrettyPrinter(indent=indent, width=width, depth=depth,
-                         compact=compact, sort_dicts=sort_dicts,
-                         underscore_numbers=underscore_numbers).pformat(object)
-
-def pp(object, *args, sort_dicts=False, **kwargs):
-    """Pretty-print a Python object"""
-    pprint(object, *args, sort_dicts=sort_dicts, **kwargs)
-
-def saferepr(object):
-    """Version of repr() which can handle recursive data structures."""
-    return PrettyPrinter()._safe_repr(object, {}, None, 0)[0]
-
-def isreadable(object):
-    """Determine if saferepr(object) is readable by eval()."""
-    return PrettyPrinter()._safe_repr(object, {}, None, 0)[1]
-
-def isrecursive(object):
-    """Determine if object requires a recursive representation."""
-    return PrettyPrinter()._safe_repr(object, {}, None, 0)[2]
 
 class _safe_key:
     """Helper function for key functions when sorting unorderable objects.
@@ -157,23 +94,11 @@ class PrettyPrinter:
         self._sort_dicts = sort_dicts
         self._underscore_numbers = underscore_numbers
 
-    def pprint(self, object):
-        if self._stream is not None:
-            self._format(object, self._stream, 0, 0, {}, 0)
-            self._stream.write("\n")
-
     def pformat(self, object):
         sio = _StringIO()
         self._format(object, sio, 0, 0, {}, 0)
         return sio.getvalue()
 
-    def isrecursive(self, object):
-        return self.format(object, {}, 0, 0)[2]
-
-    def isreadable(self, object):
-        s, readable, recursive = self.format(object, {}, 0, 0)
-        return readable and not recursive
-
     def _format(self, object, stream, indent, allowance, context, level):
         objid = id(object)
         if objid in context:

From 66f2f20effb987e24cbf4dce2c86437bc813e57f Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Fri, 17 Nov 2023 19:05:20 +0000
Subject: [PATCH 3/7] Run pyupgrade on the pprint module

---
 src/_pytest/_io/pprint.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
index c13ae99ae..66afca587 100644
--- a/src/_pytest/_io/pprint.py
+++ b/src/_pytest/_io/pprint.py
@@ -5,7 +5,8 @@
 # fmt: off
 # flake8: noqa
 # type: ignore
-
+#
+#
 #  Original Author:      Fred L. Drake, Jr.
 #                        fdrake@acm.org
 #
@@ -408,7 +409,7 @@ class PrettyPrinter:
         rdf = self._repr(object.default_factory, context, level)
         cls = object.__class__
         indent += len(cls.__name__) + 1
-        stream.write('%s(%s,\n%s' % (cls.__name__, rdf, ' ' * indent))
+        stream.write(f"{cls.__name__}({rdf},\n{' ' * indent}")
         self._pprint_dict(object, stream, indent, allowance + 1, context, level)
         stream.write(')')
 
@@ -463,7 +464,7 @@ class PrettyPrinter:
             self._format_items(object, stream, indent, 2,
                                context, level)
             rml = self._repr(object.maxlen, context, level)
-            stream.write('],\n%smaxlen=%s)' % (' ' * indent, rml))
+            stream.write(f"],\n{' ' * indent}maxlen={rml})")
 
     _dispatch[_collections.deque.__repr__] = _pprint_deque
 
@@ -519,7 +520,7 @@ class PrettyPrinter:
                     k, context, maxlevels, level)
                 vrepr, vreadable, vrecur = self.format(
                     v, context, maxlevels, level)
-                append("%s: %s" % (krepr, vrepr))
+                append(f"{krepr}: {vrepr}")
                 readable = readable and kreadable and vreadable
                 if krecur or vrecur:
                     recursive = True

From 5fae5ef73e85ac6d753aa6c9d72424734cccaf3b Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Fri, 17 Nov 2023 19:06:51 +0000
Subject: [PATCH 4/7] Apply project-wide formatting standard to the pprint
 module (black)

---
 src/_pytest/_io/pprint.py | 268 +++++++++++++++++++++-----------------
 1 file changed, 151 insertions(+), 117 deletions(-)

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
index 66afca587..3bb4a2c7d 100644
--- a/src/_pytest/_io/pprint.py
+++ b/src/_pytest/_io/pprint.py
@@ -2,7 +2,6 @@
 # (https://github.com/python/cpython/) at commit
 # c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12).
 #
-# fmt: off
 # flake8: noqa
 # type: ignore
 #
@@ -34,7 +33,7 @@ class _safe_key:
 
     """
 
-    __slots__ = ['obj']
+    __slots__ = ["obj"]
 
     def __init__(self, obj):
         self.obj = obj
@@ -43,16 +42,29 @@ class _safe_key:
         try:
             return self.obj < other.obj
         except TypeError:
-            return ((str(type(self.obj)), id(self.obj)) < \
-                    (str(type(other.obj)), id(other.obj)))
+            return (str(type(self.obj)), id(self.obj)) < (
+                str(type(other.obj)),
+                id(other.obj),
+            )
+
 
 def _safe_tuple(t):
     "Helper function for comparing 2-tuples"
     return _safe_key(t[0]), _safe_key(t[1])
 
+
 class PrettyPrinter:
-    def __init__(self, indent=1, width=80, depth=None, stream=None, *,
-                 compact=False, sort_dicts=True, underscore_numbers=False):
+    def __init__(
+        self,
+        indent=1,
+        width=80,
+        depth=None,
+        stream=None,
+        *,
+        compact=False,
+        sort_dicts=True,
+        underscore_numbers=False,
+    ):
         """Handle pretty printing operations onto a stream using a set of
         configured parameters.
 
@@ -79,11 +91,11 @@ class PrettyPrinter:
         indent = int(indent)
         width = int(width)
         if indent < 0:
-            raise ValueError('indent must be >= 0')
+            raise ValueError("indent must be >= 0")
         if depth is not None and depth <= 0:
-            raise ValueError('depth must be > 0')
+            raise ValueError("depth must be > 0")
         if not width:
-            raise ValueError('width must be != 0')
+            raise ValueError("width must be != 0")
         self._depth = depth
         self._indent_per_level = indent
         self._width = width
@@ -116,14 +128,19 @@ class PrettyPrinter:
                 p(self, object, stream, indent, allowance, context, level + 1)
                 del context[objid]
                 return
-            elif (_dataclasses.is_dataclass(object) and
-                  not isinstance(object, type) and
-                  object.__dataclass_params__.repr and
-                  # Check dataclass has generated repr method.
-                  hasattr(object.__repr__, "__wrapped__") and
-                  "__create_fn__" in object.__repr__.__wrapped__.__qualname__):
+            elif (
+                _dataclasses.is_dataclass(object)
+                and not isinstance(object, type)
+                and object.__dataclass_params__.repr
+                and
+                # Check dataclass has generated repr method.
+                hasattr(object.__repr__, "__wrapped__")
+                and "__create_fn__" in object.__repr__.__wrapped__.__qualname__
+            ):
                 context[objid] = 1
-                self._pprint_dataclass(object, stream, indent, allowance, context, level + 1)
+                self._pprint_dataclass(
+                    object, stream, indent, allowance, context, level + 1
+                )
                 del context[objid]
                 return
         stream.write(rep)
@@ -131,27 +148,32 @@ class PrettyPrinter:
     def _pprint_dataclass(self, object, stream, indent, allowance, context, level):
         cls_name = object.__class__.__name__
         indent += len(cls_name) + 1
-        items = [(f.name, getattr(object, f.name)) for f in _dataclasses.fields(object) if f.repr]
-        stream.write(cls_name + '(')
+        items = [
+            (f.name, getattr(object, f.name))
+            for f in _dataclasses.fields(object)
+            if f.repr
+        ]
+        stream.write(cls_name + "(")
         self._format_namespace_items(items, stream, indent, allowance, context, level)
-        stream.write(')')
+        stream.write(")")
 
     _dispatch = {}
 
     def _pprint_dict(self, object, stream, indent, allowance, context, level):
         write = stream.write
-        write('{')
+        write("{")
         if self._indent_per_level > 1:
-            write((self._indent_per_level - 1) * ' ')
+            write((self._indent_per_level - 1) * " ")
         length = len(object)
         if length:
             if self._sort_dicts:
                 items = sorted(object.items(), key=_safe_tuple)
             else:
                 items = object.items()
-            self._format_dict_items(items, stream, indent, allowance + 1,
-                                    context, level)
-        write('}')
+            self._format_dict_items(
+                items, stream, indent, allowance + 1, context, level
+            )
+        write("}")
 
     _dispatch[dict.__repr__] = _pprint_dict
 
@@ -160,27 +182,32 @@ class PrettyPrinter:
             stream.write(repr(object))
             return
         cls = object.__class__
-        stream.write(cls.__name__ + '(')
-        self._format(list(object.items()), stream,
-                     indent + len(cls.__name__) + 1, allowance + 1,
-                     context, level)
-        stream.write(')')
+        stream.write(cls.__name__ + "(")
+        self._format(
+            list(object.items()),
+            stream,
+            indent + len(cls.__name__) + 1,
+            allowance + 1,
+            context,
+            level,
+        )
+        stream.write(")")
 
     _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
 
     def _pprint_list(self, object, stream, indent, allowance, context, level):
-        stream.write('[')
-        self._format_items(object, stream, indent, allowance + 1,
-                           context, level)
-        stream.write(']')
+        stream.write("[")
+        self._format_items(object, stream, indent, allowance + 1, context, level)
+        stream.write("]")
 
     _dispatch[list.__repr__] = _pprint_list
 
     def _pprint_tuple(self, object, stream, indent, allowance, context, level):
-        stream.write('(')
-        endchar = ',)' if len(object) == 1 else ')'
-        self._format_items(object, stream, indent, allowance + len(endchar),
-                           context, level)
+        stream.write("(")
+        endchar = ",)" if len(object) == 1 else ")"
+        self._format_items(
+            object, stream, indent, allowance + len(endchar), context, level
+        )
         stream.write(endchar)
 
     _dispatch[tuple.__repr__] = _pprint_tuple
@@ -191,15 +218,16 @@ class PrettyPrinter:
             return
         typ = object.__class__
         if typ is set:
-            stream.write('{')
-            endchar = '}'
+            stream.write("{")
+            endchar = "}"
         else:
-            stream.write(typ.__name__ + '({')
-            endchar = '})'
+            stream.write(typ.__name__ + "({")
+            endchar = "})"
             indent += len(typ.__name__) + 1
         object = sorted(object, key=_safe_key)
-        self._format_items(object, stream, indent, allowance + len(endchar),
-                           context, level)
+        self._format_items(
+            object, stream, indent, allowance + len(endchar), context, level
+        )
         stream.write(endchar)
 
     _dispatch[set.__repr__] = _pprint_set
@@ -224,12 +252,12 @@ class PrettyPrinter:
                 chunks.append(rep)
             else:
                 # A list of alternating (non-space, space) strings
-                parts = re.findall(r'\S*\s*', line)
+                parts = re.findall(r"\S*\s*", line)
                 assert parts
                 assert not parts[-1]
                 parts.pop()  # drop empty last part
                 max_width2 = max_width
-                current = ''
+                current = ""
                 for j, part in enumerate(parts):
                     candidate = current + part
                     if j == len(parts) - 1 and i == len(lines) - 1:
@@ -246,13 +274,13 @@ class PrettyPrinter:
             write(rep)
             return
         if level == 1:
-            write('(')
+            write("(")
         for i, rep in enumerate(chunks):
             if i > 0:
-                write('\n' + ' '*indent)
+                write("\n" + " " * indent)
             write(rep)
         if level == 1:
-            write(')')
+            write(")")
 
     _dispatch[str.__repr__] = _pprint_str
 
@@ -265,83 +293,94 @@ class PrettyPrinter:
         if parens:
             indent += 1
             allowance += 1
-            write('(')
-        delim = ''
+            write("(")
+        delim = ""
         for rep in _wrap_bytes_repr(object, self._width - indent, allowance):
             write(delim)
             write(rep)
             if not delim:
-                delim = '\n' + ' '*indent
+                delim = "\n" + " " * indent
         if parens:
-            write(')')
+            write(")")
 
     _dispatch[bytes.__repr__] = _pprint_bytes
 
     def _pprint_bytearray(self, object, stream, indent, allowance, context, level):
         write = stream.write
-        write('bytearray(')
-        self._pprint_bytes(bytes(object), stream, indent + 10,
-                           allowance + 1, context, level + 1)
-        write(')')
+        write("bytearray(")
+        self._pprint_bytes(
+            bytes(object), stream, indent + 10, allowance + 1, context, level + 1
+        )
+        write(")")
 
     _dispatch[bytearray.__repr__] = _pprint_bytearray
 
     def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level):
-        stream.write('mappingproxy(')
-        self._format(object.copy(), stream, indent + 13, allowance + 1,
-                     context, level)
-        stream.write(')')
+        stream.write("mappingproxy(")
+        self._format(object.copy(), stream, indent + 13, allowance + 1, context, level)
+        stream.write(")")
 
     _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy
 
-    def _pprint_simplenamespace(self, object, stream, indent, allowance, context, level):
+    def _pprint_simplenamespace(
+        self, object, stream, indent, allowance, context, level
+    ):
         if type(object) is _types.SimpleNamespace:
             # The SimpleNamespace repr is "namespace" instead of the class
             # name, so we do the same here. For subclasses; use the class name.
-            cls_name = 'namespace'
+            cls_name = "namespace"
         else:
             cls_name = object.__class__.__name__
         indent += len(cls_name) + 1
         items = object.__dict__.items()
-        stream.write(cls_name + '(')
+        stream.write(cls_name + "(")
         self._format_namespace_items(items, stream, indent, allowance, context, level)
-        stream.write(')')
+        stream.write(")")
 
     _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace
 
-    def _format_dict_items(self, items, stream, indent, allowance, context,
-                           level):
+    def _format_dict_items(self, items, stream, indent, allowance, context, level):
         write = stream.write
         indent += self._indent_per_level
-        delimnl = ',\n' + ' ' * indent
+        delimnl = ",\n" + " " * indent
         last_index = len(items) - 1
         for i, (key, ent) in enumerate(items):
             last = i == last_index
             rep = self._repr(key, context, level)
             write(rep)
-            write(': ')
-            self._format(ent, stream, indent + len(rep) + 2,
-                         allowance if last else 1,
-                         context, level)
+            write(": ")
+            self._format(
+                ent,
+                stream,
+                indent + len(rep) + 2,
+                allowance if last else 1,
+                context,
+                level,
+            )
             if not last:
                 write(delimnl)
 
     def _format_namespace_items(self, items, stream, indent, allowance, context, level):
         write = stream.write
-        delimnl = ',\n' + ' ' * indent
+        delimnl = ",\n" + " " * indent
         last_index = len(items) - 1
         for i, (key, ent) in enumerate(items):
             last = i == last_index
             write(key)
-            write('=')
+            write("=")
             if id(ent) in context:
                 # Special-case representation of recursion to match standard
                 # recursive dataclass repr.
                 write("...")
             else:
-                self._format(ent, stream, indent + len(key) + 1,
-                             allowance if last else 1,
-                             context, level)
+                self._format(
+                    ent,
+                    stream,
+                    indent + len(key) + 1,
+                    allowance if last else 1,
+                    context,
+                    level,
+                )
             if not last:
                 write(delimnl)
 
@@ -349,9 +388,9 @@ class PrettyPrinter:
         write = stream.write
         indent += self._indent_per_level
         if self._indent_per_level > 1:
-            write((self._indent_per_level - 1) * ' ')
-        delimnl = ',\n' + ' ' * indent
-        delim = ''
+            write((self._indent_per_level - 1) * " ")
+        delimnl = ",\n" + " " * indent
+        delim = ""
         width = max_width = self._width - indent + 1
         it = iter(items)
         try:
@@ -377,18 +416,17 @@ class PrettyPrinter:
                 if width >= w:
                     width -= w
                     write(delim)
-                    delim = ', '
+                    delim = ", "
                     write(rep)
                     continue
             write(delim)
             delim = delimnl
-            self._format(ent, stream, indent,
-                         allowance if last else 1,
-                         context, level)
+            self._format(ent, stream, indent, allowance if last else 1, context, level)
 
     def _repr(self, object, context, level):
-        repr, readable, recursive = self.format(object, context.copy(),
-                                                self._depth, level)
+        repr, readable, recursive = self.format(
+            object, context.copy(), self._depth, level
+        )
         if not readable:
             self._readable = False
         if recursive:
@@ -411,7 +449,7 @@ class PrettyPrinter:
         indent += len(cls.__name__) + 1
         stream.write(f"{cls.__name__}({rdf},\n{' ' * indent}")
         self._pprint_dict(object, stream, indent, allowance + 1, context, level)
-        stream.write(')')
+        stream.write(")")
 
     _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict
 
@@ -420,14 +458,14 @@ class PrettyPrinter:
             stream.write(repr(object))
             return
         cls = object.__class__
-        stream.write(cls.__name__ + '({')
+        stream.write(cls.__name__ + "({")
         if self._indent_per_level > 1:
-            stream.write((self._indent_per_level - 1) * ' ')
+            stream.write((self._indent_per_level - 1) * " ")
         items = object.most_common()
-        self._format_dict_items(items, stream,
-                                indent + len(cls.__name__) + 1, allowance + 2,
-                                context, level)
-        stream.write('})')
+        self._format_dict_items(
+            items, stream, indent + len(cls.__name__) + 1, allowance + 2, context, level
+        )
+        stream.write("})")
 
     _dispatch[_collections.Counter.__repr__] = _pprint_counter
 
@@ -436,15 +474,15 @@ class PrettyPrinter:
             stream.write(repr(object))
             return
         cls = object.__class__
-        stream.write(cls.__name__ + '(')
+        stream.write(cls.__name__ + "(")
         indent += len(cls.__name__) + 1
         for i, m in enumerate(object.maps):
             if i == len(object.maps) - 1:
                 self._format(m, stream, indent, allowance + 1, context, level)
-                stream.write(')')
+                stream.write(")")
             else:
                 self._format(m, stream, indent, 1, context, level)
-                stream.write(',\n' + ' ' * indent)
+                stream.write(",\n" + " " * indent)
 
     _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map
 
@@ -453,16 +491,14 @@ class PrettyPrinter:
             stream.write(repr(object))
             return
         cls = object.__class__
-        stream.write(cls.__name__ + '(')
+        stream.write(cls.__name__ + "(")
         indent += len(cls.__name__) + 1
-        stream.write('[')
+        stream.write("[")
         if object.maxlen is None:
-            self._format_items(object, stream, indent, allowance + 2,
-                               context, level)
-            stream.write('])')
+            self._format_items(object, stream, indent, allowance + 2, context, level)
+            stream.write("])")
         else:
-            self._format_items(object, stream, indent, 2,
-                               context, level)
+            self._format_items(object, stream, indent, 2, context, level)
             rml = self._repr(object.maxlen, context, level)
             stream.write(f"],\n{' ' * indent}maxlen={rml})")
 
@@ -516,10 +552,8 @@ class PrettyPrinter:
             else:
                 items = object.items()
             for k, v in items:
-                krepr, kreadable, krecur = self.format(
-                    k, context, maxlevels, level)
-                vrepr, vreadable, vrecur = self.format(
-                    v, context, maxlevels, level)
+                krepr, kreadable, krecur = self.format(k, context, maxlevels, level)
+                vrepr, vreadable, vrecur = self.format(v, context, maxlevels, level)
                 append(f"{krepr}: {vrepr}")
                 readable = readable and kreadable and vreadable
                 if krecur or vrecur:
@@ -527,8 +561,9 @@ class PrettyPrinter:
             del context[objid]
             return "{%s}" % ", ".join(components), readable, recursive
 
-        if (issubclass(typ, list) and r is list.__repr__) or \
-           (issubclass(typ, tuple) and r is tuple.__repr__):
+        if (issubclass(typ, list) and r is list.__repr__) or (
+            issubclass(typ, tuple) and r is tuple.__repr__
+        ):
             if issubclass(typ, list):
                 if not object:
                     return "[]", True, False
@@ -551,8 +586,7 @@ class PrettyPrinter:
             append = components.append
             level += 1
             for o in object:
-                orepr, oreadable, orecur = self.format(
-                    o, context, maxlevels, level)
+                orepr, oreadable, orecur = self.format(o, context, maxlevels, level)
                 append(orepr)
                 if not oreadable:
                     readable = False
@@ -562,21 +596,21 @@ class PrettyPrinter:
             return format % ", ".join(components), readable, recursive
 
         rep = repr(object)
-        return rep, (rep and not rep.startswith('<')), False
+        return rep, (rep and not rep.startswith("<")), False
+
+
+_builtin_scalars = frozenset({str, bytes, bytearray, float, complex, bool, type(None)})
 
-_builtin_scalars = frozenset({str, bytes, bytearray, float, complex,
-                              bool, type(None)})
 
 def _recursion(object):
-    return ("<Recursion on %s with id=%s>"
-            % (type(object).__name__, id(object)))
+    return f"<Recursion on {type(object).__name__} with id={id(object)}>"
 
 
 def _wrap_bytes_repr(object, width, allowance):
-    current = b''
+    current = b""
     last = len(object) // 4 * 4
     for i in range(0, len(object), 4):
-        part = object[i: i+4]
+        part = object[i : i + 4]
         candidate = current + part
         if i == last:
             width -= allowance

From 295312000338670c0d83dfb75ce16a34be851eb2 Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Fri, 17 Nov 2023 19:20:55 +0000
Subject: [PATCH 5/7] Fix typing information for the pprint module

There is more type information that could be added. We can add those
later to make it easier, this is jsut the minimum to allow linting to
pass
---
 src/_pytest/_io/pprint.py   | 19 ++++++++++++-------
 src/_pytest/_io/saferepr.py | 10 ++++------
 2 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
index 3bb4a2c7d..04f7edbbe 100644
--- a/src/_pytest/_io/pprint.py
+++ b/src/_pytest/_io/pprint.py
@@ -2,9 +2,6 @@
 # (https://github.com/python/cpython/) at commit
 # c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12).
 #
-# flake8: noqa
-# type: ignore
-#
 #
 #  Original Author:      Fred L. Drake, Jr.
 #                        fdrake@acm.org
@@ -21,6 +18,11 @@ import re
 import sys as _sys
 import types as _types
 from io import StringIO as _StringIO
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import IO
+from typing import List
 
 
 class _safe_key:
@@ -49,7 +51,7 @@ class _safe_key:
 
 
 def _safe_tuple(t):
-    "Helper function for comparing 2-tuples"
+    """Helper function for comparing 2-tuples"""
     return _safe_key(t[0]), _safe_key(t[1])
 
 
@@ -107,7 +109,7 @@ class PrettyPrinter:
         self._sort_dicts = sort_dicts
         self._underscore_numbers = underscore_numbers
 
-    def pformat(self, object):
+    def pformat(self, object: Any) -> str:
         sio = _StringIO()
         self._format(object, sio, 0, 0, {}, 0)
         return sio.getvalue()
@@ -157,7 +159,10 @@ class PrettyPrinter:
         self._format_namespace_items(items, stream, indent, allowance, context, level)
         stream.write(")")
 
-    _dispatch = {}
+    _dispatch: Dict[
+        Callable[..., str],
+        Callable[["PrettyPrinter", Any, IO[str], int, int, Dict[int, int], int], str],
+    ] = {}
 
     def _pprint_dict(self, object, stream, indent, allowance, context, level):
         write = stream.write
@@ -544,7 +549,7 @@ class PrettyPrinter:
             context[objid] = 1
             readable = True
             recursive = False
-            components = []
+            components: List[str] = []
             append = components.append
             level += 1
             if self._sort_dicts:
diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py
index 50d6a303a..ba8ea4302 100644
--- a/src/_pytest/_io/saferepr.py
+++ b/src/_pytest/_io/saferepr.py
@@ -5,7 +5,7 @@ from typing import Dict
 from typing import IO
 from typing import Optional
 
-from .pprint import PrettyPrinter  # type: ignore
+from .pprint import PrettyPrinter
 
 
 def _try_repr_or_str(obj: object) -> str:
@@ -148,13 +148,11 @@ class AlwaysDispatchingPrettyPrinter(PrettyPrinter):
         context: Dict[int, Any],
         level: int,
     ) -> None:
-        # Type ignored because _dispatch is private.
-        p = self._dispatch.get(type(object).__repr__, None)  # type: ignore[attr-defined]
+        p = self._dispatch.get(type(object).__repr__, None)
 
         objid = id(object)
         if objid in context or p is None:
-            # Type ignored because _format is private.
-            super()._format(  # type: ignore[misc]
+            super()._format(
                 object,
                 stream,
                 indent,
@@ -177,6 +175,6 @@ def _pformat_dispatch(
     *,
     compact: bool = False,
 ) -> str:
-    return AlwaysDispatchingPrettyPrinter(  # type: ignore
+    return AlwaysDispatchingPrettyPrinter(
         indent=indent, width=width, depth=depth, compact=compact
     ).pformat(object)

From 19934b2b0cc83eef0830b9e2adb6c5301a7201fc Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Sat, 18 Nov 2023 09:07:00 +0000
Subject: [PATCH 6/7] Merge the AlwaysDispathPrettyPrinter into the now
 vendored PrettyPrinter

We don't need to keep the separation anymore, and this will make it
easier to extend
---
 src/_pytest/_io/pprint.py     | 47 ++++++++++++++++-----------------
 src/_pytest/_io/saferepr.py   | 49 -----------------------------------
 src/_pytest/assertion/util.py |  7 ++---
 testing/io/test_pprint.py     |  8 ++++++
 testing/io/test_saferepr.py   |  7 -----
 5 files changed, 34 insertions(+), 84 deletions(-)
 create mode 100644 testing/io/test_pprint.py

diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
index 04f7edbbe..9923d0a62 100644
--- a/src/_pytest/_io/pprint.py
+++ b/src/_pytest/_io/pprint.py
@@ -121,31 +121,28 @@ class PrettyPrinter:
             self._recursive = True
             self._readable = False
             return
-        rep = self._repr(object, context, level)
-        max_width = self._width - indent - allowance
-        if len(rep) > max_width:
-            p = self._dispatch.get(type(object).__repr__, None)
-            if p is not None:
-                context[objid] = 1
-                p(self, object, stream, indent, allowance, context, level + 1)
-                del context[objid]
-                return
-            elif (
-                _dataclasses.is_dataclass(object)
-                and not isinstance(object, type)
-                and object.__dataclass_params__.repr
-                and
-                # Check dataclass has generated repr method.
-                hasattr(object.__repr__, "__wrapped__")
-                and "__create_fn__" in object.__repr__.__wrapped__.__qualname__
-            ):
-                context[objid] = 1
-                self._pprint_dataclass(
-                    object, stream, indent, allowance, context, level + 1
-                )
-                del context[objid]
-                return
-        stream.write(rep)
+
+        p = self._dispatch.get(type(object).__repr__, None)
+        if p is not None:
+            context[objid] = 1
+            p(self, object, stream, indent, allowance, context, level + 1)
+            del context[objid]
+        elif (
+            _dataclasses.is_dataclass(object)
+            and not isinstance(object, type)
+            and object.__dataclass_params__.repr
+            and
+            # Check dataclass has generated repr method.
+            hasattr(object.__repr__, "__wrapped__")
+            and "__create_fn__" in object.__repr__.__wrapped__.__qualname__
+        ):
+            context[objid] = 1
+            self._pprint_dataclass(
+                object, stream, indent, allowance, context, level + 1
+            )
+            del context[objid]
+        else:
+            stream.write(self._repr(object, context, level))
 
     def _pprint_dataclass(self, object, stream, indent, allowance, context, level):
         cls_name = object.__class__.__name__
diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py
index ba8ea4302..c51578ed4 100644
--- a/src/_pytest/_io/saferepr.py
+++ b/src/_pytest/_io/saferepr.py
@@ -1,12 +1,7 @@
 import pprint
 import reprlib
-from typing import Any
-from typing import Dict
-from typing import IO
 from typing import Optional
 
-from .pprint import PrettyPrinter
-
 
 def _try_repr_or_str(obj: object) -> str:
     try:
@@ -134,47 +129,3 @@ def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:
         return repr(obj)
     except Exception as exc:
         return _format_repr_exception(exc, obj)
-
-
-class AlwaysDispatchingPrettyPrinter(PrettyPrinter):
-    """PrettyPrinter that always dispatches (regardless of width)."""
-
-    def _format(
-        self,
-        object: object,
-        stream: IO[str],
-        indent: int,
-        allowance: int,
-        context: Dict[int, Any],
-        level: int,
-    ) -> None:
-        p = self._dispatch.get(type(object).__repr__, None)
-
-        objid = id(object)
-        if objid in context or p is None:
-            super()._format(
-                object,
-                stream,
-                indent,
-                allowance,
-                context,
-                level,
-            )
-            return
-
-        context[objid] = 1
-        p(self, object, stream, indent, allowance, context, level + 1)
-        del context[objid]
-
-
-def _pformat_dispatch(
-    object: object,
-    indent: int = 1,
-    width: int = 80,
-    depth: Optional[int] = None,
-    *,
-    compact: bool = False,
-) -> str:
-    return AlwaysDispatchingPrettyPrinter(
-        indent=indent, width=width, depth=depth, compact=compact
-    ).pformat(object)
diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py
index 65abe8d23..4d9fd114b 100644
--- a/src/_pytest/assertion/util.py
+++ b/src/_pytest/assertion/util.py
@@ -16,7 +16,7 @@ from unicodedata import normalize
 
 import _pytest._code
 from _pytest import outcomes
-from _pytest._io.saferepr import _pformat_dispatch
+from _pytest._io.pprint import PrettyPrinter
 from _pytest._io.saferepr import saferepr
 from _pytest._io.saferepr import saferepr_unlimited
 from _pytest.config import Config
@@ -348,8 +348,9 @@ def _compare_eq_iterable(
     lines_left = len(left_formatting)
     lines_right = len(right_formatting)
     if lines_left != lines_right:
-        left_formatting = _pformat_dispatch(left).splitlines()
-        right_formatting = _pformat_dispatch(right).splitlines()
+        printer = PrettyPrinter()
+        left_formatting = printer.pformat(left).splitlines()
+        right_formatting = printer.pformat(right).splitlines()
 
     if lines_left > 1 or lines_right > 1:
         _surrounding_parens_on_own_lines(left_formatting)
diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py
new file mode 100644
index 000000000..900ccbbfa
--- /dev/null
+++ b/testing/io/test_pprint.py
@@ -0,0 +1,8 @@
+from _pytest._io.pprint import PrettyPrinter
+
+
+def test_pformat_dispatch():
+    printer = PrettyPrinter(width=5)
+    assert printer.pformat("a") == "'a'"
+    assert printer.pformat("a" * 10) == "'aaaaaaaaaa'"
+    assert printer.pformat("foo bar") == "('foo '\n 'bar')"
diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py
index 24746bc22..d94faa4f1 100644
--- a/testing/io/test_saferepr.py
+++ b/testing/io/test_saferepr.py
@@ -1,5 +1,4 @@
 import pytest
-from _pytest._io.saferepr import _pformat_dispatch
 from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
 from _pytest._io.saferepr import saferepr
 from _pytest._io.saferepr import saferepr_unlimited
@@ -159,12 +158,6 @@ def test_unicode():
     assert saferepr(val) == reprval
 
 
-def test_pformat_dispatch():
-    assert _pformat_dispatch("a") == "'a'"
-    assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
-    assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')"
-
-
 def test_broken_getattribute():
     """saferepr() can create proper representations of classes with
     broken __getattribute__ (#7145)

From 53d7b5ed3ef93780b866a9fea3d8afe48f36d319 Mon Sep 17 00:00:00 2001
From: Benjamin Schubert <bschubert15@bloomberg.net>
Date: Sat, 18 Nov 2023 09:21:44 +0000
Subject: [PATCH 7/7] Add some tests for the pprint module

---
 testing/io/test_pprint.py | 332 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 327 insertions(+), 5 deletions(-)

diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py
index 900ccbbfa..8c15740bd 100644
--- a/testing/io/test_pprint.py
+++ b/testing/io/test_pprint.py
@@ -1,8 +1,330 @@
+import textwrap
+from collections import ChainMap
+from collections import Counter
+from collections import defaultdict
+from collections import deque
+from collections import OrderedDict
+from dataclasses import dataclass
+from types import MappingProxyType
+from types import SimpleNamespace
+from typing import Any
+
+import pytest
 from _pytest._io.pprint import PrettyPrinter
 
 
-def test_pformat_dispatch():
-    printer = PrettyPrinter(width=5)
-    assert printer.pformat("a") == "'a'"
-    assert printer.pformat("a" * 10) == "'aaaaaaaaaa'"
-    assert printer.pformat("foo bar") == "('foo '\n 'bar')"
+@dataclass
+class EmptyDataclass:
+    pass
+
+
+@dataclass
+class DataclassWithOneItem:
+    foo: str
+
+
+@dataclass
+class DataclassWithTwoItems:
+    foo: str
+    bar: str
+
+
+@pytest.mark.parametrize(
+    ("data", "expected"),
+    (
+        pytest.param(
+            EmptyDataclass(),
+            "EmptyDataclass()",
+            id="dataclass-empty",
+        ),
+        pytest.param(
+            DataclassWithOneItem(foo="bar"),
+            """
+            DataclassWithOneItem(foo='bar')
+            """,
+            id="dataclass-one-item",
+        ),
+        pytest.param(
+            DataclassWithTwoItems(foo="foo", bar="bar"),
+            """
+            DataclassWithTwoItems(foo='foo',
+                                  bar='bar')
+            """,
+            id="dataclass-two-items",
+        ),
+        pytest.param(
+            {},
+            "{}",
+            id="dict-empty",
+        ),
+        pytest.param(
+            {"one": 1},
+            """
+            {'one': 1}
+            """,
+            id="dict-one-item",
+        ),
+        pytest.param(
+            {"one": 1, "two": 2},
+            """
+            {'one': 1,
+             'two': 2}
+            """,
+            id="dict-two-items",
+        ),
+        pytest.param(OrderedDict(), "OrderedDict()", id="ordereddict-empty"),
+        pytest.param(
+            OrderedDict({"one": 1}),
+            """
+            OrderedDict([('one',
+                          1)])
+            """,
+            id="ordereddict-one-item",
+        ),
+        pytest.param(
+            OrderedDict({"one": 1, "two": 2}),
+            """
+            OrderedDict([('one',
+                          1),
+                         ('two',
+                          2)])
+            """,
+            id="ordereddict-two-items",
+        ),
+        pytest.param(
+            [],
+            "[]",
+            id="list-empty",
+        ),
+        pytest.param(
+            [1],
+            """
+            [1]
+            """,
+            id="list-one-item",
+        ),
+        pytest.param(
+            [1, 2],
+            """
+            [1,
+             2]
+            """,
+            id="list-two-items",
+        ),
+        pytest.param(
+            tuple(),
+            "()",
+            id="tuple-empty",
+        ),
+        pytest.param(
+            (1,),
+            """
+            (1,)
+            """,
+            id="tuple-one-item",
+        ),
+        pytest.param(
+            (1, 2),
+            """
+            (1,
+             2)
+            """,
+            id="tuple-two-items",
+        ),
+        pytest.param(
+            set(),
+            "set()",
+            id="set-empty",
+        ),
+        pytest.param(
+            {1},
+            """
+            {1}
+            """,
+            id="set-one-item",
+        ),
+        pytest.param(
+            {1, 2},
+            """
+            {1,
+             2}
+            """,
+            id="set-two-items",
+        ),
+        pytest.param(
+            MappingProxyType({}),
+            "mappingproxy({})",
+            id="mappingproxy-empty",
+        ),
+        pytest.param(
+            MappingProxyType({"one": 1}),
+            """
+            mappingproxy({'one': 1})
+            """,
+            id="mappingproxy-one-item",
+        ),
+        pytest.param(
+            MappingProxyType({"one": 1, "two": 2}),
+            """
+            mappingproxy({'one': 1,
+                          'two': 2})
+            """,
+            id="mappingproxy-two-items",
+        ),
+        pytest.param(
+            SimpleNamespace(),
+            "namespace()",
+            id="simplenamespace-empty",
+        ),
+        pytest.param(
+            SimpleNamespace(one=1),
+            """
+            namespace(one=1)
+            """,
+            id="simplenamespace-one-item",
+        ),
+        pytest.param(
+            SimpleNamespace(one=1, two=2),
+            """
+            namespace(one=1,
+                      two=2)
+            """,
+            id="simplenamespace-two-items",
+        ),
+        pytest.param(
+            defaultdict(str), "defaultdict(<class 'str'>, {})", id="defaultdict-empty"
+        ),
+        pytest.param(
+            defaultdict(str, {"one": "1"}),
+            """
+            defaultdict(<class 'str'>,
+                        {'one': '1'})
+            """,
+            id="defaultdict-one-item",
+        ),
+        pytest.param(
+            defaultdict(str, {"one": "1", "two": "2"}),
+            """
+            defaultdict(<class 'str'>,
+                        {'one': '1',
+                         'two': '2'})
+            """,
+            id="defaultdict-two-items",
+        ),
+        pytest.param(
+            Counter(),
+            "Counter()",
+            id="counter-empty",
+        ),
+        pytest.param(
+            Counter("1"),
+            """
+            Counter({'1': 1})
+            """,
+            id="counter-one-item",
+        ),
+        pytest.param(
+            Counter("121"),
+            """
+            Counter({'1': 2,
+                     '2': 1})
+            """,
+            id="counter-two-items",
+        ),
+        pytest.param(ChainMap(), "ChainMap({})", id="chainmap-empty"),
+        pytest.param(
+            ChainMap({"one": 1, "two": 2}),
+            """
+            ChainMap({'one': 1,
+                      'two': 2})
+            """,
+            id="chainmap-one-item",
+        ),
+        pytest.param(
+            ChainMap({"one": 1}, {"two": 2}),
+            """
+            ChainMap({'one': 1},
+                     {'two': 2})
+            """,
+            id="chainmap-two-items",
+        ),
+        pytest.param(
+            deque(),
+            "deque([])",
+            id="deque-empty",
+        ),
+        pytest.param(
+            deque([1]),
+            """
+            deque([1])
+            """,
+            id="deque-one-item",
+        ),
+        pytest.param(
+            deque([1, 2]),
+            """
+            deque([1,
+                   2])
+            """,
+            id="deque-two-items",
+        ),
+        pytest.param(
+            deque([1, 2], maxlen=3),
+            """
+            deque([1,
+                   2],
+                  maxlen=3)
+            """,
+            id="deque-maxlen",
+        ),
+        pytest.param(
+            {
+                "chainmap": ChainMap({"one": 1}, {"two": 2}),
+                "counter": Counter("122"),
+                "dataclass": DataclassWithTwoItems(foo="foo", bar="bar"),
+                "defaultdict": defaultdict(str, {"one": "1", "two": "2"}),
+                "deque": deque([1, 2], maxlen=3),
+                "dict": {"one": 1, "two": 2},
+                "list": [1, 2],
+                "mappingproxy": MappingProxyType({"one": 1, "two": 2}),
+                "ordereddict": OrderedDict({"one": 1, "two": 2}),
+                "set": {1, 2},
+                "simplenamespace": SimpleNamespace(one=1, two=2),
+                "tuple": (1, 2),
+            },
+            """
+            {'chainmap': ChainMap({'one': 1},
+                                  {'two': 2}),
+             'counter': Counter({'2': 2,
+                                 '1': 1}),
+             'dataclass': DataclassWithTwoItems(foo='foo',
+                                                bar='bar'),
+             'defaultdict': defaultdict(<class 'str'>,
+                                        {'one': '1',
+                                         'two': '2'}),
+             'deque': deque([1,
+                             2],
+                            maxlen=3),
+             'dict': {'one': 1,
+                      'two': 2},
+             'list': [1,
+                      2],
+             'mappingproxy': mappingproxy({'one': 1,
+                                           'two': 2}),
+             'ordereddict': OrderedDict([('one',
+                                          1),
+                                         ('two',
+                                          2)]),
+             'set': {1,
+                     2},
+             'simplenamespace': namespace(one=1,
+                                          two=2),
+             'tuple': (1,
+                       2)}
+            """,
+            id="deep-example",
+        ),
+    ),
+)
+def test_consistent_pretty_printer(data: Any, expected: str) -> None:
+    assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip()