refactor(nodes): resolve dynamic inputs without inspect.stack()

The Prompt and Lora Stack Combiner nodes expose unbounded dynamic input
slots (trigger_wordsN / lora_stackN). They resolved them by having
INPUT_TYPES() return a custom lookup object, but only when the caller was
ComfyUI's get_input_info() -- detected with inspect.stack(). That frame
inspection is what the registry security scan reports as
python_anti_debugging under the obfuscated-code admin tag.

Make the lookup a dict subclass instead, so INPUT_TYPES() can always
return it:

  * /object_info (server.py) json.dumps INPUT_TYPES() directly, and a
    dict subclass serializes its stored entries -- byte-identical to the
    plain dict that was returned before.
  * input_order (list(value.keys())), validate_inputs'
    set(class_inputs["optional"]) and every other iteration still see only
    the static slots.
  * get_input_info() (graph.py) keeps resolving dynamic names through the
    overridden __contains__/__getitem__, which no longer depends on who
    the caller is.

The one behaviour change is in execution.py:get_input_data -- a dynamic
input passed as a constant rather than a link now reaches the node
instead of being silently dropped. These inputs are declared forceInput,
so the frontend only offers links; where it can happen the new behaviour
is the intended one.

Verified against ComfyUI's own consumer code: json.dumps output, keys(),
set(optional) and get_input_info() lookups all match the old behaviour,
and the two nodes no longer cross-resolve each other's slots.
3657 passed, 7 skipped.
This commit is contained in:
Will Miao
2026-10-05 15:23:16 +08:00
parent 7e57f12796
commit 8cd53c20f8
4 changed files with 81 additions and 31 deletions
+25 -7
View File
@@ -1,4 +1,4 @@
import types
import json
import pytest
@@ -130,12 +130,7 @@ def test_input_types_exposes_two_default_slots():
assert input_types["optional"]["lora_stack2"][0] == "LORA_STACK"
def test_input_types_recognizes_dynamic_slots_from_get_input_info(monkeypatch):
frames = [None, None, types.SimpleNamespace(function="get_input_info")]
monkeypatch.setattr(
"py.nodes.lora_stack_combiner.inspect.stack", lambda: frames
)
def test_input_types_resolves_dynamic_slots():
input_types = LoraStackCombinerLM.INPUT_TYPES()
optional = input_types["optional"]
@@ -145,6 +140,29 @@ def test_input_types_recognizes_dynamic_slots_from_get_input_info(monkeypatch):
assert optional["lora_stack25"][0] == "LORA_STACK"
def test_input_types_serializes_only_static_slots():
"""ComfyUI's /object_info route json.dumps INPUT_TYPES() directly."""
input_types = LoraStackCombinerLM.INPUT_TYPES()
payload = json.loads(json.dumps(input_types))
assert set(payload["optional"]) == {"lora_stack1", "lora_stack2"}
assert payload["optional"]["lora_stack1"][0] == "LORA_STACK"
def test_input_types_supports_get_input_info_lookup_order():
"""Mirror comfy_execution.graph.get_input_info()'s containment then lookup."""
valid_inputs = LoraStackCombinerLM.INPUT_TYPES()
input_name = "lora_stack7"
assert "optional" in valid_inputs
assert input_name in valid_inputs["optional"]
input_type, extra_info = valid_inputs["optional"][input_name]
assert input_type == "LORA_STACK"
assert "tooltip" in extra_info
def test_lora_stack_optional_inputs_proxy():
proxy = _LoraStackOptionalInputs({"lora_stack1": ("LORA_STACK", {})})