Routes moved to thin parse-args/jsonify handlers; logic now lives in three focused modules under core/automation/. 436 lines deleted from web_server.py; 53 added back as wrappers. Module split: - core/automation/api.py — CRUD + run + history helpers. Each function takes (database, automation_engine, ...) explicitly and returns (response_body, http_status). Includes signal cycle detection preflight checks for create + update. - core/automation/progress.py — owns the in-memory progress state dict + lock (mirroring the original web_server.py globals as module-level shared state so all callers see one view), init/update/history helpers, and the WebSocket emit loop. - core/automation/signals.py — collect_known_signals for the builder autocomplete. Out of scope (deferred): - _register_automation_handlers — the 23+ action handler closures stay in web_server.py because each one is tightly coupled to feature- specific implementations (wishlist, watchlist, library scan, etc.). - Worker functions (_process_wishlist_automatically, etc.) — belong with their feature lifts. - _run_sync_task / _run_playlist_discovery_worker — sync + discovery PRs. Behavior preserved 1:1: - Same route response shapes + status codes - Same JSON field hydration (trigger_config, action_config, notify_config, last_result, then_actions) - Same backward-compat: empty then_actions + notify_type set → synthesize then_actions from notify_type/notify_config - Same signal cycle detection behavior on create + update - Same system-automation protection on delete + duplicate - Same reschedule/cancel logic on toggle + bulk-toggle + update - Same progress state shape (status, progress, phase, current_item, log capped at 50, started_at/finished_at, action_type) - Same emit-on-finish socketio push from update_progress - Same emit loop semantics (1s tick, snapshot active states, reap finished after window) Pre-existing bugs preserved (will fix in follow-up PRs): - emit_progress_loop uses naive datetime.now() against tz-aware started_at/finished_at, so the timeout-zombie check raises TypeError → caught → never fires, and the cleanup-after-window check raises → caught → state is reaped on FIRST tick regardless of the window. Tests document this behavior so the next PR can flip them to the corrected expectation. Tests: 72 new under tests/automation/ (signals 10, progress 24, api 38). Full suite: 861 passing (was 789). Ruff clean.
43 lines
1.6 KiB
Python
43 lines
1.6 KiB
Python
"""Automation signal helpers — name collection for autocomplete.
|
|
|
|
Signal cycle detection itself lives in core/automation_engine.py
|
|
(`detect_signal_cycles`); this module just enumerates known signal
|
|
names from the saved automation set so the builder UI can autocomplete.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
|
|
def collect_known_signals(database) -> list[str]:
|
|
"""Return sorted, deduped signal names referenced by any saved automation.
|
|
|
|
Walks every automation and pulls signal names from both the
|
|
`signal_received` trigger config and any `fire_signal` then-actions.
|
|
Errors at every layer are swallowed — the autocomplete is best-effort.
|
|
"""
|
|
signals: set[str] = set()
|
|
try:
|
|
for auto in database.get_automations():
|
|
if auto.get('trigger_type') == 'signal_received':
|
|
try:
|
|
tc = json.loads(auto.get('trigger_config') or '{}')
|
|
sig = tc.get('signal_name', '').strip()
|
|
if sig:
|
|
signals.add(sig)
|
|
except (json.JSONDecodeError, TypeError):
|
|
pass
|
|
|
|
try:
|
|
ta = json.loads(auto.get('then_actions') or '[]')
|
|
for item in ta:
|
|
if item.get('type') == 'fire_signal':
|
|
sig = item.get('config', {}).get('signal_name', '').strip()
|
|
if sig:
|
|
signals.add(sig)
|
|
except (json.JSONDecodeError, TypeError):
|
|
pass
|
|
except Exception:
|
|
pass
|
|
return sorted(signals)
|