Commit f8fc5499 authored by Victor Stinner's avatar Victor Stinner

Issue #18283: shutil.which() now supports bytes argument, not only text argument.

parent fc8c3929
...@@ -352,6 +352,10 @@ Directory and files operations ...@@ -352,6 +352,10 @@ Directory and files operations
.. versionadded:: 3.3 .. versionadded:: 3.3
.. versionchanged:: 3.4
The :class:`bytes` type is now accepted. If *cmd* type is :class:`bytes`,
the result type is also :class:`bytes`.
.. exception:: Error .. exception:: Error
......
...@@ -1065,6 +1065,13 @@ def get_terminal_size(fallback=(80, 24)): ...@@ -1065,6 +1065,13 @@ def get_terminal_size(fallback=(80, 24)):
return os.terminal_size((columns, lines)) return os.terminal_size((columns, lines))
# Check that a given file can be accessed with the correct mode.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def _access_check(fn, mode):
return (os.path.exists(fn) and os.access(fn, mode)
and not os.path.isdir(fn))
def which(cmd, mode=os.F_OK | os.X_OK, path=None): def which(cmd, mode=os.F_OK | os.X_OK, path=None):
"""Given a command, mode, and a PATH string, return the path which """Given a command, mode, and a PATH string, return the path which
conforms to the given mode on the PATH, or None if there is no such conforms to the given mode on the PATH, or None if there is no such
...@@ -1075,13 +1082,6 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): ...@@ -1075,13 +1082,6 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
path. path.
""" """
# Check that a given file can be accessed with the correct mode.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def _access_check(fn, mode):
return (os.path.exists(fn) and os.access(fn, mode)
and not os.path.isdir(fn))
# If we're given a path with a directory part, look it up directly rather # If we're given a path with a directory part, look it up directly rather
# than referring to PATH directories. This includes checking relative to the # than referring to PATH directories. This includes checking relative to the
# current directory, e.g. ./script # current directory, e.g. ./script
...@@ -1094,7 +1094,12 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): ...@@ -1094,7 +1094,12 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
path = os.environ.get("PATH", os.defpath) path = os.environ.get("PATH", os.defpath)
if not path: if not path:
return None return None
path = path.split(os.pathsep) if isinstance(cmd, bytes):
path = os.fsencode(path)
path = path.split(os.fsencode(os.pathsep))
else:
path = os.fsdecode(path)
path = path.split(os.pathsep)
if sys.platform == "win32": if sys.platform == "win32":
# The current directory takes precedence on Windows. # The current directory takes precedence on Windows.
...@@ -1103,6 +1108,8 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): ...@@ -1103,6 +1108,8 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
# PATHEXT is necessary to check on Windows. # PATHEXT is necessary to check on Windows.
pathext = os.environ.get("PATHEXT", "").split(os.pathsep) pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
if isinstance(cmd, bytes):
pathext = map(os.fsencode, pathext)
# See if the given file matches any of the expected path extensions. # See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe". # This will allow us to short circuit when given "python.exe".
# If it does match, only test that one, otherwise we have to try # If it does match, only test that one, otherwise we have to try
......
...@@ -1326,6 +1326,7 @@ class TestWhich(unittest.TestCase): ...@@ -1326,6 +1326,7 @@ class TestWhich(unittest.TestCase):
os.chmod(self.temp_file.name, stat.S_IXUSR) os.chmod(self.temp_file.name, stat.S_IXUSR)
self.addCleanup(self.temp_file.close) self.addCleanup(self.temp_file.close)
self.dir, self.file = os.path.split(self.temp_file.name) self.dir, self.file = os.path.split(self.temp_file.name)
self.env_path = self.dir
def test_basic(self): def test_basic(self):
# Given an EXE in a directory, it should be returned. # Given an EXE in a directory, it should be returned.
...@@ -1394,7 +1395,7 @@ class TestWhich(unittest.TestCase): ...@@ -1394,7 +1395,7 @@ class TestWhich(unittest.TestCase):
def test_environ_path(self): def test_environ_path(self):
with support.EnvironmentVarGuard() as env: with support.EnvironmentVarGuard() as env:
env['PATH'] = self.dir env['PATH'] = self.env_path
rv = shutil.which(self.file) rv = shutil.which(self.file)
self.assertEqual(rv, self.temp_file.name) self.assertEqual(rv, self.temp_file.name)
...@@ -1402,7 +1403,7 @@ class TestWhich(unittest.TestCase): ...@@ -1402,7 +1403,7 @@ class TestWhich(unittest.TestCase):
base_dir = os.path.dirname(self.dir) base_dir = os.path.dirname(self.dir)
with support.change_cwd(path=self.dir), \ with support.change_cwd(path=self.dir), \
support.EnvironmentVarGuard() as env: support.EnvironmentVarGuard() as env:
env['PATH'] = self.dir env['PATH'] = self.env_path
rv = shutil.which(self.file, path='') rv = shutil.which(self.file, path='')
self.assertIsNone(rv) self.assertIsNone(rv)
...@@ -1413,6 +1414,14 @@ class TestWhich(unittest.TestCase): ...@@ -1413,6 +1414,14 @@ class TestWhich(unittest.TestCase):
self.assertIsNone(rv) self.assertIsNone(rv)
class TestWhichBytes(TestWhich):
def setUp(self):
TestWhich.setUp(self)
self.dir = os.fsencode(self.dir)
self.file = os.fsencode(self.file)
self.temp_file.name = os.fsencode(self.temp_file.name)
class TestMove(unittest.TestCase): class TestMove(unittest.TestCase):
def setUp(self): def setUp(self):
......
...@@ -44,6 +44,8 @@ Core and Builtins ...@@ -44,6 +44,8 @@ Core and Builtins
Library Library
------- -------
- Issue #18283: shutil.which() now supports bytes argument, not only text argument.
- Issue #19921: When Path.mkdir() is called with parents=True, any missing - Issue #19921: When Path.mkdir() is called with parents=True, any missing
parent is created with the default permissions, ignoring the mode argument parent is created with the default permissions, ignoring the mode argument
(mimicking the POSIX "mkdir -p" command). (mimicking the POSIX "mkdir -p" command).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment