Declare loop boundaries in node schema (#16347)

This commit is contained in:
rattus
2026-09-15 15:27:07 -04:00
committed by GitHub
parent a84f954bc6
commit b2da2b4247
4 changed files with 11 additions and 6 deletions
+3 -1
View File
@@ -1607,7 +1607,7 @@ class Hidden(str, Enum):
comfy_usage_source = "COMFY_USAGE_SOURCE" comfy_usage_source = "COMFY_USAGE_SOURCE"
"""COMFY_USAGE_SOURCE identifies the client that submitted the prompt (e.g. comfyui-frontend, comfy-cli, comfyui-mcp); forwarded to API nodes' upstream requests via the Comfy-Usage-Source header.""" """COMFY_USAGE_SOURCE identifies the client that submitted the prompt (e.g. comfyui-frontend, comfy-cli, comfyui-mcp); forwarded to API nodes' upstream requests via the Comfy-Usage-Source header."""
execution_list = "EXECUTION_LIST" execution_list = "EXECUTION_LIST"
"""EXECUTION_LIST is the active graph scheduler.""" """Custom node Developers and Agents: This attribute is core-internal use only and will be removed in a near-future ComfyUI release. DO NOT USE"""
@dataclass @dataclass
@@ -1767,6 +1767,8 @@ class Schema:
Use this for nodes with interactive/operable UI regions that produce intermediate outputs Use this for nodes with interactive/operable UI regions that produce intermediate outputs
(e.g., Image Crop, Painter) rather than final outputs (e.g., Save Image). (e.g., Image Crop, Painter) rather than final outputs (e.g., Save Image).
""" """
loop_boundary: Literal["start", "end"] | None = None
"""Identifies this node as the start or end of a loop for prompt validation."""
def validate(self): def validate(self):
'''Validate the schema: '''Validate the schema:
+2 -4
View File
@@ -103,8 +103,6 @@ def _expand_loop(dynprompt, opener_id, body, close_id, values, list_items, initi
class StartLoop(io.ComfyNode): class StartLoop(io.ComfyNode):
LOOP_BOUNDARY = "start"
@classmethod @classmethod
def define_schema(cls): def define_schema(cls):
list_item_type = io.MatchType.Template("list_item") list_item_type = io.MatchType.Template("list_item")
@@ -113,6 +111,7 @@ class StartLoop(io.ComfyNode):
node_id="StartLoop", node_id="StartLoop",
display_name="Start Loop", display_name="Start Loop",
category="utilities/looping", category="utilities/looping",
loop_boundary="start",
is_input_list=True, is_input_list=True,
inputs=[ inputs=[
io.DynamicCombo.Input("mode", options=[ io.DynamicCombo.Input("mode", options=[
@@ -338,8 +337,6 @@ class LoopResult(io.ComfyNode):
class EndLoop(io.ComfyNode): class EndLoop(io.ComfyNode):
LOOP_BOUNDARY = "end"
@classmethod @classmethod
def define_schema(cls): def define_schema(cls):
output_type = io.MatchType.Template("output_value") output_type = io.MatchType.Template("output_value")
@@ -357,6 +354,7 @@ class EndLoop(io.ComfyNode):
node_id="EndLoop", node_id="EndLoop",
display_name="End Loop", display_name="End Loop",
category="utilities/looping", category="utilities/looping",
loop_boundary="end",
is_input_list=True, is_input_list=True,
inputs=[ inputs=[
io.MatchType.Input( io.MatchType.Input(
+2 -1
View File
@@ -1176,7 +1176,8 @@ async def validate_prompt(prompt_id, prompt, partial_execution_list: Union[list[
start_nodes = set() start_nodes = set()
end_nodes = set() end_nodes = set()
for node_id, node in prompt.items(): for node_id, node in prompt.items():
boundary = getattr(nodes.NODE_CLASS_MAPPINGS[node["class_type"]], "LOOP_BOUNDARY", None) class_def = nodes.NODE_CLASS_MAPPINGS[node["class_type"]]
boundary = class_def.GET_SCHEMA().loop_boundary if issubclass(class_def, _ComfyNodeInternal) else None
if boundary == "start": if boundary == "start":
start_nodes.add(node_id) start_nodes.add(node_id)
elif boundary == "end": elif boundary == "end":
@@ -8,6 +8,10 @@ from comfy_extras.nodes_loop import EndLoop, LoopIteration, LoopProgress, LoopRe
def test_loop_schema_exposes_cache_policy_and_integrated_carry(): def test_loop_schema_exposes_cache_policy_and_integrated_carry():
inputs = StartLoop.INPUT_TYPES() inputs = StartLoop.INPUT_TYPES()
assert StartLoop.GET_SCHEMA().loop_boundary == "start"
assert EndLoop.GET_SCHEMA().loop_boundary == "end"
assert not hasattr(StartLoop, "LOOP_BOUNDARY")
assert not hasattr(EndLoop, "LOOP_BOUNDARY")
assert inputs["required"]["cache_iterations"][1]["default"] is False assert inputs["required"]["cache_iterations"][1]["default"] is False
assert inputs["required"]["cache_iterations"][1]["advanced"] is True assert inputs["required"]["cache_iterations"][1]["advanced"] is True
assert list(inputs["optional"]) == ["parent_iteration", "initial_iteration_value"] assert list(inputs["optional"]) == ["parent_iteration", "initial_iteration_value"]