diff --git a/Doc/library/os.rst b/Doc/library/os.rst --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1494,8 +1494,11 @@ .. function:: chown(path, uid, gid) - Change the owner and group id of *path* to the numeric *uid* and *gid*. To leave - one of the ids unchanged, set it to -1. + Change the owner and group id of *path* to the numeric *uid* and *gid*. To + leave one of the ids unchanged, set it to -1. + + :mod:`shutil` has an enhanced version, :func:`shutil.chown`, that accepts + also the owner and group name. Availability: Unix. diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -165,6 +165,18 @@ Otherwise, copy src (with :func:`copy2`) to the dst and then remove src. +.. function:: chown(filename, user=None, group=None) + + Change owner *user* and *group* of the given *filename*. + + *user* can either be a system user name or a uid; the same applies to + *group*. To leave one of the two unchanged, set it to *None* or omit it (but + in this case, the other has to be a named argument). + + It's an enhancement of :func:`os.chown` in that it allows to specify the + names of the *user* and/or the *group*, and not only their uid/gid. + + .. exception:: Error This exception collects exceptions that raised during a multi-file operation. For diff --git a/Lib/shutil.py b/Lib/shutil.py --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -754,3 +754,40 @@ func = _UNPACK_FORMATS[format][1] kwargs = dict(_UNPACK_FORMATS[format][2]) func(filename, extract_dir, **kwargs) + +def chown(filename, user=None, group=None): + """Change owner user and group of the given `filename`. + + `user` and `group` can either be the uid/gid or the user/group names, and in + that case, they are converted to their respective uid/gid. + """ + + if user is None and group is None: + raise ValueError("user and/or group must be set") + + _user = None + _group = None + + + # if is None, let's pass -1 to os.chown(), that means "don't change it" + if user is None: + _user = -1 + else: + # user can either be an int (the uid) or a string (the system username), + # in the latter case, we have to convert it to the uid + try: + _user = int(user) + except ValueError: + _user = _get_uid(user) + + # same goes for the group + if group is None: + _group = -1 + else: + try: + _group = int(group) + except ValueError: + _group = _get_gid(group) + + # at the end, call chown, that's actually going to change the user/group + os.chown(filename, _user, _group) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -729,6 +729,28 @@ unregister_unpack_format('Boo2') self.assertEqual(get_unpack_formats(), formats) + @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") + def test_chown(self): + + filename = tempfile.mktemp() + self._write_data(filename, 'testing chown function') + + # no other argument than the filename + with self.assertRaises(ValueError) as cm: + shutil.chown(filename) + + # three succesfull call, using uid/gid + shutil.chown(filename, os.getuid(), os.getgid()) + shutil.chown(filename, user=os.getuid()) + shutil.chown(filename, group=os.getgid()) + + # let's use user & group names + _u = pwd.getpwuid(os.getuid())[0] + _g = grp.getgrgid(os.getgid())[0] + shutil.chown(filename, _u, _g) + + # cleanup + os.remove(filename) class TestMove(unittest.TestCase):