Kaydet (Commit) 3deeeb0c authored tarafından Serhiy Storchaka's avatar Serhiy Storchaka

Issue #21883: os.path.join() and os.path.relpath() now raise a TypeError with

more helpful error message for unsupported or mismatched types of arguments.
üst 385328bf
...@@ -130,3 +130,16 @@ def _splitext(p, sep, altsep, extsep): ...@@ -130,3 +130,16 @@ def _splitext(p, sep, altsep, extsep):
filenameIndex += 1 filenameIndex += 1
return p, p[:0] return p, p[:0]
def _check_arg_types(funcname, *args):
hasstr = hasbytes = False
for s in args:
if isinstance(s, str):
hasstr = True
elif isinstance(s, bytes):
hasbytes = True
else:
raise TypeError('%s() argument must be str or bytes, not %r' %
(funcname, s.__class__.__name__)) from None
if hasstr and hasbytes:
raise TypeError("Can't mix strings and bytes in path components") from None
...@@ -50,6 +50,7 @@ def isabs(s): ...@@ -50,6 +50,7 @@ def isabs(s):
def join(s, *p): def join(s, *p):
try:
colon = _get_colon(s) colon = _get_colon(s)
path = s path = s
for t in p: for t in p:
...@@ -64,6 +65,9 @@ def join(s, *p): ...@@ -64,6 +65,9 @@ def join(s, *p):
path = path + colon path = path + colon
path = path + t path = path + t
return path return path
except (TypeError, AttributeError, BytesWarning):
genericpath._check_arg_types('join', s, *p)
raise
def split(s): def split(s):
......
...@@ -80,6 +80,7 @@ def join(path, *paths): ...@@ -80,6 +80,7 @@ def join(path, *paths):
sep = '\\' sep = '\\'
seps = '\\/' seps = '\\/'
colon = ':' colon = ':'
try:
result_drive, result_path = splitdrive(path) result_drive, result_path = splitdrive(path)
for p in paths: for p in paths:
p_drive, p_path = splitdrive(p) p_drive, p_path = splitdrive(p)
...@@ -106,6 +107,9 @@ def join(path, *paths): ...@@ -106,6 +107,9 @@ def join(path, *paths):
result_drive and result_drive[-1:] != colon): result_drive and result_drive[-1:] != colon):
return result_drive + sep + result_path return result_drive + sep + result_path
return result_drive + result_path return result_drive + result_path
except (TypeError, AttributeError, BytesWarning):
genericpath._check_arg_types('join', path, *paths)
raise
# Split a path in a drive specification (a drive letter followed by a # Split a path in a drive specification (a drive letter followed by a
...@@ -558,6 +562,7 @@ def relpath(path, start=None): ...@@ -558,6 +562,7 @@ def relpath(path, start=None):
if not path: if not path:
raise ValueError("no path specified") raise ValueError("no path specified")
try:
start_abs = abspath(normpath(start)) start_abs = abspath(normpath(start))
path_abs = abspath(normpath(path)) path_abs = abspath(normpath(path))
start_drive, start_rest = splitdrive(start_abs) start_drive, start_rest = splitdrive(start_abs)
...@@ -579,6 +584,9 @@ def relpath(path, start=None): ...@@ -579,6 +584,9 @@ def relpath(path, start=None):
if not rel_list: if not rel_list:
return curdir return curdir
return join(*rel_list) return join(*rel_list)
except (TypeError, ValueError, AttributeError, BytesWarning):
genericpath._check_arg_types('relpath', path, start)
raise
# determine if two files are in fact the same file # determine if two files are in fact the same file
......
...@@ -82,13 +82,9 @@ def join(a, *p): ...@@ -82,13 +82,9 @@ def join(a, *p):
path += b path += b
else: else:
path += sep + b path += sep + b
except (TypeError, AttributeError): except (TypeError, AttributeError, BytesWarning):
for s in (a,) + p: genericpath._check_arg_types('join', a, *p)
if not isinstance(s, (str, bytes)): raise
raise TypeError('join() argument must be str or bytes, not %r' %
s.__class__.__name__) from None
# Must have a mixture of text and binary data
raise TypeError("Can't mix strings and bytes in path components") from None
return path return path
...@@ -446,9 +442,9 @@ def relpath(path, start=None): ...@@ -446,9 +442,9 @@ def relpath(path, start=None):
if start is None: if start is None:
start = curdir start = curdir
try:
start_list = [x for x in abspath(start).split(sep) if x] start_list = [x for x in abspath(start).split(sep) if x]
path_list = [x for x in abspath(path).split(sep) if x] path_list = [x for x in abspath(path).split(sep) if x]
# Work out how much of the filepath is shared by start and path. # Work out how much of the filepath is shared by start and path.
i = len(commonprefix([start_list, path_list])) i = len(commonprefix([start_list, path_list]))
...@@ -456,3 +452,6 @@ def relpath(path, start=None): ...@@ -456,3 +452,6 @@ def relpath(path, start=None):
if not rel_list: if not rel_list:
return curdir return curdir
return join(*rel_list) return join(*rel_list)
except (TypeError, AttributeError, BytesWarning):
genericpath._check_arg_types('relpath', path, start)
raise
...@@ -434,6 +434,39 @@ class CommonTest(GenericTest): ...@@ -434,6 +434,39 @@ class CommonTest(GenericTest):
with support.temp_cwd(name): with support.temp_cwd(name):
self.test_abspath() self.test_abspath()
def test_join_errors(self):
# Check join() raises friendly TypeErrors.
with support.check_warnings(('', BytesWarning), quiet=True):
errmsg = "Can't mix strings and bytes in path components"
with self.assertRaisesRegex(TypeError, errmsg):
self.pathmodule.join(b'bytes', 'str')
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'):
self.pathmodule.join(42, 'str')
with self.assertRaisesRegex(TypeError, errmsg % 'int'):
self.pathmodule.join('str', 42)
with self.assertRaisesRegex(TypeError, errmsg % 'bytearray'):
self.pathmodule.join(bytearray(b'foo'), bytearray(b'bar'))
def test_relpath_errors(self):
# Check relpath() raises friendly TypeErrors.
with support.check_warnings(('', BytesWarning), quiet=True):
errmsg = "Can't mix strings and bytes in path components"
with self.assertRaisesRegex(TypeError, errmsg):
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'):
self.pathmodule.relpath(42, 'str')
with self.assertRaisesRegex(TypeError, errmsg % 'int'):
self.pathmodule.relpath('str', 42)
with self.assertRaisesRegex(TypeError, errmsg % 'bytearray'):
self.pathmodule.relpath(bytearray(b'foo'), bytearray(b'bar'))
if __name__=="__main__": if __name__=="__main__":
unittest.main() unittest.main()
...@@ -142,6 +142,8 @@ class MacPathTestCase(unittest.TestCase): ...@@ -142,6 +142,8 @@ class MacPathTestCase(unittest.TestCase):
class MacCommonTest(test_genericpath.CommonTest, unittest.TestCase): class MacCommonTest(test_genericpath.CommonTest, unittest.TestCase):
pathmodule = macpath pathmodule = macpath
test_relpath_errors = None
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -57,22 +57,6 @@ class PosixPathTest(unittest.TestCase): ...@@ -57,22 +57,6 @@ class PosixPathTest(unittest.TestCase):
self.assertEqual(posixpath.join(b"/foo/", b"bar/", b"baz/"), self.assertEqual(posixpath.join(b"/foo/", b"bar/", b"baz/"),
b"/foo/bar/baz/") b"/foo/bar/baz/")
def test_join_errors(self):
# Check posixpath.join raises friendly TypeErrors.
errmsg = "Can't mix strings and bytes in path components"
with self.assertRaisesRegex(TypeError, errmsg):
posixpath.join(b'bytes', 'str')
with self.assertRaisesRegex(TypeError, errmsg):
posixpath.join('str', b'bytes')
# regression, see #15377
errmsg = r'join\(\) argument must be str or bytes, not %r'
with self.assertRaisesRegex(TypeError, errmsg % 'NoneType'):
posixpath.join(None, 'str')
with self.assertRaisesRegex(TypeError, errmsg % 'NoneType'):
posixpath.join('str', None)
with self.assertRaisesRegex(TypeError, errmsg % 'bytearray'):
posixpath.join(bytearray(b'foo'), bytearray(b'bar'))
def test_split(self): def test_split(self):
self.assertEqual(posixpath.split("/foo/bar"), ("/foo", "bar")) self.assertEqual(posixpath.split("/foo/bar"), ("/foo", "bar"))
self.assertEqual(posixpath.split("/"), ("/", "")) self.assertEqual(posixpath.split("/"), ("/", ""))
......
...@@ -162,6 +162,9 @@ Core and Builtins ...@@ -162,6 +162,9 @@ Core and Builtins
Library Library
------- -------
- Issue #21883: os.path.join() and os.path.relpath() now raise a TypeError with
more helpful error message for unsupported or mismatched types of arguments.
- Issue #22219: The zipfile module CLI now adds entries for directories - Issue #22219: The zipfile module CLI now adds entries for directories
(including empty directories) in ZIP file. (including empty directories) in ZIP file.
......
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