4747import sys
4848import tempfile
4949import tokenize
50+ from enum import Enum
5051from importlib .abc import Loader , MetaPathFinder
5152from types import ModuleType , TracebackType , FunctionType
5253from typing import (
9495PATH_MODULE = "ntpath" if sys .platform == "win32" else "posixpath"
9596
9697
98+ class ModuleCleanupMode (Enum ):
99+ """Defines the behavior of module cleanup on dynamic patcher shutdown."""
100+
101+ AUTO = 1
102+ DELETE = 2
103+ RELOAD = 3
104+
105+
97106def patchfs (
98107 _func : Optional [Callable ] = None ,
99108 * ,
@@ -106,6 +115,7 @@ def patchfs(
106115 patch_default_args : bool = False ,
107116 use_cache : bool = True ,
108117 use_dynamic_patch : bool = True ,
118+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
109119) -> Callable :
110120 """Convenience decorator to use patcher with additional parameters in a
111121 test function.
@@ -134,6 +144,7 @@ def wrapped(*args, **kwargs):
134144 patch_default_args = patch_default_args ,
135145 use_cache = use_cache ,
136146 use_dynamic_patch = use_dynamic_patch ,
147+ module_cleanup_mode = module_cleanup_mode ,
137148 ) as p :
138149 args = list (args )
139150 args .append (p .fs )
@@ -170,6 +181,7 @@ def load_doctests(
170181 patch_open_code : PatchMode = PatchMode .OFF ,
171182 patch_default_args : bool = False ,
172183 use_dynamic_patch : bool = True ,
184+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
173185) -> TestSuite : # pylint:disable=unused-argument
174186 """Load the doctest tests for the specified module into unittest.
175187 Args:
@@ -190,6 +202,7 @@ def load_doctests(
190202 patch_open_code = patch_open_code ,
191203 patch_default_args = patch_default_args ,
192204 use_dynamic_patch = use_dynamic_patch ,
205+ module_cleanup_mode = module_cleanup_mode ,
193206 is_doc_test = True ,
194207 )
195208 assert Patcher .DOC_PATCHER is not None
@@ -270,6 +283,7 @@ def setUpPyfakefs(
270283 patch_default_args : bool = False ,
271284 use_cache : bool = True ,
272285 use_dynamic_patch : bool = True ,
286+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
273287 ) -> None :
274288 """Bind the file-related modules to the :py:class:`pyfakefs` fake file
275289 system instead of the real file system. Also bind the fake `open()`
@@ -302,6 +316,7 @@ def setUpPyfakefs(
302316 patch_default_args = patch_default_args ,
303317 use_cache = use_cache ,
304318 use_dynamic_patch = use_dynamic_patch ,
319+ module_cleanup_mode = module_cleanup_mode ,
305320 )
306321
307322 self ._patcher .setUp ()
@@ -319,6 +334,7 @@ def setUpClassPyfakefs(
319334 patch_default_args : bool = False ,
320335 use_cache : bool = True ,
321336 use_dynamic_patch : bool = True ,
337+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
322338 ) -> None :
323339 """Similar to :py:func:`setUpPyfakefs`, but as a class method that
324340 can be used in `setUpClass` instead of in `setUp`.
@@ -357,6 +373,7 @@ def setUpClassPyfakefs(
357373 patch_default_args = patch_default_args ,
358374 use_cache = use_cache ,
359375 use_dynamic_patch = use_dynamic_patch ,
376+ module_cleanup_mode = module_cleanup_mode ,
360377 )
361378
362379 Patcher .PATCHER .setUp ()
@@ -522,6 +539,7 @@ def __init__(
522539 patch_default_args : bool = False ,
523540 use_cache : bool = True ,
524541 use_dynamic_patch : bool = True ,
542+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
525543 is_doc_test : bool = False ,
526544 ) -> None :
527545 """
@@ -554,9 +572,14 @@ def __init__(
554572 cached between tests for performance reasons. As this is a new
555573 feature, this argument allows to turn it off in case it
556574 causes any problems.
557- use_dynamic_patch: If `True`, dynamic patching after setup is used
575+ use_dynamic_patch: If `True`, dynamic patching after setup is used
558576 (for example for modules loaded locally inside of functions).
559577 Can be switched off if it causes unwanted side effects.
578+ module_cleanup_mode: Defines how the modules in the dynamic patcher are
579+ cleaned up after the test. The default (AUTO) currently depends
580+ on the availability of the `django` module, DELETE will delete
581+ all dynamically loaded modules, RELOAD will reload them.
582+ This option is subject to change in later versions.
560583 """
561584 self .is_doc_test = is_doc_test
562585 if is_doc_test :
@@ -595,6 +618,7 @@ def __init__(
595618 self .patch_default_args = patch_default_args
596619 self .use_cache = use_cache
597620 self .use_dynamic_patch = use_dynamic_patch
621+ self .module_cleanup_mode = module_cleanup_mode
598622
599623 if use_known_patches :
600624 from pyfakefs .patched_packages import (
@@ -928,7 +952,7 @@ def start_patching(self) -> None:
928952 if sys .modules .get (module .__name__ ) is module :
929953 reload (module )
930954 if not self .use_dynamic_patch :
931- self ._dyn_patcher .cleanup ()
955+ self ._dyn_patcher .cleanup (ModuleCleanupMode . DELETE )
932956 sys .meta_path .pop (0 )
933957
934958 def patch_functions (self ) -> None :
@@ -1006,7 +1030,7 @@ def stop_patching(self, temporary=False) -> None:
10061030 self ._stubs .smart_unset_all ()
10071031 self .unset_defaults ()
10081032 if self .use_dynamic_patch and self ._dyn_patcher :
1009- self ._dyn_patcher .cleanup ()
1033+ self ._dyn_patcher .cleanup (self . module_cleanup_mode )
10101034 sys .meta_path .pop (0 )
10111035
10121036 @property
@@ -1098,7 +1122,7 @@ def __init__(self, patcher: Patcher) -> None:
10981122 for name , module in self .modules .items ():
10991123 sys .modules [name ] = module
11001124
1101- def cleanup (self ) -> None :
1125+ def cleanup (self , cleanup_mode : ModuleCleanupMode ) -> None :
11021126 for module_name in self .sysmodules :
11031127 sys .modules [module_name ] = self .sysmodules [module_name ]
11041128 for module in self ._patcher .modules_to_reload :
@@ -1107,13 +1131,24 @@ def cleanup(self) -> None:
11071131 reloaded_module_names = [
11081132 module .__name__ for module in self ._patcher .modules_to_reload
11091133 ]
1110- # Reload all modules loaded during the test, ensuring that
1111- # no faked modules are referenced after the test.
1134+ # Delete all modules loaded during the test, ensuring that
1135+ # they are reloaded after the test.
1136+ # If cleanup_mode is set to RELOAD, or it is AUTO and django is imported,
1137+ # reload the modules instead - this is a workaround related to some internal
1138+ # module caching by django, that will likely change in the future.
1139+ if cleanup_mode == ModuleCleanupMode .AUTO :
1140+ if "django" in sys .modules :
1141+ cleanup_mode = ModuleCleanupMode .RELOAD
1142+ else :
1143+ cleanup_mode = ModuleCleanupMode .DELETE
11121144 for name in self ._loaded_module_names :
11131145 if name in sys .modules and name not in reloaded_module_names :
1114- try :
1115- reload (sys .modules [name ])
1116- except Exception :
1146+ if cleanup_mode == ModuleCleanupMode .RELOAD :
1147+ try :
1148+ reload (sys .modules [name ])
1149+ except Exception :
1150+ del sys .modules [name ]
1151+ else :
11171152 del sys .modules [name ]
11181153
11191154 def needs_patch (self , name : str ) -> bool :
0 commit comments