Skip to content

Commit

Permalink
Handle other pathlib.Path.open calls in read_text, read_bytes, write_…
Browse files Browse the repository at this point in the history
…text and write_bytes (#1022)

* Add more regression tests for #1012
* Resolve pytype errors by adding __enter__, __exit__, read and write to file wrappers
  • Loading branch information
sassanh committed May 26, 2024
1 parent c23e3de commit 9689317
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ The released versions correspond to PyPI releases.
* remove support for patching legacy modules `scandir` and `pathlib2`
* remove support for Python 3.7

## Unreleased

### Fixes

* Use real open calls for remaining `pathlib` functions so that it works nice with skippedmodules (see #1012)

## [Version 5.5.0](https://pypi.python.org/pypi/pyfakefs/5.5.0) (2024-05-12)
Deprecates the usage of `pathlib2` and `scandir`.

Expand Down
39 changes: 39 additions & 0 deletions pyfakefs/fake_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,10 @@ def fileno(self) -> int:
def read(self, n: int = -1) -> bytes:
return cast(bytes, self._stream_object.read())

def write(self, contents: bytes) -> int:
self._stream_object.write(cast(str, contents))
return len(contents)

def close(self) -> None:
"""We do not support closing standard streams."""

Expand All @@ -1267,6 +1271,19 @@ def close_fd(self, fd: Optional[int]) -> None:
def is_stream(self) -> bool:
return True

def __enter__(self) -> "StandardStreamWrapper":
"""To support usage of this standard stream with the 'with' statement."""
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""To support usage of this standard stream with the 'with' statement."""
self.close()


class FakeDirWrapper:
"""Wrapper for a FakeDirectory object to be used in open files list."""
Expand Down Expand Up @@ -1302,6 +1319,28 @@ def close_fd(self, fd: Optional[int]) -> None:
assert fd is not None
self._filesystem.close_open_file(fd)

def read(self, numBytes: int = -1) -> bytes:
"""Read from the directory."""
return self.file_object.read(numBytes)

def write(self, contents: bytes) -> int:
"""Write to the directory."""
self.file_object.write(contents)
return len(contents)

def __enter__(self) -> "FakeDirWrapper":
"""To support usage of this fake directory with the 'with' statement."""
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""To support usage of this fake directory with the 'with' statement."""
self.close()


class FakePipeWrapper:
"""Wrapper for a read or write descriptor of a real pipe object to be
Expand Down
33 changes: 23 additions & 10 deletions pyfakefs/fake_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

from pyfakefs import fake_scandir
from pyfakefs.fake_filesystem import FakeFilesystem
from pyfakefs.fake_open import FakeFileOpen, fake_open
from pyfakefs.fake_open import fake_open
from pyfakefs.fake_os import FakeOsModule, use_original_os
from pyfakefs.helpers import IS_PYPY

Expand Down Expand Up @@ -650,17 +650,25 @@ def read_bytes(self):
OSError: if the target object is a directory, the path is
invalid or permission is denied.
"""
with FakeFileOpen(self.filesystem)(
self._path(), mode="rb"
) as f: # pytype: disable=attribute-error
with fake_open(
self.filesystem,
self.skip_names,
self._path(),
mode="rb",
) as f:
return f.read()

def read_text(self, encoding=None, errors=None):
"""
Open the fake file in text mode, read it, and close the file.
"""
with FakeFileOpen(self.filesystem)( # pytype: disable=attribute-error
self._path(), mode="r", encoding=encoding, errors=errors
with fake_open(
self.filesystem,
self.skip_names,
self._path(),
mode="r",
encoding=encoding,
errors=errors,
) as f:
return f.read()

Expand All @@ -674,9 +682,12 @@ def write_bytes(self, data):
"""
# type-check for the buffer interface before truncating the file
view = memoryview(data)
with FakeFileOpen(self.filesystem)(
self._path(), mode="wb"
) as f: # pytype: disable=attribute-error
with fake_open(
self.filesystem,
self.skip_names,
self._path(),
mode="wb",
) as f:
return f.write(view)

def write_text(self, data, encoding=None, errors=None, newline=None):
Expand All @@ -701,7 +712,9 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
raise TypeError(
"write_text() got an unexpected " "keyword argument 'newline'"
)
with FakeFileOpen(self.filesystem)( # pytype: disable=attribute-error
with fake_open(
self.filesystem,
self.skip_names,
self._path(),
mode="w",
encoding=encoding,
Expand Down
6 changes: 3 additions & 3 deletions pyfakefs/tests/fake_open_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from pyfakefs.helpers import is_root, IS_PYPY, get_locale_encoding
from pyfakefs.fake_io import FakeIoModule
from pyfakefs.fake_filesystem_unittest import PatchMode, Patcher
from pyfakefs.tests.skip_open import read_open
from pyfakefs.tests.skipped_pathlib import read_open
from pyfakefs.tests.test_utils import RealFsTestCase


Expand Down Expand Up @@ -2107,8 +2107,8 @@ def use_real_fs(self):

class SkipOpenTest(unittest.TestCase):
def test_open_in_skipped_module(self):
with Patcher(additional_skip_names=["skip_open"]):
contents = read_open("skip_open.py")
with Patcher(additional_skip_names=["skipped_pathlib"]):
contents = read_open("skipped_pathlib.py")
self.assertTrue(contents.startswith("# Licensed under the Apache License"))


Expand Down
29 changes: 22 additions & 7 deletions pyfakefs/tests/fake_pathlib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@

from pyfakefs import fake_pathlib, fake_filesystem, fake_filesystem_unittest, fake_os
from pyfakefs.fake_filesystem import OSType
from pyfakefs.fake_filesystem_unittest import Patcher
from pyfakefs.helpers import IS_PYPY, is_root
from pyfakefs.tests.skip_open import read_pathlib
from pyfakefs.tests.skipped_pathlib import (
read_bytes_pathlib,
read_pathlib,
read_text_pathlib,
)
from pyfakefs.tests.test_utils import RealFsTestMixin

is_windows = sys.platform == "win32"
Expand Down Expand Up @@ -1314,12 +1317,24 @@ def test_posix_pure_path_parsing(self):
)


class SkipOpenTest(unittest.TestCase):
def test_open_pathlib_in_skipped_module(self):
class SkipPathlibTest(fake_filesystem_unittest.TestCase):
def setUp(self):
self.setUpPyfakefs(additional_skip_names=["skipped_pathlib"])

def test_open_in_skipped_module(self):
# regression test for #1012
contents = read_pathlib("skipped_pathlib.py")
self.assertTrue(contents.startswith("# Licensed under the Apache License"))

def test_read_text_in_skipped_module(self):
# regression test for #1012
contents = read_text_pathlib("skipped_pathlib.py")
self.assertTrue(contents.startswith("# Licensed under the Apache License"))

def test_read_bytes_in_skipped_module(self):
# regression test for #1012
with Patcher(additional_skip_names=["skip_open"]):
contents = read_pathlib("skip_open.py")
self.assertTrue(contents.startswith("# Licensed under the Apache License"))
contents = read_bytes_pathlib("skipped_pathlib.py")
self.assertTrue(contents.startswith(b"# Licensed under the Apache License"))


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def read_pathlib(file_name):
return (Path(__file__).parent / file_name).open("r").read()


def read_text_pathlib(file_name):
return (Path(__file__).parent / file_name).read_text()


def read_bytes_pathlib(file_name):
return (Path(__file__).parent / file_name).read_bytes()


def read_open(file_name):
with open(os.path.join(os.path.dirname(__file__), file_name)) as f:
return f.read()

0 comments on commit 9689317

Please sign in to comment.