Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -981,15 +981,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)

Expand Down Expand Up @@ -1255,15 +1261,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()

Expand Down
8 changes: 5 additions & 3 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,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`).

traceback
---------
Expand Down
10 changes: 6 additions & 4 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1252,24 +1252,26 @@ def check_eloop(e):
check_eloop(e)
return p

def owner(self):
def owner(self, *, follow_symlinks=True):
"""
Return the login name of the file owner.
"""
try:
import pwd
return pwd.getpwuid(self.stat().st_uid).pw_name
uid = self.stat(follow_symlinks=follow_symlinks).st_uid
return pwd.getpwuid(uid).pw_name
except ImportError:
raise UnsupportedOperation("Path.owner() is unsupported on this system")

def group(self):
def group(self, *, follow_symlinks=True):
"""
Return the group name of the file gid.
"""

try:
import grp
return grp.getgrgid(self.stat().st_gid).gr_name
gid = self.stat(follow_symlinks=follow_symlinks).st_gid
return grp.getgrgid(gid).gr_name
except ImportError:
raise UnsupportedOperation("Path.group() is unsupported on this system")

Expand Down
81 changes: 70 additions & 11 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ def test_is_notimplemented(self):
self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))


try:
import pwd
all_users = [u.pw_uid for u in pwd.getpwall()]
except (ImportError, AttributeError):
all_users = []


try:
import grp
all_groups = [g.gr_gid for g in grp.getgrall()]
except (ImportError, AttributeError):
all_groups = []


Comment thread
barneygale marked this conversation as resolved.
Outdated
# Make sure any symbolic links in the base test path are resolved.
BASE = os.path.realpath(TESTFN)
join = lambda *x: os.path.join(BASE, *x)
Expand All @@ -40,6 +54,9 @@ def test_is_notimplemented(self):
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.
Expand Down Expand Up @@ -2567,27 +2584,69 @@ def test_chmod_follow_symlinks_true(self):

# 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(grp, "the grp module is needed for this test")
def test_group(self):
@unittest.skipUnless(pwd, "the pwd module is needed for this test")
def test_owner(self):
p = self.cls(BASE) / 'fileA'
gid = p.stat().st_gid
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")
@unittest.skipUnless(len(all_users) > 1, "test needs more than one user")
def test_owner_no_follow_symlinks(self):
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:
name = grp.getgrgid(gid).gr_name
return 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())

@unittest.skipUnless(grp, "the grp module is needed for this test")
def test_group(self):
p = self.cls(BASE) / 'fileA'
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")
@unittest.skipUnless(len(all_groups) > 1, "test needs more than one group")
def test_group_no_follow_symlinks(self):
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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.owner`
and :meth:`~pathlib.Path.group`, defaulting to ``True``.