issue (complexity): Consider splitting the large core.py file into several smaller, focused modules for loader, module, reloader, errors, and CLI logic.
Refactor suggestion: split responsibilities into focused modules
Rather than one 1,300 LOC file, break it up into:
- loader.py –
ReactiveModuleLoader & ReactiveModuleFinder
- module.py –
ReactiveModule, NamespaceProxy, signal plumbing
- reloader.py –
BaseReloader, SyncReloader, AsyncReloader
- errors.py –
ErrorFilter
- cli.py –
cli() entrypoint
Then have a short __init__.py that ties them together:
# hmr/__init__.py
from .loader import ReactiveModuleFinder, ReactiveModuleLoader
from .module import ReactiveModule, NamespaceProxy
from .reloader import SyncReloader, AsyncReloader, BaseReloader
from .errors import ErrorFilter
from .cli import cli
__version__ = "0.6.2"
1. loader.py
# hmr/loader.py
from importlib.abc import Loader, MetaPathFinder
from importlib.util import spec_from_loader
from pathlib import Path
import sys
class ReactiveModuleLoader(Loader):
def __init__(self, file: Path): ...
def create_module(self, spec): ...
def exec_module(self, module): ...
class ReactiveModuleFinder(MetaPathFinder):
def __init__(self, includes, excludes): ...
def find_spec(self, fullname, paths, _=None):
# walk sys.path, use spec_from_loader(...)
...
2. module.py
# hmr/module.py
from types import ModuleType
from .proxy import NamespaceProxy
from ..context import new_context
HMR_CONTEXT = new_context()
class ReactiveModule(ModuleType):
def __init__(...): ...
@property
def load(self): ...
def __getattr__(...): ...
# keep only module/proxy logic here
3. reloader.py
# hmr/reloader.py
from functools import cached_property
from .module import ReactiveModule, get_path_module_map
from .errors import ErrorFilter
from .hooks import call_pre_reload_hooks, call_post_reload_hooks
class BaseReloader:
def __init__(self, entry_file, includes, excludes): ...
def run_entry_file(self): ...
def on_events(self, events): ...
class SyncReloader(BaseReloader):
def start_watching(self): ...
def keep_watching_until_interrupt(self): ...
class AsyncReloader(BaseReloader):
async def start_watching(self): ...
async def keep_watching_until_interrupt(self): ...
4. errors.py
# hmr/errors.py
class ErrorFilter:
def __init__(self, *exclude_filenames): ...
def __call__(self, tb): ...
def __enter__(self): ...
def __exit__(...): ...
5. cli.py
# hmr/cli.py
import sys
from pathlib import Path
from .reloader import SyncReloader
def cli():
if len(sys.argv) < 2:
print("Usage: hmr <entry.py>")
sys.exit(1)
entry = sys.argv[1]
# insert parent into sys.path, start reloader…
reloader = SyncReloader(entry)
sys.modules["__main__"] = reloader.entry_module
reloader.keep_watching_until_interrupt()
Benefits:
- each file ~200 LOC or less
- clear separation of import‐hook, module‐proxy, reload, error‐filter, CLI logic
- easier to navigate, test, and maintain without losing existing behavior.
Originally posted by @sourcery-ai[bot] in #319 (comment)
issue (complexity): Consider splitting the large core.py file into several smaller, focused modules for loader, module, reloader, errors, and CLI logic.
Refactor suggestion: split responsibilities into focused modules
Rather than one 1,300 LOC file, break it up into:
ReactiveModuleLoader&ReactiveModuleFinderReactiveModule,NamespaceProxy, signal plumbingBaseReloader,SyncReloader,AsyncReloaderErrorFiltercli()entrypointThen have a short
__init__.pythat ties them together:1. loader.py
2. module.py
3. reloader.py
4. errors.py
5. cli.py
Benefits:
Originally posted by @sourcery-ai[bot] in #319 (comment)