Support validate_inputs for v3 replacing VALIDATE_INPUTS, support check_lazy_mix for v3, prep for renaming IS_CHANGED to fingerprint_inputs, reorder some class methods

This commit is contained in:
kosinkadink1@gmail.com 2025-07-09 02:26:35 -05:00
parent 936bf6b60f
commit 82e6eeab75
3 changed files with 81 additions and 27 deletions

View File

@ -1025,12 +1025,6 @@ class ComfyNodeV3:
resources: Resources = None
hidden: HiddenHolder = None
@classmethod
def GET_NODE_INFO_V3(cls) -> dict[str, Any]:
schema = cls.GET_SCHEMA()
# TODO: finish
return None
@classmethod
@abstractmethod
def DEFINE_SCHEMA(cls) -> SchemaV3:
@ -1046,10 +1040,46 @@ class ComfyNodeV3:
pass
execute = None
@classmethod
def validate_inputs(cls, **kwargs) -> bool:
"""Optionally, define this function to validate inputs; equivalnet to V1's VALIDATE_INPUTS."""
pass
validate_inputs = None
@classmethod
def fingerprint_inputs(cls, **kwargs) -> Any:
"""Optionally, define this function to fingerprint inputs; equivalent to V1's IS_CHANGED."""
pass
fingerprint_inputs = None
@classmethod
def check_lazy_status(cls, **kwargs) -> list[str]:
"""Optionally, define this function to return a list of input names that should be evaluated.
This basic mixin impl. requires all inputs.
:kwargs: All node inputs will be included here. If the input is ``None``, it should be assumed that it has not yet been evaluated. \
When using ``INPUT_IS_LIST = True``, unevaluated will instead be ``(None,)``.
Params should match the nodes execution ``FUNCTION`` (self, and all inputs by name).
Will be executed repeatedly until it returns an empty list, or all requested items were already evaluated (and sent as params).
Comfy Docs: https://docs.comfy.org/custom-nodes/backend/lazy_evaluation#defining-check-lazy-status
"""
need = [name for name in kwargs if kwargs[name] is None]
return need
check_lazy_status = None
@classmethod
def GET_SERIALIZERS(cls) -> list[Serializer]:
return []
@classmethod
def GET_NODE_INFO_V3(cls) -> dict[str, Any]:
schema = cls.GET_SCHEMA()
# TODO: finish
return None
def __init__(self):
self.local_state: NodeStateLocal = None
self.local_resources: ResourcesLocal = None

View File

@ -73,7 +73,7 @@ class V3TestNode(io.ComfyNodeV3):
)
@classmethod
def VALIDATE_INPUTS(cls, image: io.Image.Type, some_int: int, combo: io.Combo.Type, combo2: io.MultiCombo.Type, xyz: XYZ.Type=None, mask: io.Mask.Type=None, **kwargs):
def validate_inputs(cls, image: io.Image.Type, some_int: int, combo: io.Combo.Type, combo2: io.MultiCombo.Type, xyz: XYZ.Type=None, mask: io.Mask.Type=None, **kwargs):
if some_int < 0:
raise Exception("some_int must be greater than 0")
if combo == "c":
@ -172,6 +172,15 @@ class NInputsTest(io.ComfyNodeV3):
],
)
@classmethod
def validate_inputs(cls, nmock, nmock2):
return True
@classmethod
def check_lazy_status(cls, **kwargs) -> list[str]:
need = [name for name in kwargs if kwargs[name] is None]
return need
@classmethod
def execute(cls, nmock, nmock2):
first_image = nmock[0]

View File

@ -28,7 +28,7 @@ from comfy_execution.graph import (
)
from comfy_execution.graph_utils import GraphBuilder, is_link
from comfy_execution.validation import validate_node_input
from comfy_api.v3.io import NodeOutput, ComfyNodeV3, Hidden, NodeStateLocal, ResourcesLocal, AutogrowDynamic
from comfy_api.v3.io import NodeOutput, ComfyNodeV3, Hidden, NodeStateLocal, ResourcesLocal, AutogrowDynamic, is_class
class ExecutionResult(Enum):
@ -216,7 +216,15 @@ def _map_node_over_list(obj, input_data_all, func, allow_interrupt=False, execut
if pre_execute_cb is not None and index is not None:
pre_execute_cb(index)
# V3
if isinstance(obj, ComfyNodeV3):
if isinstance(obj, ComfyNodeV3) or (is_class(obj) and issubclass(obj, ComfyNodeV3)):
# if is just a class, then assign no resources or state, just create clone
if is_class(obj):
type_obj = obj
obj.VALIDATE_CLASS()
class_clone = obj.prepare_class_clone(hidden_inputs)
# otherwise, use class instance to populate/reuse some fields
else:
type_obj = type(obj)
type(obj).VALIDATE_CLASS()
class_clone = type(obj).prepare_class_clone(hidden_inputs)
# NOTE: this is a mock of state management; for local, just stores NodeStateLocal on node instance
@ -239,7 +247,7 @@ def _map_node_over_list(obj, input_data_all, func, allow_interrupt=False, execut
dynamic_list.append(real_inputs.pop(d.id, None))
dynamic_list = [x for x in dynamic_list if x is not None]
inputs = {**real_inputs, add_key: dynamic_list}
results.append(getattr(type(obj), func).__func__(class_clone, **inputs))
results.append(getattr(type_obj, func).__func__(class_clone, **inputs))
# V1
else:
results.append(getattr(obj, func)(**inputs))
@ -392,7 +400,7 @@ def execute(server, dynprompt, caches, current_item, extra_data, executed, promp
obj = class_def()
caches.objects.set(unique_id, obj)
if hasattr(obj, "check_lazy_status"):
if getattr(obj, "check_lazy_status", None) is not None:
required_inputs = _map_node_over_list(obj, input_data_all, "check_lazy_status", allow_interrupt=True, hidden_inputs=hidden_inputs)
required_inputs = set(sum([r for r in required_inputs if isinstance(r,list)], []))
required_inputs = [x for x in required_inputs if isinstance(x,str) and (
@ -651,8 +659,16 @@ def validate_inputs(prompt, item, validated):
validate_function_inputs = []
validate_has_kwargs = False
if hasattr(obj_class, "VALIDATE_INPUTS"):
argspec = inspect.getfullargspec(obj_class.VALIDATE_INPUTS)
validate_function_name = None
validate_function = None
if issubclass(obj_class, ComfyNodeV3):
validate_function_name = "validate_inputs"
validate_function = getattr(obj_class, validate_function_name, None)
else:
validate_function_name = "VALIDATE_INPUTS"
validate_function = getattr(obj_class, validate_function_name, None)
if validate_function is not None:
argspec = inspect.getfullargspec(validate_function)
validate_function_inputs = argspec.args
validate_has_kwargs = argspec.varkw is not None
received_types = {}
@ -835,8 +851,7 @@ def validate_inputs(prompt, item, validated):
if 'input_types' in validate_function_inputs:
input_filtered['input_types'] = [received_types]
#ret = obj_class.VALIDATE_INPUTS(**input_filtered)
ret = _map_node_over_list(obj_class, input_filtered, "VALIDATE_INPUTS", hidden_inputs=hidden_inputs)
ret = _map_node_over_list(obj_class, input_filtered, validate_function_name, hidden_inputs=hidden_inputs)
for x in input_filtered:
for i, r in enumerate(ret):
if r is not True and not isinstance(r, ExecutionBlocker):