mirror of https://github.com/python/cpython
bpo-39906: Add follow_symlinks parameter to pathlib.Path.stat() and chmod() (GH-18864)
This commit is contained in:
parent
7a7ba3d343
commit
abf964942f
|
@ -713,11 +713,14 @@ call fails (for example because the path doesn't exist).
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
|
||||||
.. method:: Path.stat()
|
.. method:: Path.stat(*, follow_symlinks=True)
|
||||||
|
|
||||||
Return a :class:`os.stat_result` object containing information about this path, like :func:`os.stat`.
|
Return a :class:`os.stat_result` object containing information about this path, like :func:`os.stat`.
|
||||||
The result is looked up at each call to this method.
|
The result is looked up at each call to this method.
|
||||||
|
|
||||||
|
This method normally follows symlinks; to stat a symlink add the argument
|
||||||
|
``follow_symlinks=False``, or use :meth:`~Path.lstat`.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
>>> p = Path('setup.py')
|
>>> p = Path('setup.py')
|
||||||
|
@ -726,10 +729,18 @@ call fails (for example because the path doesn't exist).
|
||||||
>>> p.stat().st_mtime
|
>>> p.stat().st_mtime
|
||||||
1327883547.852554
|
1327883547.852554
|
||||||
|
|
||||||
|
.. versionchanged:: 3.10
|
||||||
|
The *follow_symlinks* parameter was added.
|
||||||
|
|
||||||
.. method:: Path.chmod(mode)
|
.. method:: Path.chmod(mode, *, follow_symlinks=True)
|
||||||
|
|
||||||
Change the file mode and permissions, like :func:`os.chmod`::
|
Change the file mode and permissions, like :func:`os.chmod`.
|
||||||
|
|
||||||
|
This method normally follows symlinks. Some Unix flavours support changing
|
||||||
|
permissions on the symlink itself; on these platforms you may add the
|
||||||
|
argument ``follow_symlinks=False``, or use :meth:`~Path.lchmod`.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
>>> p = Path('setup.py')
|
>>> p = Path('setup.py')
|
||||||
>>> p.stat().st_mode
|
>>> p.stat().st_mode
|
||||||
|
@ -738,6 +749,8 @@ call fails (for example because the path doesn't exist).
|
||||||
>>> p.stat().st_mode
|
>>> p.stat().st_mode
|
||||||
33060
|
33060
|
||||||
|
|
||||||
|
.. versionchanged:: 3.10
|
||||||
|
The *follow_symlinks* parameter was added.
|
||||||
|
|
||||||
.. method:: Path.exists()
|
.. method:: Path.exists()
|
||||||
|
|
||||||
|
|
|
@ -393,8 +393,6 @@ class _NormalAccessor(_Accessor):
|
||||||
|
|
||||||
stat = os.stat
|
stat = os.stat
|
||||||
|
|
||||||
lstat = os.lstat
|
|
||||||
|
|
||||||
open = os.open
|
open = os.open
|
||||||
|
|
||||||
listdir = os.listdir
|
listdir = os.listdir
|
||||||
|
@ -403,12 +401,6 @@ class _NormalAccessor(_Accessor):
|
||||||
|
|
||||||
chmod = os.chmod
|
chmod = os.chmod
|
||||||
|
|
||||||
if hasattr(os, "lchmod"):
|
|
||||||
lchmod = os.lchmod
|
|
||||||
else:
|
|
||||||
def lchmod(self, path, mode):
|
|
||||||
raise NotImplementedError("os.lchmod() not available on this system")
|
|
||||||
|
|
||||||
mkdir = os.mkdir
|
mkdir = os.mkdir
|
||||||
|
|
||||||
unlink = os.unlink
|
unlink = os.unlink
|
||||||
|
@ -1191,12 +1183,12 @@ class Path(PurePath):
|
||||||
normed = self._flavour.pathmod.normpath(s)
|
normed = self._flavour.pathmod.normpath(s)
|
||||||
return self._from_parts((normed,))
|
return self._from_parts((normed,))
|
||||||
|
|
||||||
def stat(self):
|
def stat(self, *, follow_symlinks=True):
|
||||||
"""
|
"""
|
||||||
Return the result of the stat() system call on this path, like
|
Return the result of the stat() system call on this path, like
|
||||||
os.stat() does.
|
os.stat() does.
|
||||||
"""
|
"""
|
||||||
return self._accessor.stat(self)
|
return self._accessor.stat(self, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
def owner(self):
|
def owner(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1286,18 +1278,18 @@ class Path(PurePath):
|
||||||
if not exist_ok or not self.is_dir():
|
if not exist_ok or not self.is_dir():
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def chmod(self, mode):
|
def chmod(self, mode, *, follow_symlinks=True):
|
||||||
"""
|
"""
|
||||||
Change the permissions of the path, like os.chmod().
|
Change the permissions of the path, like os.chmod().
|
||||||
"""
|
"""
|
||||||
self._accessor.chmod(self, mode)
|
self._accessor.chmod(self, mode, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
def lchmod(self, mode):
|
def lchmod(self, mode):
|
||||||
"""
|
"""
|
||||||
Like chmod(), except if the path points to a symlink, the symlink's
|
Like chmod(), except if the path points to a symlink, the symlink's
|
||||||
permissions are changed, rather than its target's.
|
permissions are changed, rather than its target's.
|
||||||
"""
|
"""
|
||||||
self._accessor.lchmod(self, mode)
|
self.chmod(mode, follow_symlinks=False)
|
||||||
|
|
||||||
def unlink(self, missing_ok=False):
|
def unlink(self, missing_ok=False):
|
||||||
"""
|
"""
|
||||||
|
@ -1321,7 +1313,7 @@ class Path(PurePath):
|
||||||
Like stat(), except if the path points to a symlink, the symlink's
|
Like stat(), except if the path points to a symlink, the symlink's
|
||||||
status information is returned, rather than its target's.
|
status information is returned, rather than its target's.
|
||||||
"""
|
"""
|
||||||
return self._accessor.lstat(self)
|
return self.stat(follow_symlinks=False)
|
||||||
|
|
||||||
def link_to(self, target):
|
def link_to(self, target):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1828,6 +1828,21 @@ class _BasePathTest(object):
|
||||||
p.chmod(new_mode)
|
p.chmod(new_mode)
|
||||||
self.assertEqual(p.stat().st_mode, new_mode)
|
self.assertEqual(p.stat().st_mode, new_mode)
|
||||||
|
|
||||||
|
# On Windows, os.chmod does not follow symlinks (issue #15411)
|
||||||
|
@only_posix
|
||||||
|
def test_chmod_follow_symlinks_true(self):
|
||||||
|
p = self.cls(BASE) / 'linkA'
|
||||||
|
q = p.resolve()
|
||||||
|
mode = q.stat().st_mode
|
||||||
|
# Clear writable bit.
|
||||||
|
new_mode = mode & ~0o222
|
||||||
|
p.chmod(new_mode, follow_symlinks=True)
|
||||||
|
self.assertEqual(q.stat().st_mode, new_mode)
|
||||||
|
# Set writable bit
|
||||||
|
new_mode = mode | 0o222
|
||||||
|
p.chmod(new_mode, follow_symlinks=True)
|
||||||
|
self.assertEqual(q.stat().st_mode, new_mode)
|
||||||
|
|
||||||
# XXX also need a test for lchmod.
|
# XXX also need a test for lchmod.
|
||||||
|
|
||||||
def test_stat(self):
|
def test_stat(self):
|
||||||
|
@ -1839,6 +1854,17 @@ class _BasePathTest(object):
|
||||||
self.addCleanup(p.chmod, st.st_mode)
|
self.addCleanup(p.chmod, st.st_mode)
|
||||||
self.assertNotEqual(p.stat(), st)
|
self.assertNotEqual(p.stat(), st)
|
||||||
|
|
||||||
|
@os_helper.skip_unless_symlink
|
||||||
|
def test_stat_no_follow_symlinks(self):
|
||||||
|
p = self.cls(BASE) / 'linkA'
|
||||||
|
st = p.stat()
|
||||||
|
self.assertNotEqual(st, p.stat(follow_symlinks=False))
|
||||||
|
|
||||||
|
def test_stat_no_follow_symlinks_nosymlink(self):
|
||||||
|
p = self.cls(BASE) / 'fileA'
|
||||||
|
st = p.stat()
|
||||||
|
self.assertEqual(st, p.stat(follow_symlinks=False))
|
||||||
|
|
||||||
@os_helper.skip_unless_symlink
|
@os_helper.skip_unless_symlink
|
||||||
def test_lstat(self):
|
def test_lstat(self):
|
||||||
p = self.cls(BASE)/ 'linkA'
|
p = self.cls(BASE)/ 'linkA'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
:meth:`pathlib.Path.stat` and :meth:`~pathlib.Path.chmod` now accept a *follow_symlinks* keyword-only argument for consistency with corresponding functions in the :mod:`os` module.
|
Loading…
Reference in New Issue