diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -55,10 +55,10 @@ has terminated. The *input* argument is passed to :meth:`Popen.communicate` and thus to the - subprocess's stdin. If used it must be a byte sequence, or a string if - ``universal_newlines=True``. When used, the internal :class:`Popen` object - is automatically created with ``stdin=PIPE``, and the *stdin* argument may - not be used as well. + subprocess's stdin. If used it must be a byte sequence, or a string. When + used, the internal :class:`Popen` object is automatically created with + ``stdin=PIPE``, ``universal_newlines=isinstance(input, str)``, and the + *stdin* argument may not be used as well. If *check* is True, and the process exits with a non-zero exit code, a :exc:`CalledProcessError` exception will be raised. Attributes of that @@ -81,6 +81,9 @@ .. versionadded:: 3.5 + .. versionchanged:: 3.6 + if *input* is set then ``universal_newlines=isinstance(input, str)`` + .. class:: CompletedProcess The return value from :func:`run`, representing a process that has finished. @@ -100,8 +103,8 @@ .. attribute:: stdout Captured stdout from the child process. A bytes sequence, or a string if - :func:`run` was called with ``universal_newlines=True``. None if stdout - was not captured. + :func:`run` was called with ``universal_newlines=True`` or a string + *input*. None if stdout was not captured. If you ran the process with ``stderr=subprocess.STDOUT``, stdout and stderr will be combined in this attribute, and :attr:`stderr` will be @@ -110,8 +113,8 @@ .. attribute:: stderr Captured stderr from the child process. A bytes sequence, or a string if - :func:`run` was called with ``universal_newlines=True``. None if stderr - was not captured. + :func:`run` was called with ``universal_newlines=True`` or a string + *input*. None if stderr was not captured. .. method:: check_returncode() @@ -905,8 +908,9 @@ encoding of the output data may depend on the command being invoked, so the decoding to text will often need to be handled at the application level. - This behaviour may be overridden by setting *universal_newlines* to - ``True`` as described above in :ref:`frequently-used-arguments`. + This behaviour may be overridden by passing a string *input* or setting + *universal_newlines* to ``True`` as described above in + :ref:`frequently-used-arguments`. To also capture standard error in the result, use ``stderr=subprocess.STDOUT``:: diff --git a/Lib/subprocess.py b/Lib/subprocess.py --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -188,8 +188,11 @@ pass a string to the subprocess's stdin. If you use this argument you may not also use the Popen constructor's "stdin" argument. - If universal_newlines is set to True, the "input" argument must - be a string rather than bytes, and the return value will be a string. + If universal_newlines is set to True, the "input" argument must be a + string rather than bytes, and the return value will be a string. And + in reverse, if "input" argument is a string then universal_newlines + must be True. + Exceptions ---------- @@ -634,7 +637,10 @@ b'when in the course of barman events\n' If universal_newlines=True is passed, the "input" argument must be a - string and the return value will be a string rather than bytes. + string and the return value will be a string rather than bytes. And + in reverse, if "input" argument is a string then universal_newlines + must be True. + """ if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') @@ -703,14 +709,20 @@ The other arguments are the same as for the Popen constructor. - If universal_newlines=True is passed, the "input" argument must be a - string and stdout/stderr in the returned object will be strings rather than - bytes. + If universal_newlines=True is passed, the "input" argument must be a string + and stdout/stderr in the returned object will be strings rather than + bytes. And in reverse, if "input" argument is a string then + universal_newlines must be True. + """ if input is not None: if 'stdin' in kwargs: raise ValueError('stdin and input arguments may not both be used.') kwargs['stdin'] = PIPE + if ('universal_newlines' in kwargs and + bool(kwargs['universal_newlines']) != isinstance(input, str)): + raise ValueError('inconsistent universal_newlines and input arguments') + kwargs['universal_newlines'] = isinstance(input, str) with Popen(*popenargs, **kwargs) as process: try: diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1291,12 +1291,31 @@ stdin=tf, stdout=subprocess.PIPE) self.assertIn(b'PEAR', cp.stdout) - def test_check_output_input_arg(self): - # check_output() can be called with input set to a string - cp = self.run_python( + def test_input_arg(self): + # run() can be called with input set to a string or a byte sequence + for data, expected_output in [(b'pear', b'PEAR'), ('pear', 'PEAR')]: + cp = self.run_python( "import sys; sys.stdout.write(sys.stdin.read().upper())", - input=b'pear', stdout=subprocess.PIPE) - self.assertIn(b'PEAR', cp.stdout) + input=data, stdout=subprocess.PIPE) + self.assertIn(expected_output, cp.stdout) + + def test_universal_newlines_with_input_arg(self): + # run() allows 'universal_newlines' with 'input' + for data, expected_output, text_mode in [ + (b'pear', b'PEAR', False), ('pear', 'PEAR', True)]: + cp = self.run_python( + "import sys; sys.stdout.write(sys.stdin.read().upper())", + input=data, stdout=subprocess.PIPE, universal_newlines=text_mode) + self.assertIn(expected_output, cp.stdout) + + def test_inconsistent_universal_newlines_with_input_arg(self): + # run() expects 'universal_newlines' to be consistent with 'input' + for data, text_mode in [(b'pear', True), ('pear', False)]: + with self.assertRaises(ValueError) as c: + output = self.run_python("print('will not be run')", + input=data, universal_newlines=text_mode) + self.assertIn('universal_newlines', c.exception.args[0]) + self.assertIn('input', c.exception.args[0]) def test_check_output_stdin_with_input_arg(self): # run() refuses to accept 'stdin' with 'input'