mirror of
https://github.com/python/cpython.git
synced 2025-08-31 22:18:28 +00:00
gh-103363: Add follow_symlinks argument to pathlib.Path.owner()
and group()
(#107962)
This commit is contained in:
parent
2ed20d3bd8
commit
a1551b48ee
5 changed files with 95 additions and 26 deletions
|
@ -1017,15 +1017,21 @@ call fails (for example because the path doesn't exist).
|
|||
future Python release, patterns with this ending will match both files
|
||||
and directories. Add a trailing slash to match only directories.
|
||||
|
||||
.. method:: Path.group()
|
||||
.. method:: Path.group(*, follow_symlinks=True)
|
||||
|
||||
Return the name of the group owning the file. :exc:`KeyError` is raised
|
||||
Return the name of the group owning the file. :exc:`KeyError` is raised
|
||||
if the file's gid isn't found in the system database.
|
||||
|
||||
This method normally follows symlinks; to get the group of the symlink, add
|
||||
the argument ``follow_symlinks=False``.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Raises :exc:`UnsupportedOperation` if the :mod:`grp` module is not
|
||||
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
The *follow_symlinks* parameter was added.
|
||||
|
||||
|
||||
.. method:: Path.is_dir(*, follow_symlinks=True)
|
||||
|
||||
|
@ -1291,15 +1297,21 @@ call fails (for example because the path doesn't exist).
|
|||
'#!/usr/bin/env python3\n'
|
||||
|
||||
|
||||
.. method:: Path.owner()
|
||||
.. method:: Path.owner(*, follow_symlinks=True)
|
||||
|
||||
Return the name of the user owning the file. :exc:`KeyError` is raised
|
||||
Return the name of the user owning the file. :exc:`KeyError` is raised
|
||||
if the file's uid isn't found in the system database.
|
||||
|
||||
This method normally follows symlinks; to get the owner of the symlink, add
|
||||
the argument ``follow_symlinks=False``.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Raises :exc:`UnsupportedOperation` if the :mod:`pwd` module is not
|
||||
available. In previous versions, :exc:`NotImplementedError` was raised.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
The *follow_symlinks* parameter was added.
|
||||
|
||||
|
||||
.. method:: Path.read_bytes()
|
||||
|
||||
|
|
|
@ -270,9 +270,11 @@ pathlib
|
|||
(Contributed by Barney Gale in :gh:`73435`.)
|
||||
|
||||
* Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`,
|
||||
:meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`, and
|
||||
:meth:`~pathlib.Path.is_dir`.
|
||||
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.)
|
||||
:meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`,
|
||||
:meth:`~pathlib.Path.is_dir`, :meth:`~pathlib.Path.owner`,
|
||||
:meth:`~pathlib.Path.group`.
|
||||
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and
|
||||
Kamil Turek in :gh:`107962`).
|
||||
|
||||
pdb
|
||||
---
|
||||
|
|
|
@ -1319,13 +1319,13 @@ class _PathBase(PurePath):
|
|||
"""
|
||||
self._unsupported("rmdir")
|
||||
|
||||
def owner(self):
|
||||
def owner(self, *, follow_symlinks=True):
|
||||
"""
|
||||
Return the login name of the file owner.
|
||||
"""
|
||||
self._unsupported("owner")
|
||||
|
||||
def group(self):
|
||||
def group(self, *, follow_symlinks=True):
|
||||
"""
|
||||
Return the group name of the file gid.
|
||||
"""
|
||||
|
@ -1440,18 +1440,20 @@ class Path(_PathBase):
|
|||
return self.with_segments(os.path.realpath(self, strict=strict))
|
||||
|
||||
if pwd:
|
||||
def owner(self):
|
||||
def owner(self, *, follow_symlinks=True):
|
||||
"""
|
||||
Return the login name of the file owner.
|
||||
"""
|
||||
return pwd.getpwuid(self.stat().st_uid).pw_name
|
||||
uid = self.stat(follow_symlinks=follow_symlinks).st_uid
|
||||
return pwd.getpwuid(uid).pw_name
|
||||
|
||||
if grp:
|
||||
def group(self):
|
||||
def group(self, *, follow_symlinks=True):
|
||||
"""
|
||||
Return the group name of the file gid.
|
||||
"""
|
||||
return grp.getgrgid(self.stat().st_gid).gr_name
|
||||
gid = self.stat(follow_symlinks=follow_symlinks).st_gid
|
||||
return grp.getgrgid(gid).gr_name
|
||||
|
||||
if hasattr(os, "readlink"):
|
||||
def readlink(self):
|
||||
|
|
|
@ -41,6 +41,9 @@ only_nt = unittest.skipIf(os.name != 'nt',
|
|||
only_posix = unittest.skipIf(os.name == 'nt',
|
||||
'test requires a POSIX-compatible system')
|
||||
|
||||
root_in_posix = False
|
||||
if hasattr(os, 'geteuid'):
|
||||
root_in_posix = (os.geteuid() == 0)
|
||||
|
||||
#
|
||||
# Tests for the pure classes.
|
||||
|
@ -2975,27 +2978,75 @@ class PathTest(DummyPathTest, PurePathTest):
|
|||
|
||||
# XXX also need a test for lchmod.
|
||||
|
||||
@unittest.skipUnless(pwd, "the pwd module is needed for this test")
|
||||
def test_owner(self):
|
||||
p = self.cls(BASE) / 'fileA'
|
||||
uid = p.stat().st_uid
|
||||
def _get_pw_name_or_skip_test(self, uid):
|
||||
try:
|
||||
name = pwd.getpwuid(uid).pw_name
|
||||
return pwd.getpwuid(uid).pw_name
|
||||
except KeyError:
|
||||
self.skipTest(
|
||||
"user %d doesn't have an entry in the system database" % uid)
|
||||
self.assertEqual(name, p.owner())
|
||||
|
||||
@unittest.skipUnless(pwd, "the pwd module is needed for this test")
|
||||
def test_owner(self):
|
||||
p = self.cls(BASE) / 'fileA'
|
||||
expected_uid = p.stat().st_uid
|
||||
expected_name = self._get_pw_name_or_skip_test(expected_uid)
|
||||
|
||||
self.assertEqual(expected_name, p.owner())
|
||||
|
||||
@unittest.skipUnless(pwd, "the pwd module is needed for this test")
|
||||
@unittest.skipUnless(root_in_posix, "test needs root privilege")
|
||||
def test_owner_no_follow_symlinks(self):
|
||||
all_users = [u.pw_uid for u in pwd.getpwall()]
|
||||
if len(all_users) < 2:
|
||||
self.skipTest("test needs more than one user")
|
||||
|
||||
target = self.cls(BASE) / 'fileA'
|
||||
link = self.cls(BASE) / 'linkA'
|
||||
|
||||
uid_1, uid_2 = all_users[:2]
|
||||
os.chown(target, uid_1, -1)
|
||||
os.chown(link, uid_2, -1, follow_symlinks=False)
|
||||
|
||||
expected_uid = link.stat(follow_symlinks=False).st_uid
|
||||
expected_name = self._get_pw_name_or_skip_test(expected_uid)
|
||||
|
||||
self.assertEqual(expected_uid, uid_2)
|
||||
self.assertEqual(expected_name, link.owner(follow_symlinks=False))
|
||||
|
||||
def _get_gr_name_or_skip_test(self, gid):
|
||||
try:
|
||||
return grp.getgrgid(gid).gr_name
|
||||
except KeyError:
|
||||
self.skipTest(
|
||||
"group %d doesn't have an entry in the system database" % gid)
|
||||
|
||||
@unittest.skipUnless(grp, "the grp module is needed for this test")
|
||||
def test_group(self):
|
||||
p = self.cls(BASE) / 'fileA'
|
||||
gid = p.stat().st_gid
|
||||
try:
|
||||
name = grp.getgrgid(gid).gr_name
|
||||
except KeyError:
|
||||
self.skipTest(
|
||||
"group %d doesn't have an entry in the system database" % gid)
|
||||
self.assertEqual(name, p.group())
|
||||
expected_gid = p.stat().st_gid
|
||||
expected_name = self._get_gr_name_or_skip_test(expected_gid)
|
||||
|
||||
self.assertEqual(expected_name, p.group())
|
||||
|
||||
@unittest.skipUnless(grp, "the grp module is needed for this test")
|
||||
@unittest.skipUnless(root_in_posix, "test needs root privilege")
|
||||
def test_group_no_follow_symlinks(self):
|
||||
all_groups = [g.gr_gid for g in grp.getgrall()]
|
||||
if len(all_groups) < 2:
|
||||
self.skipTest("test needs more than one group")
|
||||
|
||||
target = self.cls(BASE) / 'fileA'
|
||||
link = self.cls(BASE) / 'linkA'
|
||||
|
||||
gid_1, gid_2 = all_groups[:2]
|
||||
os.chown(target, -1, gid_1)
|
||||
os.chown(link, -1, gid_2, follow_symlinks=False)
|
||||
|
||||
expected_gid = link.stat(follow_symlinks=False).st_gid
|
||||
expected_name = self._get_pw_name_or_skip_test(expected_gid)
|
||||
|
||||
self.assertEqual(expected_gid, gid_2)
|
||||
self.assertEqual(expected_name, link.group(follow_symlinks=False))
|
||||
|
||||
def test_unlink(self):
|
||||
p = self.cls(BASE) / 'fileA'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.owner`
|
||||
and :meth:`~pathlib.Path.group`, defaulting to ``True``.
|
Loading…
Add table
Add a link
Reference in a new issue