From dd42cb71f2cb02f3a32f016137b12a146bc0d0e2 Mon Sep 17 00:00:00 2001 From: Anders Lorentsen Date: Tue, 30 Jan 2018 08:27:28 +0100 Subject: bpo-31961: subprocess now accepts path-like args (GH-4329) Allow os.PathLike args in subprocess APIs. --- Doc/library/subprocess.rst | 16 ++++++----- Lib/subprocess.py | 12 +++++++-- Lib/test/test_subprocess.py | 31 ++++++++++++++++++++++ .../2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst | 4 +++ 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 86f3e06..27d4288 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -335,12 +335,12 @@ functions. the class uses the Windows ``CreateProcess()`` function. The arguments to :class:`Popen` are as follows. - *args* should be a sequence of program arguments or else a single string. - By default, the program to execute is the first item in *args* if *args* is - a sequence. If *args* is a string, the interpretation is - platform-dependent and described below. See the *shell* and *executable* - arguments for additional differences from the default behavior. Unless - otherwise stated, it is recommended to pass *args* as a sequence. + *args* should be a sequence of program arguments or else a single string or + :term:`path-like object`. By default, the program to execute is the first + item in *args* if *args* is a sequence. If *args* is a string, the + interpretation is platform-dependent and described below. See the *shell* + and *executable* arguments for additional differences from the default + behavior. Unless otherwise stated, it is recommended to pass *args* as a sequence. On POSIX, if *args* is a string, the string is interpreted as the name or path of the program to execute. However, this can only be done if not @@ -551,6 +551,10 @@ functions. Popen destructor now emits a :exc:`ResourceWarning` warning if the child process is still running. + .. versionchanged:: 3.7 + *args*, or the first element of *args* if *args* is a sequence, can now + be a :term:`path-like object`. + Exceptions ^^^^^^^^^^ diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 93635ee..2723bc9 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1097,7 +1097,12 @@ class Popen(object): assert not pass_fds, "pass_fds not supported on Windows." if not isinstance(args, str): - args = list2cmdline(args) + try: + args = os.fsdecode(args) # os.PathLike -> str + except TypeError: # not an os.PathLike, must be a sequence. + args = list(args) + args[0] = os.fsdecode(args[0]) # os.PathLike -> str + args = list2cmdline(args) # Process startup details if startupinfo is None: @@ -1369,7 +1374,10 @@ class Popen(object): if isinstance(args, (str, bytes)): args = [args] else: - args = list(args) + try: + args = list(args) + except TypeError: # os.PathLike instead of a sequence? + args = [os.fsencode(args)] # os.PathLike -> [str] if shell: # On Android the default shell is at '/system/bin/sh'. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index eee24bb..858a701 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1475,6 +1475,37 @@ class RunFuncTestCase(BaseTestCase): env=newenv) self.assertEqual(cp.returncode, 33) + def test_run_with_pathlike_path(self): + # bpo-31961: test run(pathlike_object) + class Path: + def __fspath__(self): + # the name of a command that can be run without + # any argumenets that exit fast + return 'dir' if mswindows else 'ls' + + path = Path() + if mswindows: + res = subprocess.run(path, stdout=subprocess.DEVNULL, shell=True) + else: + res = subprocess.run(path, stdout=subprocess.DEVNULL) + + self.assertEqual(res.returncode, 0) + + def test_run_with_pathlike_path_and_arguments(self): + # bpo-31961: test run([pathlike_object, 'additional arguments']) + class Path: + def __fspath__(self): + # the name of a command that can be run without + # any argumenets that exits fast + return sys.executable + + path = Path() + + args = [path, '-c', 'import sys; sys.exit(57)'] + res = subprocess.run(args) + + self.assertEqual(res.returncode, 57) + def test_capture_output(self): cp = self.run_python(("import sys;" "sys.stdout.write('BDFL'); " diff --git a/Misc/NEWS.d/next/Library/2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst b/Misc/NEWS.d/next/Library/2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst new file mode 100644 index 0000000..611f4e9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst @@ -0,0 +1,4 @@ +The *args* argument of subprocess.Popen can now be a +:term:`path-like object`. If *args* is given as a +sequence, it's first element can now be a +:term:`path-like object` as well. -- cgit v0.12