[Python-checkins] bpo-36686: Improve the documentation of the std* params in loop.subprocess_exec (GH-13586)

Miss Islington (bot) webhook-mailer at python.org
Mon May 27 19:51:23 EDT 2019


https://github.com/python/cpython/commit/f0d4c64019ecf8a5f362aa5a478786241613e5c3
commit: f0d4c64019ecf8a5f362aa5a478786241613e5c3
branch: master
author: sbstp <sbstp at users.noreply.github.com>
committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
date: 2019-05-27T16:51:19-07:00
summary:

bpo-36686: Improve the documentation of the std* params in loop.subprocess_exec (GH-13586)



https://bugs.python.org/issue36686

files:
A Misc/NEWS.d/next/Documentation/2019-05-27-17-28-58.bpo-36686.Zot4sx.rst
M Doc/library/asyncio-eventloop.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_subprocess.py

diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index 06f673be7902..4acd23f59465 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -1217,32 +1217,52 @@ async/await code consider using the high-level
 
    Other parameters:
 
-   * *stdin*: either a file-like object representing a pipe to be
-     connected to the subprocess's standard input stream using
-     :meth:`~loop.connect_write_pipe`, or the
-     :const:`subprocess.PIPE`  constant (default). By default a new
-     pipe will be created and connected.
-
-   * *stdout*: either a file-like object representing the pipe to be
-     connected to the subprocess's standard output stream using
-     :meth:`~loop.connect_read_pipe`, or the
-     :const:`subprocess.PIPE` constant (default). By default a new pipe
-     will be created and connected.
-
-   * *stderr*: either a file-like object representing the pipe to be
-     connected to the subprocess's standard error stream using
-     :meth:`~loop.connect_read_pipe`, or one of
-     :const:`subprocess.PIPE` (default) or :const:`subprocess.STDOUT`
-     constants.
-
-     By default a new pipe will be created and connected. When
-     :const:`subprocess.STDOUT` is specified, the subprocess' standard
-     error stream will be connected to the same pipe as the standard
-     output stream.
+   * *stdin* can be any of these:
+
+     * a file-like object representing a pipe to be connected to the
+       subprocess's standard input stream using
+       :meth:`~loop.connect_write_pipe`
+     * the :const:`subprocess.PIPE` constant (default) which will create a new
+       pipe and connect it,
+     * the value ``None`` which will make the subprocess inherit the file
+       descriptor from this process
+     * the :const:`subprocess.DEVNULL` constant which indicates that the
+       special :data:`os.devnull` file will be used
+
+   * *stdout* can be any of these:
+
+     * a file-like object representing a pipe to be connected to the
+       subprocess's standard output stream using
+       :meth:`~loop.connect_write_pipe`
+     * the :const:`subprocess.PIPE` constant (default) which will create a new
+       pipe and connect it,
+     * the value ``None`` which will make the subprocess inherit the file
+       descriptor from this process
+     * the :const:`subprocess.DEVNULL` constant which indicates that the
+       special :data:`os.devnull` file will be used
+
+   * *stderr* can be any of these:
+
+     * a file-like object representing a pipe to be connected to the
+       subprocess's standard error stream using
+       :meth:`~loop.connect_write_pipe`
+     * the :const:`subprocess.PIPE` constant (default) which will create a new
+       pipe and connect it,
+     * the value ``None`` which will make the subprocess inherit the file
+       descriptor from this process
+     * the :const:`subprocess.DEVNULL` constant which indicates that the
+       special :data:`os.devnull` file will be used
+     * the :const:`subprocess.STDOUT` constant which will connect the standard
+       error stream to the process' standard output stream
 
    * All other keyword arguments are passed to :class:`subprocess.Popen`
-     without interpretation, except for *bufsize*, *universal_newlines*
-     and *shell*, which should not be specified at all.
+     without interpretation, except for *bufsize*, *universal_newlines*,
+     *shell*, *text*, *encoding* and *errors*, which should not be specified
+     at all.
+
+     The ``asyncio`` subprocess API does not support decoding the streams
+     as text. :func:`bytes.decode` can be used to convert the bytes returned
+     from the stream to text.
 
    See the constructor of the :class:`subprocess.Popen` class
    for documentation on other arguments.
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index e5cd14b59af5..68105eec8132 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1555,6 +1555,7 @@ def _log_subprocess(self, msg, stdin, stdout, stderr):
                                stderr=subprocess.PIPE,
                                universal_newlines=False,
                                shell=True, bufsize=0,
+                               encoding=None, errors=None, text=None,
                                **kwargs):
         if not isinstance(cmd, (bytes, str)):
             raise ValueError("cmd must be a string")
@@ -1564,6 +1565,13 @@ def _log_subprocess(self, msg, stdin, stdout, stderr):
             raise ValueError("shell must be True")
         if bufsize != 0:
             raise ValueError("bufsize must be 0")
+        if text:
+            raise ValueError("text must be False")
+        if encoding is not None:
+            raise ValueError("encoding must be None")
+        if errors is not None:
+            raise ValueError("errors must be None")
+
         protocol = protocol_factory()
         debug_log = None
         if self._debug:
