Skip to content

Commit

Permalink
Add support for os.fchmod
Browse files Browse the repository at this point in the history
- restrict availability for fd argument in os.chmod
  (not available in Windows for Python < 3.13)
  • Loading branch information
mrbean-bremen committed Aug 17, 2024
1 parent c954966 commit 3c5db6c
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 11 deletions.
5 changes: 3 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ The released versions correspond to PyPI releases.
## Unreleased

### Enhancements

- refactor the implementation of `additional_skip_names` parameter to make it work with more modules (see [#1023](../../issues/1023))
* the `additional_skip_names` parameter now works with more modules (see [#1023](../../issues/1023))
* added support for `os.fchmod`, allow file descriptor argument for `os.chmod` only for POSIX
for Python < 3.13

## [Version 5.6.0](https://pypi.python.org/pypi/pyfakefs/5.6.0) (2024-07-12)
Adds preliminary Python 3.13 support.
Expand Down
13 changes: 8 additions & 5 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,23 +722,24 @@ def raise_for_filepath_ending_with_separator(

def chmod(
self,
path: AnyStr,
path: Union[AnyStr, int],
mode: int,
follow_symlinks: bool = True,
force_unix_mode: bool = False,
) -> None:
"""Change the permissions of a file as encoded in integer mode.
Args:
path: (str) Path to the file.
path: (str | int) Path to the file or file descriptor.
mode: (int) Permissions.
follow_symlinks: If `False` and `path` points to a symlink,
the link itself is affected instead of the linked object.
force_unix_mode: if True and run under Windows, the mode is not
adapted for Windows to allow making dirs unreadable
"""
allow_fd = not self.is_windows_fs or sys.version_info >= (3, 13)
file_object = self.resolve(
path, follow_symlinks, allow_fd=True, check_owner=True
path, follow_symlinks, allow_fd=allow_fd, check_owner=True
)
if self.is_windows_fs and not force_unix_mode:
if mode & helpers.PERM_WRITE:
Expand Down Expand Up @@ -1723,7 +1724,7 @@ def get_object(self, file_path: AnyPath, check_read_perm: bool = True) -> FakeFi

def resolve(
self,
file_path: AnyStr,
file_path: Union[AnyStr, int],
follow_symlinks: bool = True,
allow_fd: bool = False,
check_read_perm: bool = True,
Expand Down Expand Up @@ -1753,7 +1754,9 @@ def resolve(
"""
if isinstance(file_path, int):
if allow_fd:
return self.get_open_file(file_path).get_object()
open_file = self.get_open_file(file_path).get_object()
assert isinstance(open_file, FakeFile)
return open_file
raise TypeError("path should be string, bytes or " "os.PathLike, not int")

if follow_symlinks:
Expand Down
19 changes: 18 additions & 1 deletion pyfakefs/fake_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def _umask(self) -> int:
return 0o002
else:
# under Unix, we return the real umask;
# as there is no pure getter for umask, so we have to first
# there is no pure getter for umask, so we have to first
# set a mode to get the previous one and then re-set that
mask = os.umask(0)
os.umask(mask)
Expand Down Expand Up @@ -1055,6 +1055,23 @@ def access(
mode &= ~os.W_OK
return (mode & ((stat_result.st_mode >> 6) & 7)) == mode

def fchmod(
self,
fd: int,
mode: int,
) -> None:
"""Change the permissions of an open file as encoded in integer mode.
Args:
fd: (int) File descriptor.
mode: (int) Permissions.
"""
if self.filesystem.is_windows_fs and sys.version_info < (3, 13):
raise AttributeError(
"module 'os' has no attribute 'fchmod'. " "Did you mean: 'chmod'?"
)
self.filesystem.chmod(fd, mode)

def chmod(
self,
path: AnyStr,
Expand Down
27 changes: 24 additions & 3 deletions pyfakefs/tests/fake_os_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2064,16 +2064,37 @@ def test_chmod(self):
self.assertFalse(st.st_mode & stat.S_IFDIR)

def test_chmod_uses_open_fd_as_path(self):
self.check_posix_only()
if sys.version_info < (3, 13):
self.check_posix_only()
self.skip_real_fs()
self.assert_raises_os_error(errno.EBADF, self.os.chmod, 5, 0o6543)
path = self.make_path("some_file")
self.createTestFile(path)

with self.open(path, encoding="utf8") as f:
self.os.chmod(f.filedes, 0o6543)
st = self.os.stat(f.fileno())
# use a mode that will work under Windows
self.os.chmod(f.filedes, 0o444)
st = self.os.stat(path)
self.assert_mode_equal(0o6543, st.st_mode)
self.assert_mode_equal(0o444, st.st_mode)
# fchmod should work the same way
self.os.fchmod(f.filedes, 0o666)
st = self.os.stat(path)
self.assert_mode_equal(0o666, st.st_mode)

@unittest.skipIf(
sys.version_info >= (3, 13), "also available under Windows since Python 3.13"
)
def test_chmod_uses_open_fd_as_path_not_available_under_windows(self):
self.check_windows_only()
self.skip_real_fs()
path = self.make_path("some_file")
self.createTestFile(path)
with self.open(path, encoding="utf8") as f:
with self.assertRaises(TypeError):
self.os.chmod(f.fileno(), 0o666)
with self.assertRaises(AttributeError):
self.os.fchmod(f.fileno(), 0o666)

def test_chmod_follow_symlink(self):
self.check_posix_only()
Expand Down

0 comments on commit 3c5db6c

Please sign in to comment.