Renamed Hidden->HiddenHolder, HiddenEnum->Hidden for ease of usage, cls.hidden will only have values given for corresponding entries in the schema's hidden entry, fixed v3 node check in execution.get_input_data, some cleanup of whitespace and commented out code

This commit is contained in:
Jedrzej Kosinski 2025-06-19 00:10:28 -05:00
parent f9aec12ef1
commit 11d87760ca
3 changed files with 51 additions and 43 deletions

View File

@ -114,7 +114,7 @@ def comfytype(io_type: str, **kwargs):
new_cls.__module__ = cls.__module__ new_cls.__module__ = cls.__module__
new_cls.__doc__ = cls.__doc__ new_cls.__doc__ = cls.__doc__
# assign ComfyType attributes, if needed # assign ComfyType attributes, if needed
# NOTE: do we need __ne__ trick for io_type? (see IO.__ne__ for details) # NOTE: do we need __ne__ trick for io_type? (see node_typing.IO.__ne__ for details)
new_cls.io_type = io_type new_cls.io_type = io_type
if new_cls.Input is not None: if new_cls.Input is not None:
new_cls.Input.Parent = new_cls new_cls.Input.Parent = new_cls
@ -139,11 +139,6 @@ class IO_V3:
def __init__(self): def __init__(self):
pass pass
# def __init_subclass__(cls, io_type: IO | str, **kwargs):
# # TODO: do we need __ne__ trick for io_type? (see IO.__ne__ for details)
# cls.io_type = io_type
# super().__init_subclass__(**kwargs)
@property @property
def io_type(self): def io_type(self):
return self.Parent.io_type return self.Parent.io_type
@ -163,14 +158,14 @@ class InputV3(IO_V3):
self.optional = optional self.optional = optional
self.tooltip = tooltip self.tooltip = tooltip
self.lazy = lazy self.lazy = lazy
def as_dict_V1(self): def as_dict_V1(self):
return prune_dict({ return prune_dict({
"display_name": self.display_name, "display_name": self.display_name,
"tooltip": self.tooltip, "tooltip": self.tooltip,
"lazy": self.lazy "lazy": self.lazy
}) })
def get_io_type_V1(self): def get_io_type_V1(self):
return self.io_type return self.io_type
@ -219,7 +214,7 @@ class NodeStateLocal(NodeState):
def __init__(self, node_id: str): def __init__(self, node_id: str):
super().__init__(node_id) super().__init__(node_id)
self.local_state = {} self.local_state = {}
def __getattr__(self, key: str): def __getattr__(self, key: str):
local_state = type(self).__getattribute__(self, "local_state") local_state = type(self).__getattribute__(self, "local_state")
if key in local_state: if key in local_state:
@ -259,7 +254,7 @@ class Boolean:
@comfytype(io_type=IO.INT) @comfytype(io_type=IO.INT)
class Int: class Int:
Type = int Type = int
class Input(WidgetInputV3): class Input(WidgetInputV3):
'''Integer input.''' '''Integer input.'''
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
@ -272,7 +267,7 @@ class Int:
self.control_after_generate = control_after_generate self.control_after_generate = control_after_generate
self.display_mode = display_mode self.display_mode = display_mode
self.default: int self.default: int
def as_dict_V1(self): def as_dict_V1(self):
return super().as_dict_V1() | prune_dict({ return super().as_dict_V1() | prune_dict({
"min": self.min, "min": self.min,
@ -288,7 +283,7 @@ class Int:
@comfytype(io_type=IO.FLOAT) @comfytype(io_type=IO.FLOAT)
class Float: class Float:
Type = float Type = float
class Input(WidgetInputV3): class Input(WidgetInputV3):
'''Float input.''' '''Float input.'''
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
@ -302,7 +297,7 @@ class Float:
self.round = round self.round = round
self.display_mode = display_mode self.display_mode = display_mode
self.default: float self.default: float
def as_dict_V1(self): def as_dict_V1(self):
return super().as_dict_V1() | prune_dict({ return super().as_dict_V1() | prune_dict({
"min": self.min, "min": self.min,
@ -311,14 +306,14 @@ class Float:
"round": self.round, "round": self.round,
"display": self.display_mode, "display": self.display_mode,
}) })
class Output(OutputV3): class Output(OutputV3):
... ...
@comfytype(io_type=IO.STRING) @comfytype(io_type=IO.STRING)
class String: class String:
Type = str Type = str
class Input(WidgetInputV3): class Input(WidgetInputV3):
'''String input.''' '''String input.'''
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
@ -357,7 +352,7 @@ class Combo:
self.image_folder = image_folder self.image_folder = image_folder
self.remote = remote self.remote = remote
self.default: str self.default: str
def as_dict_V1(self): def as_dict_V1(self):
return super().as_dict_V1() | prune_dict({ return super().as_dict_V1() | prune_dict({
"multiselect": self.multiselect, "multiselect": self.multiselect,
@ -381,7 +376,7 @@ class MultiCombo:
self.placeholder = placeholder self.placeholder = placeholder
self.chip = chip self.chip = chip
self.default: list[str] self.default: list[str]
def as_dict_V1(self): def as_dict_V1(self):
return super().as_dict_V1() | prune_dict({ return super().as_dict_V1() | prune_dict({
"multiselect": self.multiselect, "multiselect": self.multiselect,
@ -548,7 +543,7 @@ class ComboDynamicInput(DynamicInput):
AutoGrowDynamicInput(id="dynamic", template_input=Image.Input(id="image")) AutoGrowDynamicInput(id="dynamic", template_input=Image.Input(id="image"))
class Hidden: class HiddenHolder:
def __init__(self, unique_id: str, prompt: Any, def __init__(self, unique_id: str, prompt: Any,
extra_pnginfo: Any, dynprompt: Any, extra_pnginfo: Any, dynprompt: Any,
auth_token_comfy_org: str, api_key_comfy_org: str, **kwargs): auth_token_comfy_org: str, api_key_comfy_org: str, **kwargs):
@ -572,15 +567,15 @@ class Hidden:
@classmethod @classmethod
def from_dict(cls, d: dict): def from_dict(cls, d: dict):
return cls( return cls(
unique_id=d.get(HiddenEnum.unique_id), unique_id=d.get(Hidden.unique_id, None),
prompt=d.get(HiddenEnum.prompt), prompt=d.get(Hidden.prompt, None),
extra_pnginfo=d.get(HiddenEnum.extra_pnginfo), extra_pnginfo=d.get(Hidden.extra_pnginfo, None),
dynprompt=d.get(HiddenEnum.dynprompt), dynprompt=d.get(Hidden.dynprompt, None),
auth_token_comfy_org=d.get(HiddenEnum.auth_token_comfy_org), auth_token_comfy_org=d.get(Hidden.auth_token_comfy_org, None),
api_key_comfy_org=d.get(HiddenEnum.api_key_comfy_org), api_key_comfy_org=d.get(Hidden.api_key_comfy_org, None),
) )
class HiddenEnum(str, Enum): class Hidden(str, Enum):
''' '''
Enumerator for requesting hidden variables in nodes. Enumerator for requesting hidden variables in nodes.
''' '''
@ -637,7 +632,7 @@ class SchemaV3:
"""The category of the node, as per the "Add Node" menu.""" """The category of the node, as per the "Add Node" menu."""
inputs: list[InputV3]=None inputs: list[InputV3]=None
outputs: list[OutputV3]=None outputs: list[OutputV3]=None
hidden: list[HiddenEnum]=None hidden: list[Hidden]=None
description: str="" description: str=""
"""Node description, shown as a tooltip when hovering over the node.""" """Node description, shown as a tooltip when hovering over the node."""
is_input_list: bool = False is_input_list: bool = False
@ -758,7 +753,7 @@ class ComfyNodeV3:
# filled in during execution # filled in during execution
state: NodeState = None state: NodeState = None
hidden: Hidden = None hidden: HiddenHolder = None
@classmethod @classmethod
def GET_NODE_INFO_V3(cls) -> dict[str, Any]: def GET_NODE_INFO_V3(cls) -> dict[str, Any]:
@ -803,7 +798,7 @@ class ComfyNodeV3:
type_clone: type[ComfyNodeV3] = type(f"CLEAN_{c_type.__name__}", c_type.__bases__, {}) type_clone: type[ComfyNodeV3] = type(f"CLEAN_{c_type.__name__}", c_type.__bases__, {})
# TODO: what parameters should be carried over? # TODO: what parameters should be carried over?
type_clone.SCHEMA = c_type.SCHEMA type_clone.SCHEMA = c_type.SCHEMA
type_clone.hidden = Hidden.from_dict(hidden_inputs) type_clone.hidden = HiddenHolder.from_dict(hidden_inputs)
# TODO: add anything we would want to expose inside node's execute function # TODO: add anything we would want to expose inside node's execute function
return type_clone return type_clone
@ -890,7 +885,7 @@ class ComfyNodeV3:
FUNCTION = "execute" FUNCTION = "execute"
@classmethod @classmethod
def INPUT_TYPES(cls) -> dict[str, dict]: def INPUT_TYPES(cls, include_hidden=True, return_schema=False) -> dict[str, dict] | tuple[dict[str, dict], SchemaV3]:
schema = cls.DEFINE_SCHEMA() schema = cls.DEFINE_SCHEMA()
# for V1, make inputs be a dict with potential keys {required, optional, hidden} # for V1, make inputs be a dict with potential keys {required, optional, hidden}
input = { input = {
@ -900,9 +895,11 @@ class ComfyNodeV3:
for i in schema.inputs: for i in schema.inputs:
key = "optional" if i.optional else "required" key = "optional" if i.optional else "required"
input.setdefault(key, {})[i.id] = (i.get_io_type_V1(), i.as_dict_V1()) input.setdefault(key, {})[i.id] = (i.get_io_type_V1(), i.as_dict_V1())
if schema.hidden: if schema.hidden and include_hidden:
for hidden in schema.hidden: for hidden in schema.hidden:
input.setdefault("hidden", {})[hidden.name] = (hidden.value,) input.setdefault("hidden", {})[hidden.name] = (hidden.value,)
if return_schema:
return input, schema
return input return input
@classmethod @classmethod
@ -1088,7 +1085,7 @@ class TestNode(ComfyNodeV3):
Mask.Input("thing"), Mask.Input("thing"),
], ],
outputs=[Image.Output("image_output")], outputs=[Image.Output("image_output")],
hidden=[HiddenEnum.api_key_comfy_org, HiddenEnum.auth_token_comfy_org, HiddenEnum.unique_id] hidden=[Hidden.api_key_comfy_org, Hidden.auth_token_comfy_org, Hidden.unique_id]
) )
@classmethod @classmethod

View File

@ -64,7 +64,9 @@ class V3TestNode(io.ComfyNodeV3):
io.Image.Output("img_output", display_name="img🖼", tooltip="This is an image"), io.Image.Output("img_output", display_name="img🖼", tooltip="This is an image"),
], ],
hidden=[ hidden=[
io.Hidden.prompt,
io.Hidden.auth_token_comfy_org,
io.Hidden.unique_id,
], ],
is_output_node=True, is_output_node=True,
) )

View File

@ -28,7 +28,7 @@ from comfy_execution.graph import (
) )
from comfy_execution.graph_utils import GraphBuilder, is_link from comfy_execution.graph_utils import GraphBuilder, is_link
from comfy_execution.validation import validate_node_input from comfy_execution.validation import validate_node_input
from comfy_api.v3.io import NodeOutput, ComfyNodeV3, HiddenEnum, NodeStateLocal from comfy_api.v3.io import NodeOutput, ComfyNodeV3, Hidden, NodeStateLocal
class ExecutionResult(Enum): class ExecutionResult(Enum):
@ -119,7 +119,11 @@ class CacheSet:
return result return result
def get_input_data(inputs, class_def, unique_id, outputs=None, dynprompt=None, extra_data={}): def get_input_data(inputs, class_def, unique_id, outputs=None, dynprompt=None, extra_data={}):
valid_inputs = class_def.INPUT_TYPES() is_v3 = issubclass(class_def, ComfyNodeV3)
if is_v3:
valid_inputs, schema = class_def.INPUT_TYPES(include_hidden=False, return_schema=True)
else:
valid_inputs = class_def.INPUT_TYPES()
input_data_all = {} input_data_all = {}
missing_keys = {} missing_keys = {}
hidden_inputs_v3 = {} hidden_inputs_v3 = {}
@ -147,15 +151,20 @@ def get_input_data(inputs, class_def, unique_id, outputs=None, dynprompt=None, e
elif input_category is not None: elif input_category is not None:
input_data_all[x] = [input_data] input_data_all[x] = [input_data]
# V3 if is_v3:
if isinstance(class_def, type(ComfyNodeV3)): if schema.hidden:
hidden_inputs_v3[HiddenEnum.prompt] = dynprompt.get_original_prompt() if dynprompt is not None else {} if Hidden.prompt in schema.hidden:
hidden_inputs_v3[HiddenEnum.dynprompt] = dynprompt hidden_inputs_v3[Hidden.prompt] = dynprompt.get_original_prompt() if dynprompt is not None else {}
hidden_inputs_v3[HiddenEnum.extra_pnginfo] = extra_data.get('extra_pnginfo', None) if Hidden.dynprompt in schema.hidden:
hidden_inputs_v3[HiddenEnum.unique_id] = unique_id hidden_inputs_v3[Hidden.dynprompt] = dynprompt
hidden_inputs_v3[HiddenEnum.auth_token_comfy_org] = extra_data.get("auth_token_comfy_org", None) if Hidden.extra_pnginfo in schema.hidden:
hidden_inputs_v3[HiddenEnum.api_key_comfy_org] = extra_data.get("api_key_comfy_org", None) hidden_inputs_v3[Hidden.extra_pnginfo] = extra_data.get('extra_pnginfo', None)
# V1 if Hidden.unique_id in schema.hidden:
hidden_inputs_v3[Hidden.unique_id] = unique_id
if Hidden.auth_token_comfy_org in schema.hidden:
hidden_inputs_v3[Hidden.auth_token_comfy_org] = extra_data.get("auth_token_comfy_org", None)
if Hidden.api_key_comfy_org in schema.hidden:
hidden_inputs_v3[Hidden.api_key_comfy_org] = extra_data.get("api_key_comfy_org", None)
else: else:
if "hidden" in valid_inputs: if "hidden" in valid_inputs:
h = valid_inputs["hidden"] h = valid_inputs["hidden"]