bpo-36686: Improve the documentation of the std* params in loop.subprocess_exec (GH-13586)
https://bugs.python.org/issue36686
This commit is contained in:
parent
a3568417c4
commit
f0d4c64019
|
@ -1217,32 +1217,52 @@ async/await code consider using the high-level
|
||||||
|
|
||||||
Other parameters:
|
Other parameters:
|
||||||
|
|
||||||
* *stdin*: either a file-like object representing a pipe to be
|
* *stdin* can be any of these:
|
||||||
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
|
* a file-like object representing a pipe to be connected to the
|
||||||
connected to the subprocess's standard output stream using
|
subprocess's standard input stream using
|
||||||
:meth:`~loop.connect_read_pipe`, or the
|
:meth:`~loop.connect_write_pipe`
|
||||||
:const:`subprocess.PIPE` constant (default). By default a new pipe
|
* the :const:`subprocess.PIPE` constant (default) which will create a new
|
||||||
will be created and connected.
|
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*: either a file-like object representing the pipe to be
|
* *stdout* can be any of these:
|
||||||
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
|
* a file-like object representing a pipe to be connected to the
|
||||||
:const:`subprocess.STDOUT` is specified, the subprocess' standard
|
subprocess's standard output stream using
|
||||||
error stream will be connected to the same pipe as the standard
|
:meth:`~loop.connect_write_pipe`
|
||||||
output stream.
|
* 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`
|
* All other keyword arguments are passed to :class:`subprocess.Popen`
|
||||||
without interpretation, except for *bufsize*, *universal_newlines*
|
without interpretation, except for *bufsize*, *universal_newlines*,
|
||||||
and *shell*, which should not be specified at all.
|
*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
|
See the constructor of the :class:`subprocess.Popen` class
|
||||||
for documentation on other arguments.
|
for documentation on other arguments.
|
||||||
|
|
|
@ -1555,6 +1555,7 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
universal_newlines=False,
|
universal_newlines=False,
|
||||||
shell=True, bufsize=0,
|
shell=True, bufsize=0,
|
||||||
|
encoding=None, errors=None, text=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
if not isinstance(cmd, (bytes, str)):
|
if not isinstance(cmd, (bytes, str)):
|
||||||
raise ValueError("cmd must be a string")
|
raise ValueError("cmd must be a string")
|
||||||
|
@ -1564,6 +1565,13 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
raise ValueError("shell must be True")
|
raise ValueError("shell must be True")
|
||||||
if bufsize != 0:
|
if bufsize != 0:
|
||||||
raise ValueError("bufsize must be 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()
|
protocol = protocol_factory()
|
||||||
debug_log = None
|
debug_log = None
|
||||||
if self._debug:
|
if self._debug:
|
||||||
|
@ -1580,13 +1588,22 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
async def subprocess_exec(self, protocol_factory, program, *args,
|
async def subprocess_exec(self, protocol_factory, program, *args,
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE, universal_newlines=False,
|
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:
|
if universal_newlines:
|
||||||
raise ValueError("universal_newlines must be False")
|
raise ValueError("universal_newlines must be False")
|
||||||
if shell:
|
if shell:
|
||||||
raise ValueError("shell must be False")
|
raise ValueError("shell must be False")
|
||||||
if bufsize != 0:
|
if bufsize != 0:
|
||||||
raise ValueError("bufsize must be 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
|
popen_args = (program,) + args
|
||||||
for arg in popen_args:
|
for arg in popen_args:
|
||||||
if not isinstance(arg, (str, bytes)):
|
if not isinstance(arg, (str, bytes)):
|
||||||
|
|
|
@ -335,6 +335,63 @@ class SubprocessMixin:
|
||||||
self.assertEqual(output.rstrip(), b'0')
|
self.assertEqual(output.rstrip(), b'0')
|
||||||
self.assertEqual(exitcode, 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):
|
def test_cancel_process_wait(self):
|
||||||
# Issue #23140: cancel Process.wait()
|
# Issue #23140: cancel Process.wait()
|
||||||
|
|
||||||
|
@ -531,6 +588,39 @@ class SubprocessMixin:
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertWarns(DeprecationWarning):
|
||||||
subprocess.Process(transp, proto, loop=self.loop)
|
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':
|
if sys.platform != 'win32':
|
||||||
# Unix
|
# Unix
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue