File size: 7,771 Bytes
e4ab0d4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | """ComfyUI-Koolook node registry.
@title: ComfyUI-Koolook
@nickname: Kforge Labs
@description: Project-kit sidebar plus Koolook nodes for VFX, image, and video workflows.
Two layers of resilience guard the import:
1. Duplicate-install guard (#162) — if two Koolook folders exist in
``custom_nodes/``, only the alphabetical winner registers; the loser
short-circuits before any heavy imports or route install.
2. Per-group registration (#198 / #183) — each node group is imported
independently so a single missing or broken module degrades to "that
node absent" instead of taking down the whole pack, and the Kforge Labs
snapshot/preset routes install regardless of node-import results. The
failure this guards against: a module that ``__init__`` imported but
``dev-sync`` never copied raised ``ImportError`` at startup, which zeroed
every node *and* 404'd every snapshot route.
"""
from __future__ import annotations
import importlib
from pathlib import Path
# Duplicate-install guard (#162). Detection + winner selection live in the
# sibling ``koolook_install_guard`` module so the logic can be unit-tested
# from ``tests/test_install_guard.py`` without needing the relative-import
# parent-package context that ComfyUI's custom-node loader supplies (and
# pytest does not). See that module's docstring for the failure mode this
# guards against; the short version is: two Koolook folders in
# ``custom_nodes/`` race over ``/userdata/koolook_workflows.json`` and
# silently corrupt it.
#
# Import shape mirrors ``koolook_routes`` below — relative inside the
# package, absolute as a fallback so an out-of-package test harness can
# still load this file as a script.
try:
from .koolook_install_guard import ( # noqa: E402
build_duplicate_report,
detect_duplicate_koolook_installs,
)
except ImportError: # pragma: no cover - exercised only outside package context
from koolook_install_guard import ( # type: ignore[no-redef]
build_duplicate_report,
detect_duplicate_koolook_installs,
)
NODE_CLASS_MAPPINGS: dict = {}
NODE_DISPLAY_NAME_MAPPINGS: dict = {}
def _merge_node_group(label: str, module_name: str) -> None:
"""Import one node group's mappings, isolating any import failure.
A missing module, a broken import inside it, or an absent
``NODE_CLASS_MAPPINGS`` / ``NODE_DISPLAY_NAME_MAPPINGS`` attribute is
caught and logged; the rest of the registry (and the route install
below) is unaffected.
"""
try:
module = importlib.import_module(module_name, __package__)
class_mappings = module.NODE_CLASS_MAPPINGS
display_mappings = module.NODE_DISPLAY_NAME_MAPPINGS
except (ImportError, AttributeError) as exc:
print(f"[Koolook] {label} registry skipped: {exc}")
return
NODE_CLASS_MAPPINGS.update(class_mappings)
NODE_DISPLAY_NAME_MAPPINGS.update(display_mappings)
_here = Path(__file__).resolve().parent
if "__path__" not in globals():
# Registry scanners commonly load custom-node entrypoints directly from
# ``__init__.py`` instead of as an installed package. Mark this module as a
# package so the same relative imports ComfyUI uses can still resolve.
__path__ = [str(_here)] # type: ignore[var-annotated]
__package__ = __name__
if __spec__ is not None:
__spec__.submodule_search_locations = [str(_here)]
# The guard must never take down the plugin: any failure to scan or parse
# siblings degrades to "register normally" (fail-safe) instead of aborting
# the import (fail-silent — the plugin would vanish entirely, taking the
# alphabetical winner with it). detect_/read_ already swallow per-entry
# OSError/ValueError; this try is the final backstop for anything unforeseen.
_is_winning_install = True
try:
_siblings = detect_duplicate_koolook_installs(_here)
if _siblings:
_is_winning_install, _report = build_duplicate_report(_here, _siblings)
print(_report)
except Exception as _guard_err: # pragma: no cover - defensive backstop
print(
f"[Koolook] install-guard check failed ({_guard_err!r}); "
"registering normally."
)
_is_winning_install = True
if not _is_winning_install:
# Non-winning duplicate (#162). Short-circuit BEFORE any of the heavy
# node imports (torch, PIL, diffusers via the radiance/whatdreamscost
# forks) or the aiohttp route install — the winner owns those for this
# session. No WEB_DIRECTORY either, so the loser doesn't double-register
# the sidebar web assets. Critical log already printed above.
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
else:
# Probe for a real package context. ComfyUI's custom-node loader provides
# one; pytest's package collector and static-analysis tools import this
# file as a standalone module, where relative imports raise. In that case
# we register nothing (a single message) and let tooling import the
# package directory without crashing. ``koolook_versioning`` is a
# pure-stdlib helper, so the probe can only fail on the context, never on
# its body.
try:
from . import koolook_versioning as _context_probe # noqa: F401
except ImportError as _context_exc:
print(
f"[Koolook] node registry skipped (non-Comfy import context): "
f"{_context_exc}"
)
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
else:
# Order is cosmetic (menu grouping); each group is independent.
_merge_node_group("Wan 2.2 Easy Prompt", ".k_easy_wan22_prompt")
_merge_node_group("Easy Resize", ".k_easy_resize")
_merge_node_group("Easy AI Pipeline", ".k_ai_pipeline")
_merge_node_group("Easy Image Batch", ".k_easy_image_batch")
_merge_node_group("Koolook Camera Loader", ".k_easy_track")
_merge_node_group("Easy Pattern", ".k_easy_pattern")
_merge_node_group("Easy Utility", ".k_easy_utility")
_merge_node_group("Koolook Loop Status", ".k_loop_status")
_merge_node_group("Koolook Publish Contract", ".k_publish_contract")
# The VHS-dependent video wrappers degrade to empty mappings when
# VideoHelperSuite is absent (they catch the import internally); the
# guard also covers a hard ImportError should one ever slip through.
_merge_node_group("Easy Load Video", ".k_video_load")
_merge_node_group("Easy Video Combine", ".k_video_combine")
_merge_node_group("Radiance Koolook VAE", ".forks.radiance_koolook")
_merge_node_group("WhatDreamsCost LTX Director", ".forks.whatdreamscost_koolook")
# Register the Kforge Labs snapshot/preset endpoints. Installed
# regardless of the node-import results above, so a broken node
# module can no longer take the /koolook/presets/* routes down with
# it. Both the import and the install() call are guarded: a missing
# aiohttp (a ComfyUI hard-dep) or any other load error degrades to
# "snapshot feature unavailable", never a crash.
try:
from . import koolook_routes # noqa: E402
if not koolook_routes.install():
print(
"[Koolook] PromptServer unavailable at import time; preset routes "
"(/koolook/presets/*) not registered for this session."
)
except Exception as _routes_exc: # pragma: no cover
print(
f"[Koolook] preset routes import failed; node mappings unaffected: "
f"{_routes_exc}"
)
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|