[Python-checkins] bpo-42388: Fix subprocess.check_output input=None when text=True (GH-23467)

miss-islington webhook-mailer at python.org
Fri Dec 25 00:18:41 EST 2020

commit: 7acfe4125725e86c982300cf10c0ab791a0783f4
branch: 3.9
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2020-12-24T21:18:37-08:00

bpo-42388: Fix subprocess.check_output input=None when text=True (GH-23467)

When the modern text= spelling of the universal_newlines= parameter was added
for Python 3.7, check_output's special case around input=None was overlooked.
So it behaved differently with universal_newlines=True vs text=True.  This
reconciles the behavior to be consistent and adds a test to guarantee it.

Also clarifies the existing check_output documentation.

Co-authored-by: Alexey Izbyshev <izbyshev at ispras.ru>
(cherry picked from commit 64abf373444944a240274a9b6d66d1cb01ecfcdd)

Co-authored-by: Gregory P. Smith <greg at krypto.org>

A Misc/NEWS.d/next/Library/2020-11-22-11-22-28.bpo-42388.LMgM6B.rst
M Doc/library/subprocess.rst
M Lib/subprocess.py
M Lib/test/test_subprocess.py

diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 382ce85516dfb..3150aa60700ee 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -1163,8 +1163,9 @@ calls these functions.
    The arguments shown above are merely some common ones.
    The full function signature is largely the same as that of :func:`run` -
    most arguments are passed directly through to that interface.
-   However, explicitly passing ``input=None`` to inherit the parent's
-   standard input file handle is not supported.
+   One API deviation from :func:`run` behavior exists: passing ``input=None``
+   will behave the same as ``input=b''`` (or ``input=''``, depending on other
+   arguments) rather than using the parent's standard input file handle.
    By default, this function will return the data as encoded bytes. The actual
    encoding of the output data may depend on the command being invoked, so the
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index f1d829a6f1724..ddf1128fdd1c7 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -415,7 +415,11 @@ def check_output(*popenargs, timeout=None, **kwargs):
     if 'input' in kwargs and kwargs['input'] is None:
         # Explicitly passing input=None was previously equivalent to passing an
         # empty string. That is maintained here for backwards compatibility.
-        kwargs['input'] = '' if kwargs.get('universal_newlines', False) else b''
+        if kwargs.get('universal_newlines') or kwargs.get('text'):
+            empty = ''
+        else:
+            empty = b''
+        kwargs['input'] = empty
     return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 7373fe29c47d8..e8f9699ef7635 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -196,6 +196,28 @@ def test_check_output_input_arg(self):
         self.assertIn(b'PEAR', output)
+    def test_check_output_input_none(self):
+        """input=None has a legacy meaning of input='' on check_output."""
+        output = subprocess.check_output(
+                [sys.executable, "-c",
+                 "import sys; print('XX' if sys.stdin.read() else '')"],
+                input=None)
+        self.assertNotIn(b'XX', output)
+    def test_check_output_input_none_text(self):
+        output = subprocess.check_output(
+                [sys.executable, "-c",
+                 "import sys; print('XX' if sys.stdin.read() else '')"],
+                input=None, text=True)
+        self.assertNotIn('XX', output)
+    def test_check_output_input_none_universal_newlines(self):
+        output = subprocess.check_output(
+                [sys.executable, "-c",
+                 "import sys; print('XX' if sys.stdin.read() else '')"],
+                input=None, universal_newlines=True)
+        self.assertNotIn('XX', output)
     def test_check_output_stdout_arg(self):
         # check_output() refuses to accept 'stdout' argument
         with self.assertRaises(ValueError) as c:
diff --git a/Misc/NEWS.d/next/Library/2020-11-22-11-22-28.bpo-42388.LMgM6B.rst b/Misc/NEWS.d/next/Library/2020-11-22-11-22-28.bpo-42388.LMgM6B.rst
new file mode 100644
index 0000000000000..1b19247e84148
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-11-22-11-22-28.bpo-42388.LMgM6B.rst
@@ -0,0 +1,2 @@
+Fix subprocess.check_output(..., input=None) behavior when text=True to be
+consistent with that of the documentation and universal_newlines=True.

More information about the Python-checkins mailing list