diff --git a/CHANGES.md b/CHANGES.md index 7fd08cf2..e7f76b86 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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`. diff --git a/pyfakefs/fake_file.py b/pyfakefs/fake_file.py index 9d46d772..439e91f0 100644 --- a/pyfakefs/fake_file.py +++ b/pyfakefs/fake_file.py @@ -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.""" @@ -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.""" @@ -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 diff --git a/pyfakefs/fake_pathlib.py b/pyfakefs/fake_pathlib.py index a30ae5d3..0fdaa928 100644 --- a/pyfakefs/fake_pathlib.py +++ b/pyfakefs/fake_pathlib.py @@ -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 @@ -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() @@ -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): @@ -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, diff --git a/pyfakefs/tests/fake_open_test.py b/pyfakefs/tests/fake_open_test.py index f0a8ff29..edf5d31e 100644 --- a/pyfakefs/tests/fake_open_test.py +++ b/pyfakefs/tests/fake_open_test.py @@ -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 @@ -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")) diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py index 5096ac3f..f4cc2104 100644 --- a/pyfakefs/tests/fake_pathlib_test.py +++ b/pyfakefs/tests/fake_pathlib_test.py @@ -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" @@ -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__": diff --git a/pyfakefs/tests/skip_open.py b/pyfakefs/tests/skipped_pathlib.py similarity index 82% rename from pyfakefs/tests/skip_open.py rename to pyfakefs/tests/skipped_pathlib.py index 1a9a2cea..5477f939 100644 --- a/pyfakefs/tests/skip_open.py +++ b/pyfakefs/tests/skipped_pathlib.py @@ -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()