From 2e4dadaa80f96fefe3488b99b21fe01cf1afdf0d Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sun, 11 May 2025 16:18:45 +0200 Subject: [PATCH] Consolidate boolean, casting, control flow, data list and dict nodes --- src/basic_data_handling/boolean_nodes.py | 47 +-- src/basic_data_handling/casting_nodes.py | 78 ++--- src/basic_data_handling/control_flow_nodes.py | 60 ++-- src/basic_data_handling/data_list_nodes.py | 305 +++++------------- src/basic_data_handling/dict_nodes.py | 235 +++++--------- 5 files changed, 235 insertions(+), 490 deletions(-) diff --git a/src/basic_data_handling/boolean_nodes.py b/src/basic_data_handling/boolean_nodes.py index a3ad7c0..c0e48bc 100644 --- a/src/basic_data_handling/boolean_nodes.py +++ b/src/basic_data_handling/boolean_nodes.py @@ -1,6 +1,7 @@ from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class BooleanAnd: +class BooleanAnd(ComfyNodeABC): """ Returns the logical AND result of two boolean values. @@ -10,12 +11,12 @@ class BooleanAnd: def INPUT_TYPES(cls): return { "required": { - "input1": ("BOOLEAN", {"default": False, "forceInput": True}), - "input2": ("BOOLEAN", {"default": False, "forceInput": True}), + "input1": (IO.BOOLEAN, {"default": False, "forceInput": True}), + "input2": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "and_operation" @@ -24,7 +25,7 @@ class BooleanAnd: return (input1 and input2,) -class BooleanOr: +class BooleanOr(ComfyNodeABC): """ Returns the logical OR result of two boolean values. @@ -34,12 +35,12 @@ class BooleanOr: def INPUT_TYPES(cls): return { "required": { - "input1": ("BOOLEAN", {"default": False, "forceInput": True}), - "input2": ("BOOLEAN", {"default": False, "forceInput": True}), + "input1": (IO.BOOLEAN, {"default": False, "forceInput": True}), + "input2": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "or_operation" @@ -48,7 +49,7 @@ class BooleanOr: return (input1 or input2,) -class BooleanNot: +class BooleanNot(ComfyNodeABC): """ Returns the logical NOT result of a boolean value. @@ -58,11 +59,11 @@ class BooleanNot: def INPUT_TYPES(cls): return { "required": { - "input": ("BOOLEAN", {"default": False, "forceInput": True}), + "input": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "not_operation" @@ -71,7 +72,7 @@ class BooleanNot: return (not input,) -class BooleanXor: +class BooleanXor(ComfyNodeABC): """ Returns the logical XOR result of two boolean values. @@ -81,12 +82,12 @@ class BooleanXor: def INPUT_TYPES(cls): return { "required": { - "input1": ("BOOLEAN", {"default": False, "forceInput": True}), - "input2": ("BOOLEAN", {"default": False, "forceInput": True}), + "input1": (IO.BOOLEAN, {"default": False, "forceInput": True}), + "input2": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "xor_operation" @@ -95,7 +96,7 @@ class BooleanXor: return (input1 != input2,) -class BooleanNand: +class BooleanNand(ComfyNodeABC): """ Returns the logical NAND result of two boolean values. @@ -105,12 +106,12 @@ class BooleanNand: def INPUT_TYPES(cls): return { "required": { - "input1": ("BOOLEAN", {"default": False, "forceInput": True}), - "input2": ("BOOLEAN", {"default": False, "forceInput": True}), + "input1": (IO.BOOLEAN, {"default": False, "forceInput": True}), + "input2": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "nand_operation" @@ -119,7 +120,7 @@ class BooleanNand: return (not (input1 and input2),) -class BooleanNor: +class BooleanNor(ComfyNodeABC): """ Returns the logical NOR result of two boolean values. @@ -129,12 +130,12 @@ class BooleanNor: def INPUT_TYPES(cls): return { "required": { - "input1": ("BOOLEAN", {"default": False, "forceInput": True}), - "input2": ("BOOLEAN", {"default": False, "forceInput": True}), + "input1": (IO.BOOLEAN, {"default": False, "forceInput": True}), + "input2": (IO.BOOLEAN, {"default": False, "forceInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/BOOLEAN" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "nor_operation" diff --git a/src/basic_data_handling/casting_nodes.py b/src/basic_data_handling/casting_nodes.py index 41644c5..226733c 100644 --- a/src/basic_data_handling/casting_nodes.py +++ b/src/basic_data_handling/casting_nodes.py @@ -1,8 +1,8 @@ from typing import Any from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC - -class CastToString: +class CastToString(ComfyNodeABC): """ Converts any input to a STRING. Non-string values are converted using str(). """ @@ -10,24 +10,20 @@ class CastToString: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/cast" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_string" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_string(self, input: Any) -> tuple[str]: return (str(input),) -class CastToInt: +class CastToInt(ComfyNodeABC): """ Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError. """ @@ -35,19 +31,15 @@ class CastToInt: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/cast" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_int" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_int(self, input: Any) -> tuple[int]: try: return (int(input),) @@ -55,7 +47,7 @@ class CastToInt: raise ValueError(f"Cannot convert {input} to an INT.") -class CastToFloat: +class CastToFloat(ComfyNodeABC): """ Converts any numeric input to a FLOAT. Non-numeric or invalid inputs raise a ValueError. """ @@ -63,7 +55,7 @@ class CastToFloat: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } @@ -72,10 +64,6 @@ class CastToFloat: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_float" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_float(self, input: Any) -> tuple[float]: try: return (float(input),) @@ -83,7 +71,7 @@ class CastToFloat: raise ValueError(f"Cannot convert {input} to a FLOAT.") -class CastToBoolean: +class CastToBoolean(ComfyNodeABC): """ Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules. """ @@ -91,7 +79,7 @@ class CastToBoolean: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } @@ -100,15 +88,11 @@ class CastToBoolean: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_boolean" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_boolean(self, input: Any) -> tuple[bool]: return (bool(input),) -class CastToList: +class CastToList(ComfyNodeABC): """ Converts any input to a LIST. Non-list inputs are wrapped in a list. If input is a ComfyUI data list, it converts the individual items into a Python LIST. @@ -117,7 +101,7 @@ class CastToList: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } @@ -126,17 +110,13 @@ class CastToList: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_list" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_list(self, input: Any) -> tuple[list]: if isinstance(input, list): return (input,) return ([input],) -class CastDataListToList: +class CastDataListToList(ComfyNodeABC): """ Converts a ComfyUI data list into a LIST object. @@ -147,7 +127,7 @@ class CastDataListToList: def INPUT_TYPES(cls): return { "required": { - "list": ("*", {}), + "list": (IO.ANY, {}), } } @@ -157,15 +137,11 @@ class CastDataListToList: FUNCTION = "convert" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert(self, **kwargs: list[Any]) -> tuple[list[Any]]: return (kwargs.get('list', []).copy(),) -class CastToSet: +class CastToSet(ComfyNodeABC): """ Converts any input to a SET. Non-set inputs are converted into a set. If input is a ComfyUI data list, it casts the individual items into a SET. @@ -174,7 +150,7 @@ class CastToSet: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } @@ -183,17 +159,13 @@ class CastToSet: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_set" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_set(self, input: Any) -> tuple[set]: if isinstance(input, set): return (input,) return ({input,} if not isinstance(input, list) else set(input),) -class CastDataListToSet: +class CastDataListToSet(ComfyNodeABC): """ Converts a ComfyUI data list into a LIST object. @@ -204,7 +176,7 @@ class CastDataListToSet: def INPUT_TYPES(cls): return { "required": { - "list": ("*", {}), + "list": (IO.ANY, {}), } } @@ -214,15 +186,11 @@ class CastDataListToSet: FUNCTION = "convert" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]: return (set(kwargs.get('list', [])),) -class CastToDict: +class CastToDict(ComfyNodeABC): """ Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs. """ @@ -230,7 +198,7 @@ class CastToDict: def INPUT_TYPES(cls): return { "required": { - "input": ("*", {}) + "input": (IO.ANY, {}) } } @@ -239,10 +207,6 @@ class CastToDict: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert_to_dict" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def convert_to_dict(self, input: Any) -> tuple[dict]: try: return (dict(input),) diff --git a/src/basic_data_handling/control_flow_nodes.py b/src/basic_data_handling/control_flow_nodes.py index 5737362..b7a90a9 100644 --- a/src/basic_data_handling/control_flow_nodes.py +++ b/src/basic_data_handling/control_flow_nodes.py @@ -1,7 +1,8 @@ from typing import Any from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class IfElse: +class IfElse(ComfyNodeABC): """ Implements a conditional branch (if/else) in the workflow. @@ -13,24 +14,18 @@ class IfElse: def INPUT_TYPES(cls): return { "required": { - "condition": ("BOOLEAN", {}), - "if_true": ("*", {"lazy": True}), - "if_false": ("*", {"lazy": True}), + "condition": (IO.BOOLEAN, {}), + "if_true": (IO.ANY, {"lazy": True}), + "if_false": (IO.ANY, {"lazy": True}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("result",) CATEGORY = "Basic/Flow Control" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "execute" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str: - if input_types["condition"] not in ("BOOLEAN"): - return "condition must be a BOOLEAN type" - return True - def check_lazy_status(self, condition: bool, if_true: Any, if_false: Any) -> list[str]: needed = [] if if_true is None and condition is True: @@ -43,7 +38,7 @@ class IfElse: return (if_true if condition else if_false,) -class SwitchCase: +class SwitchCase(ComfyNodeABC): """ Implements a switch/case selection in the workflow. @@ -54,48 +49,45 @@ class SwitchCase: NOTE: This version of the node will most likely be deprecated in the future. """ + EXPERIMENTAL = True + @classmethod def INPUT_TYPES(cls): return { "required": { - "selector": ("INT", {"default": 0, "min": 0}), - "case_0": ("*", {"lazy": True}), - "case_1": ("*", {"lazy": True}), + "selector": (IO.INT, {"default": 0, "min": 0}), + "case_0": (IO.ANY, {"lazy": True}), + "case_1": (IO.ANY, {"lazy": True}), }, "optional": { - "case_2": ("*", {"lazy": True}), - "case_3": ("*", {"lazy": True}), - "case_4": ("*", {"lazy": True}), - "default": ("*", {"lazy": True}), + "case_2": (IO.ANY, {"lazy": True}), + "case_3": (IO.ANY, {"lazy": True}), + "case_4": (IO.ANY, {"lazy": True}), + "default": (IO.ANY, {"lazy": True}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("result",) CATEGORY = "Basic/Flow Control" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "execute" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str: - if input_types["selector"] not in ("INT"): - return "selector must be a INT type" - return True - - def check_lazy_status(self, condition: int, case_0: Any, case_1: Any, - case_2: Any, case_3: Any, case_4: Any, default: Any) -> list[str]: + def check_lazy_status(self, selector: int, case_0: Any, case_1: Any, + case_2: Any = None, case_3: Any = None, + case_4: Any = None, default: Any = None) -> list[str]: needed = [] - if case_0 is None and condition == 0: + if case_0 is None and selector == 0: needed.append("case_0") - if case_1 is None and condition == 1: + if case_1 is None and selector == 1: needed.append("case_1") - if case_2 is None and condition == 2: + if case_2 is None and selector == 2: needed.append("case_2") - if case_3 is None and condition == 3: + if case_3 is None and selector == 3: needed.append("case_3") - if case_4 is None and condition == 4: + if case_4 is None and selector == 4: needed.append("case_4") - if default is None and not 0 <= condition <= 4: + if default is None and not 0 <= selector <= 4: needed.append("default") return needed diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index 8203ea8..26c58f1 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -1,8 +1,8 @@ from typing import Any - from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class DataListAppend: +class DataListAppend(ComfyNodeABC): """ Adds an item to the end of a list. @@ -13,12 +13,12 @@ class DataListAppend: def INPUT_TYPES(cls): return { "optional": { - "list":("*",{}), - "item":("*",{}), + "list":(IO.ANY,{}), + "item":(IO.ANY,{}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -26,10 +26,6 @@ class DataListAppend: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def append(self, **kwargs: list[Any]) -> tuple[list[Any]]: result = kwargs.get('list', []).copy() item = kwargs.get('item', []) @@ -38,7 +34,7 @@ class DataListAppend: return (result,) -class DataListExtend: +class DataListExtend(ComfyNodeABC): """ Extends a list by appending elements from another list. @@ -49,12 +45,12 @@ class DataListExtend: def INPUT_TYPES(cls): return { "optional": { - "list_a": ("*",{}), - "list_b": ("*",{}), + "list_a": (IO.ANY,{}), + "list_b": (IO.ANY,{}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -62,15 +58,11 @@ class DataListExtend: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def extend(self, **kwargs: list[Any]) -> tuple[list[Any]]: return (kwargs.get('list_a', []) + kwargs.get('list_b', []),) -class DataListInsert: +class DataListInsert(ComfyNodeABC): """ Inserts an item at a specified position in a list. @@ -81,13 +73,13 @@ class DataListInsert: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "index": ("INT", {"default": 0}), - "item": ("*",), + "list": (IO.ANY,), + "index": (IO.INT, {"default": 0}), + "item": (IO.ANY,), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -95,17 +87,13 @@ class DataListInsert: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def insert(self, **kwargs: list[Any]) -> tuple[list[Any]]: result = kwargs.get('list', []).copy() result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0]) return (result,) -class DataListRemove: +class DataListRemove(ComfyNodeABC): """ Removes the first occurrence of a specified value from a list. @@ -116,12 +104,12 @@ class DataListRemove: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "value": ("*",), + "list": (IO.ANY,), + "value": (IO.ANY,), } } - RETURN_TYPES = ("*", "BOOLEAN",) + RETURN_TYPES = (IO.ANY, IO.BOOLEAN,) RETURN_NAMES = ("list", "success",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -129,10 +117,6 @@ class DataListRemove: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True, False,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]: result = kwargs.get('list', []).copy() value = kwargs.get('value', []) @@ -143,7 +127,7 @@ class DataListRemove: return result, False -class DataListPop: +class DataListPop(ComfyNodeABC): """ Removes and returns an item at a specified position in a list. @@ -156,14 +140,14 @@ class DataListPop: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), + "list": (IO.ANY,), }, "optional": { - "index": ("INT", {"default": -1}), + "index": (IO.INT, {"default": -1}), } } - RETURN_TYPES = ("*", "*") + RETURN_TYPES = (IO.ANY, IO.ANY) RETURN_NAMES = ("list", "item") CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -171,12 +155,6 @@ class DataListPop: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True, False) - @classmethod - def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str: - if input_types[0].get("index", "INT") != "INT": - return "index must be an INT type" - return True - def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]: result = kwargs.get('list', []).copy() index = kwargs.get('index', [-1])[0] @@ -187,38 +165,7 @@ class DataListPop: return result, None -class DataListClear: - """ - Removes all items from a list. - - This node takes a list as input and returns a new empty list. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "optional": { - "list": ("*",), - } - } - - RETURN_TYPES = ("*",) - RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "clear" - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True,) - - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - - def clear(self, **kwargs: list[Any]) -> tuple[list]: - # Return a new empty list rather than modifying the input - return ([],) - - -class DataListIndex: +class DataListIndex(ComfyNodeABC): """ Returns the index of the first occurrence of a value in a list. @@ -230,26 +177,22 @@ class DataListIndex: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "value": ("*",), + "list": (IO.ANY,), + "value": (IO.ANY,), }, "optional": { - "start": ("INT", {"default": 0}), - "end": ("INT", {"default": -1}), + "start": (IO.INT, {"default": 0}), + "end": (IO.INT, {"default": -1}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("index",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "list_index" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def list_index(self, **kwargs: list[Any]) -> tuple[int]: input_list = kwargs.get('list', []) value = kwargs.get('value', [None])[0] @@ -264,7 +207,7 @@ class DataListIndex: return (-1,) -class DataListCount: +class DataListCount(ComfyNodeABC): """ Counts the number of occurrences of a value in a list. @@ -275,28 +218,24 @@ class DataListCount: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "value": ("*",), + "list": (IO.ANY,), + "value": (IO.ANY,), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("count",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "count" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def count(self, **kwargs: list[Any]) -> tuple[int]: value = kwargs.get('value', [None])[0] return (kwargs.get('list', []).count(value),) -class DataListSort: +class DataListSort(ComfyNodeABC): """ Sorts the items in a list. @@ -307,14 +246,14 @@ class DataListSort: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), + "list": (IO.ANY,), }, "optional": { "reverse": (["False", "True"], {"default": "False"}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -322,10 +261,6 @@ class DataListSort: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]: # Convert string to boolean reverse = kwargs.get('reverse', ["False"])[0] == "True" @@ -334,7 +269,7 @@ class DataListSort: return (result,) -class DataListReverse: +class DataListReverse(ComfyNodeABC): """ Reverses the order of items in a list. @@ -344,11 +279,11 @@ class DataListReverse: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), + "list": (IO.ANY,), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -356,47 +291,13 @@ class DataListReverse: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def reverse(self, **kwargs: list[Any]) -> tuple[list[Any]]: result = kwargs.get('list', []).copy() result.reverse() return (result,) -class DataListCopy: - """ - Creates a shallow copy of a list. - - This node takes a list as input and returns a new list that is a shallow copy of the original. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "list": ("*",), - } - } - - RETURN_TYPES = ("*",) - RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "copy" - INPUT_IS_LIST = True - OUTPUT_IS_LIST = (True,) - - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - - def copy(self, **kwargs: list[Any]) -> tuple[list[Any]]: - return (kwargs.get('list', []).copy(),) - - -class DataListLength: +class DataListLength(ComfyNodeABC): """ Counts the number of items in a list. @@ -406,26 +307,22 @@ class DataListLength: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), + "list": (IO.ANY,), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("length",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "length" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def length(self, **kwargs: list[Any]) -> tuple[int]: return (len(kwargs.get('list', [])),) -class DataListSlice: +class DataListSlice(ComfyNodeABC): """ Creates a slice of a list. @@ -436,16 +333,16 @@ class DataListSlice: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), + "list": (IO.ANY,), }, "optional": { - "start": ("INT", {"default": 0}), - "stop": ("INT", {"default": -1}), - "step": ("INT", {"default": 1}), + "start": (IO.INT, {"default": 0}), + "stop": (IO.INT, {"default": -1}), + "step": (IO.INT, {"default": 1}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -453,10 +350,6 @@ class DataListSlice: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def slice(self, **kwargs: list[Any]) -> tuple[list[Any]]: input_list = kwargs.get('list', []) start = kwargs.get('start', [0])[0] @@ -469,7 +362,7 @@ class DataListSlice: return (input_list[start:stop:step],) -class DataListGetItem: +class DataListGetItem(ComfyNodeABC): """ Retrieves an item at a specified position in a list. @@ -481,22 +374,18 @@ class DataListGetItem: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "index": ("INT", {"default": 0}), + "list": (IO.ANY,), + "index": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("item",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_item" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def get_item(self, **kwargs: list[Any]) -> tuple[Any]: index = kwargs.get('index', [0])[0] try: @@ -505,7 +394,7 @@ class DataListGetItem: return (None,) -class DataListSetItem: +class DataListSetItem(ComfyNodeABC): """ Sets an item at a specified position in a list. @@ -516,13 +405,13 @@ class DataListSetItem: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "index": ("INT", {"default": 0}), - "value": ("*",), + "list": (IO.ANY,), + "index": (IO.INT, {"default": 0}), + "value": (IO.ANY,), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -530,10 +419,6 @@ class DataListSetItem: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def set_item(self, **kwargs: list[Any]) -> tuple[Any]: input_list = kwargs.get('list', []) index = kwargs.get('index', [0])[0] @@ -546,7 +431,7 @@ class DataListSetItem: raise IndexError(f"Index {index} out of range for list of length {len(input_list)}") -class DataListContains: +class DataListContains(ComfyNodeABC): """ Checks if a list contains a specified value. @@ -557,22 +442,18 @@ class DataListContains: def INPUT_TYPES(cls): return { "required": { - "list": ("*",), - "value": ("*",), + "list": (IO.ANY,), + "value": (IO.ANY,), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("contains",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "contains" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def contains(self, **kwargs: list[Any]) -> tuple[bool]: value = kwargs.get('value', []) if len(value) == 0: @@ -580,7 +461,7 @@ class DataListContains: return (value[0] in kwargs.get('list', []),) -class DataListCreateEmpty: +class DataListCreateEmpty(ComfyNodeABC): """ Creates a new empty list. @@ -590,7 +471,7 @@ class DataListCreateEmpty: def INPUT_TYPES(cls): return {"required": {}} - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -601,7 +482,7 @@ class DataListCreateEmpty: return ([],) -class DataListZip: +class DataListZip(ComfyNodeABC): """ Combines multiple lists element-wise. @@ -613,16 +494,16 @@ class DataListZip: def INPUT_TYPES(cls): return { "required": { - "list1": ("*",), - "list2": ("*",), + "list1": (IO.ANY,), + "list2": (IO.ANY,), }, "optional": { - "list3": ("*",), - "list4": ("*",), + "list3": (IO.ANY,), + "list4": (IO.ANY,), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -630,10 +511,6 @@ class DataListZip: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: - return True - def zip_lists(self, **kwargs: list[Any]) -> tuple[list[Any]]: lists = [kwargs.get('list1', []), kwargs.get('list2', [])] @@ -648,7 +525,7 @@ class DataListZip: return (result,) -class DataListFilter: +class DataListFilter(ComfyNodeABC): """ Filters a data list using boolean values. @@ -663,12 +540,12 @@ class DataListFilter: def INPUT_TYPES(cls): return { "required": { - "value": ("*", {}), - "filter": ("BOOLEAN", {"forceInput": True}), + "value": (IO.ANY, {}), + "filter": (IO.BOOLEAN, {"forceInput": True}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("filtered_list",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") @@ -676,12 +553,6 @@ class DataListFilter: INPUT_IS_LIST = True OUTPUT_IS_LIST = (True,) - @classmethod - def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str: - if input_types[0]["filter"] != "BOOLEAN": - return "filter must be a BOOLEAN type" - return True - def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]: values = kwargs.get('value', []) filters = kwargs.get('filter', []) @@ -692,7 +563,7 @@ class DataListFilter: return (result,) -class DataListMin: +class DataListMin(ComfyNodeABC): """ Finds the minimum value in a list of numbers. @@ -704,23 +575,17 @@ class DataListMin: def INPUT_TYPES(cls): return { "required": { - "list": (["FLOAT", "INT"], {}), + "list": (IO.NUMBER, {}), } } - RETURN_TYPES = ("*",) - RETURN_NAMES = ("min_value",) + RETURN_TYPES = (IO.NUMBER,) + RETURN_NAMES = ("min",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "find_min" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str: - if input_types[0]["list"] not in ("FLOAT", "INT"): - return "list must contain FLOAT or INT types" - return True - def find_min(self, **kwargs: list[Any]) -> tuple[Any]: values = kwargs.get('list', []) if not values: @@ -734,7 +599,7 @@ class DataListMin: return (None,) -class DataListMax: +class DataListMax(ComfyNodeABC): """ Finds the maximum value in a list of numbers. @@ -746,23 +611,17 @@ class DataListMax: def INPUT_TYPES(cls): return { "required": { - "list": (["FLOAT", "INT"], {}), + "list": (IO.NUMBER, {}), } } - RETURN_TYPES = ("*",) - RETURN_NAMES = ("max_value",) + RETURN_TYPES = (IO.NUMBER,) + RETURN_NAMES = ("max",) CATEGORY = "Basic/data list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "find_max" INPUT_IS_LIST = True - @classmethod - def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str: - if input_types[0]["list"] not in ("FLOAT", "INT"): - return "list must contain FLOAT or INT types" - return True - def find_max(self, **kwargs: list[Any]) -> tuple[Any]: values = kwargs.get('list', []) if not values: @@ -782,12 +641,10 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: DataListInsert": DataListInsert, "Basic data handling: DataListRemove": DataListRemove, "Basic data handling: DataListPop": DataListPop, - "Basic data handling: DataListClear": DataListClear, "Basic data handling: DataListIndex": DataListIndex, "Basic data handling: DataListCount": DataListCount, "Basic data handling: DataListSort": DataListSort, "Basic data handling: DataListReverse": DataListReverse, - "Basic data handling: DataListCopy": DataListCopy, "Basic data handling: DataListLength": DataListLength, "Basic data handling: DataListSlice": DataListSlice, "Basic data handling: DataListGetItem": DataListGetItem, @@ -806,12 +663,10 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DataListInsert": "insert", "Basic data handling: DataListRemove": "remove", "Basic data handling: DataListPop": "pop", - "Basic data handling: DataListClear": "clear", "Basic data handling: DataListIndex": "index", "Basic data handling: DataListCount": "count", "Basic data handling: DataListSort": "sort", "Basic data handling: DataListReverse": "reverse", - "Basic data handling: DataListCopy": "copy", "Basic data handling: DataListLength": "length", "Basic data handling: DataListSlice": "slice", "Basic data handling: DataListGetItem": "get item", diff --git a/src/basic_data_handling/dict_nodes.py b/src/basic_data_handling/dict_nodes.py index c93ee7e..baf1f2e 100644 --- a/src/basic_data_handling/dict_nodes.py +++ b/src/basic_data_handling/dict_nodes.py @@ -1,7 +1,8 @@ from typing import Any from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class DictCreate: +class DictCreate(ComfyNodeABC): """ Creates a new empty dictionary. @@ -20,7 +21,7 @@ class DictCreate: return ({},) -class DictCreateFromItems: +class DictCreateFromItemsList(ComfyNodeABC): """ Creates a dictionary from a list of key-value pairs. @@ -43,7 +44,8 @@ class DictCreateFromItems: try: # Check if items are valid (key-value pairs) for item in items: - if not isinstance(item, tuple) or len(item) != 2: + print(f'item: "{item}", len(item): {len(item)}, items: "{items}", len(items): {len(items)}') + if not isinstance(item, tuple) and len(item) != 2: raise ValueError("Each item must be a (key, value) pair") return (dict(items),) @@ -51,7 +53,41 @@ class DictCreateFromItems: raise ValueError(f"Error creating dictionary from items: {str(e)}") -class DictGet: +class DictCreateFromItemsDataList(ComfyNodeABC): + """ + Creates a dictionary from a list of key-value pairs. + + This node takes a list of key-value pairs (tuples) and builds a dictionary from them. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "item": (IO.ANY, {}), + } + } + + RETURN_TYPES = ("DICT",) + CATEGORY = "Basic/DICT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_from_items" + INPUT_IS_LIST = True + + def create_from_items(self, **kwargs: list[Any]) -> tuple[dict]: + try: + # Check if items are valid (key-value pairs) + items = kwargs.get('item', []) + for item in items: + print(f'item: "{item}", len(item): {len(item)}, items: "{items}", len(items): {len(items)}') + if not isinstance(item, tuple) and len(item) != 2: + raise ValueError("Each item must be a (key, value) pair") + + return (dict(items),) + except Exception as e: + raise ValueError(f"Error creating dictionary from items: {str(e)}") + + +class DictGet(ComfyNodeABC): """ Retrieves a value from a dictionary using the specified key. @@ -64,14 +100,14 @@ class DictGet: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), + "key": (IO.STRING, {"default": ""}), }, "optional": { - "default": ("*", {}), + "default": (IO.ANY, {}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("value",) CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -81,7 +117,7 @@ class DictGet: return (input_dict.get(key, default),) -class DictSet: +class DictSet(ComfyNodeABC): """ Adds or updates a key-value pair in a dictionary. @@ -93,8 +129,8 @@ class DictSet: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), - "value": ("*", {}), + "key": (IO.STRING, {"default": ""}), + "value": (IO.ANY, {}), } } @@ -109,7 +145,7 @@ class DictSet: return (result,) -class DictKeys: +class DictKeys(ComfyNodeABC): """ Returns all keys in a dictionary. @@ -132,7 +168,7 @@ class DictKeys: return (list(input_dict.keys()),) -class DictValues: +class DictValues(ComfyNodeABC): """ Returns all values in a dictionary. @@ -155,7 +191,7 @@ class DictValues: return (list(input_dict.values()),) -class DictItems: +class DictItems(ComfyNodeABC): """ Returns all key-value pairs in a dictionary. @@ -179,7 +215,7 @@ class DictItems: return (list(input_dict.items()),) -class DictContainsKey: +class DictContainsKey(ComfyNodeABC): """ Checks if a key exists in a dictionary. @@ -191,11 +227,11 @@ class DictContainsKey: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), + "key": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "contains_key" @@ -204,54 +240,7 @@ class DictContainsKey: return (key in input_dict,) -class DictClear: - """ - Removes all items from a dictionary. - - This node takes a dictionary as input and returns a new empty dictionary. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_dict": ("DICT", {}), - } - } - - RETURN_TYPES = ("DICT",) - CATEGORY = "Basic/DICT" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "clear" - - def clear(self, input_dict: dict) -> tuple[dict]: - return ({},) - - -class DictCopy: - """ - Creates a shallow copy of a dictionary. - - This node takes a dictionary as input and returns a new dictionary that is - a shallow copy of the original. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input_dict": ("DICT", {}), - } - } - - RETURN_TYPES = ("DICT",) - CATEGORY = "Basic/DICT" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "copy" - - def copy(self, input_dict: dict) -> tuple[dict]: - return (input_dict.copy(),) - - -class DictFromKeys: +class DictFromKeys(ComfyNodeABC): """ Creates a dictionary from a list of keys and a default value. @@ -266,7 +255,7 @@ class DictFromKeys: "keys": ("LIST", {}), }, "optional": { - "value": ("*", {}), + "value": (IO.ANY, {}), } } @@ -279,7 +268,7 @@ class DictFromKeys: return (dict.fromkeys(keys, value),) -class DictPop: +class DictPop(ComfyNodeABC): """ Removes and returns a key-value pair from a dictionary. @@ -293,14 +282,14 @@ class DictPop: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), + "key": (IO.STRING, {"default": ""}), }, "optional": { - "default_value": ("*", {}), + "default_value": (IO.ANY, {}), } } - RETURN_TYPES = ("DICT", "*") + RETURN_TYPES = ("DICT", IO.ANY) RETURN_NAMES = ("dict", "value") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -319,7 +308,7 @@ class DictPop: raise ValueError(f"Error popping key from dictionary: {str(e)}") -class DictPopItem: +class DictPopItem(ComfyNodeABC): """ Removes and returns an arbitrary key-value pair from a dictionary. @@ -335,7 +324,7 @@ class DictPopItem: } } - RETURN_TYPES = ("DICT", "STRING", "*", "BOOLEAN") + RETURN_TYPES = ("DICT", IO.STRING, IO.ANY, IO.BOOLEAN) RETURN_NAMES = ("dict", "key", "value", "success") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -353,7 +342,7 @@ class DictPopItem: return result, "", None, False -class DictSetDefault: +class DictSetDefault(ComfyNodeABC): """ Returns the value for a key, setting a default if the key doesn't exist. @@ -366,12 +355,12 @@ class DictSetDefault: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), - "default_value": ("*", {}), + "key": (IO.STRING, {"default": ""}), + "default_value": (IO.ANY, {}), } } - RETURN_TYPES = ("DICT", "*") + RETURN_TYPES = ("DICT", IO.ANY) RETURN_NAMES = ("DICT", "value") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -383,7 +372,7 @@ class DictSetDefault: return result, value -class DictUpdate: +class DictUpdate(ComfyNodeABC): """ Updates a dictionary with key-value pairs from another dictionary. @@ -411,7 +400,7 @@ class DictUpdate: return (result,) -class DictLength: +class DictLength(ComfyNodeABC): """ Returns the number of key-value pairs in a dictionary. @@ -435,7 +424,7 @@ class DictLength: return (len(input_dict),) -class DictMerge: +class DictMerge(ComfyNodeABC): """ Merges multiple dictionaries into a single dictionary. @@ -475,7 +464,7 @@ class DictMerge: return (result,) -class DictGetKeysValues: +class DictGetKeysValues(ComfyNodeABC): """ Returns keys and values as separate lists. @@ -502,7 +491,7 @@ class DictGetKeysValues: return keys, values -class DictRemove: +class DictRemove(ComfyNodeABC): """ Removes a key-value pair from a dictionary. @@ -515,11 +504,11 @@ class DictRemove: return { "required": { "input_dict": ("DICT", {}), - "key": ("STRING", {"default": ""}), + "key": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("DICT", "BOOLEAN") + RETURN_TYPES = ("DICT", IO.BOOLEAN) RETURN_NAMES = ("dict", "key_removed") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -533,7 +522,7 @@ class DictRemove: return result, False -class DictFilterByKeys: +class DictFilterByKeys(ComfyNodeABC): """ Creates a new dictionary with only the specified keys. @@ -559,7 +548,7 @@ class DictFilterByKeys: return (result,) -class DictExcludeKeys: +class DictExcludeKeys(ComfyNodeABC): """ Creates a new dictionary excluding the specified keys. @@ -585,7 +574,7 @@ class DictExcludeKeys: return (result,) -class DictGetMultiple: +class DictGetMultiple(ComfyNodeABC): """ Retrieves multiple values from a dictionary using a list of keys. @@ -601,7 +590,7 @@ class DictGetMultiple: "keys": ("LIST", {}), }, "optional": { - "default": ("*", {}), + "default": (IO.ANY, {}), } } @@ -616,7 +605,7 @@ class DictGetMultiple: return (values,) -class DictInvert: +class DictInvert(ComfyNodeABC): """ Creates a new dictionary with keys and values swapped. @@ -632,7 +621,7 @@ class DictInvert: } } - RETURN_TYPES = ("DICT", "BOOLEAN") + RETURN_TYPES = ("DICT", IO.BOOLEAN) RETURN_NAMES = ("inverted_dict", "success") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -647,7 +636,7 @@ class DictInvert: return input_dict, False -class DictCreateFromLists: +class DictCreateFromLists(ComfyNodeABC): """ Creates a dictionary from separate lists of keys and values. @@ -675,7 +664,7 @@ class DictCreateFromLists: return (result,) -class DictCompare: +class DictCompare(ComfyNodeABC): """ Compares two dictionaries and reports differences. @@ -692,7 +681,7 @@ class DictCompare: } } - RETURN_TYPES = ("BOOLEAN", "LIST", "LIST", "LIST") + RETURN_TYPES = (IO.BOOLEAN, "LIST", "LIST", "LIST") RETURN_NAMES = ("are_equal", "only_in_dict1", "only_in_dict2", "different_values") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") @@ -715,69 +704,16 @@ class DictCompare: return are_equal, only_in_dict1, only_in_dict2, different_values -class AnyToDict: - """ - Converts compatible data structures to a dictionary. - - This node attempts to convert an input value to a dictionary. Compatible - inputs include sequences of key-value pairs, mapping objects, and objects - with a to_dict() or as_dict() method. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input": ("*", {}), - } - } - - RETURN_TYPES = ("DICT", "BOOLEAN") - CATEGORY = "Basic/DICT" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "convert" - - def convert(self, input: Any) -> tuple[dict]: - try: - if isinstance(input, dict): - return (input.copy(),) - - # Try converting from items - if hasattr(input, "items"): - return (dict(input.items()),) - - # Try converting from sequence of pairs - if hasattr(input, "__iter__") and not isinstance(input, str): - try: - result = dict(input) - return (result,) - except (TypeError, ValueError): - pass - - # Check for to_dict or as_dict methods - if hasattr(input, "to_dict") and callable(getattr(input, "to_dict")): - return (input.to_dict(),) - - if hasattr(input, "as_dict") and callable(getattr(input, "as_dict")): - return (input.as_dict(),) - - # Failed to convert - return ({},) - - except Exception: - return ({},) - - NODE_CLASS_MAPPINGS = { "Basic data handling: DictCreate": DictCreate, - "Basic data handling: DictCreateFromItems": DictCreateFromItems, + "Basic data handling: DictCreateFromItemsList": DictCreateFromItemsList, + "Basic data handling: DictCreateFromItemsDataList": DictCreateFromItemsDataList, "Basic data handling: DictGet": DictGet, "Basic data handling: DictSet": DictSet, "Basic data handling: DictKeys": DictKeys, "Basic data handling: DictValues": DictValues, "Basic data handling: DictItems": DictItems, "Basic data handling: DictContainsKey": DictContainsKey, - "Basic data handling: DictClear": DictClear, - "Basic data handling: DictCopy": DictCopy, "Basic data handling: DictFromKeys": DictFromKeys, "Basic data handling: DictPop": DictPop, "Basic data handling: DictPopItem": DictPopItem, @@ -793,20 +729,18 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: DictInvert": DictInvert, "Basic data handling: DictCreateFromLists": DictCreateFromLists, "Basic data handling: DictCompare": DictCompare, - "Basic data handling: AnyToDict": AnyToDict, } NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DictCreate": "create", - "Basic data handling: DictCreateFromItems": "from_items", + "Basic data handling: DictCreateFromItemsList": "from items (LIST)", + "Basic data handling: DictCreateFromItemsDataList": "from items (data list)", "Basic data handling: DictGet": "get", "Basic data handling: DictSet": "set", "Basic data handling: DictKeys": "keys", "Basic data handling: DictValues": "values", "Basic data handling: DictItems": "items", "Basic data handling: DictContainsKey": "contains_key", - "Basic data handling: DictClear": "clear", - "Basic data handling: DictCopy": "copy", "Basic data handling: DictFromKeys": "fromkeys", "Basic data handling: DictPop": "pop", "Basic data handling: DictPopItem": "popitem", @@ -822,5 +756,4 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DictInvert": "invert", "Basic data handling: DictCreateFromLists": "from_lists", "Basic data handling: DictCompare": "compare", - "Basic data handling: AnyToDict": "any_to_DICT", }