mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-03 13:19:23 +08:00
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:
parent
82c954bd2a
commit
bf00c39705
19
execution.py
19
execution.py
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user