Skip to content

Commit dd42cb7

Browse files
Phaquigpshead
authored andcommitted
bpo-31961: subprocess now accepts path-like args (GH-4329)
Allow os.PathLike args in subprocess APIs.
1 parent 14e976e commit dd42cb7

File tree

4 files changed

+55
-8
lines changed

4 files changed

+55
-8
lines changed

Doc/library/subprocess.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,12 @@ functions.
335335
the class uses the Windows ``CreateProcess()`` function. The arguments to
336336
:class:`Popen` are as follows.
337337

338-
*args* should be a sequence of program arguments or else a single string.
339-
By default, the program to execute is the first item in *args* if *args* is
340-
a sequence. If *args* is a string, the interpretation is
341-
platform-dependent and described below. See the *shell* and *executable*
342-
arguments for additional differences from the default behavior. Unless
343-
otherwise stated, it is recommended to pass *args* as a sequence.
338+
*args* should be a sequence of program arguments or else a single string or
339+
:term:`path-like object`. By default, the program to execute is the first
340+
item in *args* if *args* is a sequence. If *args* is a string, the
341+
interpretation is platform-dependent and described below. See the *shell*
342+
and *executable* arguments for additional differences from the default
343+
behavior. Unless otherwise stated, it is recommended to pass *args* as a sequence.
344344

345345
On POSIX, if *args* is a string, the string is interpreted as the name or
346346
path of the program to execute. However, this can only be done if not
@@ -551,6 +551,10 @@ functions.
551551
Popen destructor now emits a :exc:`ResourceWarning` warning if the child
552552
process is still running.
553553

554+
.. versionchanged:: 3.7
555+
*args*, or the first element of *args* if *args* is a sequence, can now
556+
be a :term:`path-like object`.
557+
554558

555559
Exceptions
556560
^^^^^^^^^^

Lib/subprocess.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,12 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
10971097
assert not pass_fds, "pass_fds not supported on Windows."
10981098

10991099
if not isinstance(args, str):
1100-
args = list2cmdline(args)
1100+
try:
1101+
args = os.fsdecode(args) # os.PathLike -> str
1102+
except TypeError: # not an os.PathLike, must be a sequence.
1103+
args = list(args)
1104+
args[0] = os.fsdecode(args[0]) # os.PathLike -> str
1105+
args = list2cmdline(args)
11011106

11021107
# Process startup details
11031108
if startupinfo is None:
@@ -1369,7 +1374,10 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
13691374
if isinstance(args, (str, bytes)):
13701375
args = [args]
13711376
else:
1372-
args = list(args)
1377+
try:
1378+
args = list(args)
1379+
except TypeError: # os.PathLike instead of a sequence?
1380+
args = [os.fsencode(args)] # os.PathLike -> [str]
13731381

13741382
if shell:
13751383
# On Android the default shell is at '/system/bin/sh'.

Lib/test/test_subprocess.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,37 @@ def test_run_kwargs(self):
14751475
env=newenv)
14761476
self.assertEqual(cp.returncode, 33)
14771477

1478+
def test_run_with_pathlike_path(self):
1479+
# bpo-31961: test run(pathlike_object)
1480+
class Path:
1481+
def __fspath__(self):
1482+
# the name of a command that can be run without
1483+
# any argumenets that exit fast
1484+
return 'dir' if mswindows else 'ls'
1485+
1486+
path = Path()
1487+
if mswindows:
1488+
res = subprocess.run(path, stdout=subprocess.DEVNULL, shell=True)
1489+
else:
1490+
res = subprocess.run(path, stdout=subprocess.DEVNULL)
1491+
1492+
self.assertEqual(res.returncode, 0)
1493+
1494+
def test_run_with_pathlike_path_and_arguments(self):
1495+
# bpo-31961: test run([pathlike_object, 'additional arguments'])
1496+
class Path:
1497+
def __fspath__(self):
1498+
# the name of a command that can be run without
1499+
# any argumenets that exits fast
1500+
return sys.executable
1501+
1502+
path = Path()
1503+
1504+
args = [path, '-c', 'import sys; sys.exit(57)']
1505+
res = subprocess.run(args)
1506+
1507+
self.assertEqual(res.returncode, 57)
1508+
14781509
def test_capture_output(self):
14791510
cp = self.run_python(("import sys;"
14801511
"sys.stdout.write('BDFL'); "
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The *args* argument of subprocess.Popen can now be a
2+
:term:`path-like object`. If *args* is given as a
3+
sequence, it's first element can now be a
4+
:term:`path-like object` as well.

0 commit comments

Comments
 (0)