From 2c0eddc264ee3a945ffe2f983c0724d3ded5edbc Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 16 May 2026 01:30:00 -0400 Subject: [PATCH] feat(hot-reload): Add HotModule dataclass and HotReloader registry --- src/hot_reloader.py | 32 ++++++++++++++++++++++++++++++++ tests/test_hot_reloader.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/hot_reloader.py create mode 100644 tests/test_hot_reloader.py diff --git a/src/hot_reloader.py b/src/hot_reloader.py new file mode 100644 index 00000000..530b83ff --- /dev/null +++ b/src/hot_reloader.py @@ -0,0 +1,32 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Any +import copy +import traceback + +@dataclass +class HotModule: + name: str + file_path: str + state_keys: list[str] = field(default_factory=list) + delegation_targets: list[str] = field(default_factory=list) + +class HotReloader: + HOT_MODULES: dict[str, HotModule] = {} + last_error: str | None = None + is_error_state: bool = False + + @classmethod + def register(cls, module: HotModule) -> None: + if module.name in cls.HOT_MODULES: + raise ValueError(f"Module {module.name} already registered") + cls.HOT_MODULES[module.name] = module + + @classmethod + def capture_state(cls, app: Any, state_keys: list[str]) -> dict[str, Any]: + return {key: copy.deepcopy(getattr(app, key, None)) for key in state_keys if hasattr(app, key)} + + @classmethod + def restore_state(cls, app: Any, state: dict[str, Any]) -> None: + for key, value in state.items(): + setattr(app, key, value) \ No newline at end of file diff --git a/tests/test_hot_reloader.py b/tests/test_hot_reloader.py new file mode 100644 index 00000000..5be22893 --- /dev/null +++ b/tests/test_hot_reloader.py @@ -0,0 +1,35 @@ +from __future__ import annotations +import pytest +from src.hot_reloader import HotModule, HotReloader + +def test_hot_module_dataclass_fields(): + hm = HotModule( + name="test_module", + file_path="/path/to/test_module.py", + state_keys=["attr1", "attr2"], + delegation_targets=["method_a", "method_b"], + ) + assert hm.name == "test_module" + assert hm.file_path == "/path/to/test_module.py" + assert hm.state_keys == ["attr1", "attr2"] + assert hm.delegation_targets == ["method_a", "method_b"] + +def test_hot_reloader_register_and_get(): + HotReloader.HOT_MODULES.clear() + hm = HotModule(name="test_mod", file_path="/fake/path.py", state_keys=[], delegation_targets=[]) + HotReloader.register(hm) + assert "test_mod" in HotReloader.HOT_MODULES + assert HotReloader.HOT_MODULES["test_mod"] is hm + +def test_hot_reloader_register_duplicate_raises(): + HotReloader.HOT_MODULES.clear() + hm1 = HotModule(name="dup", file_path="/a.py", state_keys=[], delegation_targets=[]) + HotReloader.register(hm1) + with pytest.raises(ValueError, match="already registered"): + HotReloader.register(hm1) + +def test_hot_reloader_is_error_state(): + HotReloader.HOT_MODULES.clear() + HotReloader.last_error = None + HotReloader.is_error_state = False + assert HotReloader.is_error_state is False \ No newline at end of file