Repository: pytest-dev/pyfakefs
Pull request #1111 (2025-01-13 20:51:24)

Fix cleanup handler documentation
- fix and expand the documentation for cleanup handlers
- add a convenience handler for module reload and a respective test

This came up in connection with #1105, where such a handler can resolve the problem.

#### Tasks
- [x] Unit tests added that reproduce the issue or prove feature is working
- [x] Fix or feature added
- [x] Entry to release notes added
- [x] Pre-commit CI shows no errors
- [x] Unit tests passing
- [x] For documentation changes: The Read the Docs preview builds and looks as expected



Patch:
diff --git a/CHANGES.md b/CHANGES.md
index b381f4a..27fb6c3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -18,6 +18,8 @@ The released versions correspond to PyPI releases.
 * fixed a problem with module and session scoped fixtures in Python 3.13
   (see [#1101](../../issues/1101))
 * fixed handling of `cwd` if set to a `pathlib.Path` (see [#1108](../../issues/1108))
+* fixed documentation for cleanup handlers, added convenience handler `reload_cleanup_handler`
+  (see [#1105](../../issues/1105))
 
 ## [Version 5.7.3](https://pypi.python.org/pypi/pyfakefs/5.7.3) (2024-12-15)
 Fixes a regression in version 5.7.3.
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index f636c5b..940c9c0 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -374,9 +374,25 @@ be added to the patcher:
 
   @pytest.fixture
   def my_fs():
-      with Patcher():
-          patcher["modulename"] = handler_no_cleanup
-          yield
+      with Patcher() as patcher:
+          patcher.cleanup_handlers["modulename"] = handler_no_cleanup
+          yield patcher.fs
+
+
+A specific problem are modules that use filesystem functions and are imported by other modules locally
+(e.g. inside a function). These kinds of modules are not correctly reset and need to be reloaded manually.
+For this case, the cleanup handler `reload_cleanup_handler` in `pyfakefs.helpers` can be used:
+
+.. code:: python
+
+  from pyfakefs.helpers import reload_cleanup_handler
+
+
+  @pytest.fixture
+  def my_fs():
+      with Patcher() as patcher:
+          patcher.cleanup_handlers["modulename"] = reload_cleanup_handler
+          yield patcher.fs
 
 As this may not be trivial, we recommend to write an issue in ``pyfakefs`` with a reproducible example.
 We will analyze the problem, and if we find a solution we will either get this fixed in ``pyfakefs``
diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py
index 6db8d96..2de8a8f 100644
--- a/pyfakefs/helpers.py
+++ b/pyfakefs/helpers.py
@@ -13,6 +13,7 @@
 """Helper classes use for fake file system implementation."""
 
 import ctypes
+import importlib
 import io
 import locale
 import os
@@ -539,3 +540,12 @@ def is_called_from_skipped_module(
         ):
             return True
     return False
+
+
+def reload_cleanup_handler(name):
+    """Cleanup handler that reloads the module with the given name.
+    Maybe needed in cases where a module is imported locally.
+    """
+    if name in sys.modules:
+        importlib.reload(sys.modules[name])
+    return True


Test patch:
diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml
index 8c575e1..162b1d4 100644
--- a/.github/workflows/testsuite.yml
+++ b/.github/workflows/testsuite.yml
@@ -32,17 +32,18 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macOS-latest, windows-latest]
-        python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
+        python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
         include:
           - python-version: "pypy-3.7"
-            os: ubuntu-latest
+            os: ubuntu-22.04
           - python-version: "pypy-3.9"
             os: ubuntu-latest
           - python-version: "pypy-3.10"
             os: ubuntu-latest
-        exclude:
-          - python-version: 3.7
-            os: macOS-latest
+          - python-version: "3.7"
+            os: ubuntu-22.04
+          - python-version: "3.7"
+            os: windows-latest
 
     steps:
     - uses: actions/checkout@v4
diff --git a/pyfakefs/pytest_tests/lib_using_pathlib.py b/pyfakefs/pytest_tests/lib_using_pathlib.py
new file mode 100644
index 0000000..944c471
--- /dev/null
+++ b/pyfakefs/pytest_tests/lib_using_pathlib.py
@@ -0,0 +1,5 @@
+import pathlib
+
+
+def use_pathlib(path: str):
+    return pathlib.Path(path)
diff --git a/pyfakefs/pytest_tests/local_import.py b/pyfakefs/pytest_tests/local_import.py
new file mode 100644
index 0000000..afe2676
--- /dev/null
+++ b/pyfakefs/pytest_tests/local_import.py
@@ -0,0 +1,4 @@
+def load(path: str) -> str:
+    from pyfakefs.pytest_tests import lib_using_pathlib
+
+    return lib_using_pathlib.use_pathlib(path)
diff --git a/pyfakefs/pytest_tests/test_reload_local_import.py b/pyfakefs/pytest_tests/test_reload_local_import.py
new file mode 100644
index 0000000..a29e06e
--- /dev/null
+++ b/pyfakefs/pytest_tests/test_reload_local_import.py
@@ -0,0 +1,26 @@
+import pytest
+
+from pyfakefs.fake_filesystem_unittest import Patcher
+from pyfakefs.fake_pathlib import FakePathlibModule
+from pyfakefs.helpers import reload_cleanup_handler
+from pyfakefs.pytest_tests import local_import
+
+
+@pytest.fixture
+def test_fs():
+    with Patcher() as patcher:
+        patcher.cleanup_handlers["pyfakefs.pytest_tests.lib_using_pathlib"] = (
+            reload_cleanup_handler
+        )
+        yield patcher.fs
+
+
+class TestReloadCleanupHandler:
+    def test1(self, test_fs):
+        path = local_import.load("some_path")
+        assert isinstance(path, FakePathlibModule.Path)
+
+    def test2(self):
+        path = local_import.load("some_path")
+        # will fail without reload handler
+        assert not isinstance(path, FakePathlibModule.Path)