[Python-checkins] bpo-31961: subprocess now accepts path-like args (GH-4329)

Gregory P. Smith webhook-mailer at python.org
Tue Jan 30 02:27:31 EST 2018


https://github.com/python/cpython/commit/dd42cb71f2cb02f3a32f016137b12a146bc0d0e2
commit: dd42cb71f2cb02f3a32f016137b12a146bc0d0e2
branch: master
author: Anders Lorentsen <Phaqui at gmail.com>
committer: Gregory P. Smith <greg at krypto.org>
date: 2018-01-29T23:27:28-08:00
summary:

bpo-31961: subprocess now accepts path-like args (GH-4329)

Allow os.PathLike args in subprocess APIs.

files:
A Misc/NEWS.d/next/Library/2017-11-08-03-38-20.bpo-31961.x5Sv0R.rst
M Doc/library/subprocess.rst
M Lib/subprocess.py
M Lib/test/test_subprocess.py

diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 86f3e06318f4..27d4288f67b3 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 93635ee61f7e..2723bc9e4274 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1097,7 +1097,12 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
             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 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
             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 eee24bbe37cf..858a70131205 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1475,6 +1475,37 @@ def test_run_kwargs(self):
                              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 000000000000..611f4e978f1a
--- /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.



More information about the Python-checkins mailing list