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.
This commit is contained in:
parent
385328bf76
commit
3deeeb0c39
|
@ -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,20 +50,24 @@ def isabs(s):
|
||||||
|
|
||||||
|
|
||||||
def join(s, *p):
|
def join(s, *p):
|
||||||
colon = _get_colon(s)
|
try:
|
||||||
path = s
|
colon = _get_colon(s)
|
||||||
for t in p:
|
path = s
|
||||||
if (not path) or isabs(t):
|
for t in p:
|
||||||
path = t
|
if (not path) or isabs(t):
|
||||||
continue
|
path = t
|
||||||
if t[:1] == colon:
|
continue
|
||||||
t = t[1:]
|
if t[:1] == colon:
|
||||||
if colon not in path:
|
t = t[1:]
|
||||||
path = colon + path
|
if colon not in path:
|
||||||
if path[-1:] != colon:
|
path = colon + path
|
||||||
path = path + colon
|
if path[-1:] != colon:
|
||||||
path = path + t
|
path = path + colon
|
||||||
return path
|
path = path + t
|
||||||
|
return path
|
||||||
|
except (TypeError, AttributeError, BytesWarning):
|
||||||
|
genericpath._check_arg_types('join', s, *p)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def split(s):
|
def split(s):
|
||||||
|
|
|
@ -80,32 +80,36 @@ def join(path, *paths):
|
||||||
sep = '\\'
|
sep = '\\'
|
||||||
seps = '\\/'
|
seps = '\\/'
|
||||||
colon = ':'
|
colon = ':'
|
||||||
result_drive, result_path = splitdrive(path)
|
try:
|
||||||
for p in paths:
|
result_drive, result_path = splitdrive(path)
|
||||||
p_drive, p_path = splitdrive(p)
|
for p in paths:
|
||||||
if p_path and p_path[0] in seps:
|
p_drive, p_path = splitdrive(p)
|
||||||
# Second path is absolute
|
if p_path and p_path[0] in seps:
|
||||||
if p_drive or not result_drive:
|
# Second path is absolute
|
||||||
result_drive = p_drive
|
if p_drive or not result_drive:
|
||||||
result_path = p_path
|
result_drive = p_drive
|
||||||
continue
|
|
||||||
elif p_drive and p_drive != result_drive:
|
|
||||||
if p_drive.lower() != result_drive.lower():
|
|
||||||
# Different drives => ignore the first path entirely
|
|
||||||
result_drive = p_drive
|
|
||||||
result_path = p_path
|
result_path = p_path
|
||||||
continue
|
continue
|
||||||
# Same drive in different case
|
elif p_drive and p_drive != result_drive:
|
||||||
result_drive = p_drive
|
if p_drive.lower() != result_drive.lower():
|
||||||
# Second path is relative to the first
|
# Different drives => ignore the first path entirely
|
||||||
if result_path and result_path[-1] not in seps:
|
result_drive = p_drive
|
||||||
result_path = result_path + sep
|
result_path = p_path
|
||||||
result_path = result_path + p_path
|
continue
|
||||||
## add separator between UNC and non-absolute path
|
# Same drive in different case
|
||||||
if (result_path and result_path[0] not in seps and
|
result_drive = p_drive
|
||||||
result_drive and result_drive[-1:] != colon):
|
# Second path is relative to the first
|
||||||
return result_drive + sep + result_path
|
if result_path and result_path[-1] not in seps:
|
||||||
return result_drive + result_path
|
result_path = result_path + sep
|
||||||
|
result_path = result_path + p_path
|
||||||
|
## add separator between UNC and non-absolute path
|
||||||
|
if (result_path and result_path[0] not in seps and
|
||||||
|
result_drive and result_drive[-1:] != colon):
|
||||||
|
return result_drive + sep + 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,27 +562,31 @@ def relpath(path, start=None):
|
||||||
if not path:
|
if not path:
|
||||||
raise ValueError("no path specified")
|
raise ValueError("no path specified")
|
||||||
|
|
||||||
start_abs = abspath(normpath(start))
|
try:
|
||||||
path_abs = abspath(normpath(path))
|
start_abs = abspath(normpath(start))
|
||||||
start_drive, start_rest = splitdrive(start_abs)
|
path_abs = abspath(normpath(path))
|
||||||
path_drive, path_rest = splitdrive(path_abs)
|
start_drive, start_rest = splitdrive(start_abs)
|
||||||
if normcase(start_drive) != normcase(path_drive):
|
path_drive, path_rest = splitdrive(path_abs)
|
||||||
raise ValueError("path is on mount %r, start on mount %r" % (
|
if normcase(start_drive) != normcase(path_drive):
|
||||||
path_drive, start_drive))
|
raise ValueError("path is on mount %r, start on mount %r" % (
|
||||||
|
path_drive, start_drive))
|
||||||
|
|
||||||
start_list = [x for x in start_rest.split(sep) if x]
|
start_list = [x for x in start_rest.split(sep) if x]
|
||||||
path_list = [x for x in path_rest.split(sep) if x]
|
path_list = [x for x in path_rest.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 = 0
|
i = 0
|
||||||
for e1, e2 in zip(start_list, path_list):
|
for e1, e2 in zip(start_list, path_list):
|
||||||
if normcase(e1) != normcase(e2):
|
if normcase(e1) != normcase(e2):
|
||||||
break
|
break
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
|
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
|
||||||
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):
|
||||||
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,13 +442,16 @@ def relpath(path, start=None):
|
||||||
if start is None:
|
if start is None:
|
||||||
start = curdir
|
start = curdir
|
||||||
|
|
||||||
start_list = [x for x in abspath(start).split(sep) if x]
|
try:
|
||||||
path_list = [x for x in abspath(path).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]
|
||||||
|
# Work out how much of the filepath is shared by start and path.
|
||||||
|
i = len(commonprefix([start_list, path_list]))
|
||||||
|
|
||||||
# Work out how much of the filepath is shared by start and path.
|
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
|
||||||
i = len(commonprefix([start_list, path_list]))
|
if not rel_list:
|
||||||
|
return curdir
|
||||||
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
|
return join(*rel_list)
|
||||||
if not rel_list:
|
except (TypeError, AttributeError, BytesWarning):
|
||||||
return curdir
|
genericpath._check_arg_types('relpath', path, start)
|
||||||
return join(*rel_list)
|
raise
|
||||||
|
|
|
@ -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):
|
||||||
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):
|
||||||
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
|
||||||
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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue