Piping JSON to a pseudo-terminal in Python for guaranteed color highlighting
I usually print my Python data structures encoded as JSON to make them more readable:
1
2
3
4
import json
def print_data(value):
print(json.dumps(value, indent=2, default=repr))
This is great, but sometimes I miss colors that one would normally have in shell scripts or Makefile targets piping output to jq
or yq
. There is actually a neat and easy way to get the colors in Python scripts too:
1
2
3
4
import os
os.system('echo true | yq')
os.system('echo null | jq')
that one could use in a helper function:
1
2
3
4
import subprocess
def color_print(value):
subprocess.run(['yq', '-P'], input=json.dumps(value).encode())
The colors are visible almost everywhere except when executing scripts directly in PyCharm. Even then one could change the default Run/Debug configuration template to use terminal emulation when running Python scripts.
However, this is not known and used by everyone, so to ensure that the script output is always colorized, one could use pseudo-terminal:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import select
UTF = 'utf-8', 'surrogatepass'
def read(fd, process):
result = bytearray()
while True:
# Wait until file descriptor is ready for I/O
r, _, _ = select.select([fd], [], [], 0.1)
if r:
chunk = os.read(fd, 1024)
if not chunk: # EOF
break
result += chunk
elif process.poll() is not None: # Process ended
break
return result.decode(*UTF)
def pty_pipe(string, to):
alpha, bravo = os.openpty()
proc = subprocess.Popen(
to, stdin=subprocess.PIPE, stdout=bravo, stderr=subprocess.PIPE, bufsize=0, text=False,
)
proc.stdin.write(string.encode(*UTF))
proc.stdin.close()
if proc.returncode:
raise Exception(proc.stderr.read().decode(*UTF).strip())
result = read(alpha, proc).strip()
proc.wait()
os.close(alpha)
os.close(bravo)
return result
and our color print function becomes:
1
2
3
def color_print(value):
text = json.dumps(value, ensure_ascii=False)
print(pty_pipe(text, to=['yq', '-P']))
Enjoy!
This post is licensed under CC BY 4.0 by the author.