soulsync/core/automation/handlers/registration.py
Broque Thomas ea7d5c65bb Extract automation handlers (1/N): infrastructure + 3 simple handlers
Begins the lift of `web_server._register_automation_handlers` (1530
lines, 20 nested closures) into `core/automation/handlers/`. Each
extracted handler is a top-level function that accepts
`(config, deps)` instead of reaching for module-level globals --
makes them unit-testable in isolation.

Infrastructure:
- `core/automation/deps.py`: `AutomationDeps` (dependency-injection
  bundle of clients + callables) and `AutomationState` (mutable flags
  shared across handler invocations, with thread-safe accessors).
- `core/automation/handlers/__init__.py` + `registration.py`: one-stop
  `register_all(deps)` that wires every extracted handler to the
  engine.

First batch of handlers extracted:
- `process_wishlist` -> `core/automation/handlers/process_wishlist.py`
- `scan_watchlist`   -> `core/automation/handlers/scan_watchlist.py`
- `scan_library`     -> `core/automation/handlers/scan_library.py`

`web_server._register_automation_handlers` now builds the deps once
and calls `register_all(deps)` for the extracted batch. Remaining
17 closures still live below; subsequent commits in this branch
finish the lift.

14 boundary tests in `tests/automation/test_handlers_simple.py` pin
every shape: success path, exception swallow contract, fresh-vs-stale
state detection (scan_watchlist's id() trick), guard short-circuits,
state cleanup on exceptions, AutomationState concurrent-safe accessors.
All 101 automation tests pass; no regression.
2026-05-15 10:25:41 -07:00

45 lines
1.7 KiB
Python

"""One-stop registration of every extracted automation handler.
``web_server`` builds the deps once at startup and calls
:func:`register_all` here. Each new handler module gets one line in
this file when it lands.
"""
from __future__ import annotations
from core.automation.deps import AutomationDeps
from core.automation.handlers.process_wishlist import auto_process_wishlist
from core.automation.handlers.scan_watchlist import auto_scan_watchlist
from core.automation.handlers.scan_library import auto_scan_library
def register_all(deps: AutomationDeps) -> None:
"""Wire every extracted handler to the engine.
Each ``register_action_handler`` call binds the action name (the
string the trigger uses to look up its action) to a thin lambda
that injects ``deps`` and forwards the engine-supplied config.
Guards stay alongside their handler so duplicate-run prevention
behaves identically to the pre-extraction code.
"""
engine = deps.engine
# Self-guards prevent duplicate runs of the SAME operation, but
# different operations can run concurrently — wishlist downloads
# use bandwidth, watchlist scans use API calls, library scans use
# media-server CPU. Different resources, no contention.
engine.register_action_handler(
'process_wishlist',
lambda config: auto_process_wishlist(config, deps),
guard_fn=deps.is_wishlist_actually_processing,
)
engine.register_action_handler(
'scan_watchlist',
lambda config: auto_scan_watchlist(config, deps),
guard_fn=deps.is_watchlist_actually_scanning,
)
engine.register_action_handler(
'scan_library',
lambda config: auto_scan_library(config, deps),
deps.state.is_scan_library_active,
)