diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py index 20c3955d..373636e9 100644 --- a/pyfakefs/fake_filesystem_unittest.py +++ b/pyfakefs/fake_filesystem_unittest.py @@ -130,6 +130,44 @@ def stop_patching(self): tempfile.tempdir = None +class LinecachePatcher: + """Handles linecache patching for Python >= 3.13.""" + + def __init__(self): + self.linecache_checkcache = None + self.linecache_updatecache = None + + def checkcache(self, filename=None): + """Calls the original linecache.checkcache making sure no fake OS calls + are used.""" + print(f"checkcache({filename})") + with use_original_os(): + return self.linecache_checkcache(filename) + + def updatecache(self, filename, module_globals=None): + """Calls the original linecache.updatecache making sure no fake OS calls + are used.""" + print(f"updatecache({filename})") + with use_original_os(): + return self.linecache_updatecache(filename, module_globals) + + def start_patching(self): + if self.linecache_updatecache is not None: + return + if sys.version_info >= (3, 13): + # in linecache, 'os' is now imported locally, which involves the + # dynamic patcher, therefore we patch the affected functions + self.linecache_updatecache = linecache.updatecache + linecache.updatecache = self.updatecache + self.linecache_checkcache = linecache.checkcache + linecache.checkcache = self.checkcache + + def stop_patching(self): + if self.linecache_updatecache is not None: + linecache.updatecache = self.linecache_updatecache + linecache.checkcache = self.linecache_checkcache + + def patchfs( _func: Optional[Callable] = None, *, @@ -609,8 +647,7 @@ def __init__( # save the original open function for use in pytest plugin self.original_open = open self.patch_open_code = patch_open_code - self.linecache_updatecache = None - self.linecache_checkcache = None + self.linecache_patcher = LinecachePatcher() self.tempfile_patcher = TempfilePatcher() if additional_skip_names is not None: @@ -683,18 +720,6 @@ def __init__( self._patching = False self._paused = False - def checkcache(self, filename=None): - """Calls the original linecache.checkcache making sure no fake OS calls - are used.""" - with use_original_os(): - return self.linecache_checkcache(filename) - - def updatecache(self, filename, module_globals=None): - """Calls the original linecache.updatecache making sure no fake OS calls - are used.""" - with use_original_os(): - return self.linecache_updatecache(filename, module_globals) - @classmethod def clear_fs_cache(cls) -> None: """Clear the module cache.""" @@ -1003,14 +1028,7 @@ def start_patching(self) -> None: self._patching = True self._paused = False - if sys.version_info >= (3, 13): - # in linecache, 'os' is now imported locally, which involves the - # dynamic patcher, therefore we patch the affected functions - self.linecache_updatecache = linecache.updatecache - linecache.updatecache = self.updatecache - self.linecache_checkcache = linecache.checkcache - linecache.checkcache = self.checkcache - + self.linecache_patcher.start_patching() self.tempfile_patcher.start_patching() self.patch_modules() @@ -1112,9 +1130,7 @@ def stop_patching(self, temporary=False) -> None: self._dyn_patcher.cleanup() sys.meta_path.pop(0) self.tempfile_patcher.stop_patching() - if self.linecache_updatecache is not None: - linecache.updatecache = self.linecache_updatecache - linecache.checkcache = self.linecache_checkcache + self.linecache_patcher.stop_patching() self._set_glob_os_functions() @property