Issue #26027, #27524: Add PEP 519/__fspath__() support to os and

os.path.

Thanks to Jelle Zijlstra for the initial patch against posixmodule.c.
This commit is contained in:
Brett Cannon 2016-08-26 14:44:48 -07:00
parent 6ed442c48d
commit 3f9183b5ac
11 changed files with 424 additions and 52 deletions

View File

@ -69,6 +69,12 @@ def getctime(filename):
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):

View File

@ -46,6 +46,7 @@ def normcase(s):
"""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 normcase(s):
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 @@ def join(path, *paths):
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 @@ def splitdrive(p):
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 @@ def split(p):
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 @@ def split(p):
# 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 @@ except ImportError:
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 @@ def expanduser(path):
"""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 @@ def expandvars(path):
"""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 expandvars(path):
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 @@ try:
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 @@ else: # use native Windows method on Windows
"""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 @@ supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
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 @@ def relpath(path, start=None):
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 @@ def commonpath(paths):
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'/'

View File

@ -353,7 +353,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
dirs.remove('CVS') # don't visit CVS directories
"""
top = fspath(top)
dirs = []
nondirs = []
walk_dirs = []
@ -536,6 +536,8 @@ if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
if 'CVS' in dirs:
dirs.remove('CVS') # don't visit CVS directories
"""
if not isinstance(top, int) or not hasattr(top, '__index__'):
top = fspath(top)
# Note: To guard against symlink races, we use the standard
# lstat()/open()/fstat() trick.
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)

View File

@ -49,6 +49,7 @@ def _get_sep(path):
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 normcase(s):
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 @@ def join(a, *p):
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 join(a, *p):
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 @@ def split(p):
# 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 @@ splitext.__doc__ = genericpath._splitext.__doc__
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 splitdrive(p):
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 basename(p):
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]
@ -222,6 +230,7 @@ def ismount(path):
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:
@ -267,6 +276,7 @@ _varprogb = None
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:
@ -318,6 +328,7 @@ def expandvars(path):
def normpath(path):
"""Normalize path, eliminating double slashes, etc."""
path = os.fspath(path)
if isinstance(path, bytes):
sep = b'/'
empty = b''
@ -355,6 +366,7 @@ def normpath(path):
def abspath(path):
"""Return an absolute path."""
path = os.fspath(path)
if not isabs(path):
if isinstance(path, bytes):
cwd = os.getcwdb()
@ -370,6 +382,7 @@ def abspath(path):
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)
@ -434,6 +447,7 @@ def relpath(path, start=None):
if not path:
raise ValueError("no path specified")
path = os.fspath(path)
if isinstance(path, bytes):
curdir = b'.'
sep = b'/'
@ -445,6 +459,8 @@ def relpath(path, start=None):
if start is None:
start = curdir
else:
start = os.fspath(start)
try:
start_list = [x for x in abspath(start).split(sep) if x]
@ -472,6 +488,7 @@ def commonpath(paths):
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'.'

View File

@ -450,16 +450,15 @@ class CommonTest(GenericTest):
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 @@ class CommonTest(GenericTest):
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()

View File

@ -452,5 +452,88 @@ class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
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()

View File

@ -874,10 +874,12 @@ class WalkTests(unittest.TestCase):
self.assertEqual(all[2 + flipped], (self.sub11_path, [], []))
self.assertEqual(all[3 - 2 * flipped], self.sub2_tree)
def test_walk_prune(self):
def test_walk_prune(self, walk_path=None):
if walk_path is None:
walk_path = self.walk_path
# Prune the search.
all = []
for root, dirs, files in self.walk(self.walk_path):
for root, dirs, files in self.walk(walk_path):
all.append((root, dirs, files))
# Don't descend into SUB1.
if 'SUB1' in dirs:
@ -886,11 +888,22 @@ class WalkTests(unittest.TestCase):
self.assertEqual(len(all), 2)
self.assertEqual(all[0],
(self.walk_path, ["SUB2"], ["tmp1"]))
(str(walk_path), ["SUB2"], ["tmp1"]))
all[1][-1].sort()
self.assertEqual(all[1], self.sub2_tree)
def test_file_like_path(self):
class FileLike:
def __init__(self, path):
self._path = path
def __str__(self):
return str(self._path)
def __fspath__(self):
return self._path
self.test_walk_prune(FileLike(self.walk_path))
def test_walk_bottom_up(self):
# Walk bottom-up.
all = list(self.walk(self.walk_path, topdown=False))
@ -2807,6 +2820,70 @@ class FDInheritanceTests(unittest.TestCase):
self.assertEqual(os.get_inheritable(slave_fd), False)
class PathTConverterTests(unittest.TestCase):
# tuples of (function name, allows fd arguments, additional arguments to
# function, cleanup function)
functions = [
('stat', True, (), None),
('lstat', False, (), None),
('access', True, (os.F_OK,), None),
('chflags', False, (0,), None),
('lchflags', False, (0,), None),
('open', False, (0,), getattr(os, 'close', None)),
]
def test_path_t_converter(self):
class PathLike:
def __init__(self, path):
self.path = path
def __fspath__(self):
return self.path
str_filename = support.TESTFN
bytes_filename = support.TESTFN.encode('ascii')
bytearray_filename = bytearray(bytes_filename)
fd = os.open(PathLike(str_filename), os.O_WRONLY|os.O_CREAT)
self.addCleanup(os.close, fd)
self.addCleanup(support.unlink, support.TESTFN)
int_fspath = PathLike(fd)
str_fspath = PathLike(str_filename)
bytes_fspath = PathLike(bytes_filename)
bytearray_fspath = PathLike(bytearray_filename)
for name, allow_fd, extra_args, cleanup_fn in self.functions:
with self.subTest(name=name):
try:
fn = getattr(os, name)
except AttributeError:
continue
for path in (str_filename, bytes_filename, bytearray_filename,
str_fspath, bytes_fspath):
with self.subTest(name=name, path=path):
result = fn(path, *extra_args)
if cleanup_fn is not None:
cleanup_fn(result)
with self.assertRaisesRegex(
TypeError, 'should be string, bytes'):
fn(int_fspath, *extra_args)
with self.assertRaisesRegex(
TypeError, 'should be string, bytes'):
fn(bytearray_fspath, *extra_args)
if allow_fd:
result = fn(fd, *extra_args) # should not fail
if cleanup_fn is not None:
cleanup_fn(result)
else:
with self.assertRaisesRegex(
TypeError,
'os.PathLike'):
fn(fd, *extra_args)
@unittest.skipUnless(hasattr(os, 'get_blocking'),
'needs os.get_blocking() and os.set_blocking()')
class BlockingTests(unittest.TestCase):

View File

@ -397,7 +397,7 @@ class PosixTester(unittest.TestCase):
self.assertTrue(posix.stat(fp.fileno()))
self.assertRaisesRegex(TypeError,
'should be string, bytes or integer, not',
'should be string, bytes, os.PathLike or integer, not',
posix.stat, float(fp.fileno()))
finally:
fp.close()
@ -409,16 +409,16 @@ class PosixTester(unittest.TestCase):
self.assertTrue(posix.stat(os.fsencode(support.TESTFN)))
self.assertWarnsRegex(DeprecationWarning,
'should be string, bytes or integer, not',
'should be string, bytes, os.PathLike or integer, not',
posix.stat, bytearray(os.fsencode(support.TESTFN)))
self.assertRaisesRegex(TypeError,
'should be string, bytes or integer, not',
'should be string, bytes, os.PathLike or integer, not',
posix.stat, None)
self.assertRaisesRegex(TypeError,
'should be string, bytes or integer, not',
'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(support.TESTFN))
self.assertRaisesRegex(TypeError,
'should be string, bytes or integer, not',
'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os.fsencode(support.TESTFN)))
@unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()")

View File

@ -596,5 +596,85 @@ class PosixCommonTest(test_genericpath.CommonTest, unittest.TestCase):
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()

View File

@ -142,7 +142,10 @@ Core and Builtins
Library
-------
- Issue 27598: Add Collections to collections.abc.
- Issue #26027, #27524: Add PEP 519/__fspath__() support to the os and os.path
modules. Includes code from Jelle Zijlstra.
- Issue #27598: Add Collections to collections.abc.
Patch by Ivan Levkivskyi, docs by Neil Girdhar.
- Issue #25958: Support "anti-registration" of special methods from

View File

@ -834,8 +834,11 @@ static int
path_converter(PyObject *o, void *p)
{
path_t *path = (path_t *)p;
PyObject *bytes;
PyObject *bytes, *to_cleanup = NULL;
Py_ssize_t length;
int is_index, is_buffer, is_bytes, is_unicode;
/* Default to failure, forcing explicit signaling of succcess. */
int ret = 0;
const char *narrow;
#define FORMAT_EXCEPTION(exc, fmt) \
@ -850,7 +853,7 @@ path_converter(PyObject *o, void *p)
return 1;
}
/* ensure it's always safe to call path_cleanup() */
/* Ensure it's always safe to call path_cleanup(). */
path->cleanup = NULL;
if ((o == Py_None) && path->nullable) {
@ -862,21 +865,54 @@ path_converter(PyObject *o, void *p)
return 1;
}
if (PyUnicode_Check(o)) {
/* Only call this here so that we don't treat the return value of
os.fspath() as an fd or buffer. */
is_index = path->allow_fd && PyIndex_Check(o);
is_buffer = PyObject_CheckBuffer(o);
is_bytes = PyBytes_Check(o);
is_unicode = PyUnicode_Check(o);
if (!is_index && !is_buffer && !is_unicode && !is_bytes) {
/* Inline PyOS_FSPath() for better error messages. */
_Py_IDENTIFIER(__fspath__);
PyObject *func = NULL;
func = _PyObject_LookupSpecial(o, &PyId___fspath__);
if (NULL == func) {
goto error_exit;
}
o = to_cleanup = PyObject_CallFunctionObjArgs(func, NULL);
Py_DECREF(func);
if (NULL == o) {
goto error_exit;
}
else if (PyUnicode_Check(o)) {
is_unicode = 1;
}
else if (PyBytes_Check(o)) {
is_bytes = 1;
}
else {
goto error_exit;
}
}
if (is_unicode) {
#ifdef MS_WINDOWS
const wchar_t *wide;
wide = PyUnicode_AsUnicodeAndSize(o, &length);
if (!wide) {
return 0;
goto exit;
}
if (length > 32767) {
FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows");
return 0;
goto exit;
}
if (wcslen(wide) != length) {
FORMAT_EXCEPTION(PyExc_ValueError, "embedded null character in %s");
return 0;
goto exit;
}
path->wide = wide;
@ -884,66 +920,71 @@ path_converter(PyObject *o, void *p)
path->length = length;
path->object = o;
path->fd = -1;
return 1;
ret = 1;
goto exit;
#else
if (!PyUnicode_FSConverter(o, &bytes)) {
return 0;
goto exit;
}
#endif
}
else if (PyBytes_Check(o)) {
else if (is_bytes) {
#ifdef MS_WINDOWS
if (win32_warn_bytes_api()) {
return 0;
goto exit;
}
#endif
bytes = o;
Py_INCREF(bytes);
}
else if (PyObject_CheckBuffer(o)) {
else if (is_buffer) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%s%s%s should be %s, not %.200s",
path->function_name ? path->function_name : "",
path->function_name ? ": " : "",
path->argument_name ? path->argument_name : "path",
path->allow_fd && path->nullable ? "string, bytes, integer or None" :
path->allow_fd ? "string, bytes or integer" :
path->nullable ? "string, bytes or None" :
"string or bytes",
path->allow_fd && path->nullable ? "string, bytes, os.PathLike, "
"integer or None" :
path->allow_fd ? "string, bytes, os.PathLike or integer" :
path->nullable ? "string, bytes, os.PathLike or None" :
"string, bytes or os.PathLike",
Py_TYPE(o)->tp_name)) {
return 0;
goto exit;
}
#ifdef MS_WINDOWS
if (win32_warn_bytes_api()) {
return 0;
goto exit;
}
#endif
bytes = PyBytes_FromObject(o);
if (!bytes) {
return 0;
goto exit;
}
}
else if (path->allow_fd && PyIndex_Check(o)) {
if (!_fd_converter(o, &path->fd)) {
return 0;
goto exit;
}
path->wide = NULL;
path->narrow = NULL;
path->length = 0;
path->object = o;
return 1;
ret = 1;
goto exit;
}
else {
error_exit:
PyErr_Format(PyExc_TypeError, "%s%s%s should be %s, not %.200s",
path->function_name ? path->function_name : "",
path->function_name ? ": " : "",
path->argument_name ? path->argument_name : "path",
path->allow_fd && path->nullable ? "string, bytes, integer or None" :
path->allow_fd ? "string, bytes or integer" :
path->nullable ? "string, bytes or None" :
"string or bytes",
path->allow_fd && path->nullable ? "string, bytes, os.PathLike, "
"integer or None" :
path->allow_fd ? "string, bytes, os.PathLike or integer" :
path->nullable ? "string, bytes, os.PathLike or None" :
"string, bytes or os.PathLike",
Py_TYPE(o)->tp_name);
return 0;
goto exit;
}
length = PyBytes_GET_SIZE(bytes);
@ -951,7 +992,7 @@ path_converter(PyObject *o, void *p)
if (length > MAX_PATH-1) {
FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows");
Py_DECREF(bytes);
return 0;
goto exit;
}
#endif
@ -959,7 +1000,7 @@ path_converter(PyObject *o, void *p)
if ((size_t)length != strlen(narrow)) {
FORMAT_EXCEPTION(PyExc_ValueError, "embedded null character in %s");
Py_DECREF(bytes);
return 0;
goto exit;
}
path->wide = NULL;
@ -969,12 +1010,15 @@ path_converter(PyObject *o, void *p)
path->fd = -1;
if (bytes == o) {
Py_DECREF(bytes);
return 1;
ret = 1;
}
else {
path->cleanup = bytes;
return Py_CLEANUP_SUPPORTED;
ret = Py_CLEANUP_SUPPORTED;
}
exit:
Py_XDECREF(to_cleanup);
return ret;
}
static void
@ -12329,6 +12373,8 @@ error:
PyObject *
PyOS_FSPath(PyObject *path)
{
/* For error message reasons, this function is manually inlined in
path_converter(). */
_Py_IDENTIFIER(__fspath__);
PyObject *func = NULL;
PyObject *path_repr = NULL;