47
47
import sys
48
48
import tempfile
49
49
import tokenize
50
+ from enum import Enum
50
51
from importlib .abc import Loader , MetaPathFinder
51
52
from types import ModuleType , TracebackType , FunctionType
52
53
from typing import (
94
95
PATH_MODULE = "ntpath" if sys .platform == "win32" else "posixpath"
95
96
96
97
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
+
97
106
def patchfs (
98
107
_func : Optional [Callable ] = None ,
99
108
* ,
@@ -106,6 +115,7 @@ def patchfs(
106
115
patch_default_args : bool = False ,
107
116
use_cache : bool = True ,
108
117
use_dynamic_patch : bool = True ,
118
+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
109
119
) -> Callable :
110
120
"""Convenience decorator to use patcher with additional parameters in a
111
121
test function.
@@ -134,6 +144,7 @@ def wrapped(*args, **kwargs):
134
144
patch_default_args = patch_default_args ,
135
145
use_cache = use_cache ,
136
146
use_dynamic_patch = use_dynamic_patch ,
147
+ module_cleanup_mode = module_cleanup_mode ,
137
148
) as p :
138
149
args = list (args )
139
150
args .append (p .fs )
@@ -170,6 +181,7 @@ def load_doctests(
170
181
patch_open_code : PatchMode = PatchMode .OFF ,
171
182
patch_default_args : bool = False ,
172
183
use_dynamic_patch : bool = True ,
184
+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
173
185
) -> TestSuite : # pylint:disable=unused-argument
174
186
"""Load the doctest tests for the specified module into unittest.
175
187
Args:
@@ -190,6 +202,7 @@ def load_doctests(
190
202
patch_open_code = patch_open_code ,
191
203
patch_default_args = patch_default_args ,
192
204
use_dynamic_patch = use_dynamic_patch ,
205
+ module_cleanup_mode = module_cleanup_mode ,
193
206
is_doc_test = True ,
194
207
)
195
208
assert Patcher .DOC_PATCHER is not None
@@ -270,6 +283,7 @@ def setUpPyfakefs(
270
283
patch_default_args : bool = False ,
271
284
use_cache : bool = True ,
272
285
use_dynamic_patch : bool = True ,
286
+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
273
287
) -> None :
274
288
"""Bind the file-related modules to the :py:class:`pyfakefs` fake file
275
289
system instead of the real file system. Also bind the fake `open()`
@@ -302,6 +316,7 @@ def setUpPyfakefs(
302
316
patch_default_args = patch_default_args ,
303
317
use_cache = use_cache ,
304
318
use_dynamic_patch = use_dynamic_patch ,
319
+ module_cleanup_mode = module_cleanup_mode ,
305
320
)
306
321
307
322
self ._patcher .setUp ()
@@ -319,6 +334,7 @@ def setUpClassPyfakefs(
319
334
patch_default_args : bool = False ,
320
335
use_cache : bool = True ,
321
336
use_dynamic_patch : bool = True ,
337
+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
322
338
) -> None :
323
339
"""Similar to :py:func:`setUpPyfakefs`, but as a class method that
324
340
can be used in `setUpClass` instead of in `setUp`.
@@ -357,6 +373,7 @@ def setUpClassPyfakefs(
357
373
patch_default_args = patch_default_args ,
358
374
use_cache = use_cache ,
359
375
use_dynamic_patch = use_dynamic_patch ,
376
+ module_cleanup_mode = module_cleanup_mode ,
360
377
)
361
378
362
379
Patcher .PATCHER .setUp ()
@@ -522,6 +539,7 @@ def __init__(
522
539
patch_default_args : bool = False ,
523
540
use_cache : bool = True ,
524
541
use_dynamic_patch : bool = True ,
542
+ module_cleanup_mode : ModuleCleanupMode = ModuleCleanupMode .AUTO ,
525
543
is_doc_test : bool = False ,
526
544
) -> None :
527
545
"""
@@ -554,9 +572,14 @@ def __init__(
554
572
cached between tests for performance reasons. As this is a new
555
573
feature, this argument allows to turn it off in case it
556
574
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
558
576
(for example for modules loaded locally inside of functions).
559
577
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.
560
583
"""
561
584
self .is_doc_test = is_doc_test
562
585
if is_doc_test :
@@ -595,6 +618,7 @@ def __init__(
595
618
self .patch_default_args = patch_default_args
596
619
self .use_cache = use_cache
597
620
self .use_dynamic_patch = use_dynamic_patch
621
+ self .module_cleanup_mode = module_cleanup_mode
598
622
599
623
if use_known_patches :
600
624
from pyfakefs .patched_packages import (
@@ -928,7 +952,7 @@ def start_patching(self) -> None:
928
952
if sys .modules .get (module .__name__ ) is module :
929
953
reload (module )
930
954
if not self .use_dynamic_patch :
931
- self ._dyn_patcher .cleanup ()
955
+ self ._dyn_patcher .cleanup (ModuleCleanupMode . DELETE )
932
956
sys .meta_path .pop (0 )
933
957
934
958
def patch_functions (self ) -> None :
@@ -1006,7 +1030,7 @@ def stop_patching(self, temporary=False) -> None:
1006
1030
self ._stubs .smart_unset_all ()
1007
1031
self .unset_defaults ()
1008
1032
if self .use_dynamic_patch and self ._dyn_patcher :
1009
- self ._dyn_patcher .cleanup ()
1033
+ self ._dyn_patcher .cleanup (self . module_cleanup_mode )
1010
1034
sys .meta_path .pop (0 )
1011
1035
1012
1036
@property
@@ -1098,7 +1122,7 @@ def __init__(self, patcher: Patcher) -> None:
1098
1122
for name , module in self .modules .items ():
1099
1123
sys .modules [name ] = module
1100
1124
1101
- def cleanup (self ) -> None :
1125
+ def cleanup (self , cleanup_mode : ModuleCleanupMode ) -> None :
1102
1126
for module_name in self .sysmodules :
1103
1127
sys .modules [module_name ] = self .sysmodules [module_name ]
1104
1128
for module in self ._patcher .modules_to_reload :
@@ -1107,13 +1131,24 @@ def cleanup(self) -> None:
1107
1131
reloaded_module_names = [
1108
1132
module .__name__ for module in self ._patcher .modules_to_reload
1109
1133
]
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
1112
1144
for name in self ._loaded_module_names :
1113
1145
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 :
1117
1152
del sys .modules [name ]
1118
1153
1119
1154
def needs_patch (self , name : str ) -> bool :
0 commit comments