diff options
author | James Taylor <user234683@users.noreply.github.com> | 2018-09-14 19:32:27 -0700 |
---|---|---|
committer | James Taylor <user234683@users.noreply.github.com> | 2018-09-14 19:32:27 -0700 |
commit | 4212164e91ba2f49583cf44ad623a29b36db8f77 (patch) | |
tree | 47aefe3c0162f03e0c823b43873356f69c1cd636 /python/gevent/subprocess.py | |
parent | 6ca20ff7010f2bafc7fefcb8cad982be27a8aeae (diff) | |
download | yt-local-4212164e91ba2f49583cf44ad623a29b36db8f77.tar.lz yt-local-4212164e91ba2f49583cf44ad623a29b36db8f77.tar.xz yt-local-4212164e91ba2f49583cf44ad623a29b36db8f77.zip |
Windows: Use 32-bit distribution of python
Diffstat (limited to 'python/gevent/subprocess.py')
-rw-r--r-- | python/gevent/subprocess.py | 492 |
1 files changed, 342 insertions, 150 deletions
diff --git a/python/gevent/subprocess.py b/python/gevent/subprocess.py index 2ea165e..e1e28ef 100644 --- a/python/gevent/subprocess.py +++ b/python/gevent/subprocess.py @@ -40,10 +40,15 @@ import signal import sys import traceback from gevent.event import AsyncResult -from gevent.hub import get_hub, linkproxy, sleep, getcurrent +from gevent.hub import _get_hub_noargs as get_hub +from gevent.hub import linkproxy +from gevent.hub import sleep +from gevent.hub import getcurrent from gevent._compat import integer_types, string_types, xrange from gevent._compat import PY3 from gevent._compat import reraise +from gevent._compat import fspath +from gevent._compat import fsencode from gevent._util import _NONE from gevent._util import copy_globals from gevent.fileobject import FileObject @@ -108,6 +113,7 @@ __extra__ = [ 'CreateProcess', 'INFINITE', 'TerminateProcess', + 'STILL_ACTIVE', # These were added for 3.5, but we make them available everywhere. 'run', @@ -145,6 +151,17 @@ if sys.version_info[:2] >= (3, 6): __extra__.remove('STARTUPINFO') __imports__.append('STARTUPINFO') +if sys.version_info[:2] >= (3, 7): + __imports__.extend([ + 'ABOVE_NORMAL_PRIORITY_CLASS', 'BELOW_NORMAL_PRIORITY_CLASS', + 'HIGH_PRIORITY_CLASS', 'IDLE_PRIORITY_CLASS', + 'NORMAL_PRIORITY_CLASS', + 'REALTIME_PRIORITY_CLASS', + 'CREATE_NO_WINDOW', 'DETACHED_PROCESS', + 'CREATE_DEFAULT_ERROR_MODE', + 'CREATE_BREAKAWAY_FROM_JOB' + ]) + actually_imported = copy_globals(__subprocess__, globals(), only_names=__imports__, ignore_missing_names=True) @@ -357,17 +374,28 @@ if 'TimeoutExpired' not in globals(): .. versionadded:: 1.2a1 """ + def __init__(self, cmd, timeout, output=None): - _Timeout.__init__(self, timeout, _use_timer=False) + _Timeout.__init__(self, None) self.cmd = cmd - self.timeout = timeout + self.seconds = timeout self.output = output + @property + def timeout(self): + return self.seconds + def __str__(self): return ("Command '%s' timed out after %s seconds" % (self.cmd, self.timeout)) +if hasattr(os, 'set_inheritable'): + _set_inheritable = os.set_inheritable +else: + _set_inheritable = lambda i, v: True + + class Popen(object): """ The underlying process creation and management in this module is @@ -385,42 +413,59 @@ class Popen(object): .. versionchanged:: 1.2a1 Instances now save the ``args`` attribute under Python 2.7. Previously this was restricted to Python 3. + + .. versionchanged:: 1.2b1 + Add the ``encoding`` and ``errors`` parameters for Python 3. + + .. versionchanged:: 1.3a1 + Accept "path-like" objects for the *cwd* parameter on all platforms. + This was added to Python 3.6. Previously with gevent, it only worked + on POSIX platforms on 3.6. + + .. versionchanged:: 1.3a1 + Add the ``text`` argument as a synonym for ``universal_newlines``, + as added on Python 3.7. + + .. versionchanged:: 1.3a2 + Allow the same keyword arguments under Python 2 as Python 3: + ``pass_fds``, ``start_new_session``, ``restore_signals``, ``encoding`` + and ``errors``. Under Python 2, ``encoding`` and ``errors`` are ignored + because native handling of universal newlines is used. + + .. versionchanged:: 1.3a2 + Under Python 2, ``restore_signals`` defaults to ``False``. Previously it + defaulted to ``True``, the same as it did in Python 3. """ - def __init__(self, args, bufsize=None, executable=None, + # The value returned from communicate() when there was nothing to read. + # Changes if we're in text mode or universal newlines mode. + _communicate_empty_value = b'' + + def __init__(self, args, + bufsize=-1 if PY3 else 0, + executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0, threadpool=None, - **kwargs): - """Create new Popen instance. - - :param kwargs: *Only* allowed under Python 3; under Python 2, any - unrecognized keyword arguments will result in a :exc:`TypeError`. - Under Python 3, keyword arguments can include ``pass_fds``, ``start_new_session``, - ``restore_signals``, ``encoding`` and ``errors`` - - .. versionchanged:: 1.2b1 - Add the ``encoding`` and ``errors`` parameters for Python 3. - """ - - if not PY3 and kwargs: - raise TypeError("Got unexpected keyword arguments", kwargs) - pass_fds = kwargs.pop('pass_fds', ()) - start_new_session = kwargs.pop('start_new_session', False) - restore_signals = kwargs.pop('restore_signals', True) - # Added in 3.6. These are kept as ivars - encoding = self.encoding = kwargs.pop('encoding', None) - errors = self.errors = kwargs.pop('errors', None) + cwd=None, env=None, universal_newlines=None, + startupinfo=None, creationflags=0, + restore_signals=PY3, start_new_session=False, + pass_fds=(), + # Added in 3.6. These are kept as ivars + encoding=None, errors=None, + # Added in 3.7. Not an ivar directly. + text=None, + # gevent additions + threadpool=None): + + self.encoding = encoding + self.errors = errors hub = get_hub() if bufsize is None: - # bufsize has different defaults on Py3 and Py2 - if PY3: - bufsize = -1 - else: - bufsize = 0 + # Python 2 doesn't allow None at all, but Python 3 treats + # it the same as the default. We do as well. + bufsize = -1 if PY3 else 0 if not isinstance(bufsize, integer_types): raise TypeError("bufsize must be an integer") @@ -428,16 +473,20 @@ class Popen(object): if preexec_fn is not None: raise ValueError("preexec_fn is not supported on Windows " "platforms") - any_stdio_set = (stdin is not None or stdout is not None or - stderr is not None) - if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS: - if any_stdio_set: - close_fds = False - else: + if sys.version_info[:2] >= (3, 7): + if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS: close_fds = True - elif close_fds and any_stdio_set: - raise ValueError("close_fds is not supported on Windows " - "platforms if you redirect stdin/stdout/stderr") + else: + any_stdio_set = (stdin is not None or stdout is not None or + stderr is not None) + if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS: + if any_stdio_set: + close_fds = False + else: + close_fds = True + elif close_fds and any_stdio_set: + raise ValueError("close_fds is not supported on Windows " + "platforms if you redirect stdin/stdout/stderr") if threadpool is None: threadpool = hub.threadpool self.threadpool = threadpool @@ -464,6 +513,13 @@ class Popen(object): assert threadpool is None self._loop = hub.loop + # Validate the combinations of text and universal_newlines + if (text is not None and universal_newlines is not None + and bool(universal_newlines) != bool(text)): + raise SubprocessError('Cannot disambiguate when both text ' + 'and universal_newlines are supplied but ' + 'different. Pass one or the other.') + self.args = args # Previously this was Py3 only. self.stdin = None self.stdout = None @@ -485,7 +541,7 @@ class Popen(object): # On POSIX, the child objects are file descriptors. On # Windows, these are Windows file handles. The parent objects # are file descriptors on both platforms. The parent objects - # are None when not using PIPEs. The child objects are None + # are -1 when not using PIPEs. The child objects are -1 # when not redirecting. (p2cread, p2cwrite, @@ -496,16 +552,21 @@ class Popen(object): # quickly terminating child could make our fds unwrappable # (see #8458). if mswindows: - if p2cwrite is not None: + if p2cwrite != -1: p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0) - if c2pread is not None: + if c2pread != -1: c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0) - if errread is not None: + if errread != -1: errread = msvcrt.open_osfhandle(errread.Detach(), 0) - text_mode = PY3 and (self.encoding or self.errors or universal_newlines) + text_mode = PY3 and (self.encoding or self.errors or universal_newlines or text) + if text_mode or universal_newlines: + # Always a native str in universal_newlines mode, even when that + # str type is bytes. Additionally, text_mode is only true under + # Python 3, so it's actually a unicode str + self._communicate_empty_value = '' - if p2cwrite is not None: + if p2cwrite != -1: if PY3 and text_mode: # Under Python 3, if we left on the 'b' we'd get different results # depending on whether we used FileObjectPosix or FileObjectThread @@ -516,7 +577,7 @@ class Popen(object): encoding=self.encoding, errors=self.errors) else: self.stdin = FileObject(p2cwrite, 'wb', bufsize) - if c2pread is not None: + if c2pread != -1: if universal_newlines or text_mode: if PY3: # FileObjectThread doesn't support the 'U' qualifier @@ -533,7 +594,7 @@ class Popen(object): self.stdout = FileObject(c2pread, 'rU', bufsize) else: self.stdout = FileObject(c2pread, 'rb', bufsize) - if errread is not None: + if errread != -1: if universal_newlines or text_mode: if PY3: self.stderr = FileObject(errread, 'rb', bufsize) @@ -544,6 +605,10 @@ class Popen(object): self.stderr = FileObject(errread, 'rb', bufsize) self._closed_child_pipe_fds = False + # Convert here for the sake of all platforms. os.chdir accepts + # path-like objects natively under 3.6, but CreateProcess + # doesn't. + cwd = fspath(cwd) if cwd is not None else None try: self._execute_child(args, executable, preexec_fn, close_fds, pass_fds, cwd, env, universal_newlines, @@ -643,31 +708,33 @@ class Popen(object): # that no output should be lost in the event of a timeout.) Instead, we're # watching for the exception and ignoring it. It's not elegant, # but it works - if self.stdout: - def _read_out(): + def _make_pipe_reader(pipe_name): + pipe = getattr(self, pipe_name) + buf_name = '_' + pipe_name + '_buffer' + + def _read(): try: - data = self.stdout.read() + data = pipe.read() except RuntimeError: return - if self._stdout_buffer is not None: - self._stdout_buffer += data + if not data: + return + the_buffer = getattr(self, buf_name) + if the_buffer: + the_buffer.append(data) else: - self._stdout_buffer = data + setattr(self, buf_name, [data]) + return _read + + if self.stdout: + _read_out = _make_pipe_reader('stdout') stdout = spawn(_read_out) greenlets.append(stdout) else: stdout = None if self.stderr: - def _read_err(): - try: - data = self.stderr.read() - except RuntimeError: - return - if self._stderr_buffer is not None: - self._stderr_buffer += data - else: - self._stderr_buffer = data + _read_err = _make_pipe_reader('stderr') stderr = spawn(_read_err) greenlets.append(stderr) else: @@ -686,25 +753,30 @@ class Popen(object): if timeout is not None and len(done) != len(greenlets): raise TimeoutExpired(self.args, timeout) - if self.stdout: - try: - self.stdout.close() - except RuntimeError: - pass - if self.stderr: - try: - self.stderr.close() - except RuntimeError: - pass + for pipe in (self.stdout, self.stderr): + if pipe: + try: + pipe.close() + except RuntimeError: + pass + self.wait() - stdout_value = self._stdout_buffer - self._stdout_buffer = None - stderr_value = self._stderr_buffer - self._stderr_buffer = None - # XXX: Under python 3 in universal newlines mode we should be - # returning str, not bytes - return (None if stdout is None else stdout_value or b'', - None if stderr is None else stderr_value or b'') + + def _get_output_value(pipe_name): + buf_name = '_' + pipe_name + '_buffer' + buf_value = getattr(self, buf_name) + setattr(self, buf_name, None) + if buf_value: + buf_value = self._communicate_empty_value.join(buf_value) + else: + buf_value = self._communicate_empty_value + return buf_value + + stdout_value = _get_output_value('stdout') + stderr_value = _get_output_value('stderr') + + return (None if stdout is None else stdout_value, + None if stderr is None else stderr_value) def poll(self): """Check if child process has terminated. Set and return :attr:`returncode` attribute.""" @@ -743,11 +815,11 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ if stdin is None and stdout is None and stderr is None: - return (None, None, None, None, None, None) + return (-1, -1, -1, -1, -1, -1) - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None + p2cread, p2cwrite = -1, -1 + c2pread, c2pwrite = -1, -1 + errread, errwrite = -1, -1 try: DEVNULL @@ -843,6 +915,21 @@ class Popen(object): "shell or platform.") return w9xpopen + + def _filter_handle_list(self, handle_list): + """Filter out console handles that can't be used + in lpAttributeList["handle_list"] and make sure the list + isn't empty. This also removes duplicate handles.""" + # An handle with it's lowest two bits set might be a special console + # handle that if passed in lpAttributeList["handle_list"], will + # cause it to fail. + # Only works on 3.7+ + return list({handle for handle in handle_list + if handle & 0x3 != 0x3 + or _winapi.GetFileType(handle) != + _winapi.FILE_TYPE_CHAR}) + + def _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, @@ -860,12 +947,44 @@ class Popen(object): # Process startup details if startupinfo is None: startupinfo = STARTUPINFO() - if None not in (p2cread, c2pwrite, errwrite): + use_std_handles = -1 not in (p2cread, c2pwrite, errwrite) + if use_std_handles: startupinfo.dwFlags |= STARTF_USESTDHANDLES startupinfo.hStdInput = p2cread startupinfo.hStdOutput = c2pwrite startupinfo.hStdError = errwrite + if hasattr(startupinfo, 'lpAttributeList'): + # Support for Python >= 3.7 + + attribute_list = startupinfo.lpAttributeList + have_handle_list = bool(attribute_list and + "handle_list" in attribute_list and + attribute_list["handle_list"]) + + # If we were given an handle_list or need to create one + if have_handle_list or (use_std_handles and close_fds): + if attribute_list is None: + attribute_list = startupinfo.lpAttributeList = {} + handle_list = attribute_list["handle_list"] = \ + list(attribute_list.get("handle_list", [])) + + if use_std_handles: + handle_list += [int(p2cread), int(c2pwrite), int(errwrite)] + + handle_list[:] = self._filter_handle_list(handle_list) + + if handle_list: + if not close_fds: + import warnings + warnings.warn("startupinfo.lpAttributeList['handle_list'] " + "overriding close_fds", RuntimeWarning) + + # When using the handle_list we always request to inherit + # handles but the only handles that will be inherited are + # the ones in the handle_list + close_fds = False + if shell: startupinfo.dwFlags |= STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE @@ -979,7 +1098,21 @@ class Popen(object): def terminate(self): """Terminates the process """ - TerminateProcess(self._handle, 1) + # Don't terminate a process that we know has already died. + if self.returncode is not None: + return + try: + TerminateProcess(self._handle, 1) + except OSError as e: + # ERROR_ACCESS_DENIED (winerror 5) is received when the + # process already died. + if e.winerror != 5: + raise + rc = GetExitCodeProcess(self._handle) + if rc == STILL_ACTIVE: + raise + self.returncode = rc + self.result.set(self.returncode) kill = terminate @@ -997,9 +1130,9 @@ class Popen(object): """Construct and return tuple with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None + p2cread, p2cwrite = -1, -1 + c2pread, c2pwrite = -1, -1 + errread, errwrite = -1, -1 try: DEVNULL @@ -1037,7 +1170,10 @@ class Popen(object): elif stderr == PIPE: errread, errwrite = self.pipe_cloexec() elif stderr == STDOUT: - errwrite = c2pwrite + if c2pwrite != -1: + errwrite = c2pwrite + else: # child's stdout is not set, use parent's stdout + errwrite = sys.__stdout__.fileno() elif stderr == _devnull: errwrite = self._get_devnull() elif isinstance(stderr, int): @@ -1077,42 +1213,75 @@ class Popen(object): self._set_cloexec_flag(w) return r, w - def _close_fds(self, keep): - # `keep` is a set of fds, so we - # use os.closerange from 3 to min(keep) - # and then from max(keep + 1) to MAXFD and - # loop through filling in the gaps. - # Under new python versions, we need to explicitly set - # passed fds to be inheritable or they will go away on exec - if hasattr(os, 'set_inheritable'): - set_inheritable = os.set_inheritable + _POSSIBLE_FD_DIRS = ( + '/proc/self/fd', # Linux + '/dev/fd', # BSD, including macOS + ) + + @classmethod + def _close_fds(cls, keep, errpipe_write): + # From the C code: + # errpipe_write is part of keep. It must be closed at + # exec(), but kept open in the child process until exec() is + # called. + for path in cls._POSSIBLE_FD_DIRS: + if os.path.isdir(path): + return cls._close_fds_from_path(path, keep, errpipe_write) + return cls._close_fds_brute_force(keep, errpipe_write) + + @classmethod + def _close_fds_from_path(cls, path, keep, errpipe_write): + # path names a directory whose only entries have + # names that are ascii strings of integers in base10, + # corresponding to the fds the current process has open + try: + fds = [int(fname) for fname in os.listdir(path)] + except (ValueError, OSError): + cls._close_fds_brute_force(keep, errpipe_write) else: - set_inheritable = lambda i, v: True - if hasattr(os, 'closerange'): - keep = sorted(keep) - min_keep = min(keep) - max_keep = max(keep) - os.closerange(3, min_keep) - os.closerange(max_keep + 1, MAXFD) - for i in xrange(min_keep, max_keep): - if i in keep: - set_inheritable(i, True) + for i in keep: + if i == errpipe_write: continue + _set_inheritable(i, True) - try: - os.close(i) - except: - pass - else: - for i in xrange(3, MAXFD): - if i in keep: - set_inheritable(i, True) + for fd in fds: + if fd in keep or fd < 3: continue try: - os.close(i) + os.close(fd) except: pass + @classmethod + def _close_fds_brute_force(cls, keep, errpipe_write): + # `keep` is a set of fds, so we + # use os.closerange from 3 to min(keep) + # and then from max(keep + 1) to MAXFD and + # loop through filling in the gaps. + + # Under new python versions, we need to explicitly set + # passed fds to be inheritable or they will go away on exec + + # XXX: Bug: We implicitly rely on errpipe_write being the largest open + # FD so that we don't change its cloexec flag. + + assert hasattr(os, 'closerange') # Added in 2.7 + keep = sorted(keep) + min_keep = min(keep) + max_keep = max(keep) + os.closerange(3, min_keep) + os.closerange(max_keep + 1, MAXFD) + + for i in xrange(min_keep, max_keep): + if i in keep: + _set_inheritable(i, True) + continue + + try: + os.close(i) + except: + pass + def _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, @@ -1127,7 +1296,10 @@ class Popen(object): elif not PY3 and isinstance(args, string_types): args = [args] else: - args = list(args) + try: + args = list(args) + except TypeError: # os.PathLike instead of a sequence? + args = [fsencode(args)] # os.PathLike -> [str] if shell: args = ["/bin/sh", "-c"] + args @@ -1164,13 +1336,20 @@ class Popen(object): raise if self.pid == 0: # Child + + # XXX: Technically we're doing a lot of stuff here that + # may not be safe to do before a exec(), depending on the OS. + # CPython 3 goes to great lengths to precompute a lot + # of this info before the fork and pass it all to C functions that + # try hard not to call things like malloc(). (Of course, + # CPython 2 pretty much did what we're doing.) try: # Close parent's pipe ends - if p2cwrite is not None: + if p2cwrite != -1: os.close(p2cwrite) - if c2pread is not None: + if c2pread != -1: os.close(c2pread) - if errread is not None: + if errread != -1: os.close(errread) os.close(errpipe_read) @@ -1179,19 +1358,25 @@ class Popen(object): # is possible that it is overwritten (#12607). if c2pwrite == 0: c2pwrite = os.dup(c2pwrite) - if errwrite == 0 or errwrite == 1: + while errwrite in (0, 1): errwrite = os.dup(errwrite) # Dup fds for child - def _dup2(a, b): + def _dup2(existing, desired): # dup2() removes the CLOEXEC flag but # we must do it ourselves if dup2() # would be a no-op (issue #10806). - if a == b: - self._set_cloexec_flag(a, False) - elif a is not None: - os.dup2(a, b) - self._remove_nonblock_flag(b) + if existing == desired: + self._set_cloexec_flag(existing, False) + elif existing != -1: + os.dup2(existing, desired) + try: + self._remove_nonblock_flag(desired) + except OSError: + # Ignore EBADF, it may not actually be + # open yet. + # Tested beginning in 3.7.0b3 test_subprocess.py + pass _dup2(p2cread, 0) _dup2(c2pwrite, 1) _dup2(errwrite, 2) @@ -1205,7 +1390,11 @@ class Popen(object): closed.add(fd) if cwd is not None: - os.chdir(cwd) + try: + os.chdir(cwd) + except OSError as e: + e._failed_chdir = True + raise if preexec_fn: preexec_fn() @@ -1215,21 +1404,7 @@ class Popen(object): if close_fds: fds_to_keep = set(pass_fds) fds_to_keep.add(errpipe_write) - self._close_fds(fds_to_keep) - elif hasattr(os, 'get_inheritable'): - # close_fds was false, and we're on - # Python 3.4 or newer, so "all file - # descriptors except standard streams - # are closed, and inheritable handles - # are only inherited if the close_fds - # parameter is False." - for i in xrange(3, MAXFD): - try: - if i == errpipe_write or os.get_inheritable(i): - continue - os.close(i) - except: - pass + self._close_fds(fds_to_keep, errpipe_write) if restore_signals: # restore the documented signals back to sig_dfl; @@ -1283,11 +1458,11 @@ class Popen(object): # self._devnull is not always defined. devnull_fd = getattr(self, '_devnull', None) - if p2cread is not None and p2cwrite is not None and p2cread != devnull_fd: + if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: os.close(p2cread) - if c2pwrite is not None and c2pread is not None and c2pwrite != devnull_fd: + if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: os.close(c2pwrite) - if errwrite is not None and errread is not None and errwrite != devnull_fd: + if errwrite != -1 and errread != -1 and errwrite != devnull_fd: os.close(errwrite) if devnull_fd is not None: os.close(devnull_fd) @@ -1307,8 +1482,12 @@ class Popen(object): self.wait() child_exception = pickle.loads(data) for fd in (p2cwrite, c2pread, errread): - if fd is not None: + if fd is not None and fd != -1: os.close(fd) + if isinstance(child_exception, OSError): + child_exception.filename = executable + if hasattr(child_exception, '_failed_chdir'): + child_exception.filename = cwd raise child_exception def _handle_exitstatus(self, sts): @@ -1452,16 +1631,29 @@ def run(*popenargs, **kwargs): .. versionadded:: 1.2a1 This function first appeared in Python 3.5. It is available on all Python versions gevent supports. + + .. versionchanged:: 1.3a2 + Add the ``capture_output`` argument from Python 3.7. It automatically sets + ``stdout`` and ``stderr`` to ``PIPE``. It is an error to pass either + of those arguments along with ``capture_output``. """ input = kwargs.pop('input', None) timeout = kwargs.pop('timeout', None) check = kwargs.pop('check', False) + capture_output = kwargs.pop('capture_output', False) if input is not None: if 'stdin' in kwargs: raise ValueError('stdin and input arguments may not both be used.') kwargs['stdin'] = PIPE + if capture_output: + if ('stdout' in kwargs) or ('stderr' in kwargs): + raise ValueError('stdout and stderr arguments may not be used ' + 'with capture_output.') + kwargs['stdout'] = PIPE + kwargs['stderr'] = PIPE + with Popen(*popenargs, **kwargs) as process: try: stdout, stderr = process.communicate(input, timeout=timeout) |