@@ -1580,13 +1588,22 @@ def _log_subprocess(self, msg, stdin, stdout, stderr):
     async def subprocess_exec(self, protocol_factory, program, *args,
                               stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE, universal_newlines=False,
-                              shell=False, bufsize=0, **kwargs):
+                              shell=False, bufsize=0,
+                              encoding=None, errors=None, text=None,
+                              **kwargs):
         if universal_newlines:
             raise ValueError("universal_newlines must be False")
         if shell:
             raise ValueError("shell must be False")
         if bufsize != 0:
             raise ValueError("bufsize must be 0")
+        if text:
+            raise ValueError("text must be False")
+        if encoding is not None:
+            raise ValueError("encoding must be None")
+        if errors is not None:
+            raise ValueError("errors must be None")
+
         popen_args = (program,) + args
         for arg in popen_args:
             if not isinstance(arg, (str, bytes)):
diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
index 551974a1806a..f1ab039ad663 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -335,6 +335,63 @@ def test_empty_input(self):
         self.assertEqual(output.rstrip(), b'0')
         self.assertEqual(exitcode, 0)
 
+    def test_devnull_input(self):
+
+        async def empty_input():
+            code = 'import sys; data = sys.stdin.read(); print(len(data))'
+            proc = await asyncio.create_subprocess_exec(
+                                          sys.executable, '-c', code,
+                                          stdin=asyncio.subprocess.DEVNULL,
+                                          stdout=asyncio.subprocess.PIPE,
+                                          stderr=asyncio.subprocess.PIPE,
+                                          close_fds=False,
+                                          loop=self.loop)
+            stdout, stderr = await proc.communicate()
+            exitcode = await proc.wait()
+            return (stdout, exitcode)
+
+        output, exitcode = self.loop.run_until_complete(empty_input())
+        self.assertEqual(output.rstrip(), b'0')
+        self.assertEqual(exitcode, 0)
+
+    def test_devnull_output(self):
+
+        async def empty_output():
+            code = 'import sys; data = sys.stdin.read(); print(len(data))'
+            proc = await asyncio.create_subprocess_exec(
+                                          sys.executable, '-c', code,
+                                          stdin=asyncio.subprocess.PIPE,
+                                          stdout=asyncio.subprocess.DEVNULL,
+                                          stderr=asyncio.subprocess.PIPE,
+                                          close_fds=False,
+                                          loop=self.loop)
+            stdout, stderr = await proc.communicate(b"abc")
+            exitcode = await proc.wait()
+            return (stdout, exitcode)
+
+        output, exitcode = self.loop.run_until_complete(empty_output())
+        self.assertEqual(output, None)
+        self.assertEqual(exitcode, 0)
+
+    def test_devnull_error(self):
+
+        async def empty_error():
+            code = 'import sys; data = sys.stdin.read(); print(len(data))'
+            proc = await asyncio.create_subprocess_exec(
+                                          sys.executable, '-c', code,
+                                          stdin=asyncio.subprocess.PIPE,
+                                          stdout=asyncio.subprocess.PIPE,
+                                          stderr=asyncio.subprocess.DEVNULL,
+                                          close_fds=False,
+                                          loop=self.loop)
+            stdout, stderr = await proc.communicate(b"abc")
+            exitcode = await proc.wait()
+            return (stderr, exitcode)
+
+        output, exitcode = self.loop.run_until_complete(empty_error())
+        self.assertEqual(output, None)
+        self.assertEqual(exitcode, 0)
+
     def test_cancel_process_wait(self):
         # Issue #23140: cancel Process.wait()
 
@@ -531,6 +588,39 @@ def test_process_create_warning(self):
         with self.assertWarns(DeprecationWarning):
             subprocess.Process(transp, proto, loop=self.loop)
 
+    def test_create_subprocess_exec_text_mode_fails(self):
+        async def execute():
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_exec(sys.executable,
+                                                        text=True)
+
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_exec(sys.executable,
+                                                        encoding="utf-8")
+
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_exec(sys.executable,
+                                                        errors="strict")
+
+        self.loop.run_until_complete(execute())
+
+    def test_create_subprocess_shell_text_mode_fails(self):
+
+        async def execute():
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_shell(sys.executable,
+                                                         text=True)
+
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_shell(sys.executable,
+                                                         encoding="utf-8")
+
+            with self.assertRaises(ValueError):
+                await subprocess.create_subprocess_shell(sys.executable,
+                                                         errors="strict")
+
+        self.loop.run_until_complete(execute())
+
 
 if sys.platform != 'win32':
     # Unix
diff --git a/Misc/NEWS.d/next/Documentation/2019-05-27-17-28-58.bpo-36686.Zot4sx.rst b/Misc/NEWS.d/next/Documentation/2019-05-27-17-28-58.bpo-36686.Zot4sx.rst
new file mode 100644
index 000000000000..2ea42adf1317
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2019-05-27-17-28-58.bpo-36686.Zot4sx.rst
@@ -0,0 +1,6 @@
+Improve documentation of the stdin, stdout, and stderr arguments of of the
+``asyncio.subprocess_exec`` function to specficy which values are supported.
+Also mention that decoding as text is not supported.
+
+Add a few tests to verify that the various values passed to the std*
+arguments actually work.



More information about the Python-checkins mailing list