Skip to content

[hmr] Consider splitting the large core.py file into several smaller, focused modules #321

Description

@CNSeniorious000

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:

  1. loader.pyReactiveModuleLoader & ReactiveModuleFinder
  2. module.pyReactiveModule, NamespaceProxy, signal plumbing
  3. reloader.pyBaseReloader, SyncReloader, AsyncReloader
  4. errors.pyErrorFilter
  5. cli.pycli() 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions