From 11d87760ca9ed0f5ac0a707871d292778e81ff81 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 19 Jun 2025 00:10:28 -0500 Subject: [PATCH] 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 --- comfy_api/v3/io.py | 59 +++++++++++++++++------------------ comfy_extras/nodes_v3_test.py | 4 ++- execution.py | 31 +++++++++++------- 3 files changed, 51 insertions(+), 43 deletions(-) diff --git a/comfy_api/v3/io.py b/comfy_api/v3/io.py index ec6f135dd..da5050ca0 100644 --- a/comfy_api/v3/io.py +++ b/comfy_api/v3/io.py @@ -114,7 +114,7 @@ def comfytype(io_type: str, **kwargs): new_cls.__module__ = cls.__module__ new_cls.__doc__ = cls.__doc__ # 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 if new_cls.Input is not None: new_cls.Input.Parent = new_cls @@ -139,11 +139,6 @@ class IO_V3: def __init__(self): 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 def io_type(self): return self.Parent.io_type @@ -163,14 +158,14 @@ class InputV3(IO_V3): self.optional = optional self.tooltip = tooltip self.lazy = lazy - + def as_dict_V1(self): return prune_dict({ "display_name": self.display_name, "tooltip": self.tooltip, "lazy": self.lazy }) - + def get_io_type_V1(self): return self.io_type @@ -219,7 +214,7 @@ class NodeStateLocal(NodeState): def __init__(self, node_id: str): super().__init__(node_id) self.local_state = {} - + def __getattr__(self, key: str): local_state = type(self).__getattribute__(self, "local_state") if key in local_state: @@ -259,7 +254,7 @@ class Boolean: @comfytype(io_type=IO.INT) class Int: Type = int - + class Input(WidgetInputV3): '''Integer input.''' 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.display_mode = display_mode self.default: int - + def as_dict_V1(self): return super().as_dict_V1() | prune_dict({ "min": self.min, @@ -288,7 +283,7 @@ class Int: @comfytype(io_type=IO.FLOAT) class Float: Type = float - + class Input(WidgetInputV3): '''Float input.''' 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.display_mode = display_mode self.default: float - + def as_dict_V1(self): return super().as_dict_V1() | prune_dict({ "min": self.min, @@ -311,14 +306,14 @@ class Float: "round": self.round, "display": self.display_mode, }) - + class Output(OutputV3): ... @comfytype(io_type=IO.STRING) class String: Type = str - + class Input(WidgetInputV3): '''String input.''' 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.remote = remote self.default: str - + def as_dict_V1(self): return super().as_dict_V1() | prune_dict({ "multiselect": self.multiselect, @@ -381,7 +376,7 @@ class MultiCombo: self.placeholder = placeholder self.chip = chip self.default: list[str] - + def as_dict_V1(self): return super().as_dict_V1() | prune_dict({ "multiselect": self.multiselect, @@ -548,7 +543,7 @@ class ComboDynamicInput(DynamicInput): AutoGrowDynamicInput(id="dynamic", template_input=Image.Input(id="image")) -class Hidden: +class HiddenHolder: def __init__(self, unique_id: str, prompt: Any, extra_pnginfo: Any, dynprompt: Any, auth_token_comfy_org: str, api_key_comfy_org: str, **kwargs): @@ -572,15 +567,15 @@ class Hidden: @classmethod def from_dict(cls, d: dict): return cls( - unique_id=d.get(HiddenEnum.unique_id), - prompt=d.get(HiddenEnum.prompt), - extra_pnginfo=d.get(HiddenEnum.extra_pnginfo), - dynprompt=d.get(HiddenEnum.dynprompt), - auth_token_comfy_org=d.get(HiddenEnum.auth_token_comfy_org), - api_key_comfy_org=d.get(HiddenEnum.api_key_comfy_org), + unique_id=d.get(Hidden.unique_id, None), + prompt=d.get(Hidden.prompt, None), + extra_pnginfo=d.get(Hidden.extra_pnginfo, None), + dynprompt=d.get(Hidden.dynprompt, None), + auth_token_comfy_org=d.get(Hidden.auth_token_comfy_org, None), + 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. ''' @@ -637,7 +632,7 @@ class SchemaV3: """The category of the node, as per the "Add Node" menu.""" inputs: list[InputV3]=None outputs: list[OutputV3]=None - hidden: list[HiddenEnum]=None + hidden: list[Hidden]=None description: str="" """Node description, shown as a tooltip when hovering over the node.""" is_input_list: bool = False @@ -758,7 +753,7 @@ class ComfyNodeV3: # filled in during execution state: NodeState = None - hidden: Hidden = None + hidden: HiddenHolder = None @classmethod 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__, {}) # TODO: what parameters should be carried over? 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 return type_clone @@ -890,7 +885,7 @@ class ComfyNodeV3: FUNCTION = "execute" @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() # for V1, make inputs be a dict with potential keys {required, optional, hidden} input = { @@ -900,9 +895,11 @@ class ComfyNodeV3: for i in schema.inputs: key = "optional" if i.optional else "required" 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: input.setdefault("hidden", {})[hidden.name] = (hidden.value,) + if return_schema: + return input, schema return input @classmethod @@ -1088,7 +1085,7 @@ class TestNode(ComfyNodeV3): Mask.Input("thing"), ], 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 diff --git a/comfy_extras/nodes_v3_test.py b/comfy_extras/nodes_v3_test.py index ef601825d..4bd8a150a 100644 --- a/comfy_extras/nodes_v3_test.py +++ b/comfy_extras/nodes_v3_test.py @@ -64,7 +64,9 @@ class V3TestNode(io.ComfyNodeV3): io.Image.Output("img_output", display_name="img🖼️", tooltip="This is an image"), ], hidden=[ - + io.Hidden.prompt, + io.Hidden.auth_token_comfy_org, + io.Hidden.unique_id, ], is_output_node=True, ) diff --git a/execution.py b/execution.py index 884f3664b..d9e1b101a 100644 --- a/execution.py +++ b/execution.py @@ -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, HiddenEnum, NodeStateLocal +from comfy_api.v3.io import NodeOutput, ComfyNodeV3, Hidden, NodeStateLocal class ExecutionResult(Enum): @@ -119,7 +119,11 @@ class CacheSet: return result 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 = {} missing_keys = {} 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: input_data_all[x] = [input_data] - # V3 - if isinstance(class_def, type(ComfyNodeV3)): - hidden_inputs_v3[HiddenEnum.prompt] = dynprompt.get_original_prompt() if dynprompt is not None else {} - hidden_inputs_v3[HiddenEnum.dynprompt] = dynprompt - hidden_inputs_v3[HiddenEnum.extra_pnginfo] = extra_data.get('extra_pnginfo', None) - hidden_inputs_v3[HiddenEnum.unique_id] = unique_id - hidden_inputs_v3[HiddenEnum.auth_token_comfy_org] = extra_data.get("auth_token_comfy_org", None) - hidden_inputs_v3[HiddenEnum.api_key_comfy_org] = extra_data.get("api_key_comfy_org", None) - # V1 + if is_v3: + if schema.hidden: + if Hidden.prompt in schema.hidden: + hidden_inputs_v3[Hidden.prompt] = dynprompt.get_original_prompt() if dynprompt is not None else {} + if Hidden.dynprompt in schema.hidden: + hidden_inputs_v3[Hidden.dynprompt] = dynprompt + if Hidden.extra_pnginfo in schema.hidden: + hidden_inputs_v3[Hidden.extra_pnginfo] = extra_data.get('extra_pnginfo', None) + 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: if "hidden" in valid_inputs: h = valid_inputs["hidden"]