Commit fc5a6bbe authored by Jason Madden's avatar Jason Madden

Unify subprocess.check_output for Py2/3.

This adds the *timeout* and *input* arguments for Py2.

This required a small doctest renormalizer to handle the byte string
difference.
parent 3ca42f93
......@@ -12,7 +12,7 @@ env:
# - TASK=test-py27-noembed
- TASK=test-pypy
- TASK=test-py36
# - TASK=lint-py27
- TASK=lint-py27
# - TASK=test-pypy3
# - TASK=test-py35
# - TASK=test-py278
......
......@@ -176,8 +176,12 @@ Other Changes
exception; all instances where a ``Timeout`` exception would
previously be thrown under Python 2 will now throw a
``TimeoutExpired`` exception.
- :func:`gevent.subprocess.call` accepts the *timeout* keyword
argument on Python 2.
- :func:`gevent.subprocess.call` (and ``check_call``) accepts the
*timeout* keyword argument on Python 2. This is standard on Python
3, but a gevent extension on Python 2.
- :func:`gevent.subprocess.check_output` accepts the *timeout* and
*input* arguments on Python 2. This is standard on Python 3, but a
gevent extension on Python 2.
1.1.2 (Jul 21, 2016)
====================
......
......@@ -233,7 +233,10 @@ def call(*popenargs, **kwargs):
raise
def check_call(*popenargs, **kwargs):
"""Run command with arguments. Wait for command to complete. If
"""
check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None) -> 0
Run command with arguments. Wait for command to complete. If
the exit code was zero then return, otherwise raise
:exc:`CalledProcessError`. The ``CalledProcessError`` object will have the
return code in the returncode attribute.
......@@ -250,9 +253,11 @@ def check_call(*popenargs, **kwargs):
raise CalledProcessError(retcode, cmd)
return 0
if PY3:
def check_output(*popenargs, **kwargs):
r"""Run command with arguments and return its output.
def check_output(*popenargs, **kwargs):
r"""
check_output(args, *, input=None, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None) -> output
Run command with arguments and return its output.
If the exit code was non-zero it raises a :exc:`CalledProcessError`. The
``CalledProcessError`` object will have the return code in the returncode
......@@ -262,7 +267,7 @@ if PY3:
The arguments are the same as for the Popen constructor. Example::
>>> check_output(["ls", "-1", "/dev/null"])
b'/dev/null\n'
'/dev/null\n'
The ``stdout`` argument is not allowed as it is used internally.
......@@ -271,7 +276,7 @@ if PY3:
>>> check_output(["/bin/sh", "-c",
... "ls -l non_existent_file ; exit 0"],
... stderr=STDOUT)
b'ls: non_existent_file: No such file or directory\n'
'ls: non_existent_file: No such file or directory\n'
There is an additional optional argument, "input", allowing you to
pass a string to the subprocess's stdin. If you use this argument
......@@ -280,10 +285,18 @@ if PY3:
>>> check_output(["sed", "-e", "s/foo/bar/"],
... input=b"when in the course of fooman events\n")
b'when in the course of barman events\n'
'when in the course of barman events\n'
If ``universal_newlines=True`` is passed, the return value will be a
string rather than bytes.
.. versionchanged:: 1.2a1
The ``timeout`` keyword argument is now accepted on all supported
versions of Python (not just Python 3) and if it expires will raise a
:exc:`TimeoutExpired` exception (under Python 2 this is a subclass of :exc:`~.Timeout`).
.. versionchanged:: 1.2a1
The ``input`` keyword argument is now accepted on all supported
versions of Python, not just Python 3
"""
timeout = kwargs.pop('timeout', None)
if 'stdout' in kwargs:
......@@ -311,41 +324,6 @@ if PY3:
if retcode:
raise CalledProcessError(retcode, process.args, output=output)
return output
else:
def check_output(*popenargs, **kwargs):
r"""Run command with arguments and return its output as a byte string.
If the exit code was non-zero it raises a CalledProcessError. The
CalledProcessError object will have the return code in the returncode
attribute and output in the output attribute.
The arguments are the same as for the Popen constructor. Example:
>>> print(check_output(["ls", "-1", "/dev/null"]).decode('ascii'))
/dev/null
<BLANKLINE>
The stdout argument is not allowed as it is used internally.
To capture standard error in the result, use stderr=STDOUT.
>>> print(check_output(["/bin/sh", "-c", "echo hello world"], stderr=STDOUT).decode('ascii'))
hello world
<BLANKLINE>
"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = Popen(stdout=PIPE, *popenargs, **kwargs)
output = process.communicate()[0]
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
ex = CalledProcessError(retcode, cmd)
# on Python 2.6 and older CalledProcessError does not accept 'output' argument
ex.output = output
raise ex
return output
_PLATFORM_DEFAULT_CLOSE_FDS = object()
......@@ -632,7 +610,9 @@ class Popen(object):
communicate() returns a tuple (stdout, stderr).
:keyword timeout: Under Python 2, this is a gevent extension; if
given and it expires, we will raise :class:`gevent.timeout.Timeout`.
given and it expires, we will raise :exc:`TimeoutExpired`, which
extends :exc:`gevent.timeout.Timeout` (note that this only extends :exc:`BaseException`,
*not* :exc:`Exception`)
Under Python 3, this raises the standard :exc:`TimeoutExpired` exception.
.. versionchanged:: 1.1a2
......@@ -693,11 +673,7 @@ class Popen(object):
# RunFuncTestCase.test_timeout). Instead, we go directly to
# self.wait
if not greenlets and timeout is not None:
result = self.wait(timeout=timeout)
# Python 3 would have already raised, but Python 2 would not
# so we need to do that manually
if result is None:
raise TimeoutExpired(self.args, timeout)
self.wait(timeout=timeout, _raise_exc=True)
done = joinall(greenlets, timeout=timeout)
if timeout is not None and len(done) != len(greenlets):
......@@ -1442,7 +1418,7 @@ class CompletedProcess(object):
def run(*popenargs, **kwargs):
"""
`subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False)`
run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False) -> CompletedProcess
Run command with arguments and return a CompletedProcess instance.
......
......@@ -68,6 +68,9 @@ if __name__ == '__main__':
# 'cannot access'
(re.compile('cannot access non_existent_file: No such file or directory'),
'non_existent_file: No such file or directory'),
# Python 3 bytes add a "b".
(re.compile(r'b(".*?")'), r"\1"),
(re.compile(r"b('.*?')"), r"\1"),
))
tests_count = 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment