mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
gh-99547: Add isjunction methods for checking if a path is a junction (GH-99548)
This commit is contained in:
parent
c2102136be
commit
1b2de89bce
15 changed files with 182 additions and 24 deletions
|
@ -266,6 +266,15 @@ the :mod:`glob` module.)
|
||||||
Accepts a :term:`path-like object`.
|
Accepts a :term:`path-like object`.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: isjunction(path)
|
||||||
|
|
||||||
|
Return ``True`` if *path* refers to an :func:`existing <lexists>` directory
|
||||||
|
entry that is a junction. Always return ``False`` if junctions are not
|
||||||
|
supported on the current platform.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
|
||||||
.. function:: islink(path)
|
.. function:: islink(path)
|
||||||
|
|
||||||
Return ``True`` if *path* refers to an :func:`existing <exists>` directory
|
Return ``True`` if *path* refers to an :func:`existing <exists>` directory
|
||||||
|
|
|
@ -2738,6 +2738,17 @@ features:
|
||||||
This method can raise :exc:`OSError`, such as :exc:`PermissionError`,
|
This method can raise :exc:`OSError`, such as :exc:`PermissionError`,
|
||||||
but :exc:`FileNotFoundError` is caught and not raised.
|
but :exc:`FileNotFoundError` is caught and not raised.
|
||||||
|
|
||||||
|
.. method:: is_junction()
|
||||||
|
|
||||||
|
Return ``True`` if this entry is a junction (even if broken);
|
||||||
|
return ``False`` if the entry points to a regular directory, any kind
|
||||||
|
of file, a symlink, or if it doesn't exist anymore.
|
||||||
|
|
||||||
|
The result is cached on the ``os.DirEntry`` object. Call
|
||||||
|
:func:`os.path.isjunction` to fetch up-to-date information.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
.. method:: stat(*, follow_symlinks=True)
|
.. method:: stat(*, follow_symlinks=True)
|
||||||
|
|
||||||
Return a :class:`stat_result` object for this entry. This method
|
Return a :class:`stat_result` object for this entry. This method
|
||||||
|
@ -2760,8 +2771,8 @@ features:
|
||||||
Note that there is a nice correspondence between several attributes
|
Note that there is a nice correspondence between several attributes
|
||||||
and methods of ``os.DirEntry`` and of :class:`pathlib.Path`. In
|
and methods of ``os.DirEntry`` and of :class:`pathlib.Path`. In
|
||||||
particular, the ``name`` attribute has the same
|
particular, the ``name`` attribute has the same
|
||||||
meaning, as do the ``is_dir()``, ``is_file()``, ``is_symlink()``
|
meaning, as do the ``is_dir()``, ``is_file()``, ``is_symlink()``,
|
||||||
and ``stat()`` methods.
|
``is_junction()``, and ``stat()`` methods.
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
|
|
@ -891,6 +891,14 @@ call fails (for example because the path doesn't exist).
|
||||||
other errors (such as permission errors) are propagated.
|
other errors (such as permission errors) are propagated.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: Path.is_junction()
|
||||||
|
|
||||||
|
Return ``True`` if the path points to a junction, and ``False`` for any other
|
||||||
|
type of file. Currently only Windows supports junctions.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.is_mount()
|
.. method:: Path.is_mount()
|
||||||
|
|
||||||
Return ``True`` if the path is a :dfn:`mount point`: a point in a
|
Return ``True`` if the path is a :dfn:`mount point`: a point in a
|
||||||
|
|
|
@ -234,6 +234,10 @@ pathlib
|
||||||
more consistent with :func:`os.path.relpath`.
|
more consistent with :func:`os.path.relpath`.
|
||||||
(Contributed by Domenico Ragusa in :issue:`40358`.)
|
(Contributed by Domenico Ragusa in :issue:`40358`.)
|
||||||
|
|
||||||
|
* Add :meth:`pathlib.Path.is_junction` as a proxy to :func:`os.path.isjunction`.
|
||||||
|
(Contributed by Charles Machalow in :gh:`99547`.)
|
||||||
|
|
||||||
|
|
||||||
dis
|
dis
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -252,6 +256,14 @@ os
|
||||||
for a process with :func:`os.pidfd_open` in non-blocking mode.
|
for a process with :func:`os.pidfd_open` in non-blocking mode.
|
||||||
(Contributed by Kumar Aditya in :gh:`93312`.)
|
(Contributed by Kumar Aditya in :gh:`93312`.)
|
||||||
|
|
||||||
|
* Add :func:`os.path.isjunction` to check if a given path is a junction.
|
||||||
|
(Contributed by Charles Machalow in :gh:`99547`.)
|
||||||
|
|
||||||
|
* :class:`os.DirEntry` now includes an :meth:`os.DirEntry.is_junction`
|
||||||
|
method to check if the entry is a junction.
|
||||||
|
(Contributed by Charles Machalow in :gh:`99547`.)
|
||||||
|
|
||||||
|
|
||||||
shutil
|
shutil
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
|
||||||
"ismount", "expanduser","expandvars","normpath","abspath",
|
"ismount", "expanduser","expandvars","normpath","abspath",
|
||||||
"curdir","pardir","sep","pathsep","defpath","altsep",
|
"curdir","pardir","sep","pathsep","defpath","altsep",
|
||||||
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
|
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
|
||||||
"samefile", "sameopenfile", "samestat", "commonpath"]
|
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
|
||||||
|
|
||||||
def _get_bothseps(path):
|
def _get_bothseps(path):
|
||||||
if isinstance(path, bytes):
|
if isinstance(path, bytes):
|
||||||
|
@ -267,6 +267,24 @@ def islink(path):
|
||||||
return False
|
return False
|
||||||
return stat.S_ISLNK(st.st_mode)
|
return stat.S_ISLNK(st.st_mode)
|
||||||
|
|
||||||
|
|
||||||
|
# Is a path a junction?
|
||||||
|
|
||||||
|
if hasattr(os.stat_result, 'st_reparse_tag'):
|
||||||
|
def isjunction(path):
|
||||||
|
"""Test whether a path is a junction"""
|
||||||
|
try:
|
||||||
|
st = os.lstat(path)
|
||||||
|
except (OSError, ValueError, AttributeError):
|
||||||
|
return False
|
||||||
|
return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)
|
||||||
|
else:
|
||||||
|
def isjunction(path):
|
||||||
|
"""Test whether a path is a junction"""
|
||||||
|
os.fspath(path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Being true for dangling symbolic links is also useful.
|
# Being true for dangling symbolic links is also useful.
|
||||||
|
|
||||||
def lexists(path):
|
def lexists(path):
|
||||||
|
|
|
@ -1223,6 +1223,12 @@ class Path(PurePath):
|
||||||
# Non-encodable path
|
# Non-encodable path
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_junction(self):
|
||||||
|
"""
|
||||||
|
Whether this path is a junction.
|
||||||
|
"""
|
||||||
|
return self._flavour.pathmod.isjunction(self)
|
||||||
|
|
||||||
def is_block_device(self):
|
def is_block_device(self):
|
||||||
"""
|
"""
|
||||||
Whether this path is a block device.
|
Whether this path is a block device.
|
||||||
|
|
|
@ -35,7 +35,7 @@ __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
|
||||||
"samefile","sameopenfile","samestat",
|
"samefile","sameopenfile","samestat",
|
||||||
"curdir","pardir","sep","pathsep","defpath","altsep","extsep",
|
"curdir","pardir","sep","pathsep","defpath","altsep","extsep",
|
||||||
"devnull","realpath","supports_unicode_filenames","relpath",
|
"devnull","realpath","supports_unicode_filenames","relpath",
|
||||||
"commonpath"]
|
"commonpath", "isjunction"]
|
||||||
|
|
||||||
|
|
||||||
def _get_sep(path):
|
def _get_sep(path):
|
||||||
|
@ -169,6 +169,16 @@ def islink(path):
|
||||||
return False
|
return False
|
||||||
return stat.S_ISLNK(st.st_mode)
|
return stat.S_ISLNK(st.st_mode)
|
||||||
|
|
||||||
|
|
||||||
|
# Is a path a junction?
|
||||||
|
|
||||||
|
def isjunction(path):
|
||||||
|
"""Test whether a path is a junction
|
||||||
|
Junctions are not a part of posix semantics"""
|
||||||
|
os.fspath(path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Being true for dangling symbolic links is also useful.
|
# Being true for dangling symbolic links is also useful.
|
||||||
|
|
||||||
def lexists(path):
|
def lexists(path):
|
||||||
|
|
|
@ -565,18 +565,6 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
||||||
dirs_exist_ok=dirs_exist_ok)
|
dirs_exist_ok=dirs_exist_ok)
|
||||||
|
|
||||||
if hasattr(os.stat_result, 'st_file_attributes'):
|
if hasattr(os.stat_result, 'st_file_attributes'):
|
||||||
# Special handling for directory junctions to make them behave like
|
|
||||||
# symlinks for shutil.rmtree, since in general they do not appear as
|
|
||||||
# regular links.
|
|
||||||
def _rmtree_isdir(entry):
|
|
||||||
try:
|
|
||||||
st = entry.stat(follow_symlinks=False)
|
|
||||||
return (stat.S_ISDIR(st.st_mode) and not
|
|
||||||
(st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
|
|
||||||
and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT))
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _rmtree_islink(path):
|
def _rmtree_islink(path):
|
||||||
try:
|
try:
|
||||||
st = os.lstat(path)
|
st = os.lstat(path)
|
||||||
|
@ -586,12 +574,6 @@ if hasattr(os.stat_result, 'st_file_attributes'):
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
def _rmtree_isdir(entry):
|
|
||||||
try:
|
|
||||||
return entry.is_dir(follow_symlinks=False)
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _rmtree_islink(path):
|
def _rmtree_islink(path):
|
||||||
return os.path.islink(path)
|
return os.path.islink(path)
|
||||||
|
|
||||||
|
@ -605,7 +587,12 @@ def _rmtree_unsafe(path, onerror):
|
||||||
entries = []
|
entries = []
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
fullname = entry.path
|
fullname = entry.path
|
||||||
if _rmtree_isdir(entry):
|
try:
|
||||||
|
is_dir = entry.is_dir(follow_symlinks=False)
|
||||||
|
except OSError:
|
||||||
|
is_dir = False
|
||||||
|
|
||||||
|
if is_dir and not entry.is_junction():
|
||||||
try:
|
try:
|
||||||
if entry.is_symlink():
|
if entry.is_symlink():
|
||||||
# This can only happen if someone replaces
|
# This can only happen if someone replaces
|
||||||
|
|
|
@ -856,6 +856,23 @@ class TestNtpath(NtpathTestCase):
|
||||||
self.assertIsInstance(b_final_path, bytes)
|
self.assertIsInstance(b_final_path, bytes)
|
||||||
self.assertGreater(len(b_final_path), 0)
|
self.assertGreater(len(b_final_path), 0)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.")
|
||||||
|
def test_isjunction(self):
|
||||||
|
with os_helper.temp_dir() as d:
|
||||||
|
with os_helper.change_cwd(d):
|
||||||
|
os.mkdir('tmpdir')
|
||||||
|
|
||||||
|
import _winapi
|
||||||
|
try:
|
||||||
|
_winapi.CreateJunction('tmpdir', 'testjunc')
|
||||||
|
except OSError:
|
||||||
|
raise unittest.SkipTest('creating the test junction failed')
|
||||||
|
|
||||||
|
self.assertTrue(ntpath.isjunction('testjunc'))
|
||||||
|
self.assertFalse(ntpath.isjunction('tmpdir'))
|
||||||
|
self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir'))
|
||||||
|
|
||||||
|
|
||||||
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
|
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
|
||||||
pathmodule = ntpath
|
pathmodule = ntpath
|
||||||
attributes = ['relpath']
|
attributes = ['relpath']
|
||||||
|
|
|
@ -4158,6 +4158,8 @@ class TestScandir(unittest.TestCase):
|
||||||
self.assertEqual(entry.is_file(follow_symlinks=False),
|
self.assertEqual(entry.is_file(follow_symlinks=False),
|
||||||
stat.S_ISREG(entry_lstat.st_mode))
|
stat.S_ISREG(entry_lstat.st_mode))
|
||||||
|
|
||||||
|
self.assertEqual(entry.is_junction(), os.path.isjunction(entry.path))
|
||||||
|
|
||||||
self.assert_stat_equal(entry.stat(),
|
self.assert_stat_equal(entry.stat(),
|
||||||
entry_stat,
|
entry_stat,
|
||||||
os.name == 'nt' and not is_symlink)
|
os.name == 'nt' and not is_symlink)
|
||||||
|
@ -4206,6 +4208,21 @@ class TestScandir(unittest.TestCase):
|
||||||
entry = entries['symlink_file.txt']
|
entry = entries['symlink_file.txt']
|
||||||
self.check_entry(entry, 'symlink_file.txt', False, True, True)
|
self.check_entry(entry, 'symlink_file.txt', False, True, True)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.")
|
||||||
|
def test_attributes_junctions(self):
|
||||||
|
dirname = os.path.join(self.path, "tgtdir")
|
||||||
|
os.mkdir(dirname)
|
||||||
|
|
||||||
|
import _winapi
|
||||||
|
try:
|
||||||
|
_winapi.CreateJunction(dirname, os.path.join(self.path, "srcjunc"))
|
||||||
|
except OSError:
|
||||||
|
raise unittest.SkipTest('creating the test junction failed')
|
||||||
|
|
||||||
|
entries = self.get_entries(['srcjunc', 'tgtdir'])
|
||||||
|
self.assertEqual(entries['srcjunc'].is_junction(), True)
|
||||||
|
self.assertEqual(entries['tgtdir'].is_junction(), False)
|
||||||
|
|
||||||
def get_entry(self, name):
|
def get_entry(self, name):
|
||||||
path = self.bytes_path if isinstance(name, bytes) else self.path
|
path = self.bytes_path if isinstance(name, bytes) else self.path
|
||||||
entries = list(os.scandir(path))
|
entries = list(os.scandir(path))
|
||||||
|
|
|
@ -2411,6 +2411,13 @@ class _BasePathTest(object):
|
||||||
self.assertIs((P / 'linkA\udfff').is_file(), False)
|
self.assertIs((P / 'linkA\udfff').is_file(), False)
|
||||||
self.assertIs((P / 'linkA\x00').is_file(), False)
|
self.assertIs((P / 'linkA\x00').is_file(), False)
|
||||||
|
|
||||||
|
def test_is_junction(self):
|
||||||
|
P = self.cls(BASE)
|
||||||
|
|
||||||
|
with mock.patch.object(P._flavour, 'pathmod'):
|
||||||
|
self.assertEqual(P.is_junction(), P._flavour.pathmod.isjunction.return_value)
|
||||||
|
P._flavour.pathmod.isjunction.assert_called_once_with(P)
|
||||||
|
|
||||||
def test_is_fifo_false(self):
|
def test_is_fifo_false(self):
|
||||||
P = self.cls(BASE)
|
P = self.cls(BASE)
|
||||||
self.assertFalse((P / 'fileA').is_fifo())
|
self.assertFalse((P / 'fileA').is_fifo())
|
||||||
|
|
|
@ -244,6 +244,9 @@ class PosixPathTest(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
os.lstat = save_lstat
|
os.lstat = save_lstat
|
||||||
|
|
||||||
|
def test_isjunction(self):
|
||||||
|
self.assertFalse(posixpath.isjunction(ABSTFN))
|
||||||
|
|
||||||
def test_expanduser(self):
|
def test_expanduser(self):
|
||||||
self.assertEqual(posixpath.expanduser("foo"), "foo")
|
self.assertEqual(posixpath.expanduser("foo"), "foo")
|
||||||
self.assertEqual(posixpath.expanduser(b"foo"), b"foo")
|
self.assertEqual(posixpath.expanduser(b"foo"), b"foo")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add a function to os.path to check if a path is a junction: isjunction. Add similar functionality to pathlib.Path as is_junction.
|
34
Modules/clinic/posixmodule.c.h
generated
34
Modules/clinic/posixmodule.c.h
generated
|
@ -10269,6 +10269,38 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(os_DirEntry_is_junction__doc__,
|
||||||
|
"is_junction($self, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Return True if the entry is a junction; cached per entry.");
|
||||||
|
|
||||||
|
#define OS_DIRENTRY_IS_JUNCTION_METHODDEF \
|
||||||
|
{"is_junction", _PyCFunction_CAST(os_DirEntry_is_junction), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, os_DirEntry_is_junction__doc__},
|
||||||
|
|
||||||
|
static int
|
||||||
|
os_DirEntry_is_junction_impl(DirEntry *self, PyTypeObject *defining_class);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
os_DirEntry_is_junction(DirEntry *self, PyTypeObject *defining_class, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int _return_value;
|
||||||
|
|
||||||
|
if (nargs) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "is_junction() takes no arguments");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
_return_value = os_DirEntry_is_junction_impl(self, defining_class);
|
||||||
|
if ((_return_value == -1) && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = PyBool_FromLong((long)_return_value);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(os_DirEntry_stat__doc__,
|
PyDoc_STRVAR(os_DirEntry_stat__doc__,
|
||||||
"stat($self, /, *, follow_symlinks=True)\n"
|
"stat($self, /, *, follow_symlinks=True)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -11517,4 +11549,4 @@ exit:
|
||||||
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
|
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
|
||||||
/*[clinic end generated code: output=90f5e6995114e5ca input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=4192d8e09e216300 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -13633,6 +13633,25 @@ os_DirEntry_is_symlink_impl(DirEntry *self, PyTypeObject *defining_class)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
os.DirEntry.is_junction -> bool
|
||||||
|
defining_class: defining_class
|
||||||
|
/
|
||||||
|
|
||||||
|
Return True if the entry is a junction; cached per entry.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static int
|
||||||
|
os_DirEntry_is_junction_impl(DirEntry *self, PyTypeObject *defining_class)
|
||||||
|
/*[clinic end generated code: output=7061a07b0ef2cd1f input=475cd36fb7d4723f]*/
|
||||||
|
{
|
||||||
|
#ifdef MS_WINDOWS
|
||||||
|
return self->win32_lstat.st_reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
DirEntry_fetch_stat(PyObject *module, DirEntry *self, int follow_symlinks)
|
DirEntry_fetch_stat(PyObject *module, DirEntry *self, int follow_symlinks)
|
||||||
{
|
{
|
||||||
|
@ -13927,6 +13946,7 @@ static PyMethodDef DirEntry_methods[] = {
|
||||||
OS_DIRENTRY_IS_DIR_METHODDEF
|
OS_DIRENTRY_IS_DIR_METHODDEF
|
||||||
OS_DIRENTRY_IS_FILE_METHODDEF
|
OS_DIRENTRY_IS_FILE_METHODDEF
|
||||||
OS_DIRENTRY_IS_SYMLINK_METHODDEF
|
OS_DIRENTRY_IS_SYMLINK_METHODDEF
|
||||||
|
OS_DIRENTRY_IS_JUNCTION_METHODDEF
|
||||||
OS_DIRENTRY_STAT_METHODDEF
|
OS_DIRENTRY_STAT_METHODDEF
|
||||||
OS_DIRENTRY_INODE_METHODDEF
|
OS_DIRENTRY_INODE_METHODDEF
|
||||||
OS_DIRENTRY___FSPATH___METHODDEF
|
OS_DIRENTRY___FSPATH___METHODDEF
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue