Issue #18283: shutil.which() now supports bytes argument, not only text argument.
authorVictor Stinner <victor.stinner@gmail.com>
Mon, 16 Dec 2013 21:48:48 +0000 (22:48 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Mon, 16 Dec 2013 21:48:48 +0000 (22:48 +0100)
Doc/library/shutil.rst
Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS

index e4f348cd82ce9ed7a7535cc10243c7ceb22fb4ea..82942db0bfa9e90f1c5b35f7013f4c8eb5933f7e 100644 (file)
@@ -352,6 +352,10 @@ Directory and files operations
 
    .. 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
 
index 502bb6782c41864e7687626b5da81f89bcee0b95..c80a5cc4e5bb13dbefe547a5bd9677768abb2e89 100644 (file)
@@ -1065,6 +1065,13 @@ def get_terminal_size(fallback=(80, 24)):
 
     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):
     """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
@@ -1075,13 +1082,6 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
     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
     # than referring to PATH directories. This includes checking relative to the
     # current directory, e.g. ./script
@@ -1094,7 +1094,12 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
         path = os.environ.get("PATH", os.defpath)
     if not path:
         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":
         # The current directory takes precedence on Windows.
@@ -1103,6 +1108,8 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
 
         # PATHEXT is necessary to check on Windows.
         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.
         # This will allow us to short circuit when given "python.exe".
         # If it does match, only test that one, otherwise we have to try
index 98ea6d19c031ce4f8b2b63d3b9c0c3b06dd1ed2f..73147824739a3fef6f30103ac1dcbd0191c886e1 100644 (file)
@@ -1326,6 +1326,7 @@ class TestWhich(unittest.TestCase):
         os.chmod(self.temp_file.name, stat.S_IXUSR)
         self.addCleanup(self.temp_file.close)
         self.dir, self.file = os.path.split(self.temp_file.name)
+        self.env_path = self.dir
 
     def test_basic(self):
         # Given an EXE in a directory, it should be returned.
@@ -1394,7 +1395,7 @@ class TestWhich(unittest.TestCase):
 
     def test_environ_path(self):
         with support.EnvironmentVarGuard() as env:
-            env['PATH'] = self.dir
+            env['PATH'] = self.env_path
             rv = shutil.which(self.file)
             self.assertEqual(rv, self.temp_file.name)
 
@@ -1402,7 +1403,7 @@ class TestWhich(unittest.TestCase):
         base_dir = os.path.dirname(self.dir)
         with support.change_cwd(path=self.dir), \
              support.EnvironmentVarGuard() as env:
-            env['PATH'] = self.dir
+            env['PATH'] = self.env_path
             rv = shutil.which(self.file, path='')
             self.assertIsNone(rv)
 
@@ -1413,6 +1414,14 @@ class TestWhich(unittest.TestCase):
             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):
 
     def setUp(self):
index ee3c79302b909555946acd31240682b061f92605..5de3451ddcb8aaff24667aa06a2a17173a37d10e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -44,6 +44,8 @@ Core and Builtins
 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
   parent is created with the default permissions, ignoring the mode argument
   (mimicking the POSIX "mkdir -p" command).