Commit ce0f33d0 authored by Bo Bayles's avatar Bo Bayles Committed by Gregory P. Smith

bpo-32102 Add "capture_output=True" to subprocess.run (GH-5149)

Add "capture_output=True" option to subprocess.run, this is equivalent to
setting stdout=PIPE, stderr=PIPE but is much more readable.
parent 95441809
...@@ -47,12 +47,14 @@ compatibility with older versions, see the :ref:`call-function-trio` section. ...@@ -47,12 +47,14 @@ compatibility with older versions, see the :ref:`call-function-trio` section.
The arguments shown above are merely the most common ones, described below The arguments shown above are merely the most common ones, described below
in :ref:`frequently-used-arguments` (hence the use of keyword-only notation in :ref:`frequently-used-arguments` (hence the use of keyword-only notation
in the abbreviated signature). The full function signature is largely the in the abbreviated signature). The full function signature is largely the
same as that of the :class:`Popen` constructor - apart from *timeout*, same as that of the :class:`Popen` constructor - most of the arguments to
*input* and *check*, all the arguments to this function are passed through to this function are passed through to that interface. (*timeout*, *input*,
that interface. *check*, and *capture_output* are not.)
This does not capture stdout or stderr by default. To do so, pass If *capture_output* is true, stdout and stderr will be captured.
:data:`PIPE` for the *stdout* and/or *stderr* arguments. When used, the internal :class:`Popen` object is automatically created with
``stdout=PIPE`` and ``stderr=PIPE``. The *stdout* and *stderr* arguments may
not be used as well.
The *timeout* argument is passed to :meth:`Popen.communicate`. If the timeout The *timeout* argument is passed to :meth:`Popen.communicate`. If the timeout
expires, the child process will be killed and waited for. The expires, the child process will be killed and waited for. The
...@@ -86,9 +88,9 @@ compatibility with older versions, see the :ref:`call-function-trio` section. ...@@ -86,9 +88,9 @@ compatibility with older versions, see the :ref:`call-function-trio` section.
... ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1 subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE) >>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0, CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n') stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')
.. versionadded:: 3.5 .. versionadded:: 3.5
...@@ -98,7 +100,8 @@ compatibility with older versions, see the :ref:`call-function-trio` section. ...@@ -98,7 +100,8 @@ compatibility with older versions, see the :ref:`call-function-trio` section.
.. versionchanged:: 3.7 .. versionchanged:: 3.7
Added the *text* parameter, as a more understandable alias of *universal_newlines* Added the *text* parameter, as a more understandable alias of *universal_newlines*.
Added the *capture_output* parameter.
.. class:: CompletedProcess .. class:: CompletedProcess
......
...@@ -409,7 +409,8 @@ class CompletedProcess(object): ...@@ -409,7 +409,8 @@ class CompletedProcess(object):
self.stderr) self.stderr)
def run(*popenargs, input=None, timeout=None, check=False, **kwargs): def run(*popenargs,
input=None, capture_output=False, timeout=None, check=False, **kwargs):
"""Run command with arguments and return a CompletedProcess instance. """Run command with arguments and return a CompletedProcess instance.
The returned instance will have attributes args, returncode, stdout and The returned instance will have attributes args, returncode, stdout and
...@@ -442,6 +443,13 @@ def run(*popenargs, input=None, timeout=None, check=False, **kwargs): ...@@ -442,6 +443,13 @@ def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
raise ValueError('stdin and input arguments may not both be used.') raise ValueError('stdin and input arguments may not both be used.')
kwargs['stdin'] = PIPE kwargs['stdin'] = PIPE
if capture_output:
if ('stdout' in kwargs) or ('stderr' in kwargs):
raise ValueError('stdout and stderr arguments may not be used '
'with capture_output.')
kwargs['stdout'] = PIPE
kwargs['stderr'] = PIPE
with Popen(*popenargs, **kwargs) as process: with Popen(*popenargs, **kwargs) as process:
try: try:
stdout, stderr = process.communicate(input, timeout=timeout) stdout, stderr = process.communicate(input, timeout=timeout)
......
...@@ -1475,6 +1475,38 @@ class RunFuncTestCase(BaseTestCase): ...@@ -1475,6 +1475,38 @@ class RunFuncTestCase(BaseTestCase):
env=newenv) env=newenv)
self.assertEqual(cp.returncode, 33) self.assertEqual(cp.returncode, 33)
def test_capture_output(self):
cp = self.run_python(("import sys;"
"sys.stdout.write('BDFL'); "
"sys.stderr.write('FLUFL')"),
capture_output=True)
self.assertIn(b'BDFL', cp.stdout)
self.assertIn(b'FLUFL', cp.stderr)
def test_stdout_with_capture_output_arg(self):
# run() refuses to accept 'stdout' with 'capture_output'
tf = tempfile.TemporaryFile()
self.addCleanup(tf.close)
with self.assertRaises(ValueError,
msg=("Expected ValueError when stdout and capture_output "
"args supplied.")) as c:
output = self.run_python("print('will not be run')",
capture_output=True, stdout=tf)
self.assertIn('stdout', c.exception.args[0])
self.assertIn('capture_output', c.exception.args[0])
def test_stderr_with_capture_output_arg(self):
# run() refuses to accept 'stderr' with 'capture_output'
tf = tempfile.TemporaryFile()
self.addCleanup(tf.close)
with self.assertRaises(ValueError,
msg=("Expected ValueError when stderr and capture_output "
"args supplied.")) as c:
output = self.run_python("print('will not be run')",
capture_output=True, stderr=tf)
self.assertIn('stderr', c.exception.args[0])
self.assertIn('capture_output', c.exception.args[0])
@unittest.skipIf(mswindows, "POSIX specific tests") @unittest.skipIf(mswindows, "POSIX specific tests")
class POSIXProcessTestCase(BaseTestCase): class POSIXProcessTestCase(BaseTestCase):
......
...@@ -108,6 +108,7 @@ Michael R Bax ...@@ -108,6 +108,7 @@ Michael R Bax
Anthony Baxter Anthony Baxter
Mike Bayer Mike Bayer
Samuel L. Bayer Samuel L. Bayer
Bo Bayles
Tommy Beadle Tommy Beadle
Donald Beaudry Donald Beaudry
David Beazley David Beazley
......
New argument ``capture_output`` for subprocess.run
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment