Auto-generated nodes for the entire live fal catalog (1,391 models → 1,482 total nodes) built at startup from a committed registry, plus a full rewrite of the core layer. All 87 v1 node keys, inputs and outputs are preserved — existing workflows load and run unchanged. New: - data/fal_registry.json + scripts/build_registry.py: distills fal's catalog and per-endpoint OpenAPI schemas into a deterministic registry - nodes/dynamic/: schema→node factory (typed widgets, tooltips, pricing in node help, IMAGE/VIDEO/AUDIO auto-upload, native VIDEO/AUDIO outputs, seed -1=random, force_rerun), plus a generic Fal Any Endpoint node for arbitrary endpoints - nodes/utils/: rewritten core — FalApiError surfacing fal's real error payloads, queue progress logs, ComfyUI cancel support, retries and timeouts everywhere, parallel uploads/downloads, structured logging - tests/ (45 tests incl. a legacy-key snapshot lock), CI lint+test workflow, weekly registry-refresh workflow that opens a PR on changes - README regenerated from the registry (scripts/build_readme.py) Fixed: - GPTImage2 / GPTImage2Edit were defined but never registered - Topaz video upscale sent a nonexistent API field (desired_increase) - SeedVR video upscaler had its error handling commented out + wrong model label; ProRes enum value mismatch translated at argument level - 10x `is ""` comparisons, bare excepts, missing HTTP timeouts, temp-file leaks, fps=0 crash in LoadVideoURL, duplicate enum entry - opencv-python & friends missing from pyproject (Registry installs) - API failures now raise visible errors instead of silently returning blank images or "Error:" strings that downstream nodes tried to load BREAKING (behavioral only): failed API calls raise instead of returning blank/black images or error strings. Node signatures are unchanged.
46 lines
1.7 KiB
Python
46 lines
1.7 KiB
Python
"""Integration: the loaded pack must keep every legacy key and stay coherent."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
LEGACY_SNAPSHOT = Path(__file__).with_name("legacy_node_keys.json")
|
|
|
|
|
|
def test_all_legacy_keys_present(pack):
|
|
"""Backward-compat lock: keys registered at v1.0.12 must never disappear."""
|
|
legacy = set(json.loads(LEGACY_SNAPSHOT.read_text())["keys"])
|
|
current = set(pack.NODE_CLASS_MAPPINGS)
|
|
missing = legacy - current
|
|
assert not missing, f"legacy node keys removed (breaks user workflows): {sorted(missing)}"
|
|
|
|
|
|
def test_display_names_complete(pack):
|
|
missing = [k for k in pack.NODE_CLASS_MAPPINGS if k not in pack.NODE_DISPLAY_NAME_MAPPINGS]
|
|
assert not missing
|
|
|
|
|
|
def test_dynamic_nodes_registered(pack):
|
|
dynamic = [k for k in pack.NODE_CLASS_MAPPINGS if k.startswith("FalAPI_")]
|
|
assert len(dynamic) > 500, "dynamic registry failed to load"
|
|
assert "FalAnyEndpoint_fal" in pack.NODE_CLASS_MAPPINGS
|
|
|
|
|
|
def test_every_node_class_is_valid(pack):
|
|
for key, cls in pack.NODE_CLASS_MAPPINGS.items():
|
|
input_types = cls.INPUT_TYPES()
|
|
assert isinstance(input_types, dict), key
|
|
assert "required" in input_types or "optional" in input_types, key
|
|
assert isinstance(cls.RETURN_TYPES, tuple), key
|
|
assert isinstance(cls.FUNCTION, str) and hasattr(cls, cls.FUNCTION), key
|
|
assert isinstance(cls.CATEGORY, str) and cls.CATEGORY, key
|
|
|
|
|
|
def test_no_bare_video_category_left(pack):
|
|
bare = [
|
|
k for k, cls in pack.NODE_CLASS_MAPPINGS.items()
|
|
if cls.CATEGORY.lower() == "video"
|
|
]
|
|
assert not bare, f"nodes escaped the FAL/ menu namespace: {bare}"
|