Skip to content

Commit 7f1ef61

Browse files
committed
bpo-47222: Allow pass-thru with stdout/stderr capture (GH-91378)
1 parent becf1f6 commit 7f1ef61

File tree

4 files changed

+45
-0
lines changed

4 files changed

+45
-0
lines changed

Doc/library/subprocess.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,13 @@ functions.
699699
The ``read_stdout_callback`` and ``read_stderr_callback`` parameters
700700
were added.
701701
702+
Add a tee'ing handler that may be accessed by calling
703+
``tee_pipe_to(handle)``. It takes the handle of the file to clone to
704+
as an argument, such as *sys.stdout* or *sys.stderr*.
705+
706+
.. versionadded: 3.11
707+
The ``tee_pipe_to()`` method was added.
708+
702709
Popen objects are supported as context managers via the :keyword:`with` statement:
703710
on exit, standard file descriptors are closed, and the process is waited for.
704711
::

Lib/subprocess.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,12 @@ def _close_pipe_fds(self,
13251325
# Prevent a double close of these handles/fds from __init__ on error.
13261326
self._closed_child_pipe_fds = True
13271327

1328+
def tee_pipe_to(output_fh):
1329+
def _tee_handler(self, buffer, data):
1330+
buffer.append(data)
1331+
output_fh.write(data)
1332+
return _tee_handler
1333+
13281334
def _read_common_handler(self, buffer, data):
13291335
"""Default handler for read_stdout_callback and read_stderr_callback."""
13301336
buffer.append(data)

Lib/test/test_subprocess.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,36 @@ def test_stdout_none(self):
340340
self.assertEqual(out, b'test_stdout_none' + NEWLINE)
341341
self.assertEqual(err, b'')
342342

343+
def test_stdout_tee(self):
344+
# .stdout is PIPE when not redirected, and the child's stdout will
345+
# be inherited from the parent. In order to test this we run a
346+
# subprocess in a subprocess:
347+
# this_test
348+
# \-- subprocess created by this test (parent)
349+
# \-- subprocess created by the parent subprocess (child)
350+
# The parent doesn't specify stdout, so the child will use the
351+
# parent's stdout. This test checks that the message printed by the
352+
# child goes to the parent stdout. The parent also checks that the
353+
# child's stdout is cloned. See #47222.
354+
code = ('import sys\n'
355+
'from subprocess import Popen, PIPE\n'
356+
'with Popen([sys.executable, "-c", "print(\'test_stdout_teed\')"],\n'
357+
' stdout=PIPE, stderr=PIPE, read_stdout_callback=Popen.tee_pipe_to(sys.stdout.buffer)) as p:\n'
358+
' out, err = p.communicate()\n'
359+
' assert p.returncode == 0\n'
360+
' assert out.rstrip() == b\'test_stdout_teed\'\n'
361+
' assert err == b\'\'\n'
362+
)
363+
with subprocess.Popen([sys.executable, "-c", code],
364+
stdout=subprocess.PIPE,
365+
stderr=subprocess.PIPE) as p:
366+
self.addCleanup(p.stdout.close)
367+
self.addCleanup(p.stderr.close)
368+
out, err = p.communicate()
369+
self.assertEqual(p.returncode, 0, err.decode())
370+
self.assertEqual(out, b'test_stdout_teed' + NEWLINE)
371+
self.assertEqual(err, b'')
372+
343373
def test_stderr_none(self):
344374
# .stderr is None when not redirected
345375
with subprocess.Popen([sys.executable, "-c", 'print("banana")'],
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
In the :mod:`subprocess` :class:`Popen`, factor the read callback handler for the ``PIPE`` file descriptors into its own function. Also add class variables pointing to the *stdout* and *stderr* read handlers. Lastly, add constructor variable ``read_stdout_callback`` and ``read_stderr_callback`` to allow overriding the handler with a user-supplied function.
2+
3+
Also add the method ``tee_pipe_to(handle)`` to this class, which indicates that output on the pipes should be copied to the parent's buffers, as well as being cloned onto the named *handle*.

0 commit comments

Comments
 (0)