Skip to content

Commit 055eda7

Browse files
committed
Use a rich.pretty compatible pretty printer API
1 parent 28fa67d commit 055eda7

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

Lib/pprint.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,14 +609,50 @@ def _pprint_user_string(self, object, stream, indent, allowance, context, level)
609609

610610
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
611611

612+
def _format_pprint(self, object, method, context, maxlevels, level):
613+
"""Format an object using its __pprint__ method.
614+
615+
The __pprint__ method should be a generator yielding values:
616+
- yield value -> positional arg
617+
- yield (name, value) -> keyword arg, always shown
618+
- yield (name, value, default) -> keyword arg, shown if value != default
619+
"""
620+
cls_name = type(object).__name__
621+
parts = []
622+
readable = True
623+
624+
for item in method(object):
625+
if isinstance(item, tuple):
626+
if len(item) == 2:
627+
# (name, value) - always show
628+
name, value = item
629+
vrep, vreadable, _ = self.format(value, context, maxlevels, level + 1)
630+
parts.append(f"{name}={vrep}")
631+
readable = readable and vreadable
632+
elif len(item) == 3:
633+
# (name, value, default) - show only if value != default
634+
name, value, default = item
635+
if value != default:
636+
vrep, vreadable, _ = self.format(value, context, maxlevels, level + 1)
637+
parts.append(f"{name}={vrep}")
638+
readable = readable and vreadable
639+
else:
640+
# Positional argument
641+
vrep, vreadable, _ = self.format(item, context, maxlevels, level + 1)
642+
parts.append(vrep)
643+
readable = readable and vreadable
644+
645+
rep = f"{cls_name}({', '.join(parts)})"
646+
return rep, readable, False
647+
612648
def _safe_repr(self, object, context, maxlevels, level):
613649
# Return triple (repr_string, isreadable, isrecursive).
614650
typ = type(object)
615651
if typ in _builtin_scalars:
616652
return repr(object), True, False
617653

618654
if (p := getattr(typ, "__pprint__", None)):
619-
return p(object, context, maxlevels, level)
655+
return self._format_pprint(object, p, context, maxlevels, level)
620656

621657
r = getattr(typ, "__repr__", None)
622658

Lib/test/test_pprint.py

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,19 @@ def __hash__(self):
136136

137137

138138
class CustomPrintable:
139+
def __init__(self, name="my pprint", value=42):
140+
self.name = name
141+
self.value = value
142+
139143
def __str__(self):
140144
return "my str"
141145

142146
def __repr__(self):
143147
return "my str"
144148

145-
def __pprint__(self, context, maxlevels, level):
146-
# The custom pretty repr, not-readable bool, no recursion detected.
147-
return "my pprint", False, False
149+
def __pprint__(self):
150+
yield self.name
151+
yield ("value", self.value)
148152

149153

150154
class QueryTestCase(unittest.TestCase):
@@ -1490,7 +1494,72 @@ def test_custom_pprinter(self):
14901494
pp = pprint.PrettyPrinter(stream=stream)
14911495
custom_obj = CustomPrintable()
14921496
pp.pprint(custom_obj)
1493-
self.assertEqual(stream.getvalue(), "my pprint\n")
1497+
self.assertEqual(stream.getvalue(), "CustomPrintable('my pprint', value=42)\n")
1498+
1499+
def test_pprint_protocol_positional(self):
1500+
# Test __pprint__ with positional arguments only
1501+
class Point:
1502+
def __init__(self, x, y):
1503+
self.x = x
1504+
self.y = y
1505+
def __pprint__(self):
1506+
yield self.x
1507+
yield self.y
1508+
self.assertEqual(pprint.pformat(Point(1, 2)), "Point(1, 2)")
1509+
1510+
def test_pprint_protocol_keyword(self):
1511+
# Test __pprint__ with keyword arguments
1512+
class Config:
1513+
def __init__(self, host, port):
1514+
self.host = host
1515+
self.port = port
1516+
def __pprint__(self):
1517+
yield ("host", self.host)
1518+
yield ("port", self.port)
1519+
self.assertEqual(pprint.pformat(Config("localhost", 8080)),
1520+
"Config(host='localhost', port=8080)")
1521+
1522+
def test_pprint_protocol_default(self):
1523+
# Test __pprint__ with default values (3-tuple form)
1524+
class Bird:
1525+
def __init__(self, name, fly=True, extinct=False):
1526+
self.name = name
1527+
self.fly = fly
1528+
self.extinct = extinct
1529+
def __pprint__(self):
1530+
yield self.name
1531+
yield ("fly", self.fly, True) # hide if True
1532+
yield ("extinct", self.extinct, False) # hide if False
1533+
1534+
# Defaults should be hidden
1535+
self.assertEqual(pprint.pformat(Bird("sparrow")),
1536+
"Bird('sparrow')")
1537+
# Non-defaults should be shown
1538+
self.assertEqual(pprint.pformat(Bird("dodo", fly=False, extinct=True)),
1539+
"Bird('dodo', fly=False, extinct=True)")
1540+
1541+
def test_pprint_protocol_nested(self):
1542+
# Test __pprint__ with nested objects
1543+
class Container:
1544+
def __init__(self, items):
1545+
self.items = items
1546+
def __pprint__(self):
1547+
yield ("items", self.items)
1548+
c = Container([1, 2, 3])
1549+
self.assertEqual(pprint.pformat(c), "Container(items=[1, 2, 3])")
1550+
# Nested in a list
1551+
self.assertEqual(pprint.pformat([c]), "[Container(items=[1, 2, 3])]")
1552+
1553+
def test_pprint_protocol_isreadable(self):
1554+
# Test that isreadable works correctly with __pprint__
1555+
class Readable:
1556+
def __pprint__(self):
1557+
yield 42
1558+
class Unreadable:
1559+
def __pprint__(self):
1560+
yield open # built-in function, not readable
1561+
self.assertTrue(pprint.isreadable(Readable()))
1562+
self.assertFalse(pprint.isreadable(Unreadable()))
14941563

14951564

14961565
class DottedPrettyPrinter(pprint.PrettyPrinter):

Lib/test/test_print.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ def test_string_in_loop_on_same_line(self):
202202

203203

204204
class PPrintable:
205-
def __pprint__(self, context, maxlevels, level):
206-
return 'I feel pretty', False, False
205+
def __pprint__(self):
206+
yield 'I feel pretty'
207207

208208

209209
class PrettySmart(PrettyPrinter):
@@ -229,11 +229,11 @@ def test_default_pretty_printer(self):
229229

230230
def test_pprint_magic(self):
231231
print('one', PPrintable(), 2, file=self.file, pretty=True)
232-
self.assertEqual(self.file.getvalue(), "'one' I feel pretty 2\n")
232+
self.assertEqual(self.file.getvalue(), "'one' PPrintable('I feel pretty') 2\n")
233233

234234
def test_custom_pprinter(self):
235235
print('one', PPrintable(), 2, file=self.file, pretty=PrettySmart())
236-
self.assertEqual(self.file.getvalue(), "one I feel pretty 2\n")
236+
self.assertEqual(self.file.getvalue(), "one PPrintable('I feel pretty') 2\n")
237237

238238
def test_bad_pprinter(self):
239239
with self.assertRaises(AttributeError):

0 commit comments

Comments
 (0)