summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsbstp <sbstp@users.noreply.github.com>2019-05-27 23:51:19 (GMT)
committerMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2019-05-27 23:51:19 (GMT)
commitf0d4c64019ecf8a5f362aa5a478786241613e5c3 (patch)
treeba78feaf8749f5ddc5ad66fb369aba3a90d1567d
parenta3568417c49f36860393075b21c93996a5f6799b (diff)
downloadcpython-f0d4c64019ecf8a5f362aa5a478786241613e5c3.zip
cpython-f0d4c64019ecf8a5f362aa5a478786241613e5c3.tar.gz
cpython-f0d4c64019ecf8a5f362aa5a478786241613e5c3.tar.bz2
bpo-36686: Improve the documentation of the std* params in loop.subprocess_exec (GH-13586)
https://bugs.python.org/issue36686
-rw-r--r--Doc/library/asyncio-eventloop.rst68
-rw-r--r--Lib/asyncio/base_events.py19
-rw-r--r--Lib/test/test_asyncio/test_subprocess.py90
-rw-r--r--Misc/NEWS.d/next/Documentation/2019-05-27-17-28-58.bpo-36686.Zot4sx.rst6
4 files changed, 158 insertions, 25 deletions
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index 06f673b..4acd23f 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 e5cd14b..68105ee 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1555,6 +1555,7 @@ class BaseEventLoop(events.AbstractEventLoop):
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 @@ class BaseEventLoop(events.AbstractEventLoop):
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 @@ class BaseEventLoop(events.AbstractEventLoop):
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 551974a..f1ab039 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -335,6 +335,63 @@ class SubprocessMixin:
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 @@ class SubprocessMixin:
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 0000000..2ea42ad
--- /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.