]> granicus.if.org Git - python/commitdiff
bpo-32102 Add "capture_output=True" to subprocess.run (GH-5149)
authorBo Bayles <bbayles@gmail.com>
Tue, 30 Jan 2018 06:40:39 +0000 (00:40 -0600)
committerGregory P. Smith <greg@krypto.org>
Tue, 30 Jan 2018 06:40:39 +0000 (22:40 -0800)
Add "capture_output=True" option to subprocess.run, this is equivalent to
setting stdout=PIPE, stderr=PIPE but is much more readable.

Doc/library/subprocess.rst
Lib/subprocess.py
Lib/test/test_subprocess.py
Misc/ACKS
Misc/NEWS.d/next/Library/2018-01-10-18-04-21.bpo-32102.9-CZgD.rst [new file with mode: 0644]

index af96f41ef40a3381c1d9629f399fa22b20d615b4..86f3e06318f49ad52684785bd971b156f305a732 100644 (file)
@@ -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
    in :ref:`frequently-used-arguments` (hence the use of keyword-only notation
    in the abbreviated signature). The full function signature is largely the
-   same as that of the :class:`Popen` constructor - apart from *timeout*,
-   *input* and *check*, all the arguments to this function are passed through to
-   that interface.
+   same as that of the :class:`Popen` constructor - most of the arguments to
+   this function are passed through to that interface. (*timeout*,  *input*,
+   *check*, and *capture_output* are not.)
 
-   This does not capture stdout or stderr by default. To do so, pass
-   :data:`PIPE` for the *stdout* and/or *stderr* arguments.
+   If *capture_output* is true, stdout and stderr will be captured.
+   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
    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.
         ...
       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,
-      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
 
@@ -98,7 +100,8 @@ compatibility with older versions, see the :ref:`call-function-trio` section.
 
    .. 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
 
index f69159e3aadcf96446fbee389454fdb04968b347..93635ee61f7e9ffce2361d8b63384107fd04cd4a 100644 (file)
@@ -409,7 +409,8 @@ class CompletedProcess(object):
                                      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.
 
     The returned instance will have attributes args, returncode, stdout and
@@ -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.')
         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:
         try:
             stdout, stderr = process.communicate(input, timeout=timeout)
index dd63818f3b2f1155ced60c5c615f12da1df4d6e7..eee24bbe37cfca28109f7370306050eba12b7a67 100644 (file)
@@ -1475,6 +1475,38 @@ class RunFuncTestCase(BaseTestCase):
                              env=newenv)
         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")
 class POSIXProcessTestCase(BaseTestCase):
index bfcdd33b8ed7b41306a161ae7766033bfeb37717..3fdfa3041f87e4f478cdaaa4df2f1abd11512d76 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -108,6 +108,7 @@ Michael R Bax
 Anthony Baxter
 Mike Bayer
 Samuel L. Bayer
+Bo Bayles
 Tommy Beadle
 Donald Beaudry
 David Beazley
diff --git a/Misc/NEWS.d/next/Library/2018-01-10-18-04-21.bpo-32102.9-CZgD.rst b/Misc/NEWS.d/next/Library/2018-01-10-18-04-21.bpo-32102.9-CZgD.rst
new file mode 100644 (file)
index 0000000..cd4d0b5
--- /dev/null
@@ -0,0 +1 @@
+New argument ``capture_output`` for subprocess.run