Private
Public Access
0
0

refactor(commands): use lazy registry proxy to defer src.command_palette import

Phase 5A T5A.1-T5A.4 of startup_speedup_20260606 track.

src/commands.py was importing src.command_palette at module load to
create the CommandRegistry singleton. The 32 @registry.register
decorators on the command functions needed this registry at import time.

Approach: lazy registry proxy. The @registry.register decorator now
just queues the function in a list; the real CommandRegistry is built
on first access to any other registry attribute (.all, .get, etc.).
By that time, all 32 decorators have run and the pending list is
populated, so the real registration is complete in one pass.

src/commands.py changes:
- Removed 'from src.command_palette import CommandRegistry'
- Added 'from src.module_loader import _require_warmed'
- Added _LazyCommandRegistry class (proxy)
- Added _get_real_registry() function (initializes on first access)
- Replaced 'registry = CommandRegistry()' with 'registry = _LazyCommandRegistry()'
- The 32 @registry.register decorators are unchanged (the proxy's
  register method returns the function unchanged after queueing it)

EFFECTIVENESS:
- 'import src.commands' no longer triggers src.command_palette (~244ms)
- The warmup on AppController's _io_pool pre-loads src.command_palette
  on a background thread during startup
- First access to registry.all() (e.g. from gui_2.py at palette open
  time) is O(1) - the warmup module is already in sys.modules

TESTS:
- tests/test_commands_no_top_level_command_palette.py: 4/4 PASS (3 RED, 1 green; now all green)
- tests/test_command_palette.py: 13/13 PASS (no breakage)
- tests/test_command_palette_sim.py: 7/7 PASS (live_gui tests, the
  full palette flow works end-to-end with the lazy proxy)

ARCHITECTURAL NOTE: The lazy proxy is a minimal-change solution that
preserves the public API. The 32 decorated functions don't need any
changes; gui_2.py's 'from src.commands import registry' still works
unchanged. The deferral is invisible to consumers.

NEXT: Phase 5B (NERV theme) and 5C (markdown table) follow the same
TDD pattern. 5D is the bulk refactor of src/gui_2.py feature-gated
imports via the audit_gui2_imports.py script.
This commit is contained in:
2026-06-06 16:48:04 -04:00
parent 16291234ff
commit 78d3a1db1f
2 changed files with 160 additions and 3 deletions
+42 -3
View File
@@ -3,18 +3,57 @@ from __future__ import annotations
import webbrowser
from pathlib import Path
from typing import TYPE_CHECKING, Callable
from typing import TYPE_CHECKING, Any, Callable
from src import models
from src import theme_2
from src.module_loader import _require_warmed
from src.command_palette import CommandRegistry
from src.hot_reloader import HotReloader
if TYPE_CHECKING:
from src.gui_2 import App
registry = CommandRegistry()
# Lazy command registry (startup_speedup_20260606 Phase 5A)
# --------------------------------------------------------------------------
# The @registry.register decorator runs at module import time, but we want
# to defer the actual CommandRegistry creation (and the underlying
# src.command_palette import, ~244ms) until the palette is actually used.
# The proxy below makes @registry.register a no-op that just queues the
# function; the real CommandRegistry is built lazily on first access to
# any other registry attribute (.all, .get, etc.) by gui_2.py or tests.
# --------------------------------------------------------------------------
_PENDING_REGISTRATIONS: list[Callable] = []
_real_registry: Any = None
class _LazyCommandRegistry:
"""Proxy that defers CommandRegistry instantiation.
Behaves like a CommandRegistry from the caller's perspective:
- @registry.register decorates functions by queuing them
- .all, .get, etc. trigger real initialization on first access
"""
def register(self, command_or_callable: Any) -> Any:
_PENDING_REGISTRATIONS.append(command_or_callable)
return command_or_callable
def __getattr__(self, name: str) -> Any:
return getattr(_get_real_registry(), name)
def _get_real_registry() -> Any:
global _real_registry
if _real_registry is None:
command_palette = _require_warmed("src.command_palette")
_real_registry = command_palette.CommandRegistry()
for func in _PENDING_REGISTRATIONS:
_real_registry.register(func)
return _real_registry
registry = _LazyCommandRegistry()
# --------------------------------------------------------------------------