diff --git a/CHANGES.md b/CHANGES.md index 57ad071e..05532957 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,11 +18,12 @@ The released versions correspond to PyPI releases. * the `additional_skip_names` parameter now works with more modules (see [#1023](../../issues/1023)) * added support for `os.fchmod`, allow file descriptor argument for `os.chmod` only for POSIX for Python < 3.13 +* avoid reloading `glob` in Python 3.13 (did affect test performance) ### Fixes * removing files while iterating over `scandir` results is now possible (see [#1051](../../issues/1051)) * fake `pathlib.PosixPath` and `pathlib.WindowsPath` now behave more like in the real filesystem - (see [#1055](../../issues/1055)) + (see [#1053](../../issues/1053)) ## [Version 5.6.0](https://pypi.python.org/pypi/pyfakefs/5.6.0) (2024-07-12) Adds preliminary Python 3.13 support. diff --git a/pyfakefs/fake_filesystem_unittest.py b/pyfakefs/fake_filesystem_unittest.py index ac53b6c6..6465b420 100644 --- a/pyfakefs/fake_filesystem_unittest.py +++ b/pyfakefs/fake_filesystem_unittest.py @@ -593,9 +593,6 @@ def __init__( self.modules_to_reload: List[ModuleType] = ( [] if sys.platform == "win32" else [tempfile] ) - if sys.version_info >= (3, 13): - # need to reload glob which holds references to os functions - self.modules_to_reload.append(glob) if modules_to_reload is not None: self.modules_to_reload.extend(modules_to_reload) self.patch_default_args = patch_default_args @@ -983,6 +980,7 @@ def start_patching(self) -> None: self.patch_modules() self.patch_functions() self.patch_defaults() + self._set_glob_os_functions() self._dyn_patcher = DynamicPatcher(self) sys.meta_path.insert(0, self._dyn_patcher) @@ -993,6 +991,13 @@ def start_patching(self) -> None: self._dyn_patcher.cleanup() sys.meta_path.pop(0) + def _set_glob_os_functions(self): + # make sure the os functions cached in glob are patched + if sys.version_info >= (3, 13): + globber = glob._StringGlobber # type: ignore[module-attr] + globber.lstat = staticmethod(os.lstat) + globber.scandir = staticmethod(os.scandir) + def patch_functions(self) -> None: assert self._stubs is not None for (name, ft_name, ft_mod), modules in self.FS_FUNCTIONS.items(): @@ -1073,6 +1078,7 @@ def stop_patching(self, temporary=False) -> None: if self.linecache_updatecache is not None: linecache.updatecache = self.linecache_updatecache linecache.checkcache = self.linecache_checkcache + self._set_glob_os_functions() @property def is_patching(self): diff --git a/pyfakefs/fake_pathlib.py b/pyfakefs/fake_pathlib.py index c437eb5a..b9a1e773 100644 --- a/pyfakefs/fake_pathlib.py +++ b/pyfakefs/fake_pathlib.py @@ -32,7 +32,6 @@ import errno import fnmatch import functools -import glob import inspect import ntpath import os @@ -640,15 +639,6 @@ def _init(self, template=None): # only needed until Python 3.8 self._closed = False - if sys.version_info >= (3, 13): - - def _glob_selector(self, parts, case_sensitive, recurse_symlinks): - # make sure we get the patched version of the globber - self._globber = glob._StringGlobber # type: ignore[module-attr] - return super()._glob_selector( # type: ignore[attribute-error] - parts, case_sensitive, recurse_symlinks - ) - def _path(self): """Returns the underlying path string as used by the fake filesystem. diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py index afc438aa..73b6f76a 100644 --- a/pyfakefs/tests/fake_pathlib_test.py +++ b/pyfakefs/tests/fake_pathlib_test.py @@ -822,11 +822,11 @@ def test_glob(self): self.create_file(self.make_path("foo", "setup.pyc")) path = self.path(self.make_path("foo")) self.assertEqual( - sorted(path.glob("*.py")), [ self.path(self.make_path("foo", "all_tests.py")), self.path(self.make_path("foo", "setup.py")), ], + sorted(path.glob("*.py")), ) @unittest.skipIf(not is_windows, "Windows specific test") @@ -837,12 +837,12 @@ def test_glob_case_windows(self): self.create_file(self.make_path("foo", "example.Py")) path = self.path(self.make_path("foo")) self.assertEqual( - sorted(path.glob("*.py")), [ self.path(self.make_path("foo", "all_tests.PY")), self.path(self.make_path("foo", "example.Py")), self.path(self.make_path("foo", "setup.py")), ], + sorted(path.glob("*.py")), ) @unittest.skipIf(is_windows, "Posix specific test")