Skip to content

Commit 0906a8d

Browse files
committed
Add colour to pprint output
1 parent f8ce51a commit 0906a8d

File tree

5 files changed

+136
-12
lines changed

5 files changed

+136
-12
lines changed

Doc/library/pprint.rst

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ Functions
3131
---------
3232

3333
.. function:: pp(object, stream=None, indent=1, width=80, depth=None, *, \
34-
compact=False, sort_dicts=False, underscore_numbers=False)
34+
color=True, compact=False, sort_dicts=False, \
35+
underscore_numbers=False)
3536

3637
Prints the formatted representation of *object*, followed by a newline.
3738
This function may be used in the interactive interpreter
@@ -63,6 +64,12 @@ Functions
6364
on the depth of the objects being formatted.
6465
:type depth: int | None
6566

67+
:param bool color:
68+
If ``True`` (the default), output will be syntax highlighted using ANSI
69+
escape sequences, if the *stream* and :ref:`environment variables
70+
<using-on-controlling-color>` permit.
71+
If ``False``, colored output is always disabled.
72+
6673
:param bool compact:
6774
Control the way long :term:`sequences <sequence>` are formatted.
6875
If ``False`` (the default),
@@ -93,14 +100,21 @@ Functions
93100

94101
.. versionadded:: 3.8
95102

103+
.. versionchanged:: next
104+
Added the *color* parameter.
105+
96106

97107
.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \
98-
compact=False, sort_dicts=True, underscore_numbers=False)
108+
color=True, compact=False, sort_dicts=True, \
109+
underscore_numbers=False)
99110

100111
Alias for :func:`~pprint.pp` with *sort_dicts* set to ``True`` by default,
101112
which would automatically sort the dictionaries' keys,
102113
you might want to use :func:`~pprint.pp` instead where it is ``False`` by default.
103114

115+
.. versionchanged:: next
116+
Added the *color* parameter.
117+
104118

105119
.. function:: pformat(object, indent=1, width=80, depth=None, *, \
106120
compact=False, sort_dicts=True, underscore_numbers=False)
@@ -144,13 +158,14 @@ Functions
144158

145159
.. _prettyprinter-objects:
146160

147-
PrettyPrinter Objects
161+
PrettyPrinter objects
148162
---------------------
149163

150164
.. index:: single: ...; placeholder
151165

152166
.. class:: PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, \
153-
compact=False, sort_dicts=True, underscore_numbers=False)
167+
color=True, compact=False, sort_dicts=True, \
168+
underscore_numbers=False)
154169

155170
Construct a :class:`PrettyPrinter` instance.
156171

@@ -193,6 +208,9 @@ PrettyPrinter Objects
193208
.. versionchanged:: 3.11
194209
No longer attempts to write to :data:`!sys.stdout` if it is ``None``.
195210

211+
.. versionchanged:: next
212+
Added the *color* parameter.
213+
196214

197215
:class:`PrettyPrinter` instances have the following methods:
198216

Doc/whatsnew/3.15.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,16 @@ pickle
864864
(Contributed by Zackery Spytz and Serhiy Storchaka in :gh:`77188`.)
865865

866866

867+
pprint
868+
------
869+
870+
* Add *color* parameter to :func:`~pprint.pp` and :func:`~pprint.pprint`.
871+
If ``True`` (the default), output is highlighted in color, when the stream
872+
and :ref:`environment variables <using-on-controlling-color>` permit.
873+
If ``False``, colored output is always disabled.
874+
(Contributed by Hugo van Kemenade in :gh:`123456`.)
875+
876+
867877
re
868878
--
869879

Lib/pprint.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,36 @@
3939
import types as _types
4040
from io import StringIO as _StringIO
4141

42+
lazy import _colorize
43+
lazy from _pyrepl.utils import disp_str, gen_colors
44+
4245
__all__ = ["pprint","pformat","isreadable","isrecursive","saferepr",
4346
"PrettyPrinter", "pp"]
4447

4548

46-
def pprint(object, stream=None, indent=1, width=80, depth=None, *,
47-
compact=False, sort_dicts=True, underscore_numbers=False):
49+
def pprint(
50+
object,
51+
stream=None,
52+
indent=1,
53+
width=80,
54+
depth=None,
55+
*,
56+
color=True,
57+
compact=False,
58+
sort_dicts=True,
59+
underscore_numbers=False,
60+
):
4861
"""Pretty-print a Python object to a stream [default is sys.stdout]."""
4962
printer = PrettyPrinter(
50-
stream=stream, indent=indent, width=width, depth=depth,
51-
compact=compact, sort_dicts=sort_dicts,
52-
underscore_numbers=underscore_numbers)
63+
stream=stream,
64+
indent=indent,
65+
width=width,
66+
depth=depth,
67+
color=color,
68+
compact=compact,
69+
sort_dicts=sort_dicts,
70+
underscore_numbers=underscore_numbers,
71+
)
5372
printer.pprint(object)
5473

5574

