Don't instantiate nodes during validation

Addresses review feedback: the V1 executability check fell back to
constructing the node (class_def()) when the FUNCTION method wasn't found on
the class. That runs __init__ during validation, so a constructor's side
effects or failure could be misreported as invalid_node_definition for an
otherwise valid node.

Inspect only the class. No core/extra node defines its FUNCTION method on the
instance, so this loses no real coverage while removing the side-effect risk.

Replace the instance-fallback test with one asserting a node with a raising
__init__ but a valid class-level method still passes validation (i.e. it is
never instantiated).
This commit is contained in:
Wei Hai 2026-06-26 16:04:29 -07:00
parent 82c954bd2a
commit bf00c39705
2 changed files with 18 additions and 20 deletions

View File

@ -1113,12 +1113,6 @@ def full_type_name(klass):
return klass.__qualname__
return module + '.' + klass.__qualname__
def _v1_function_resolves(node):
"""Whether node.FUNCTION names a callable on node (a class or an instance)."""
function_name = getattr(node, "FUNCTION", None)
return function_name is not None and callable(getattr(node, function_name, None))
def node_not_executable_reason(class_def, class_type):
"""Return a human-readable reason the node cannot be executed, or None if it's fine.
@ -1126,21 +1120,22 @@ def node_not_executable_reason(class_def, class_type):
(e.g. a V1 ``FUNCTION = "invert"`` where the method is misspelled, or a V3 node
missing its ``execute`` override). Running this during validation surfaces the
problem before execution starts, instead of after upstream nodes have run.
Only the class is inspected; the node is never instantiated here, so a node's
``__init__`` side effects cannot run (or fail) during validation.
"""
try:
if issubclass(class_def, _ComfyNodeInternal):
# V3: validates that execute()/define_schema() overrides exist.
class_def.VALIDATE_CLASS()
return None
# V1: FUNCTION names the method to call. Check the class first (the common
# case); fall back to an instance, since the node is invoked on an instance
# and may define FUNCTION or its method in __init__.
if _v1_function_resolves(class_def) or _v1_function_resolves(class_def()):
return None
# V1: FUNCTION names the method to call; it must exist on the class.
function_name = getattr(class_def, "FUNCTION", None)
if function_name is None:
return f"'{class_type}' does not define FUNCTION"
return f"'{class_type}' has no method '{function_name}' (declared in FUNCTION)"
if not callable(getattr(class_def, function_name, None)):
return f"'{class_type}' has no method '{function_name}' (declared in FUNCTION)"
return None
except Exception as ex:
return str(ex)

View File

@ -39,8 +39,8 @@ class _TypoV1Node:
return (None,)
class _InstanceMethodV1Node:
"""Defines its FUNCTION method on the instance, as the runtime resolves it."""
class _SideEffectInitV1Node:
"""Valid class-level method, but a constructor that must never run in validation."""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
@ -51,7 +51,10 @@ class _InstanceMethodV1Node:
CATEGORY = "Test"
def __init__(self):
self.run = lambda: (None,)
raise RuntimeError("__init__ must not run during validation")
def run(self):
return (None,)
def _v3_schema(node_id):
@ -111,11 +114,11 @@ def test_typo_node_rejected_with_node_error():
assert "invert" in node_errors["1"]["errors"][0]["details"]
def test_instance_defined_method_not_false_positived():
"""A node whose method is defined in __init__ runs fine and must not be blocked."""
_register("InstanceMethodV1Node", _InstanceMethodV1Node)
assert node_not_executable_reason(_InstanceMethodV1Node, "InstanceMethodV1Node") is None
valid, _, _, _ = _validate("InstanceMethodV1Node")
def test_validation_does_not_instantiate_node():
"""A valid node is not constructed during validation, so __init__ never runs."""
_register("SideEffectInitV1Node", _SideEffectInitV1Node)
assert node_not_executable_reason(_SideEffectInitV1Node, "SideEffectInitV1Node") is None
valid, _, _, _ = _validate("SideEffectInitV1Node")
assert valid is True