* refactor(nodes): remove superseded LTX Director extra nodes + harden registry (#198) Remove the two experimental nodes layered on the WhatDreamsCost fork — KoolookAudioTranscriptTimeline (k_audio_timeline.py) and KoolookTimelineEditor (k_timeline_editor.py) — plus the transcribe_audio_timeline.py CLI and its test. The audio-transcript to Director-timing job now lives inside the fork via the Director's audio_transcript_json input, so the standalone nodes are redundant. Also drops the orphaned [audio]/faster-whisper extra and updates packaging, dev-sync, web, and docs references. Harden the node registry against the #183 failure: __init__.py now imports each node group independently and installs the /koolook/presets/* routes regardless of node-import results, so one missing or broken module degrades to 'that node absent' instead of zeroing every node and 404'ing the sidebar disk routes. A new test keeps every root k_*.py listed in dev-sync's RUNTIME_PATHS. Closes #198. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(nodes): ship loader gates via dev-sync-audio; generalize RUNTIME_PATHS guard (#198) Address the K-Flame review on #199: MEDIUM — the #198 refactor made koolook_versioning.py a single registration gate, but dev-sync-audio shipped the new __init__.py without it (and the k_*.py-glob guard test couldn't see it). Add koolook_versioning.py AND koolook_install_guard.py (whose absolute-import fallback raises uncaught) to AUDIO_PATHS so a scoped sync can never leave the loader pointing at a module the target lacks. Generalize the guard test: parse __init__.py's actual load-time import targets (relative imports + _merge_node_group calls) and assert each is in RUNTIME_PATHS, instead of globbing k_*.py which missed the non-k_ gates. Add an AUDIO_PATHS gate-coverage test. LOW — CHANGELOG now records the [audio]/faster-whisper + transcribe_audio_timeline.py removal as an external-contract break. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
3.5 KiB
Python
79 lines
3.5 KiB
Python
"""Guards on which modules ``dev-sync`` ships, so the loader can never point
|
|
at a file the install lacks.
|
|
|
|
The #183 incident (root cause behind #198): a module that ``__init__.py``
|
|
imported but ``scripts/sync_to_dev.py``'s ``RUNTIME_PATHS`` never copied raised
|
|
``ImportError`` at startup. After the #198 refactor the blast radius of such a
|
|
gap depends on *where* the import sits — a per-group node import degrades to
|
|
"that node absent", but the context probe (``koolook_versioning``) and the
|
|
install guard gate *all* registration. So the real invariant is: every module
|
|
``__init__.py`` imports at load must be shipped by ``RUNTIME_PATHS``. This test
|
|
derives those imports from ``__init__.py`` itself (rather than a ``k_*.py``
|
|
glob, which misses the non-``k_`` gates) so it can't drift.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
_SCRIPT = REPO_ROOT / "scripts" / "sync_to_dev.py"
|
|
_spec = importlib.util.spec_from_file_location("koolook_sync_to_dev_under_test", _SCRIPT)
|
|
assert _spec is not None and _spec.loader is not None
|
|
sync_to_dev = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(sync_to_dev)
|
|
|
|
|
|
def _init_load_time_module_deps() -> set[str]:
|
|
"""Top-level package modules/packages ``__init__.py`` imports at load.
|
|
|
|
Covers relative ``from .X import ...`` / ``from . import X`` statements and
|
|
the ``_merge_node_group(label, ".X")`` node-group imports. Returns the
|
|
first path segment of each (e.g. ``forks.radiance_koolook`` -> ``forks``),
|
|
the granularity ``RUNTIME_PATHS`` ships at. Absolute imports (the install
|
|
guard's out-of-package fallback) are level 0 and intentionally ignored.
|
|
"""
|
|
tree = ast.parse((REPO_ROOT / "__init__.py").read_text(encoding="utf-8"))
|
|
deps: set[str] = set()
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.ImportFrom) and (node.level or 0) >= 1:
|
|
if node.module: # from .pkg import ...
|
|
deps.add(node.module.split(".")[0])
|
|
else: # from . import a, b
|
|
deps.update(alias.name.split(".")[0] for alias in node.names)
|
|
elif (
|
|
isinstance(node, ast.Call)
|
|
and getattr(node.func, "id", None) == "_merge_node_group"
|
|
):
|
|
for arg in node.args:
|
|
if (
|
|
isinstance(arg, ast.Constant)
|
|
and isinstance(arg.value, str)
|
|
and arg.value.startswith(".")
|
|
):
|
|
deps.add(arg.value.lstrip(".").split(".")[0])
|
|
return deps
|
|
|
|
|
|
def test_init_load_deps_are_shipped_by_runtime_paths():
|
|
runtime_paths = set(sync_to_dev.RUNTIME_PATHS)
|
|
deps = _init_load_time_module_deps()
|
|
# Sanity: the parse actually found the imports (guards against a refactor
|
|
# that renames _merge_node_group and silently makes this test vacuous).
|
|
assert "koolook_versioning" in deps and "k_ai_pipeline" in deps, (
|
|
f"__init__.py import parse looks broken — only found: {sorted(deps)}"
|
|
)
|
|
# A single-file module ships as "<dep>.py"; a package (e.g. forks) ships as
|
|
# its top-level dir entry.
|
|
missing = sorted(
|
|
dep
|
|
for dep in deps
|
|
if f"{dep}.py" not in runtime_paths and dep not in runtime_paths
|
|
)
|
|
assert not missing, (
|
|
"__init__.py imports these at load but dev-sync RUNTIME_PATHS does not "
|
|
f"ship them: {missing}. Add them to RUNTIME_PATHS in "
|
|
"scripts/sync_to_dev.py so a plain dev-sync can't 404 the loader."
|
|
)
|