diff --git a/CHANGES.md b/CHANGES.md index 362e6f52..04e76e5f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,10 @@ The released versions correspond to PyPi releases. ## Unreleased +### Fixes +* fixed support for `opener` introduced in previous patch release + (see [#689](../../issues/689)) + ## [Version 4.6.1](https://pypi.python.org/pypi/pyfakefs/4.6.1) (2022-07-13) Fixes incompatibility with Python 3.11 beta 4. diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 94d6d90a..3eea18ef 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -5746,7 +5746,7 @@ def call(self, file_: Union[AnyStr, int], if opener is not None: # opener shall return a file descriptor, which will be handled # here as if directly passed - file_ = opener(file_, open_modes) + file_ = opener(file_, self._open_flags_from_open_modes(open_modes)) file_object, file_path, filedes, real_path = self._handle_file_arg( file_) @@ -5770,7 +5770,7 @@ def call(self, file_: Union[AnyStr, int], if not filedes: closefd = True - if (open_modes.must_not_exist and + if (not opener and open_modes.must_not_exist and (file_object or self.filesystem.islink(file_path) and not self.filesystem.is_windows_fs)): self.filesystem.raise_os_error(errno.EEXIST, file_path) @@ -5819,6 +5819,26 @@ def call(self, file_: Union[AnyStr, int], fakefile.filedes = self.filesystem._add_open_file(fakefile) return fakefile + @staticmethod + def _open_flags_from_open_modes(open_modes: _OpenModes) -> int: + flags = 0 + if open_modes.can_read and open_modes.can_write: + flags |= os.O_RDWR + elif open_modes.can_read: + flags |= os.O_RDONLY + elif open_modes.can_write: + flags |= os.O_WRONLY + + if open_modes.append: + flags |= os.O_APPEND + if open_modes.truncate: + flags |= os.O_TRUNC + if not open_modes.must_exist and open_modes.can_write: + flags |= os.O_CREAT + if open_modes.must_not_exist and open_modes.can_write: + flags |= os.O_EXCL + return flags + def _init_file_object(self, file_object: Optional[FakeFile], file_path: AnyStr, open_modes: _OpenModes, diff --git a/pyfakefs/tests/fake_open_test.py b/pyfakefs/tests/fake_open_test.py index 399660cc..85e7f04a 100644 --- a/pyfakefs/tests/fake_open_test.py +++ b/pyfakefs/tests/fake_open_test.py @@ -949,6 +949,98 @@ def use_real_fs(self): return True +class FakeFileOpenWithOpenerTest(FakeFileOpenTestBase): + def opener(self, path, flags): + return self.os.open(path, flags) + + def test_use_opener_with_read(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='test') + with self.open(file_path, opener=self.opener) as f: + assert f.read() == 'test' + with self.assertRaises(OSError): + f.write('foo') + + def test_use_opener_with_read_plus(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='test') + with self.open(file_path, 'r+', opener=self.opener) as f: + assert f.read() == 'test' + assert f.write('bar') == 3 + with self.open(file_path) as f: + assert f.read() == 'testbar' + + def test_use_opener_with_write(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='foo') + with self.open(file_path, 'w', opener=self.opener) as f: + with self.assertRaises(OSError): + f.read() + assert f.write('bar') == 3 + with self.open(file_path) as f: + assert f.read() == 'bar' + + def test_use_opener_with_write_plus(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='test') + with self.open(file_path, 'w+', opener=self.opener) as f: + assert f.read() == '' + assert f.write('bar') == 3 + with self.open(file_path) as f: + assert f.read() == 'bar' + + def test_use_opener_with_append(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='foo') + with self.open(file_path, 'a', opener=self.opener) as f: + assert f.write('bar') == 3 + with self.assertRaises(OSError): + f.read() + with self.open(file_path) as f: + assert f.read() == 'foobar' + + def test_use_opener_with_append_plus(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='foo') + with self.open(file_path, 'a+', opener=self.opener) as f: + assert f.read() == '' + assert f.write('bar') == 3 + with self.open(file_path) as f: + assert f.read() == 'foobar' + + def test_use_opener_with_exclusive_write(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='test') + with self.assertRaises(OSError): + self.open(file_path, 'x', opener=self.opener) + + file_path = self.make_path('bar') + with self.open(file_path, 'x', opener=self.opener) as f: + assert f.write('bar') == 3 + with self.assertRaises(OSError): + f.read() + with self.open(file_path) as f: + assert f.read() == 'bar' + + def test_use_opener_with_exclusive_plus(self): + file_path = self.make_path('foo') + self.create_file(file_path, contents='test') + with self.assertRaises(OSError): + self.open(file_path, 'x+', opener=self.opener) + + file_path = self.make_path('bar') + with self.open(file_path, 'x+', opener=self.opener) as f: + assert f.write('bar') == 3 + assert f.read() == '' + with self.open(file_path) as f: + assert f.read() == 'bar' + + +class RealFileOpenWithOpenerTest(FakeFileOpenWithOpenerTest): + def use_real_fs(self): + return True + + @unittest.skipIf(sys.version_info < (3, 8), 'open_code only present since Python 3.8') class FakeFilePatchedOpenCodeTest(FakeFileOpenTestBase):