@@ -619,18 +619,21 @@ def __init__(
619
619
self .use_cache = use_cache
620
620
self .use_dynamic_patch = use_dynamic_patch
621
621
self .module_cleanup_mode = module_cleanup_mode
622
+ self .cleanup_handlers : Dict [str , Callable [[], bool ]] = {}
622
623
623
624
if use_known_patches :
624
625
from pyfakefs .patched_packages import (
625
626
get_modules_to_patch ,
626
627
get_classes_to_patch ,
627
628
get_fake_module_classes ,
629
+ get_cleanup_handlers ,
628
630
)
629
631
630
632
modules_to_patch = modules_to_patch or {}
631
633
modules_to_patch .update (get_modules_to_patch ())
632
634
self ._class_modules .update (get_classes_to_patch ())
633
635
self ._fake_module_classes .update (get_fake_module_classes ())
636
+ self .cleanup_handlers .update (get_cleanup_handlers ())
634
637
635
638
if modules_to_patch is not None :
636
639
for name , fake_module in modules_to_patch .items ():
@@ -680,6 +683,22 @@ def clear_cache(self) -> None:
680
683
"""Clear the module cache (convenience instance method)."""
681
684
self .__class__ .clear_fs_cache ()
682
685
686
+ def register_cleanup_handler (self , name : str , handler : Callable [[], bool ]):
687
+ """Register a handler for cleaning up a module after it had been loaded by
688
+ the dynamic patcher. This allows to handle modules that cannot be reloaded
689
+ without unwanted side effects.
690
+
691
+ Args:
692
+ name: The fully qualified module name.
693
+ handler: A callable that may do any module cleanup, or do nothing
694
+ and return `True` in case reloading shall be prevented.
695
+
696
+ Returns:
697
+ `True` if no further cleanup/reload shall occur after the handler is
698
+ executed, `False` if the cleanup/reload shall still happen.
699
+ """
700
+ self .cleanup_handlers [name ] = handler
701
+
683
702
def _init_fake_module_classes (self ) -> None :
684
703
# IMPORTANT TESTING NOTE: Whenever you add a new module below, test
685
704
# it by adding an attribute in fixtures/module_with_attributes.py
@@ -946,7 +965,9 @@ def start_patching(self) -> None:
946
965
self .patch_functions ()
947
966
self .patch_defaults ()
948
967
949
- self ._dyn_patcher = DynamicPatcher (self )
968
+ self ._dyn_patcher = DynamicPatcher (
969
+ self , cleanup_handlers = self .cleanup_handlers
970
+ )
950
971
sys .meta_path .insert (0 , self ._dyn_patcher )
951
972
for module in self .modules_to_reload :
952
973
if sys .modules .get (module .__name__ ) is module :
@@ -1106,11 +1127,16 @@ class DynamicPatcher(MetaPathFinder, Loader):
1106
1127
Implements the protocol needed for import hooks.
1107
1128
"""
1108
1129
1109
- def __init__ (self , patcher : Patcher ) -> None :
1130
+ def __init__ (
1131
+ self ,
1132
+ patcher : Patcher ,
1133
+ cleanup_handlers : Optional [Dict [str , Callable [[], bool ]]] = None ,
1134
+ ) -> None :
1110
1135
self ._patcher = patcher
1111
1136
self .sysmodules = {}
1112
1137
self .modules = self ._patcher .fake_modules
1113
1138
self ._loaded_module_names : Set [str ] = set ()
1139
+ self .cleanup_handlers = cleanup_handlers or {}
1114
1140
1115
1141
# remove all modules that have to be patched from `sys.modules`,
1116
1142
# otherwise the find_... methods will not be called
@@ -1143,6 +1169,8 @@ def cleanup(self, cleanup_mode: ModuleCleanupMode) -> None:
1143
1169
cleanup_mode = ModuleCleanupMode .DELETE
1144
1170
for name in self ._loaded_module_names :
1145
1171
if name in sys .modules and name not in reloaded_module_names :
1172
+ if name in self .cleanup_handlers and self .cleanup_handlers [name ]():
1173
+ continue
1146
1174
if cleanup_mode == ModuleCleanupMode .RELOAD :
1147
1175
try :
1148
1176
reload (sys .modules [name ])
0 commit comments