diff -r 11ed7c49f5df Lib/genericpath.py --- a/Lib/genericpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/genericpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -69,6 +69,12 @@ def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" if not m: return '' + # Some people pass in a list of pathname parts to operate in an OS-agnostic + # fashion; don't try to translate in that case as that's an abuse of the + # API and they are already doing what they need to be OS-agnostic and so + # they most likely won't be using an os.PathLike object in the sublists. + if not isinstance(m[0], (list, tuple)): + m = tuple(map(os.fspath, m)) s1 = min(m) s2 = max(m) for i, c in enumerate(s1): diff -r 11ed7c49f5df Lib/ntpath.py --- a/Lib/ntpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/ntpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -46,6 +46,7 @@ """Normalize case of pathname. Makes all characters lowercase and all slashes into backslashes.""" + s = os.fspath(s) try: if isinstance(s, bytes): return s.replace(b'/', b'\\').lower() @@ -66,12 +67,14 @@ def isabs(s): """Test whether a path is absolute""" + s = os.fspath(s) s = splitdrive(s)[1] return len(s) > 0 and s[0] in _get_bothseps(s) # Join two (or more) paths. def join(path, *paths): + path = os.fspath(path) if isinstance(path, bytes): sep = b'\\' seps = b'\\/' @@ -84,7 +87,7 @@ if not paths: path[:0] + sep #23780: Ensure compatible data type even if p is null. result_drive, result_path = splitdrive(path) - for p in paths: + for p in map(os.fspath, paths): p_drive, p_path = splitdrive(p) if p_path and p_path[0] in seps: # Second path is absolute @@ -136,6 +139,7 @@ Paths cannot contain both a drive letter and a UNC path. """ + p = os.fspath(p) if len(p) >= 2: if isinstance(p, bytes): sep = b'\\' @@ -199,7 +203,7 @@ Return tuple (head, tail) where tail is everything after the final slash. Either part may be empty.""" - + p = os.fspath(p) seps = _get_bothseps(p) d, p = splitdrive(p) # set i to index beyond p's last slash @@ -218,6 +222,7 @@ # It is always true that root + ext == p. def splitext(p): + p = os.fspath(p) if isinstance(p, bytes): return genericpath._splitext(p, b'\\', b'/', b'.') else: @@ -278,6 +283,7 @@ def ismount(path): """Test whether a path is a mount point (a drive root, the root of a share, or a mounted volume)""" + path = os.fspath(path) seps = _get_bothseps(path) path = abspath(path) root, rest = splitdrive(path) @@ -305,6 +311,7 @@ """Expand ~ and ~user constructs. If user or $HOME is unknown, do nothing.""" + path = os.fspath(path) if isinstance(path, bytes): tilde = b'~' else: @@ -354,6 +361,7 @@ """Expand shell variables of the forms $var, ${var} and %var%. Unknown variables are left unchanged.""" + path = os.fspath(path) if isinstance(path, bytes): if b'$' not in path and b'%' not in path: return path @@ -464,6 +472,7 @@ def normpath(path): """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) if isinstance(path, bytes): sep = b'\\' altsep = b'/' @@ -518,6 +527,7 @@ except ImportError: # not running on Windows - mock up something sensible def abspath(path): """Return the absolute version of a path.""" + path = os.fspath(path) if not isabs(path): if isinstance(path, bytes): cwd = os.getcwdb() @@ -531,6 +541,7 @@ """Return the absolute version of a path.""" if path: # Empty path must return current working directory. + path = os.fspath(path) try: path = _getfullpathname(path) except OSError: @@ -549,6 +560,7 @@ def relpath(path, start=None): """Return a relative version of a path""" + path = os.fspath(path) if isinstance(path, bytes): sep = b'\\' curdir = b'.' @@ -564,6 +576,7 @@ if not path: raise ValueError("no path specified") + start = os.fspath(start) try: start_abs = abspath(normpath(start)) path_abs = abspath(normpath(path)) @@ -607,6 +620,7 @@ if not paths: raise ValueError('commonpath() arg is an empty sequence') + paths = tuple(map(os.fspath, paths)) if isinstance(paths[0], bytes): sep = b'\\' altsep = b'/' diff -r 11ed7c49f5df Lib/posixpath.py --- a/Lib/posixpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/posixpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -49,6 +49,7 @@ def normcase(s): """Normalize case of pathname. Has no effect under Posix""" + s = os.fspath(s) if not isinstance(s, (bytes, str)): raise TypeError("normcase() argument must be str or bytes, " "not '{}'".format(s.__class__.__name__)) @@ -60,6 +61,7 @@ def isabs(s): """Test whether a path is absolute""" + s = os.fspath(s) sep = _get_sep(s) return s.startswith(sep) @@ -73,12 +75,13 @@ If any component is an absolute path, all previous path components will be discarded. An empty last part will result in a path that ends with a separator.""" + a = os.fspath(a) sep = _get_sep(a) path = a try: if not p: path[:0] + sep #23780: Ensure compatible data type even if p is null. - for b in p: + for b in map(os.fspath, p): if b.startswith(sep): path = b elif not path or path.endswith(sep): @@ -99,6 +102,7 @@ def split(p): """Split a pathname. Returns tuple "(head, tail)" where "tail" is everything after the final slash. Either part may be empty.""" + p = os.fspath(p) sep = _get_sep(p) i = p.rfind(sep) + 1 head, tail = p[:i], p[i:] @@ -113,6 +117,7 @@ # It is always true that root + ext == p. def splitext(p): + p = os.fspath(p) if isinstance(p, bytes): sep = b'/' extsep = b'.' @@ -128,6 +133,7 @@ def splitdrive(p): """Split a pathname into drive and path. On Posix, drive is always empty.""" + p = os.fspath(p) return p[:0], p @@ -135,6 +141,7 @@ def basename(p): """Returns the final component of a pathname""" + p = os.fspath(p) sep = _get_sep(p) i = p.rfind(sep) + 1 return p[i:] @@ -144,6 +151,7 @@ def dirname(p): """Returns the directory component of a pathname""" + p = os.fspath(p) sep = _get_sep(p) i = p.rfind(sep) + 1 head = p[:i] @@ -221,6 +229,7 @@ def expanduser(path): """Expand ~ and ~user constructions. If user or $HOME is unknown, do nothing.""" + path = os.fspath(path) if isinstance(path, bytes): tilde = b'~' else: @@ -266,6 +275,7 @@ def expandvars(path): """Expand shell variables of form $var and ${var}. Unknown variables are left unchanged.""" + path = os.fspath(path) global _varprog, _varprogb if isinstance(path, bytes): if b'$' not in path: @@ -317,6 +327,7 @@ def normpath(path): """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) if isinstance(path, bytes): sep = b'/' empty = b'' @@ -354,6 +365,7 @@ def abspath(path): """Return an absolute path.""" + path = os.fspath(path) if not isabs(path): if isinstance(path, bytes): cwd = os.getcwdb() @@ -369,6 +381,7 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" + filename = os.fspath(filename) path, ok = _joinrealpath(filename[:0], filename, {}) return abspath(path) @@ -433,6 +446,7 @@ if not path: raise ValueError("no path specified") + path = os.fspath(path) if isinstance(path, bytes): curdir = b'.' sep = b'/' @@ -444,6 +458,8 @@ if start is None: start = curdir + else: + start = os.fspath(start) try: start_list = [x for x in abspath(start).split(sep) if x] @@ -471,6 +487,7 @@ if not paths: raise ValueError('commonpath() arg is an empty sequence') + paths = tuple(map(os.fspath, paths)) if isinstance(paths[0], bytes): sep = b'/' curdir = b'.' diff -r 11ed7c49f5df Lib/test/test_genericpath.py --- a/Lib/test/test_genericpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/test/test_genericpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -450,16 +450,15 @@ with self.assertRaisesRegex(TypeError, errmsg): self.pathmodule.join('str', b'bytes') # regression, see #15377 - errmsg = r'join\(\) argument must be str or bytes, not %r' - with self.assertRaisesRegex(TypeError, errmsg % 'int'): + with self.assertRaisesRegex(TypeError, 'int'): self.pathmodule.join(42, 'str') - with self.assertRaisesRegex(TypeError, errmsg % 'int'): + with self.assertRaisesRegex(TypeError, 'int'): self.pathmodule.join('str', 42) - with self.assertRaisesRegex(TypeError, errmsg % 'int'): + with self.assertRaisesRegex(TypeError, 'int'): self.pathmodule.join(42) - with self.assertRaisesRegex(TypeError, errmsg % 'list'): + with self.assertRaisesRegex(TypeError, 'list'): self.pathmodule.join([]) - with self.assertRaisesRegex(TypeError, errmsg % 'bytearray'): + with self.assertRaisesRegex(TypeError, 'bytearray'): self.pathmodule.join(bytearray(b'foo'), bytearray(b'bar')) def test_relpath_errors(self): @@ -471,14 +470,59 @@ self.pathmodule.relpath(b'bytes', 'str') with self.assertRaisesRegex(TypeError, errmsg): self.pathmodule.relpath('str', b'bytes') - errmsg = r'relpath\(\) argument must be str or bytes, not %r' - with self.assertRaisesRegex(TypeError, errmsg % 'int'): + with self.assertRaisesRegex(TypeError, 'int'): self.pathmodule.relpath(42, 'str') - with self.assertRaisesRegex(TypeError, errmsg % 'int'): + with self.assertRaisesRegex(TypeError, 'int'): self.pathmodule.relpath('str', 42) - with self.assertRaisesRegex(TypeError, errmsg % 'bytearray'): + with self.assertRaisesRegex(TypeError, 'bytearray'): self.pathmodule.relpath(bytearray(b'foo'), bytearray(b'bar')) +class PathLikeTests(unittest.TestCase): + + class PathLike: + def __init__(self, path=''): + self.path = path + def __fspath__(self): + if isinstance(self.path, BaseException): + raise self.path + else: + return self.path + + def setUp(self): + self.file_name = support.TESTFN.lower() + self.file_path = self.PathLike(support.TESTFN) + self.addCleanup(support.unlink, self.file_name) + create_file(self.file_name, b"test_genericpath.PathLikeTests") + + def assertPathEqual(self, func): + self.assertEqual(func(self.file_path), func(self.file_name)) + + def test_path_exists(self): + self.assertPathEqual(os.path.exists) + + def test_path_isfile(self): + self.assertPathEqual(os.path.isfile) + + def test_path_isdir(self): + self.assertPathEqual(os.path.isdir) + + def test_path_commonprefix(self): + self.assertEqual(os.path.commonprefix([self.file_path, self.file_name]), + self.file_name) + + def test_path_getsize(self): + self.assertPathEqual(os.path.getsize) + + def test_path_getmtime(self): + self.assertPathEqual(os.path.getatime) + + def test_path_getctime(self): + self.assertPathEqual(os.path.getctime) + + def test_path_samefile(self): + self.assertTrue(os.path.samefile(self.file_path, self.file_name)) + + if __name__=="__main__": unittest.main() diff -r 11ed7c49f5df Lib/test/test_ntpath.py --- a/Lib/test/test_ntpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/test/test_ntpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -452,5 +452,88 @@ attributes = ['relpath', 'splitunc'] +class PathLikeTests(unittest.TestCase): + + path = ntpath + + class PathLike: + def __init__(self, path=''): + self.path = path + def __fspath__(self): + if isinstance(self.path, BaseException): + raise self.path + else: + return self.path + + def setUp(self): + self.file_name = support.TESTFN.lower() + self.file_path = self.PathLike(support.TESTFN) + self.addCleanup(support.unlink, self.file_name) + with open(self.file_name, 'xb', 0) as file: + file.write(b"test_ntpath.PathLikeTests") + + def assertPathEqual(self, func): + self.assertEqual(func(self.file_path), func(self.file_name)) + + def test_path_normcase(self): + self.assertPathEqual(self.path.normcase) + + def test_path_isabs(self): + self.assertPathEqual(self.path.isabs) + + def test_path_join(self): + self.assertEqual(self.path.join('a', self.PathLike('b'), 'c'), + self.path.join('a', 'b', 'c')) + + def test_path_split(self): + self.assertPathEqual(self.path.split) + + def test_path_splitext(self): + self.assertPathEqual(self.path.splitext) + + def test_path_splitdrive(self): + self.assertPathEqual(self.path.splitdrive) + + def test_path_basename(self): + self.assertPathEqual(self.path.basename) + + def test_path_dirname(self): + self.assertPathEqual(self.path.dirname) + + def test_path_islink(self): + self.assertPathEqual(self.path.islink) + + def test_path_lexists(self): + self.assertPathEqual(self.path.lexists) + + def test_path_ismount(self): + self.assertPathEqual(self.path.ismount) + + def test_path_expanduser(self): + self.assertPathEqual(self.path.expanduser) + + def test_path_expandvars(self): + self.assertPathEqual(self.path.expandvars) + + def test_path_normpath(self): + self.assertPathEqual(self.path.normpath) + + def test_path_abspath(self): + self.assertPathEqual(self.path.abspath) + + def test_path_realpath(self): + self.assertPathEqual(self.path.realpath) + + def test_path_relpath(self): + self.assertPathEqual(self.path.relpath) + + def test_path_commonpath(self): + common_path = self.path.commonpath([self.file_path, self.file_name]) + self.assertEqual(common_path, self.file_name) + + def test_path_isdir(self): + self.assertPathEqual(self.path.isdir) + + if __name__ == "__main__": unittest.main() diff -r 11ed7c49f5df Lib/test/test_posixpath.py --- a/Lib/test/test_posixpath.py Fri Jul 29 04:00:44 2016 +0000 +++ b/Lib/test/test_posixpath.py Fri Jul 29 15:27:42 2016 -0700 @@ -574,5 +574,85 @@ attributes = ['relpath', 'samefile', 'sameopenfile', 'samestat'] +class PathLikeTests(unittest.TestCase): + + path = posixpath + + class PathLike: + def __init__(self, path=''): + self.path = path + def __fspath__(self): + if isinstance(self.path, BaseException): + raise self.path + else: + return self.path + + def setUp(self): + self.file_name = support.TESTFN.lower() + self.file_path = self.PathLike(support.TESTFN) + self.addCleanup(support.unlink, self.file_name) + with open(self.file_name, 'xb', 0) as file: + file.write(b"test_posixpath.PathLikeTests") + + def assertPathEqual(self, func): + self.assertEqual(func(self.file_path), func(self.file_name)) + + def test_path_normcase(self): + self.assertPathEqual(self.path.normcase) + + def test_path_isabs(self): + self.assertPathEqual(self.path.isabs) + + def test_path_join(self): + self.assertEqual(self.path.join('a', self.PathLike('b'), 'c'), + self.path.join('a', 'b', 'c')) + + def test_path_split(self): + self.assertPathEqual(self.path.split) + + def test_path_splitext(self): + self.assertPathEqual(self.path.splitext) + + def test_path_splitdrive(self): + self.assertPathEqual(self.path.splitdrive) + + def test_path_basename(self): + self.assertPathEqual(self.path.basename) + + def test_path_dirname(self): + self.assertPathEqual(self.path.dirname) + + def test_path_islink(self): + self.assertPathEqual(self.path.islink) + + def test_path_lexists(self): + self.assertPathEqual(self.path.lexists) + + def test_path_ismount(self): + self.assertPathEqual(self.path.ismount) + + def test_path_expanduser(self): + self.assertPathEqual(self.path.expanduser) + + def test_path_expandvars(self): + self.assertPathEqual(self.path.expandvars) + + def test_path_normpath(self): + self.assertPathEqual(self.path.normpath) + + def test_path_abspath(self): + self.assertPathEqual(self.path.abspath) + + def test_path_realpath(self): + self.assertPathEqual(self.path.realpath) + + def test_path_relpath(self): + self.assertPathEqual(self.path.relpath) + + def test_path_commonpath(self): + common_path = self.path.commonpath([self.file_path, self.file_name]) + self.assertEqual(common_path, self.file_name) + + if __name__=="__main__": unittest.main()