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 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 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
@@ -1767,6 +1767,8 @@ class Schema:
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).
"""
loop_boundary: Literal["start", "end"] | None = None
"""Identifies this node as the start or end of a loop for prompt validation."""
def validate(self):
'''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):
LOOP_BOUNDARY = "start"
@classmethod
def define_schema(cls):
list_item_type = io.MatchType.Template("list_item")
@@ -113,6 +111,7 @@ class StartLoop(io.ComfyNode):
node_id="StartLoop",
display_name="Start Loop",
category="utilities/looping",
loop_boundary="start",
is_input_list=True,
inputs=[
io.DynamicCombo.Input("mode", options=[
@@ -338,8 +337,6 @@ class LoopResult(io.ComfyNode):
class EndLoop(io.ComfyNode):
LOOP_BOUNDARY = "end"
@classmethod
def define_schema(cls):
output_type = io.MatchType.Template("output_value")
@@ -357,6 +354,7 @@ class EndLoop(io.ComfyNode):
node_id="EndLoop",
display_name="End Loop",
category="utilities/looping",
loop_boundary="end",
is_input_list=True,
inputs=[
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()
end_nodes = set()
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":
start_nodes.add(node_id)
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():
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]["advanced"] is True
assert list(inputs["optional"]) == ["parent_iteration", "initial_iteration_value"]