diff --git a/core/superduperreload/__init__.py b/core/superduperreload/__init__.py index b09c47f..d98ee6f 100644 --- a/core/superduperreload/__init__.py +++ b/core/superduperreload/__init__.py @@ -11,7 +11,7 @@ __version__ = _version.get_versions()['version'] -def make_autoreload_magics(shell: "InteractiveShell", enable_file_watching: bool = True) -> AutoreloadMagics: +def make_autoreload_magics(shell: "InteractiveShell") -> AutoreloadMagics: try: from ipyflow import flow @@ -19,7 +19,7 @@ def make_autoreload_magics(shell: "InteractiveShell", enable_file_watching: bool except: flow_ = None - return AutoreloadMagics(shell, flow=flow_, enable_file_watching=enable_file_watching) + return AutoreloadMagics(shell, flow=flow_) def load_ipython_extension(ip: "InteractiveShell", magics: Optional[AutoreloadMagics] = None) -> None: diff --git a/core/superduperreload/magics.py b/core/superduperreload/magics.py index d9c9543..d75de4c 100644 --- a/core/superduperreload/magics.py +++ b/core/superduperreload/magics.py @@ -105,11 +105,8 @@ def __init__(self, *a, **kw): if platform.python_implementation().lower() != "cpython": raise RuntimeError("CPython required for superduperreload extension") flow = kw.pop("flow", None) - enable_file_watching = kw.pop("enable_file_watching", True) super().__init__(*a, **kw) - self._reloader = ModuleReloader( - self.shell, flow=flow, enable_file_watching=enable_file_watching - ) + self._reloader = ModuleReloader.instance(self.shell, flow=flow) self.loaded_modules = set(sys.modules) @line_magic diff --git a/core/superduperreload/patching.py b/core/superduperreload/patching.py index fd8fab4..ccaca46 100644 --- a/core/superduperreload/patching.py +++ b/core/superduperreload/patching.py @@ -7,6 +7,8 @@ from types import FunctionType, MethodType from typing import Callable, Dict, List, Optional, Set, Sized, Tuple, Type, Union +from traitlets.config import SingletonConfigurable + from superduperreload.utils import isinstance2 if sys.maxsize > 2**32: @@ -82,10 +84,11 @@ class UnpatchableList(list): pass -class ObjectPatcher: +class ObjectPatcher(SingletonConfigurable): _FIELD_OFFSET_LOOKUP_TABLE_BY_STRUCT_TYPE: Dict[str, Dict[str, int]] = {} - def __init__(self, patch_referrers: bool) -> None: + def __init__(self, patch_referrers: bool, **kwargs) -> None: + super().__init__(**kwargs) self._patched_obj_ids: Set[int] = set() self._remapped_classes: Dict[Type[object], Type[object]] = UnpatchableDict() self._patch_rules: List[Tuple[Callable, Callable]] = [ diff --git a/core/superduperreload/superduperreload.py b/core/superduperreload/superduperreload.py index 379ec68..56741d2 100644 --- a/core/superduperreload/superduperreload.py +++ b/core/superduperreload/superduperreload.py @@ -80,12 +80,12 @@ class ImportTracer(pyc.BaseTracer): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self._reloader: "ModuleReloader" = kwargs.pop("reloader") super().__init__(*args, **kwargs) @pyc.register_raw_handler(pyc.after_import) - def after_import(self, *_, module: ModuleType, **__): + def after_import(self, *_, module: ModuleType, **__) -> None: self._reloader.handle_module_refreshed(module) @@ -94,7 +94,6 @@ def __init__( self, shell: Optional[Union["InteractiveShell", "FakeShell"]] = None, flow: Optional["NotebookFlow"] = None, - enable_file_watching: bool = True, ) -> None: super().__init__(patch_referrers=SHOULD_PATCH_REFERRERS) # Whether this reloader is enabled @@ -140,8 +139,22 @@ def __init__( # Cache module modification times self.check(do_reload=False) - if self.flow is not None and enable_file_watching: - Thread(target=self._watch, daemon=True).start() + self._watcher: Optional[Thread] = None + self._watcher_running = False + if self.flow is not None: + self._watcher = Thread(target=self._watch, daemon=True) + self._watcher_running = True + self._watcher.start() + + @classmethod + def clear_instance(cls) -> None: + if cls.initialized(): + reloader: "ModuleReloader" = cls._instance + if reloader._watcher_running: + reloader._watcher_running = False + if reloader._watcher is not None: + reloader._watcher.join() + super().clear_instance() def _report(self, msg: str) -> None: if self.verbose: @@ -303,7 +316,7 @@ def _watch(self, interval: float = 1) -> None: assert self.flow is not None for m in list(sys.modules.values()): self.handle_module_refreshed(m) - while True: + while self._watcher_running: try: with self._reloading_lock: self._poll_module_changes_once() diff --git a/core/test/test_ipyflow_superduperreload.py b/core/test/test_ipyflow_superduperreload.py index 7460f31..d9b5034 100644 --- a/core/test/test_ipyflow_superduperreload.py +++ b/core/test/test_ipyflow_superduperreload.py @@ -27,6 +27,7 @@ from ipyflow.tracing.ipyflow_tracer import DataflowTracer from superduperreload import load_ipython_extension, make_autoreload_magics +from superduperreload.superduperreload import ModuleReloader try: import numpy @@ -105,13 +106,12 @@ def setUp(self): self.test_dir = tempfile.mkdtemp() self.old_sys_path = list(sys.path) sys.path.insert(0, self.test_dir) + ModuleReloader.clear_instance() DataflowTracer.clear_instance() NotebookFlow.clear_instance() IPyflowInteractiveShell.clear_instance() self.shell = IPyflowInteractiveShell.instance() - self.auto_magics = make_autoreload_magics( - self.shell, enable_file_watching=False - ) + self.auto_magics = make_autoreload_magics(self.shell) load_ipython_extension(self.shell, magics=self.auto_magics) def tearDown(self):