Capture printed diagnostics with redirect_stderr
Capture printed diagnostics with redirect_stderr
How do you capture printed diagnostics with redirect_stderr? Put the call that writes to sys.stderr inside with redirect_stderr(StringIO()), then read the buffer after the block. In this synthetic validator, the failing row returns False and prints exactly bad row 2 to stderr. Without that boundary, the diagnostic reaches the surrounding test runner's stderr stream.
The corrected branch redirects stderr to captured_stderr. A separate stdout buffer is only an observation point: it verifies the validator did not write normal output. The assertions check both channels precisely—captured_stderr.getvalue() is "bad row 2\n", and normal_stdout.getvalue() is empty. Printing the summary happens after the redirects have ended, so it is the script's deliberate observable output, not validator output.
redirect_stderr temporarily replaces the process-global sys.stderr; it was added in Python 3.5. This makes it useful for small command-line utilities and tightly scoped tests of code that is already hard-wired to print(..., file=sys.stderr). It is not a general isolation boundary for threaded or library code, and it cannot capture a child process's output. If the validator can be redesigned, returning structured diagnostics may be easier to compose than printing them. Python contextlib documentation
AI assistance disclosure: This article was drafted with AI assistance and checked against the cited documentation and a synthetic example.
import sys
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO
def validate(row):
if row != "ok":
print("bad row 2", file=sys.stderr)
return False
return True
captured_stderr = StringIO()
normal_stdout = StringIO()
with redirect_stdout(normal_stdout):
with redirect_stderr(captured_stderr):
accepted = validate("broken")
assert accepted is False
assert captured_stderr.getvalue() == "bad row 2\n"
assert normal_stdout.getvalue() == ""
print(f"captured_stderr={captured_stderr.getvalue()!r}")
print(f"normal_stdout={normal_stdout.getvalue()!r}")
captured_stderr='bad row 2\n'
normal_stdout=''