@@ -109,9 +128,26 @@ def _safe_tuple(t):
109128
return _safe_key(t[0]), _safe_key(t[1])
110129

111130

131+
def _colorize_output(text):
132+
"""Apply syntax highlighting."""
133+
colors = list(gen_colors(text))
134+
chars, _ = disp_str(text, colors=colors, force_color=True)
135+
return "".join(chars)
136+
137+
112138
class PrettyPrinter:
113-
def __init__(self, indent=1, width=80, depth=None, stream=None, *,
114-
compact=False, sort_dicts=True, underscore_numbers=False):
139+
def __init__(
140+
self,
141+
indent=1,
142+
width=80,
143+
depth=None,
144+
stream=None,
145+
*,
146+
color=True,
147+
compact=False,
148+
sort_dicts=True,
149+
underscore_numbers=False,
150+
):
115151
"""Handle pretty printing operations onto a stream using a set of
116152
configured parameters.
117153
@@ -128,6 +164,11 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
128164
The desired output stream. If omitted (or false), the standard
129165
output stream available at construction will be used.
130166
167+
color
168+
If true (the default), syntax highlighting is enabled for pprint
169+
when the stream and environment variables permit.
170+
If false, colored output is always disabled.
171+
131172
compact
132173
If true, several items will be combined in one line.
133174
@@ -156,10 +197,16 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *,
156197
self._compact = bool(compact)
157198
self._sort_dicts = sort_dicts
158199
self._underscore_numbers = underscore_numbers
200+
self._color = color
159201

160202
def pprint(self, object):
161203
if self._stream is not None:
162-
self._format(object, self._stream, 0, 0, {}, 0)
204+
if self._color and _colorize.can_colorize(file=self._stream):
205+
sio = _StringIO()
206+
self._format(object, sio, 0, 0, {}, 0)
207+
self._stream.write(_colorize_output(sio.getvalue()))
208+
else:
209+
self._format(object, self._stream, 0, 0, {}, 0)
163210
self._stream.write("\n")
164211

165212
def pformat(self, object):

Lib/test/test_pickle.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ def invoke_pickle(self, *flags):
779779
pickle._main(args=[*flags, self.filename])
780780
return self.text_normalize(output.getvalue())
781781

782+
@support.force_not_colorized
782783
def test_invocation(self):
783784
# test 'python -m pickle pickle_file'
784785
data = {

Lib/test/test_pprint.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
import types
1212
import unittest
13+
import unittest.mock
1314
from collections.abc import ItemsView, KeysView, Mapping, MappingView, ValuesView
1415

1516
from test.support import cpython_only
@@ -165,6 +166,53 @@ def test_init(self):
165166
self.assertRaises(ValueError, pprint.PrettyPrinter, depth=-1)
166167
self.assertRaises(ValueError, pprint.PrettyPrinter, width=0)
167168

169+
def test_color_pprint(self):
170+
"""Test pprint color parameter."""
171+
obj = {"key": "value"}
172+
stream = io.StringIO()
173+
174+
# color=False should produce no ANSI codes
175+
pprint.pprint(obj, stream=stream, color=False)
176+
result = stream.getvalue()
177+
self.assertNotIn("\x1b[", result)
178+
179+
# Explicit color=False should override FORCE_COLOR
180+
stream = io.StringIO()
181+
with unittest.mock.patch.dict(
182+
"os.environ", {"FORCE_COLOR": "1", "NO_COLOR": ""}
183+
):
184+
pprint.pprint(obj, stream=stream, color=False)
185+
result = stream.getvalue()
186+
self.assertNotIn("\x1b[", result)
187+
188+
def test_color_prettyprinter(self):
189+
"""Test PrettyPrinter color parameter."""
190+
obj = {"key": "value"}
191+
192+
# color=False should produce no ANSI codes in pprint
193+
stream = io.StringIO()
194+
pp = pprint.PrettyPrinter(stream=stream, color=False)
195+
pp.pprint(obj)
196+
self.assertNotIn("\x1b[", stream.getvalue())
197+
198+
# color=True with FORCE_COLOR should produce ANSI codes in pprint
199+
with unittest.mock.patch.dict(
200+
"os.environ", {"FORCE_COLOR": "1", "NO_COLOR": ""}
201+
):
202+
stream = io.StringIO()
203+
pp = pprint.PrettyPrinter(stream=stream, color=True)
204+
pp.pprint(obj)
205+
self.assertIn("\x1b[", stream.getvalue())
206+
207+
# Explicit color=False should override FORCE_COLOR
208+
with unittest.mock.patch.dict(
209+
"os.environ", {"FORCE_COLOR": "1", "NO_COLOR": ""}
210+
):
211+
stream = io.StringIO()
212+
pp = pprint.PrettyPrinter(stream=stream, color=False)
213+
pp.pprint(obj)
214+
self.assertNotIn("\x1b[", stream.getvalue())
215+
168216
def test_basic(self):
169217
# Verify .isrecursive() and .isreadable() w/o recursion
170218
pp = pprint.PrettyPrinter()

0 commit comments

Comments
 (0)