Skip to content

Commit 0d5d2b1

Browse files
committed
Fix recursion on dynamic import from fake file
- happened in Python > 3.11 due to the handling of linecache in connection with a faked open_code call - fixes #1121
1 parent 4b69449 commit 0d5d2b1

File tree

4 files changed

+20
-2
lines changed

4 files changed

+20
-2
lines changed

CHANGES.md

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ The released versions correspond to PyPI releases.
1212
* the default for `FakeFilesystem.shuffle_listdir_results` will change to `True` to reflect
1313
the real filesystem behavior
1414

15+
## Unreleased
16+
17+
### Fixes
18+
* fixed handling of dynamic imports from code in the fake filesystem in Python > 3.11
19+
(see [#1121](../../issues/1121))
20+
1521
## [Version 5.8.0](https://pypi.python.org/pypi/pyfakefs/5.8.0) (2025-03-11)
1622
Adds preliminary support for Python 3.14.
1723

pyfakefs/fake_io.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def open(
8686
newline: Optional[str] = None,
8787
closefd: bool = True,
8888
opener: Optional[Callable] = None,
89+
is_fake_open_code: bool = False,
8990
) -> Union[AnyFileWrapper, IO[Any]]:
9091
"""Redirect the call to FakeFileOpen.
9192
See FakeFileOpen.call() for description.
@@ -101,6 +102,7 @@ def open(
101102
newline,
102103
closefd,
103104
opener,
105+
is_fake_open_code,
104106
)
105107

106108
if sys.version_info >= (3, 8):
@@ -118,7 +120,7 @@ def open_code(self, path):
118120
and self.filesystem.exists(path)
119121
or patch_mode == PatchMode.ON
120122
):
121-
return self.open(path, mode="rb")
123+
return self.open(path, mode="rb", is_fake_open_code=True)
122124
# mostly this is used for compiled code -
123125
# don't patch these, as the files are probably in the real fs
124126
return self._io_module.open_code(path)

pyfakefs/fake_open.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,14 @@ def fake_open(
8282
newline: Optional[str] = None,
8383
closefd: bool = True,
8484
opener: Optional[Callable] = None,
85+
is_fake_open_code: bool = False,
8586
) -> Union[AnyFileWrapper, IO[Any]]:
8687
"""Redirect the call to FakeFileOpen.
8788
See FakeFileOpen.call() for description.
8889
"""
89-
if is_called_from_skipped_module(
90+
# We don't need to check this if we are in an `open_code` call
91+
# from a faked file (and this might cause recursions in `linecache`)
92+
if not is_fake_open_code and is_called_from_skipped_module(
9093
skip_names=skip_names,
9194
case_sensitive=filesystem.is_case_sensitive,
9295
check_open_code=sys.version_info >= (3, 12),

pyfakefs/tests/fake_filesystem_unittest_test.py

+7
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,13 @@ def test_exec_module_in_fake_fs(self):
848848
self.import_foo()
849849
assert stdout.getvalue() == "hello\n"
850850

851+
def test_dynamic_import(self):
852+
# regression test for #1121
853+
self.fs.create_file("/foo.py")
854+
self.fs.create_file("/bar.py", contents="import foo")
855+
sys.path.insert(0, "")
856+
import foo # noqa
857+
851858

852859
class TestOtherFS(fake_filesystem_unittest.TestCase):
853860
def setUp(self):

0 commit comments

Comments
 (0)