Merge pull request #6 from StableLlama/sort

Sort
This commit is contained in:
StableLlama
2025-05-16 00:10:49 +02:00
committed by GitHub
22 changed files with 2757 additions and 2823 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "basic_data_handling"
version = "0.2.0"
version = "0.2.1"
description = """NOTE: Still in development! Expect breaking changes!
Basic Python functions for manipulating data that every programmer is used to.
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
+77 -77
View File
@@ -25,77 +25,6 @@ class BooleanAnd(ComfyNodeABC):
return (input1 and input2,)
class BooleanOr(ComfyNodeABC):
"""
Returns the logical OR result of two boolean values.
This node takes two boolean inputs and returns their logical OR result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "or_operation"
def or_operation(self, input1: bool, input2: bool) -> tuple[bool]:
return (input1 or input2,)
class BooleanNot(ComfyNodeABC):
"""
Returns the logical NOT result of a boolean value.
This node takes one boolean input and returns its logical NOT result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "not_operation"
def not_operation(self, input: bool) -> tuple[bool]:
return (not input,)
class BooleanXor(ComfyNodeABC):
"""
Returns the logical XOR result of two boolean values.
This node takes two boolean inputs and returns their logical XOR result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "xor_operation"
def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]:
return (input1 != input2,)
class BooleanNand(ComfyNodeABC):
"""
Returns the logical NAND result of two boolean values.
@@ -144,20 +73,91 @@ class BooleanNor(ComfyNodeABC):
return (not (input1 or input2),)
class BooleanNot(ComfyNodeABC):
"""
Returns the logical NOT result of a boolean value.
This node takes one boolean input and returns its logical NOT result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "not_operation"
def not_operation(self, input: bool) -> tuple[bool]:
return (not input,)
class BooleanOr(ComfyNodeABC):
"""
Returns the logical OR result of two boolean values.
This node takes two boolean inputs and returns their logical OR result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "or_operation"
def or_operation(self, input1: bool, input2: bool) -> tuple[bool]:
return (input1 or input2,)
class BooleanXor(ComfyNodeABC):
"""
Returns the logical XOR result of two boolean values.
This node takes two boolean inputs and returns their logical XOR result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "xor_operation"
def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]:
return (input1 != input2,)
NODE_CLASS_MAPPINGS = {
"Basic data handling: BooleanAnd": BooleanAnd,
"Basic data handling: BooleanOr": BooleanOr,
"Basic data handling: BooleanNot": BooleanNot,
"Basic data handling: BooleanXor": BooleanXor,
"Basic data handling: BooleanNand": BooleanNand,
"Basic data handling: BooleanNor": BooleanNor,
"Basic data handling: BooleanNot": BooleanNot,
"Basic data handling: BooleanOr": BooleanOr,
"Basic data handling: BooleanXor": BooleanXor,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: BooleanAnd": "and",
"Basic data handling: BooleanOr": "or",
"Basic data handling: BooleanNot": "not",
"Basic data handling: BooleanXor": "xor",
"Basic data handling: BooleanNand": "nand",
"Basic data handling: BooleanNor": "nor",
"Basic data handling: BooleanNot": "not",
"Basic data handling: BooleanOr": "or",
"Basic data handling: BooleanXor": "xor",
}
+36 -36
View File
@@ -2,9 +2,9 @@ from typing import Any
from inspect import cleandoc
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
class CastToString(ComfyNodeABC):
class CastToBoolean(ComfyNodeABC):
"""
Converts any input to a STRING. Non-string values are converted using str().
Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -14,18 +14,18 @@ class CastToString(ComfyNodeABC):
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_string"
FUNCTION = "convert_to_boolean"
def convert_to_string(self, input: Any) -> tuple[str]:
return (str(input),)
def convert_to_boolean(self, input: Any) -> tuple[bool]:
return (bool(input),)
class CastToInt(ComfyNodeABC):
class CastToDict(ComfyNodeABC):
"""
Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError.
Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -35,16 +35,16 @@ class CastToInt(ComfyNodeABC):
}
}
RETURN_TYPES = (IO.INT,)
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_int"
FUNCTION = "convert_to_dict"
def convert_to_int(self, input: Any) -> tuple[int]:
def convert_to_dict(self, input: Any) -> tuple[dict]:
try:
return (int(input),)
return (dict(input),)
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to an INT.")
raise ValueError(f"Cannot convert {input} to a DICT. Ensure it is a mapping or list of key-value pairs.")
class CastToFloat(ComfyNodeABC):
@@ -71,9 +71,9 @@ class CastToFloat(ComfyNodeABC):
raise ValueError(f"Cannot convert {input} to a FLOAT.")
class CastToBoolean(ComfyNodeABC):
class CastToInt(ComfyNodeABC):
"""
Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules.
Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -83,13 +83,16 @@ class CastToBoolean(ComfyNodeABC):
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_boolean"
FUNCTION = "convert_to_int"
def convert_to_boolean(self, input: Any) -> tuple[bool]:
return (bool(input),)
def convert_to_int(self, input: Any) -> tuple[int]:
try:
return (int(input),)
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to an INT.")
class CastToList(ComfyNodeABC):
@@ -140,9 +143,9 @@ class CastToSet(ComfyNodeABC):
return ({input,} if not isinstance(input, list) else set(input),)
class CastToDict(ComfyNodeABC):
class CastToString(ComfyNodeABC):
"""
Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs.
Converts any input to a STRING. Non-string values are converted using str().
"""
@classmethod
def INPUT_TYPES(cls):
@@ -152,34 +155,31 @@ class CastToDict(ComfyNodeABC):
}
}
RETURN_TYPES = ("DICT",)
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_dict"
FUNCTION = "convert_to_string"
def convert_to_dict(self, input: Any) -> tuple[dict]:
try:
return (dict(input),)
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to a DICT. Ensure it is a mapping or list of key-value pairs.")
def convert_to_string(self, input: Any) -> tuple[str]:
return (str(input),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: CastToString": CastToString,
"Basic data handling: CastToInt": CastToInt,
"Basic data handling: CastToFloat": CastToFloat,
"Basic data handling: CastToBoolean": CastToBoolean,
"Basic data handling: CastToDict": CastToDict,
"Basic data handling: CastToFloat": CastToFloat,
"Basic data handling: CastToInt": CastToInt,
"Basic data handling: CastToList": CastToList,
"Basic data handling: CastToSet": CastToSet,
"Basic data handling: CastToDict": CastToDict,
"Basic data handling: CastToString": CastToString,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: CastToString": "to STRING",
"Basic data handling: CastToInt": "to INT",
"Basic data handling: CastToFloat": "to FLOAT",
"Basic data handling: CastToBoolean": "to BOOLEAN",
"Basic data handling: CastToDict": "to DICT",
"Basic data handling: CastToFloat": "to FLOAT",
"Basic data handling: CastToInt": "to INT",
"Basic data handling: CastToList": "to LIST",
"Basic data handling: CastToSet": "to SET",
"Basic data handling: CastToDict": "to DICT",
"Basic data handling: CastToString": "to STRING",
}
+112 -112
View File
@@ -58,32 +58,6 @@ class NotEqual(ComfyNodeABC):
return (value1 != value2,)
class GreaterThan:
"""
Checks if the first value is greater than the second.
This node takes two numerical inputs and returns True if the first value
is greater than the second value, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("result",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "compare"
def compare(self, value1: float, value2: float) -> tuple[bool]:
return (value1 > value2,)
class LessThan:
"""
Checks if the first value is less than the second.
@@ -110,32 +84,6 @@ class LessThan:
return (value1 < value2,)
class GreaterThanOrEqual:
"""
Checks if the first value is greater than or equal to the second.
This node takes two numerical inputs and returns True if the first value
is greater than or equal to the second value, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("result",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "compare"
def compare(self, value1: float, value2: float) -> tuple[bool]:
return (value1 >= value2,)
class LessThanOrEqual:
"""
Checks if the first value is less than or equal to the second.
@@ -162,21 +110,19 @@ class LessThanOrEqual:
return (value1 <= value2,)
class StringComparison:
class GreaterThan:
"""
Compares two strings using a selected comparison operator.
Checks if the first value is greater than the second.
This node takes two string inputs and a comparison operator, and returns
a boolean result based on the selected comparison.
This node takes two numerical inputs and returns True if the first value
is greater than the second value, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string1": ("STRING", {"default": ""}),
"string2": ("STRING", {"default": ""}),
"operator": (["==", "!=", ">", "<", ">=", "<="], {"default": "=="}),
"case_sensitive": (IO.BOOLEAN, {"default": True}),
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
}
}
@@ -186,25 +132,59 @@ class StringComparison:
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "compare"
def compare(self, string1: str, string2: str, operator: str, case_sensitive: str) -> tuple[bool]:
if case_sensitive == "False":
string1 = string1.lower()
string2 = string2.lower()
def compare(self, value1: float, value2: float) -> tuple[bool]:
return (value1 > value2,)
if operator == "==":
return (string1 == string2,)
elif operator == "!=":
return (string1 != string2,)
elif operator == ">":
return (string1 > string2,)
elif operator == "<":
return (string1 < string2,)
elif operator == ">=":
return (string1 >= string2,)
elif operator == "<=":
return (string1 <= string2,)
else:
raise ValueError(f"Unknown operator: {operator}")
class GreaterThanOrEqual:
"""
Checks if the first value is greater than or equal to the second.
This node takes two numerical inputs and returns True if the first value
is greater than or equal to the second value, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("result",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "compare"
def compare(self, value1: float, value2: float) -> tuple[bool]:
return (value1 >= value2,)
class IsNull:
"""
Checks if a value is None/null.
This node takes any input value and returns True if the value is None,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_null",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_null"
def check_null(self, value: Any) -> tuple[bool]:
return (value is None,)
class NumberInRange:
@@ -236,38 +216,13 @@ class NumberInRange:
FUNCTION = "check_range"
def check_range(self, value: float, min_value: float, max_value: float,
include_min: str = "True", include_max: str = "True") -> tuple[bool]:
include_min: str = "True", include_max: str = "True") -> tuple[bool]:
min_check = value >= min_value if include_min == "True" else value > min_value
max_check = value <= max_value if include_max == "True" else value < max_value
return (min_check and max_check,)
class IsNull:
"""
Checks if a value is None/null.
This node takes any input value and returns True if the value is None,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_null",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_null"
def check_null(self, value: Any) -> tuple[bool]:
return (value is None,)
class CompareLength:
"""
Compares the length of a container (string, list, etc) with a value.
@@ -314,28 +269,73 @@ class CompareLength:
raise ValueError(f"Unknown operator: {operator}")
class StringComparison:
"""
Compares two strings using a selected comparison operator.
This node takes two string inputs and a comparison operator, and returns
a boolean result based on the selected comparison.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string1": ("STRING", {"default": ""}),
"string2": ("STRING", {"default": ""}),
"operator": (["==", "!=", ">", "<", ">=", "<="], {"default": "=="}),
"case_sensitive": (IO.BOOLEAN, {"default": True}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("result",)
CATEGORY = "Basic/comparison"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "compare"
def compare(self, string1: str, string2: str, operator: str, case_sensitive: str) -> tuple[bool]:
if case_sensitive == "False":
string1 = string1.lower()
string2 = string2.lower()
if operator == "==":
return (string1 == string2,)
elif operator == "!=":
return (string1 != string2,)
elif operator == ">":
return (string1 > string2,)
elif operator == "<":
return (string1 < string2,)
elif operator == ">=":
return (string1 >= string2,)
elif operator == "<=":
return (string1 <= string2,)
else:
raise ValueError(f"Unknown operator: {operator}")
NODE_CLASS_MAPPINGS = {
"Basic data handling: Equal": Equal,
"Basic data handling: NotEqual": NotEqual,
"Basic data handling: GreaterThan": GreaterThan,
"Basic data handling: LessThan": LessThan,
"Basic data handling: GreaterThanOrEqual": GreaterThanOrEqual,
"Basic data handling: LessThanOrEqual": LessThanOrEqual,
"Basic data handling: StringComparison": StringComparison,
"Basic data handling: NumberInRange": NumberInRange,
"Basic data handling: GreaterThan": GreaterThan,
"Basic data handling: GreaterThanOrEqual": GreaterThanOrEqual,
"Basic data handling: IsNull": IsNull,
"Basic data handling: NumberInRange": NumberInRange,
"Basic data handling: CompareLength": CompareLength,
"Basic data handling: StringComparison": StringComparison,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: Equal": "==",
"Basic data handling: NotEqual": "!=",
"Basic data handling: GreaterThan": ">",
"Basic data handling: LessThan": "<",
"Basic data handling: GreaterThanOrEqual": ">=",
"Basic data handling: LessThanOrEqual": "<=",
"Basic data handling: StringComparison": "string compare",
"Basic data handling: NumberInRange": "in range",
"Basic data handling: GreaterThan": ">",
"Basic data handling: GreaterThanOrEqual": ">=",
"Basic data handling: IsNull": "is null",
"Basic data handling: NumberInRange": "in range",
"Basic data handling: CompareLength": "compare length",
"Basic data handling: StringComparison": "string compare",
}
+371 -371
View File
@@ -36,6 +36,85 @@ class DataListAppend(ComfyNodeABC):
return (result,)
class DataListContains(ComfyNodeABC):
"""
Checks if a list contains a specified value.
This node takes a list and a value as inputs, then returns True if the value
is present in the list, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"value": (IO.ANY,),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
INPUT_IS_LIST = True
def contains(self, **kwargs: list[Any]) -> tuple[bool]:
value = kwargs.get('value', [])
if len(value) == 0:
return (False,)
return (value[0] in kwargs.get('list', []),)
class DataListCount(ComfyNodeABC):
"""
Counts the number of occurrences of a value in a list.
This node takes a list and a value as inputs, then returns the number of times
the value appears in the list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"value": (IO.ANY,),
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("count",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
INPUT_IS_LIST = True
def count(self, **kwargs: list[Any]) -> tuple[int]:
value = kwargs.get('value', [None])[0]
return (kwargs.get('list', []).count(value),)
class DataListCreateEmpty(ComfyNodeABC):
"""
Creates a new empty list.
This node creates and returns an empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_empty"
OUTPUT_IS_LIST = (True,)
def create_empty(self) -> tuple[list]:
return ([],)
class DataListExtend(ComfyNodeABC):
"""
Extends a list by appending elements from another list.
@@ -64,12 +143,51 @@ class DataListExtend(ComfyNodeABC):
return (kwargs.get('list_a', []) + kwargs.get('list_b', []),)
class DataListInsert(ComfyNodeABC):
class DataListFilter(ComfyNodeABC):
"""
Inserts an item at a specified position in a list.
Filters a data list using boolean values.
This node takes a list, an index, and any item as inputs, then returns a new
list with the item inserted at the specified index.
This node takes a value data list and a filter data list (containing only boolean values).
It returns a new data list containing only the elements from the value list where the
corresponding element in the filter list is False.
If the lists have different lengths, the last element of the shorter list is repeated
till the lengths are matching.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
"filter": (IO.BOOLEAN, {"forceInput": True}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("filtered_list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_data"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]:
values = kwargs.get('value', [])
filters = kwargs.get('filter', [])
# Create a new list with only items where the filter is False
result = [_val for _val, _filter in zip(values, filters) if not _filter]
return (result,)
class DataListGetItem(ComfyNodeABC):
"""
Retrieves an item at a specified position in a list.
This node takes a list and an index as inputs, then returns the item at the specified index.
Negative indices count from the end of the list.
Out of range indices return None.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -77,94 +195,22 @@ class DataListInsert(ComfyNodeABC):
"required": {
"list": (IO.ANY,),
"index": (IO.INT, {"default": 0}),
"item": (IO.ANY,),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
FUNCTION = "get_item"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (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(ComfyNodeABC):
"""
Removes the first occurrence of a specified value from a list.
This node takes a list and a value as inputs, then returns a new list with
the first occurrence of the value removed. Raises a ValueError if the value is not present.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"value": (IO.ANY,),
}
}
RETURN_TYPES = (IO.ANY, IO.BOOLEAN,)
RETURN_NAMES = ("list", "success",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False,)
def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]:
result = kwargs.get('list', []).copy()
value = kwargs.get('value', [])
def get_item(self, **kwargs: list[Any]) -> tuple[Any]:
index = kwargs.get('index', [0])[0]
try:
result.remove(value[0])
return result, True
except ValueError:
return result, False
class DataListPop(ComfyNodeABC):
"""
Removes and returns an item at a specified position in a list.
This node takes a list and an index as inputs, then returns both the new list
with the item removed and the removed item. If no index is specified,
removes and returns the last item.
When the list is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
},
"optional": {
"index": (IO.INT, {"default": -1}),
}
}
RETURN_TYPES = (IO.ANY, IO.ANY)
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False)
def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
result = kwargs.get('list', []).copy()
index = kwargs.get('index', [-1])[0]
try:
item = result.pop(index)
return result, item
return (kwargs.get('list', [])[index],)
except IndexError:
return result, None
return (None,)
class DataListIndex(ComfyNodeABC):
@@ -209,12 +255,178 @@ class DataListIndex(ComfyNodeABC):
return (-1,)
class DataListCount(ComfyNodeABC):
class DataListInsert(ComfyNodeABC):
"""
Counts the number of occurrences of a value in a list.
Inserts an item at a specified position in a list.
This node takes a list and a value as inputs, then returns the number of times
the value appears in the list.
This node takes a list, an index, and any item as inputs, then returns a new
list with the item inserted at the specified index.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"index": (IO.INT, {"default": 0}),
"item": (IO.ANY,),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (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 DataListLength(ComfyNodeABC):
"""
Counts the number of items in a list.
This node takes a list as input and returns its length as an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
INPUT_IS_LIST = True
def length(self, **kwargs: list[Any]) -> tuple[int]:
return (len(kwargs.get('list', [])),)
class DataListMax(ComfyNodeABC):
"""
Finds the maximum value in a list of numbers.
This node takes a list of numbers (either FLOAT or INT) and returns
the maximum value. Returns None if the list is empty or if it contains
non-numeric values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.NUMBER, {}),
}
}
RETURN_TYPES = (IO.NUMBER,)
RETURN_NAMES = ("max",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_max"
INPUT_IS_LIST = True
def find_max(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
try:
# Return the same type as found in the maximum value
return (max(values),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class DataListMin(ComfyNodeABC):
"""
Finds the minimum value in a list of numbers.
This node takes a list of numbers (either FLOAT or INT) and returns
the minimum value. Returns None if the list is empty or if it contains
non-numeric values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.NUMBER, {}),
}
}
RETURN_TYPES = (IO.NUMBER,)
RETURN_NAMES = ("min",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_min"
INPUT_IS_LIST = True
def find_min(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
try:
# Return the same type as found in the minimum value
return (min(values),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class DataListPop(ComfyNodeABC):
"""
Removes and returns an item at a specified position in a list.
This node takes a list and an index as inputs, then returns both the new list
with the item removed and the removed item. If no index is specified,
removes and returns the last item.
When the list is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
},
"optional": {
"index": (IO.INT, {"default": -1}),
}
}
RETURN_TYPES = (IO.ANY, IO.ANY)
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False)
def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
result = kwargs.get('list', []).copy()
index = kwargs.get('index', [-1])[0]
try:
item = result.pop(index)
return result, item
except IndexError:
return result, None
class DataListRemove(ComfyNodeABC):
"""
Removes the first occurrence of a specified value from a list.
This node takes a list and a value as inputs, then returns a new list with
the first occurrence of the value removed. Raises a ValueError if the value is not present.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -225,50 +437,22 @@ class DataListCount(ComfyNodeABC):
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("count",)
RETURN_TYPES = (IO.ANY, IO.BOOLEAN,)
RETURN_NAMES = ("list", "success",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
FUNCTION = "remove"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False,)
def count(self, **kwargs: list[Any]) -> tuple[int]:
value = kwargs.get('value', [None])[0]
return (kwargs.get('list', []).count(value),)
class DataListSort(ComfyNodeABC):
"""
Sorts the items in a list.
This node takes a list as input and returns a new sorted list.
Options include sorting in reverse order and using a key function.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
},
"optional": {
"reverse": (["False", "True"], {"default": "False"}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "sort"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]:
# Convert string to boolean
reverse = kwargs.get('reverse', ["False"])[0] == "True"
result = sorted(kwargs.get('list', []), reverse=reverse)
return (result,)
def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]:
result = kwargs.get('list', []).copy()
value = kwargs.get('value', [])
try:
result.remove(value[0])
return result, True
except ValueError:
return result, False
class DataListReverse(ComfyNodeABC):
@@ -299,29 +483,41 @@ class DataListReverse(ComfyNodeABC):
return (result,)
class DataListLength(ComfyNodeABC):
class DataListSetItem(ComfyNodeABC):
"""
Counts the number of items in a list.
Sets an item at a specified position in a list.
This node takes a list as input and returns its length as an integer.
This node takes a list, an index, and a value, then returns a new list with
the item at the specified index replaced by the value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"index": (IO.INT, {"default": 0}),
"value": (IO.ANY,),
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("length",)
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
FUNCTION = "set_item"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def length(self, **kwargs: list[Any]) -> tuple[int]:
return (len(kwargs.get('list', [])),)
def set_item(self, **kwargs: list[Any]) -> tuple[Any]:
input_list = kwargs.get('list', [])
index = kwargs.get('index', [0])[0]
value = kwargs.get('value', [None])[0]
try:
result = input_list.copy()
result[index] = value
return (result,)
except IndexError:
raise IndexError(f"Index {index} out of range for list of length {len(input_list)}")
class DataListSlice(ComfyNodeABC):
@@ -362,52 +558,21 @@ class DataListSlice(ComfyNodeABC):
return (input_list[start:stop:step],)
class DataListGetItem(ComfyNodeABC):
class DataListSort(ComfyNodeABC):
"""
Retrieves an item at a specified position in a list.
Sorts the items in a list.
This node takes a list and an index as inputs, then returns the item at the specified index.
Negative indices count from the end of the list.
Out of range indices return None.
This node takes a list as input and returns a new sorted list.
Options include sorting in reverse order and using a key function.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"index": (IO.INT, {"default": 0}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_item"
INPUT_IS_LIST = True
def get_item(self, **kwargs: list[Any]) -> tuple[Any]:
index = kwargs.get('index', [0])[0]
try:
return (kwargs.get('list', [])[index],)
except IndexError:
return (None,)
class DataListSetItem(ComfyNodeABC):
"""
Sets an item at a specified position in a list.
This node takes a list, an index, and a value, then returns a new list with
the item at the specified index replaced by the value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"index": (IO.INT, {"default": 0}),
"value": (IO.ANY,),
},
"optional": {
"reverse": (["False", "True"], {"default": "False"}),
}
}
@@ -415,71 +580,16 @@ class DataListSetItem(ComfyNodeABC):
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set_item"
FUNCTION = "sort"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def set_item(self, **kwargs: list[Any]) -> tuple[Any]:
input_list = kwargs.get('list', [])
index = kwargs.get('index', [0])[0]
value = kwargs.get('value', [None])[0]
try:
result = input_list.copy()
result[index] = value
return (result,)
except IndexError:
raise IndexError(f"Index {index} out of range for list of length {len(input_list)}")
def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]:
# Convert string to boolean
reverse = kwargs.get('reverse', ["False"])[0] == "True"
class DataListContains(ComfyNodeABC):
"""
Checks if a list contains a specified value.
This node takes a list and a value as inputs, then returns True if the value
is present in the list, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY,),
"value": (IO.ANY,),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
INPUT_IS_LIST = True
def contains(self, **kwargs: list[Any]) -> tuple[bool]:
value = kwargs.get('value', [])
if len(value) == 0:
return (False,)
return (value[0] in kwargs.get('list', []),)
class DataListCreateEmpty(ComfyNodeABC):
"""
Creates a new empty list.
This node creates and returns an empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_empty"
OUTPUT_IS_LIST = (True,)
def create_empty(self) -> tuple[list]:
return ([],)
result = sorted(kwargs.get('list', []), reverse=reverse)
return (result,)
class DataListZip(ComfyNodeABC):
@@ -525,116 +635,6 @@ class DataListZip(ComfyNodeABC):
return (result,)
class DataListFilter(ComfyNodeABC):
"""
Filters a data list using boolean values.
This node takes a value data list and a filter data list (containing only boolean values).
It returns a new data list containing only the elements from the value list where the
corresponding element in the filter list is False.
If the lists have different lengths, the last element of the shorter list is repeated
till the lengths are matching.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
"filter": (IO.BOOLEAN, {"forceInput": True}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("filtered_list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_data"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]:
values = kwargs.get('value', [])
filters = kwargs.get('filter', [])
# Create a new list with only items where the filter is False
result = [_val for _val, _filter in zip(values, filters) if not _filter]
return (result,)
class DataListMin(ComfyNodeABC):
"""
Finds the minimum value in a list of numbers.
This node takes a list of numbers (either FLOAT or INT) and returns
the minimum value. Returns None if the list is empty or if it contains
non-numeric values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.NUMBER, {}),
}
}
RETURN_TYPES = (IO.NUMBER,)
RETURN_NAMES = ("min",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_min"
INPUT_IS_LIST = True
def find_min(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
try:
# Return the same type as found in the minimum value
return (min(values),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class DataListMax(ComfyNodeABC):
"""
Finds the maximum value in a list of numbers.
This node takes a list of numbers (either FLOAT or INT) and returns
the maximum value. Returns None if the list is empty or if it contains
non-numeric values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.NUMBER, {}),
}
}
RETURN_TYPES = (IO.NUMBER,)
RETURN_NAMES = ("max",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_max"
INPUT_IS_LIST = True
def find_max(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
try:
# Return the same type as found in the maximum value
return (max(values),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class DataListToList(ComfyNodeABC):
"""
Converts a ComfyUI data list into a LIST object.
@@ -687,48 +687,48 @@ class DataListToSet(ComfyNodeABC):
NODE_CLASS_MAPPINGS = {
"Basic data handling: DataListAppend": DataListAppend,
"Basic data handling: DataListExtend": DataListExtend,
"Basic data handling: DataListInsert": DataListInsert,
"Basic data handling: DataListRemove": DataListRemove,
"Basic data handling: DataListPop": DataListPop,
"Basic data handling: DataListIndex": DataListIndex,
"Basic data handling: DataListCount": DataListCount,
"Basic data handling: DataListSort": DataListSort,
"Basic data handling: DataListReverse": DataListReverse,
"Basic data handling: DataListLength": DataListLength,
"Basic data handling: DataListSlice": DataListSlice,
"Basic data handling: DataListGetItem": DataListGetItem,
"Basic data handling: DataListSetItem": DataListSetItem,
"Basic data handling: DataListContains": DataListContains,
"Basic data handling: DataListCount": DataListCount,
"Basic data handling: DataListCreateEmpty": DataListCreateEmpty,
"Basic data handling: DataListZip": DataListZip,
"Basic data handling: DataListExtend": DataListExtend,
"Basic data handling: DataListFilter": DataListFilter,
"Basic data handling: DataListMin": DataListMin,
"Basic data handling: DataListGetItem": DataListGetItem,
"Basic data handling: DataListIndex": DataListIndex,
"Basic data handling: DataListInsert": DataListInsert,
"Basic data handling: DataListLength": DataListLength,
"Basic data handling: DataListMax": DataListMax,
"Basic data handling: DataListMin": DataListMin,
"Basic data handling: DataListPop": DataListPop,
"Basic data handling: DataListRemove": DataListRemove,
"Basic data handling: DataListReverse": DataListReverse,
"Basic data handling: DataListSetItem": DataListSetItem,
"Basic data handling: DataListSlice": DataListSlice,
"Basic data handling: DataListSort": DataListSort,
"Basic data handling: DataListZip": DataListZip,
"Basic data handling: DataListToList": DataListToList,
"Basic data handling: DataListToSet": DataListToSet,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: DataListAppend": "append",
"Basic data handling: DataListExtend": "extend",
"Basic data handling: DataListInsert": "insert",
"Basic data handling: DataListRemove": "remove",
"Basic data handling: DataListPop": "pop",
"Basic data handling: DataListIndex": "index",
"Basic data handling: DataListCount": "count",
"Basic data handling: DataListSort": "sort",
"Basic data handling: DataListReverse": "reverse",
"Basic data handling: DataListLength": "length",
"Basic data handling: DataListSlice": "slice",
"Basic data handling: DataListGetItem": "get item",
"Basic data handling: DataListSetItem": "set item",
"Basic data handling: DataListContains": "contains",
"Basic data handling: DataListCount": "count",
"Basic data handling: DataListCreateEmpty": "create empty",
"Basic data handling: DataListZip": "zip",
"Basic data handling: DataListExtend": "extend",
"Basic data handling: DataListFilter": "filter",
"Basic data handling: DataListMin": "min",
"Basic data handling: DataListGetItem": "get item",
"Basic data handling: DataListIndex": "index",
"Basic data handling: DataListInsert": "insert",
"Basic data handling: DataListLength": "length",
"Basic data handling: DataListMax": "max",
"Basic data handling: DataListMin": "min",
"Basic data handling: DataListPop": "pop",
"Basic data handling: DataListRemove": "remove",
"Basic data handling: DataListReverse": "reverse",
"Basic data handling: DataListSetItem": "set item",
"Basic data handling: DataListSlice": "slice",
"Basic data handling: DataListSort": "sort",
"Basic data handling: DataListZip": "zip",
"Basic data handling: DataListToList": "convert to LIST",
"Basic data handling: DataListToSet": "convert to SET",
}
+403 -403
View File
@@ -21,38 +21,6 @@ class DictCreate(ComfyNodeABC):
return ({},)
class DictCreateFromItemsList(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": {
"items": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_from_items"
def create_from_items(self, items: list) -> tuple[dict]:
try:
# Check if items are valid (key-value pairs)
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 DictCreateFromItemsDataList(ComfyNodeABC):
"""
Creates a dictionary from a list of key-value pairs.
@@ -87,6 +55,211 @@ class DictCreateFromItemsDataList(ComfyNodeABC):
raise ValueError(f"Error creating dictionary from items: {str(e)}")
class DictCreateFromItemsList(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": {
"items": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_from_items"
def create_from_items(self, items: list) -> tuple[dict]:
try:
# Check if items are valid (key-value pairs)
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 DictCreateFromLists(ComfyNodeABC):
"""
Creates a dictionary from separate lists of keys and values.
This node takes a list of keys and a list of values, then creates a dictionary
by pairing corresponding elements from each list. If the lists are of different
lengths, only pairs up to the length of the shorter list are used.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"keys": ("LIST", {}),
"values": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_from_lists"
def create_from_lists(self, keys: list, values: list) -> tuple[dict]:
# Pair keys with values up to the length of the shorter list
result = dict(zip(keys, values))
return (result,)
class DictCompare(ComfyNodeABC):
"""
Compares two dictionaries and reports differences.
This node takes two dictionaries and compares them, returning information about
their equality, any keys that exist in only one dictionary, and any keys with
different values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"dict1": ("DICT", {}),
"dict2": ("DICT", {}),
}
}
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 "")
FUNCTION = "compare"
def compare(self, dict1: dict, dict2: dict) -> tuple[bool, list, list, list]:
are_equal = (dict1 == dict2)
keys1 = set(dict1.keys())
keys2 = set(dict2.keys())
only_in_dict1 = list(keys1 - keys2)
only_in_dict2 = list(keys2 - keys1)
different_values = []
for key in keys1 & keys2:
if dict1[key] != dict2[key]:
different_values.append(key)
return are_equal, only_in_dict1, only_in_dict2, different_values
class DictContainsKey(ComfyNodeABC):
"""
Checks if a key exists in a dictionary.
This node takes a dictionary and a key as inputs, then returns True if the key
exists in the dictionary, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains_key"
def contains_key(self, input_dict: dict, key: str) -> tuple[bool]:
return (key in input_dict,)
class DictExcludeKeys(ComfyNodeABC):
"""
Creates a new dictionary excluding the specified keys.
This node takes a dictionary and a list of keys, then returns a new dictionary
containing all key-value pairs except those with keys in the provided list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys_to_exclude": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "exclude_keys"
def exclude_keys(self, input_dict: dict, keys_to_exclude: list) -> tuple[dict]:
result = {k: v for k, v in input_dict.items() if k not in keys_to_exclude}
return (result,)
class DictFilterByKeys(ComfyNodeABC):
"""
Creates a new dictionary with only the specified keys.
This node takes a dictionary and a list of keys, then returns a new dictionary
containing only the key-value pairs for the keys that exist in the original dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_by_keys"
def filter_by_keys(self, input_dict: dict, keys: list) -> tuple[dict]:
result = {k: input_dict[k] for k in keys if k in input_dict}
return (result,)
class DictFromKeys(ComfyNodeABC):
"""
Creates a dictionary from a list of keys and a default value.
This node takes a list of keys and an optional value, then creates a new
dictionary where each key is associated with the value. If no value is
provided, None is used.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"keys": ("LIST", {}),
},
"optional": {
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_keys"
def from_keys(self, keys: list, value=None) -> tuple[dict]:
return (dict.fromkeys(keys, value),)
class DictGet(ComfyNodeABC):
"""
Retrieves a value from a dictionary using the specified key.
@@ -117,62 +290,71 @@ class DictGet(ComfyNodeABC):
return (input_dict.get(key, default),)
class DictSet(ComfyNodeABC):
class DictGetKeysValues(ComfyNodeABC):
"""
Adds or updates a key-value pair in a dictionary.
Returns keys and values as separate lists.
This node takes a dictionary, key, and value as inputs, then returns
a modified dictionary with the new key-value pair.
This node takes a dictionary and returns two lists: one containing
all keys and another containing all corresponding values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("DICT",)
RETURN_TYPES = ("LIST", "LIST")
RETURN_NAMES = ("keys", "values")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set"
FUNCTION = "get_keys_values"
def set(self, input_dict: dict, key: str, value: Any) -> tuple[dict]:
result = input_dict.copy()
result[key] = value
return (result,)
def get_keys_values(self, input_dict: dict) -> tuple[list, list]:
keys = list(input_dict.keys())
values = list(input_dict.values())
return keys, values
class DictKeys(ComfyNodeABC):
class DictGetMultiple(ComfyNodeABC):
"""
Returns all keys in a dictionary.
Retrieves multiple values from a dictionary using a list of keys.
This node takes a dictionary and returns a list containing all of its keys.
This node takes a dictionary and a list of keys, then returns a list
containing the corresponding values. If a key is not found, the default
value is used for that position.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys": ("LIST", {}),
},
"optional": {
"default": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
RETURN_NAMES = ("values",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "keys"
FUNCTION = "get_multiple"
def keys(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.keys()),)
def get_multiple(self, input_dict: dict, keys: list, default=None) -> tuple[list]:
values = [input_dict.get(key, default) for key in keys]
return (values,)
class DictValues(ComfyNodeABC):
class DictInvert(ComfyNodeABC):
"""
Returns all values in a dictionary.
Creates a new dictionary with keys and values swapped.
This node takes a dictionary and returns a list containing all of its values.
This node takes a dictionary as input and returns a new dictionary where
the keys become values and values become keys. Note that values must be
hashable to be used as keys in the new dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -182,13 +364,19 @@ class DictValues(ComfyNodeABC):
}
}
RETURN_TYPES = ("LIST",)
RETURN_TYPES = ("DICT", IO.BOOLEAN)
RETURN_NAMES = ("inverted_dict", "success")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "values"
FUNCTION = "invert"
def values(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.values()),)
def invert(self, input_dict: dict) -> tuple[dict, bool]:
try:
inverted = {v: k for k, v in input_dict.items()}
return inverted, True
except Exception:
# Return original dictionary if inversion fails (e.g., unhashable values)
return input_dict, False
class DictItems(ComfyNodeABC):
@@ -215,57 +403,91 @@ class DictItems(ComfyNodeABC):
return (list(input_dict.items()),)
class DictContainsKey(ComfyNodeABC):
class DictKeys(ComfyNodeABC):
"""
Checks if a key exists in a dictionary.
Returns all keys in a dictionary.
This node takes a dictionary and a key as inputs, then returns True if the key
exists in the dictionary, and False otherwise.
This node takes a dictionary and returns a list containing all of its keys.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains_key"
FUNCTION = "keys"
def contains_key(self, input_dict: dict, key: str) -> tuple[bool]:
return (key in input_dict,)
def keys(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.keys()),)
class DictFromKeys(ComfyNodeABC):
class DictLength(ComfyNodeABC):
"""
Creates a dictionary from a list of keys and a default value.
Returns the number of key-value pairs in a dictionary.
This node takes a list of keys and an optional value, then creates a new
dictionary where each key is associated with the value. If no value is
provided, None is used.
This node takes a dictionary as input and returns its length (number of items).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"keys": ("LIST", {}),
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, input_dict: dict) -> tuple[int]:
return (len(input_dict),)
class DictMerge(ComfyNodeABC):
"""
Merges multiple dictionaries into a single dictionary.
This node takes multiple dictionaries as input and combines them into a single
dictionary. If there are duplicate keys, values from later dictionaries take precedence.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"dict1": ("DICT", {}),
},
"optional": {
"value": (IO.ANY, {}),
"dict2": ("DICT", {}),
"dict3": ("DICT", {}),
"dict4": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_keys"
FUNCTION = "merge"
def from_keys(self, keys: list, value=None) -> tuple[dict]:
return (dict.fromkeys(keys, value),)
def merge(self, dict1: dict, dict2=None, dict3=None, dict4=None) -> tuple[dict]:
result = dict1.copy()
if dict2 is not None:
result.update(dict2)
if dict3 is not None:
result.update(dict3)
if dict4 is not None:
result.update(dict4)
return (result,)
class DictPop(ComfyNodeABC):
@@ -342,6 +564,65 @@ class DictPopItem(ComfyNodeABC):
return result, "", None, False
class DictRemove(ComfyNodeABC):
"""
Removes a key-value pair from a dictionary.
This node takes a dictionary and a key as inputs, then returns a new
dictionary with the specified key removed. If the key doesn't exist,
the dictionary remains unchanged.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = ("DICT", IO.BOOLEAN)
RETURN_NAMES = ("dict", "key_removed")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
def remove(self, input_dict: dict, key: str) -> tuple[dict, bool]:
result = input_dict.copy()
if key in result:
del result[key]
return result, True
return result, False
class DictSet(ComfyNodeABC):
"""
Adds or updates a key-value pair in a dictionary.
This node takes a dictionary, key, and value as inputs, then returns
a modified dictionary with the new key-value pair.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set"
def set(self, input_dict: dict, key: str, value: Any) -> tuple[dict]:
result = input_dict.copy()
result[key] = value
return (result,)
class DictSetDefault(ComfyNodeABC):
"""
Returns the value for a key, setting a default if the key doesn't exist.
@@ -400,11 +681,11 @@ class DictUpdate(ComfyNodeABC):
return (result,)
class DictLength(ComfyNodeABC):
class DictValues(ComfyNodeABC):
"""
Returns the number of key-value pairs in a dictionary.
Returns all values in a dictionary.
This node takes a dictionary as input and returns its length (number of items).
This node takes a dictionary and returns a list containing all of its values.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -414,346 +695,65 @@ class DictLength(ComfyNodeABC):
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, input_dict: dict) -> tuple[int]:
return (len(input_dict),)
class DictMerge(ComfyNodeABC):
"""
Merges multiple dictionaries into a single dictionary.
This node takes multiple dictionaries as input and combines them into a single
dictionary. If there are duplicate keys, values from later dictionaries take precedence.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"dict1": ("DICT", {}),
},
"optional": {
"dict2": ("DICT", {}),
"dict3": ("DICT", {}),
"dict4": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "merge"
def merge(self, dict1: dict, dict2=None, dict3=None, dict4=None) -> tuple[dict]:
result = dict1.copy()
if dict2 is not None:
result.update(dict2)
if dict3 is not None:
result.update(dict3)
if dict4 is not None:
result.update(dict4)
return (result,)
class DictGetKeysValues(ComfyNodeABC):
"""
Returns keys and values as separate lists.
This node takes a dictionary and returns two lists: one containing
all keys and another containing all corresponding values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("LIST", "LIST")
RETURN_NAMES = ("keys", "values")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_keys_values"
def get_keys_values(self, input_dict: dict) -> tuple[list, list]:
keys = list(input_dict.keys())
values = list(input_dict.values())
return keys, values
class DictRemove(ComfyNodeABC):
"""
Removes a key-value pair from a dictionary.
This node takes a dictionary and a key as inputs, then returns a new
dictionary with the specified key removed. If the key doesn't exist,
the dictionary remains unchanged.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = ("DICT", IO.BOOLEAN)
RETURN_NAMES = ("dict", "key_removed")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
def remove(self, input_dict: dict, key: str) -> tuple[dict, bool]:
result = input_dict.copy()
if key in result:
del result[key]
return result, True
return result, False
class DictFilterByKeys(ComfyNodeABC):
"""
Creates a new dictionary with only the specified keys.
This node takes a dictionary and a list of keys, then returns a new dictionary
containing only the key-value pairs for the keys that exist in the original dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_by_keys"
def filter_by_keys(self, input_dict: dict, keys: list) -> tuple[dict]:
result = {k: input_dict[k] for k in keys if k in input_dict}
return (result,)
class DictExcludeKeys(ComfyNodeABC):
"""
Creates a new dictionary excluding the specified keys.
This node takes a dictionary and a list of keys, then returns a new dictionary
containing all key-value pairs except those with keys in the provided list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys_to_exclude": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "exclude_keys"
def exclude_keys(self, input_dict: dict, keys_to_exclude: list) -> tuple[dict]:
result = {k: v for k, v in input_dict.items() if k not in keys_to_exclude}
return (result,)
class DictGetMultiple(ComfyNodeABC):
"""
Retrieves multiple values from a dictionary using a list of keys.
This node takes a dictionary and a list of keys, then returns a list
containing the corresponding values. If a key is not found, the default
value is used for that position.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"keys": ("LIST", {}),
},
"optional": {
"default": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
RETURN_NAMES = ("values",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_multiple"
FUNCTION = "values"
def get_multiple(self, input_dict: dict, keys: list, default=None) -> tuple[list]:
values = [input_dict.get(key, default) for key in keys]
return (values,)
class DictInvert(ComfyNodeABC):
"""
Creates a new dictionary with keys and values swapped.
This node takes a dictionary as input and returns a new dictionary where
the keys become values and values become keys. Note that values must be
hashable to be used as keys in the new dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT", IO.BOOLEAN)
RETURN_NAMES = ("inverted_dict", "success")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "invert"
def invert(self, input_dict: dict) -> tuple[dict, bool]:
try:
inverted = {v: k for k, v in input_dict.items()}
return inverted, True
except Exception:
# Return original dictionary if inversion fails (e.g., unhashable values)
return input_dict, False
class DictCreateFromLists(ComfyNodeABC):
"""
Creates a dictionary from separate lists of keys and values.
This node takes a list of keys and a list of values, then creates a dictionary
by pairing corresponding elements from each list. If the lists are of different
lengths, only pairs up to the length of the shorter list are used.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"keys": ("LIST", {}),
"values": ("LIST", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_from_lists"
def create_from_lists(self, keys: list, values: list) -> tuple[dict]:
# Pair keys with values up to the length of the shorter list
result = dict(zip(keys, values))
return (result,)
class DictCompare(ComfyNodeABC):
"""
Compares two dictionaries and reports differences.
This node takes two dictionaries and compares them, returning information about
their equality, any keys that exist in only one dictionary, and any keys with
different values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"dict1": ("DICT", {}),
"dict2": ("DICT", {}),
}
}
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 "")
FUNCTION = "compare"
def compare(self, dict1: dict, dict2: dict) -> tuple[bool, list, list, list]:
are_equal = (dict1 == dict2)
keys1 = set(dict1.keys())
keys2 = set(dict2.keys())
only_in_dict1 = list(keys1 - keys2)
only_in_dict2 = list(keys2 - keys1)
different_values = []
for key in keys1 & keys2:
if dict1[key] != dict2[key]:
different_values.append(key)
return are_equal, only_in_dict1, only_in_dict2, different_values
def values(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.values()),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: DictCreate": DictCreate,
"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: DictFromKeys": DictFromKeys,
"Basic data handling: DictPop": DictPop,
"Basic data handling: DictPopItem": DictPopItem,
"Basic data handling: DictSetDefault": DictSetDefault,
"Basic data handling: DictUpdate": DictUpdate,
"Basic data handling: DictLength": DictLength,
"Basic data handling: DictMerge": DictMerge,
"Basic data handling: DictGetKeysValues": DictGetKeysValues,
"Basic data handling: DictRemove": DictRemove,
"Basic data handling: DictFilterByKeys": DictFilterByKeys,
"Basic data handling: DictExcludeKeys": DictExcludeKeys,
"Basic data handling: DictGetMultiple": DictGetMultiple,
"Basic data handling: DictInvert": DictInvert,
"Basic data handling: DictCreateFromItemsList": DictCreateFromItemsList,
"Basic data handling: DictCreateFromLists": DictCreateFromLists,
"Basic data handling: DictCompare": DictCompare,
"Basic data handling: DictContainsKey": DictContainsKey,
"Basic data handling: DictExcludeKeys": DictExcludeKeys,
"Basic data handling: DictFilterByKeys": DictFilterByKeys,
"Basic data handling: DictFromKeys": DictFromKeys,
"Basic data handling: DictGet": DictGet,
"Basic data handling: DictGetKeysValues": DictGetKeysValues,
"Basic data handling: DictGetMultiple": DictGetMultiple,
"Basic data handling: DictInvert": DictInvert,
"Basic data handling: DictItems": DictItems,
"Basic data handling: DictKeys": DictKeys,
"Basic data handling: DictLength": DictLength,
"Basic data handling: DictMerge": DictMerge,
"Basic data handling: DictPop": DictPop,
"Basic data handling: DictPopItem": DictPopItem,
"Basic data handling: DictRemove": DictRemove,
"Basic data handling: DictSet": DictSet,
"Basic data handling: DictSetDefault": DictSetDefault,
"Basic data handling: DictUpdate": DictUpdate,
"Basic data handling: DictValues": DictValues,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: DictCreate": "create",
"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: DictCreate": "create empty",
"Basic data handling: DictCreateFromItemsDataList": "create from items (data list)",
"Basic data handling: DictCreateFromItemsList": "create from items (LIST)",
"Basic data handling: DictCreateFromLists": "create from LISTs",
"Basic data handling: DictCompare": "compare",
"Basic data handling: DictContainsKey": "contains key",
"Basic data handling: DictExcludeKeys": "exclude keys",
"Basic data handling: DictFilterByKeys": "filter by keys",
"Basic data handling: DictFromKeys": "fromkeys",
"Basic data handling: DictPop": "pop",
"Basic data handling: DictPopItem": "popitem",
"Basic data handling: DictSetDefault": "setdefault",
"Basic data handling: DictUpdate": "update",
"Basic data handling: DictGet": "get",
"Basic data handling: DictGetKeysValues": "get keys values",
"Basic data handling: DictGetMultiple": "get multiple",
"Basic data handling: DictInvert": "invert",
"Basic data handling: DictItems": "items",
"Basic data handling: DictKeys": "keys",
"Basic data handling: DictLength": "length",
"Basic data handling: DictMerge": "merge",
"Basic data handling: DictGetKeysValues": "get_keys_values",
"Basic data handling: DictPop": "pop",
"Basic data handling: DictPopItem": "popitem",
"Basic data handling: DictRemove": "remove",
"Basic data handling: DictFilterByKeys": "filter_by_keys",
"Basic data handling: DictExcludeKeys": "exclude_keys",
"Basic data handling: DictGetMultiple": "get_multiple",
"Basic data handling: DictInvert": "invert",
"Basic data handling: DictCreateFromLists": "from_lists",
"Basic data handling: DictCompare": "compare",
"Basic data handling: DictSet": "set",
"Basic data handling: DictSetDefault": "setdefault",
"Basic data handling: DictUpdate": "update",
"Basic data handling: DictValues": "values",
}
+105 -105
View File
@@ -128,6 +128,103 @@ class FloatDivideSafe(ComfyNodeABC):
return (float1 / float2,)
class FloatAsIntegerRatio(ComfyNodeABC):
"""
Returns the integer ratio of a floating-point number.
This node takes a floating-point number and returns two integers,
which represent the ratio as numerator and denominator.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = (IO.INT, IO.INT)
RETURN_NAMES = ("numerator", "denominator")
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "as_integer_ratio"
def as_integer_ratio(self, float_value: float) -> tuple[int, int]:
# Decompose the float into numerator and denominator
numerator, denominator = float_value.as_integer_ratio()
return numerator, denominator
class FloatFromHex(ComfyNodeABC):
"""
Converts a hexadecimal string to its corresponding floating-point number.
This node takes a hexadecimal float string as input and returns the float.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"hex_value": (IO.STRING, {"default": "0x0.0p+0"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_hex"
def from_hex(self, hex_value: str) -> tuple[float]:
return (float.fromhex(hex_value),)
class FloatHex(ComfyNodeABC):
"""
Converts a floating-point number to its hexadecimal representation.
This node takes a float as input and returns its hexadecimal string representation.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "to_hex"
def to_hex(self, float_value: float) -> tuple[str]:
return (float_value.hex(),)
class FloatIsInteger(ComfyNodeABC):
"""
Checks if a floating-point number is an integer.
This node takes a floating-point number as input and returns True if the number
is an integer, otherwise False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "is_integer"
def is_integer(self, float_value: float) -> tuple[bool]:
return (float_value.is_integer(),)
class FloatPower(ComfyNodeABC):
"""
Raises one floating-point number to the power of another.
@@ -178,115 +275,18 @@ class FloatRound(ComfyNodeABC):
return (round(float_value, decimal_places),)
class FloatIsInteger(ComfyNodeABC):
"""
Checks if a floating-point number is an integer.
This node takes a floating-point number as input and returns True if the number
is an integer, otherwise False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "is_integer"
def is_integer(self, float_value: float) -> tuple[bool]:
return (float_value.is_integer(),)
class FloatAsIntegerRatio(ComfyNodeABC):
"""
Returns the integer ratio of a floating-point number.
This node takes a floating-point number and returns two integers,
which represent the ratio as numerator and denominator.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = (IO.INT, IO.INT)
RETURN_NAMES = ("numerator", "denominator")
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "as_integer_ratio"
def as_integer_ratio(self, float_value: float) -> tuple[int, int]:
# Decompose the float into numerator and denominator
numerator, denominator = float_value.as_integer_ratio()
return numerator, denominator
class FloatHex(ComfyNodeABC):
"""
Converts a floating-point number to its hexadecimal representation.
This node takes a float as input and returns its hexadecimal string representation.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"float_value": (IO.FLOAT, {"default": 0.0}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "to_hex"
def to_hex(self, float_value: float) -> tuple[str]:
return (float_value.hex(),)
class FloatFromHex(ComfyNodeABC):
"""
Converts a hexadecimal string to its corresponding floating-point number.
This node takes a hexadecimal float string as input and returns the float.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"hex_value": (IO.STRING, {"default": "0x0.0p+0"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/FLOAT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_hex"
def from_hex(self, hex_value: str) -> tuple[float]:
return (float.fromhex(hex_value),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: FloatAdd": FloatAdd,
"Basic data handling: FloatSubtract": FloatSubtract,
"Basic data handling: FloatMultiply": FloatMultiply,
"Basic data handling: FloatDivide": FloatDivide,
"Basic data handling: FloatDivideSafe": FloatDivideSafe,
"Basic data handling: FloatAsIntegerRatio": FloatAsIntegerRatio,
"Basic data handling: FloatFromHex": FloatFromHex,
"Basic data handling: FloatHex": FloatHex,
"Basic data handling: FloatIsInteger": FloatIsInteger,
"Basic data handling: FloatPower": FloatPower,
"Basic data handling: FloatRound": FloatRound,
"Basic data handling: FloatIsInteger": FloatIsInteger,
"Basic data handling: FloatAsIntegerRatio": FloatAsIntegerRatio,
"Basic data handling: FloatHex": FloatHex,
"Basic data handling: FloatFromHex": FloatFromHex,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -295,10 +295,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: FloatMultiply": "multiply",
"Basic data handling: FloatDivide": "divide",
"Basic data handling: FloatDivideSafe": "divide (division by zero safe)",
"Basic data handling: FloatAsIntegerRatio": "integer ratio",
"Basic data handling: FloatFromHex": "from hex",
"Basic data handling: FloatHex": "to hex",
"Basic data handling: FloatIsInteger": "is integer",
"Basic data handling: FloatPower": "power",
"Basic data handling: FloatRound": "round",
"Basic data handling: FloatIsInteger": "is_integer",
"Basic data handling: FloatAsIntegerRatio": "as_integer_ratio",
"Basic data handling: FloatHex": "to_hex",
"Basic data handling: FloatFromHex": "from_hex",
}
+82 -82
View File
@@ -130,6 +130,81 @@ class IntDivideSafe(ComfyNodeABC):
return (int1 // int2,)
class IntBitCount(ComfyNodeABC):
"""
Returns the number of 1 bits in the binary representation of an integer.
This node takes an integer as input and returns the count of set bits.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"int_value": (IO.INT, {"default": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "bit_count"
def bit_count(self, int_value: int) -> tuple[int]:
# Introduced in Python 3.10
return (int_value.bit_count(),)
class IntBitLength(ComfyNodeABC):
"""
Returns the number of bits required to represent an integer in binary.
This node takes an integer as input and returns the number of bits needed
to represent it, excluding the sign and leading zeros.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"int_value": (IO.INT, {"default": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "bit_length"
def bit_length(self, int_value: int) -> tuple[int]:
return (int_value.bit_length(),)
class IntFromBytes(ComfyNodeABC):
"""
Converts a bytes object to an integer.
This class method takes bytes, byte order, and signed flag as inputs and
returns an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"bytes_value": ("BYTES", {}),
"byteorder": (["big", "little"], {"default": "big"}),
"signed": (["True", "False"], {"default": "False"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_bytes"
def from_bytes(self, bytes_value, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[int]:
signed_bool = (signed == "True")
return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),)
class IntModulus(ComfyNodeABC):
"""
Returns the modulus of two integers.
@@ -182,30 +257,6 @@ class IntPower(ComfyNodeABC):
return (base**exponent,)
class IntBitLength(ComfyNodeABC):
"""
Returns the number of bits required to represent an integer in binary.
This node takes an integer as input and returns the number of bits needed
to represent it, excluding the sign and leading zeros.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"int_value": (IO.INT, {"default": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "bit_length"
def bit_length(self, int_value: int) -> tuple[int]:
return (int_value.bit_length(),)
class IntToBytes(ComfyNodeABC):
"""
Converts an integer to its byte representation.
@@ -234,69 +285,18 @@ class IntToBytes(ComfyNodeABC):
return (int_value.to_bytes(length, byteorder=byteorder, signed=signed_bool),)
class IntFromBytes(ComfyNodeABC):
"""
Converts a bytes object to an integer.
This class method takes bytes, byte order, and signed flag as inputs and
returns an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"bytes_value": ("BYTES", {}),
"byteorder": (["big", "little"], {"default": "big"}),
"signed": (["True", "False"], {"default": "False"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "from_bytes"
def from_bytes(self, bytes_value, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[int]:
signed_bool = (signed == "True")
return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),)
class IntBitCount(ComfyNodeABC):
"""
Returns the number of 1 bits in the binary representation of an integer.
This node takes an integer as input and returns the count of set bits.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"int_value": (IO.INT, {"default": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/INT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "bit_count"
def bit_count(self, int_value: int) -> tuple[int]:
# Introduced in Python 3.10
return (int_value.bit_count(),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: IntAdd": IntAdd,
"Basic data handling: IntSubtract": IntSubtract,
"Basic data handling: IntMultiply": IntMultiply,
"Basic data handling: IntDivide": IntDivide,
"Basic data handling: IntDivideSafe": IntDivideSafe,
"Basic data handling: IntBitCount": IntBitCount,
"Basic data handling: IntBitLength": IntBitLength,
"Basic data handling: IntFromBytes": IntFromBytes,
"Basic data handling: IntModulus": IntModulus,
"Basic data handling: IntPower": IntPower,
"Basic data handling: IntBitLength": IntBitLength,
"Basic data handling: IntToBytes": IntToBytes,
"Basic data handling: IntFromBytes": IntFromBytes,
"Basic data handling: IntBitCount": IntBitCount,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -305,11 +305,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: IntMultiply": "multiply",
"Basic data handling: IntDivide": "divide",
"Basic data handling: IntDivideSafe": "divide (division by zero safe)",
"Basic data handling: IntBitCount": "bit count",
"Basic data handling: IntBitLength": "bit length",
"Basic data handling: IntFromBytes": "from bytes",
"Basic data handling: IntModulus": "modulus",
"Basic data handling: IntPower": "power",
"Basic data handling: IntBitLength": "bit_length",
"Basic data handling: IntToBytes": "to_bytes",
"Basic data handling: IntFromBytes": "from_bytes",
"Basic data handling: IntBitCount": "bit_count",
"Basic data handling: IntToBytes": "to bytes",
}
+352 -352
View File
@@ -4,6 +4,25 @@ from comfy.comfy_types.node_typing import IO, ComfyNodeABC
INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes
class ListEmpty(ComfyNodeABC):
"""
Create an empty LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create"
def create(self) -> tuple[list[Any]]:
return ([],)
class ListAppend(ComfyNodeABC):
"""
Adds an item to the end of a LIST.
@@ -31,23 +50,56 @@ class ListAppend(ComfyNodeABC):
return (result,)
class ListEmpty(ComfyNodeABC):
class ListContains(ComfyNodeABC):
"""
Create an empty LIST.
Checks if a LIST contains a specified value.
This node takes a LIST and a value as inputs, then returns True if the value
is present in the LIST, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {}
"required": {
"list": ("LIST", {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create"
FUNCTION = "contains"
def create(self) -> tuple[list[Any]]:
return ([],)
def contains(self, list: list[Any], value: Any) -> tuple[bool]:
return (value in list,)
class ListCount(ComfyNodeABC):
"""
Counts the number of occurrences of a value in a LIST.
This node takes a LIST and a value as inputs, then returns the number of times
the value appears in the LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("count",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
def count(self, list: list[Any], value: Any) -> tuple[int]:
return (list.count(value),)
class ListExtend(ComfyNodeABC):
@@ -77,12 +129,13 @@ class ListExtend(ComfyNodeABC):
return (result,)
class ListInsert(ComfyNodeABC):
class ListGetItem(ComfyNodeABC):
"""
Inserts an item at a specified position in a LIST.
Retrieves an item at a specified position in a LIST.
This node takes a LIST, an index, and any item as inputs, then returns a new
LIST with the item inserted at the specified index.
This node takes a LIST and an index as inputs, then returns the item at the specified index.
Negative indices count from the end of the LIST.
Out of range indices return None.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -90,86 +143,20 @@ class ListInsert(ComfyNodeABC):
"required": {
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
"item": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
FUNCTION = "get_item"
def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
result = list.copy()
result.insert(index, item)
return (result,)
class ListRemove(ComfyNodeABC):
"""
Removes the first occurrence of a specified value from a LIST.
This node takes a LIST and a value as inputs, then returns a new LIST with
the first occurrence of the value removed and a success indicator. If the value
is not present, the original LIST is returned with success set to False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST", "BOOLEAN")
RETURN_NAMES = ("list", "success")
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
def remove(self, list: list[Any], value: Any) -> tuple[list[Any], bool]:
result = list.copy()
def get_item(self, list: list[Any], index: int) -> tuple[Any]:
try:
result.remove(value)
return result, True
except ValueError:
return result, False
class ListPop(ComfyNodeABC):
"""
Removes and returns an item at a specified position in a LIST.
This node takes a LIST and an index as inputs, then returns both the new LIST
with the item removed and the removed item. If no index is specified,
removes and returns the last item.
When the LIST is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
},
"optional": {
"index": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("LIST", IO.ANY)
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
result = list.copy()
try:
item = result.pop(index)
return result, item
return (list[index],)
except IndexError:
return result, None
return (None,)
class ListIndex(ComfyNodeABC):
@@ -209,12 +196,164 @@ class ListIndex(ComfyNodeABC):
return (-1,)
class ListCount(ComfyNodeABC):
class ListInsert(ComfyNodeABC):
"""
Counts the number of occurrences of a value in a LIST.
Inserts an item at a specified position in a LIST.
This node takes a LIST and a value as inputs, then returns the number of times
the value appears in the LIST.
This node takes a LIST, an index, and any item as inputs, then returns a new
LIST with the item inserted at the specified index.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
"item": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
result = list.copy()
result.insert(index, item)
return (result,)
class ListLength(ComfyNodeABC):
"""
Returns the number of items in a LIST.
This node takes a LIST as input and returns its length as an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, list: list[Any]) -> tuple[int]:
return (len(list),)
class ListMax(ComfyNodeABC):
"""
Returns the maximum value in a LIST.
This node takes a LIST of comparable items and returns the maximum value.
Returns None if the LIST is empty or if items are not comparable.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("max_value",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_max"
def find_max(self, list: list[Any]) -> tuple[Any]:
if not list:
return (None,)
try:
return (max(list),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class ListMin(ComfyNodeABC):
"""
Returns the minimum value in a LIST.
This node takes a LIST of comparable items and returns the minimum value.
Returns None if the LIST is empty or if items are not comparable.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("min_value",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_min"
def find_min(self, list: list[Any]) -> tuple[Any]:
if not list:
return (None,)
try:
return (min(list),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class ListPop(ComfyNodeABC):
"""
Removes and returns an item at a specified position in a LIST.
This node takes a LIST and an index as inputs, then returns both the new LIST
with the item removed and the removed item. If no index is specified,
removes and returns the last item.
When the LIST is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
},
"optional": {
"index": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("LIST", IO.ANY)
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
result = list.copy()
try:
item = result.pop(index)
return result, item
except IndexError:
return result, None
class ListRemove(ComfyNodeABC):
"""
Removes the first occurrence of a specified value from a LIST.
This node takes a LIST and a value as inputs, then returns a new LIST with
the first occurrence of the value removed and a success indicator. If the value
is not present, the original LIST is returned with success set to False.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -225,14 +364,105 @@ class ListCount(ComfyNodeABC):
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("count",)
RETURN_TYPES = ("LIST", "BOOLEAN")
RETURN_NAMES = ("list", "success")
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
FUNCTION = "remove"
def count(self, list: list[Any], value: Any) -> tuple[int]:
return (list.count(value),)
def remove(self, list: list[Any], value: Any) -> tuple[list[Any], bool]:
result = list.copy()
try:
result.remove(value)
return result, True
except ValueError:
return result, False
class ListReverse(ComfyNodeABC):
"""
Reverses the order of items in a LIST.
This node takes a LIST as input and returns a new LIST with the items in reversed order.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "reverse"
def reverse(self, list: list[Any]) -> tuple[list[Any]]:
result = list.copy()
result.reverse()
return (result,)
class ListSetItem(ComfyNodeABC):
"""
Sets an item at a specified position in a LIST.
This node takes a LIST, an index, and a value, then returns a new LIST with
the item at the specified index replaced by the value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set_item"
def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]:
result = list.copy()
try:
result[index] = value
return (result,)
except IndexError:
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
class ListSlice(ComfyNodeABC):
"""
Creates a slice of a LIST.
This node takes a LIST and start/stop/step parameters, and returns a new LIST
containing the specified slice of the original LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
},
"optional": {
"start": ("INT", {"default": 0}),
"stop": ("INT", {"default": INT_MAX}),
"step": ("INT", {"default": 1}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "slice"
def slice(self, list: list[Any], start: int = 0, stop: int = INT_MAX,
step: int = 1) -> tuple[list[Any]]:
return (list[start:stop:step],)
class ListSort(ComfyNodeABC):
@@ -271,172 +501,6 @@ class ListSort(ComfyNodeABC):
return (list.copy(),)
class ListReverse(ComfyNodeABC):
"""
Reverses the order of items in a LIST.
This node takes a LIST as input and returns a new LIST with the items in reversed order.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "reverse"
def reverse(self, list: list[Any]) -> tuple[list[Any]]:
result = list.copy()
result.reverse()
return (result,)
class ListLength(ComfyNodeABC):
"""
Returns the number of items in a LIST.
This node takes a LIST as input and returns its length as an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, list: list[Any]) -> tuple[int]:
return (len(list),)
class ListSlice(ComfyNodeABC):
"""
Creates a slice of a LIST.
This node takes a LIST and start/stop/step parameters, and returns a new LIST
containing the specified slice of the original LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
},
"optional": {
"start": ("INT", {"default": 0}),
"stop": ("INT", {"default": INT_MAX}),
"step": ("INT", {"default": 1}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "slice"
def slice(self, list: list[Any], start: int = 0, stop: int = INT_MAX,
step: int = 1) -> tuple[list[Any]]:
return (list[start:stop:step],)
class ListGetItem(ComfyNodeABC):
"""
Retrieves an item at a specified position in a LIST.
This node takes a LIST and an index as inputs, then returns the item at the specified index.
Negative indices count from the end of the LIST.
Out of range indices return None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_item"
def get_item(self, list: list[Any], index: int) -> tuple[Any]:
try:
return (list[index],)
except IndexError:
return (None,)
class ListSetItem(ComfyNodeABC):
"""
Sets an item at a specified position in a LIST.
This node takes a LIST, an index, and a value, then returns a new LIST with
the item at the specified index replaced by the value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set_item"
def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]:
result = list.copy()
try:
result[index] = value
return (result,)
except IndexError:
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
class ListContains(ComfyNodeABC):
"""
Checks if a LIST contains a specified value.
This node takes a LIST and a value as inputs, then returns True if the value
is present in the LIST, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
def contains(self, list: list[Any], value: Any) -> tuple[bool]:
return (value in list,)
class ListToDataList(ComfyNodeABC):
"""
Converts a LIST object into a ComfyUI data list.
@@ -487,110 +551,46 @@ class ListToSet(ComfyNodeABC):
return (set(list),)
class ListMin(ComfyNodeABC):
"""
Returns the minimum value in a LIST.
This node takes a LIST of comparable items and returns the minimum value.
Returns None if the LIST is empty or if items are not comparable.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("min_value",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_min"
def find_min(self, list: list[Any]) -> tuple[Any]:
if not list:
return (None,)
try:
return (min(list),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
class ListMax(ComfyNodeABC):
"""
Returns the maximum value in a LIST.
This node takes a LIST of comparable items and returns the maximum value.
Returns None if the LIST is empty or if items are not comparable.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("max_value",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "find_max"
def find_max(self, list: list[Any]) -> tuple[Any]:
if not list:
return (None,)
try:
return (max(list),)
except (TypeError, ValueError):
# Handle case where list contains non-comparable elements
return (None,)
NODE_CLASS_MAPPINGS = {
"Basic data handling: ListAppend": ListAppend,
"Basic data handling: ListEmpty": ListEmpty,
"Basic data handling: ListExtend": ListExtend,
"Basic data handling: ListInsert": ListInsert,
"Basic data handling: ListRemove": ListRemove,
"Basic data handling: ListPop": ListPop,
"Basic data handling: ListIndex": ListIndex,
"Basic data handling: ListCount": ListCount,
"Basic data handling: ListSort": ListSort,
"Basic data handling: ListReverse": ListReverse,
"Basic data handling: ListLength": ListLength,
"Basic data handling: ListSlice": ListSlice,
"Basic data handling: ListGetItem": ListGetItem,
"Basic data handling: ListSetItem": ListSetItem,
"Basic data handling: ListAppend": ListAppend,
"Basic data handling: ListContains": ListContains,
"Basic data handling: ListCount": ListCount,
"Basic data handling: ListExtend": ListExtend,
"Basic data handling: ListGetItem": ListGetItem,
"Basic data handling: ListIndex": ListIndex,
"Basic data handling: ListInsert": ListInsert,
"Basic data handling: ListLength": ListLength,
"Basic data handling: ListMax": ListMax,
"Basic data handling: ListMin": ListMin,
"Basic data handling: ListPop": ListPop,
"Basic data handling: ListRemove": ListRemove,
"Basic data handling: ListReverse": ListReverse,
"Basic data handling: ListSetItem": ListSetItem,
"Basic data handling: ListSlice": ListSlice,
"Basic data handling: ListSort": ListSort,
"Basic data handling: ListToDataList": ListToDataList,
"Basic data handling: ListToSet": ListToSet,
"Basic data handling: ListMin": ListMin,
"Basic data handling: ListMax": ListMax,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: ListEmpty": "create empty",
"Basic data handling: ListAppend": "append",
"Basic data handling: ListEmpty": "empty",
"Basic data handling: ListExtend": "extend",
"Basic data handling: ListInsert": "insert",
"Basic data handling: ListRemove": "remove",
"Basic data handling: ListPop": "pop",
"Basic data handling: ListIndex": "index",
"Basic data handling: ListCount": "count",
"Basic data handling: ListSort": "sort",
"Basic data handling: ListReverse": "reverse",
"Basic data handling: ListLength": "length",
"Basic data handling: ListSlice": "slice",
"Basic data handling: ListGetItem": "get item",
"Basic data handling: ListSetItem": "set item",
"Basic data handling: ListContains": "contains",
"Basic data handling: ListCount": "count",
"Basic data handling: ListExtend": "extend",
"Basic data handling: ListGetItem": "get item",
"Basic data handling: ListIndex": "index",
"Basic data handling: ListInsert": "insert",
"Basic data handling: ListLength": "length",
"Basic data handling: ListMax": "max",
"Basic data handling: ListMin": "min",
"Basic data handling: ListPop": "pop",
"Basic data handling: ListRemove": "remove",
"Basic data handling: ListReverse": "reverse",
"Basic data handling: ListSetItem": "set item",
"Basic data handling: ListSlice": "slice",
"Basic data handling: ListSort": "sort",
"Basic data handling: ListToDataList": "convert to data list",
"Basic data handling: ListToSet": "convert to SET",
"Basic data handling: ListMin": "min",
"Basic data handling: ListMax": "max",
}
+280 -280
View File
@@ -3,111 +3,17 @@ from inspect import cleandoc
from typing import Literal, Union
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
class MathSin(ComfyNodeABC):
class MathAbs(ComfyNodeABC):
"""
Calculates the sine of an angle in radians or degrees.
Returns the absolute value of a number.
This node takes an angle value and unit type, and returns the sine of that angle.
The sine of an angle is the ratio of the length of the opposite side to the length
of the hypotenuse in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
return (math.sin(angle),)
class MathCos(ComfyNodeABC):
"""
Calculates the cosine of an angle in radians or degrees.
This node takes an angle value and unit type, and returns the cosine of that angle.
The cosine of an angle is the ratio of the length of the adjacent side to the length
of the hypotenuse in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
return (math.cos(angle),)
class MathTan(ComfyNodeABC):
"""
Calculates the tangent of an angle in radians or degrees.
This node takes an angle value and unit type, and returns the tangent of that angle.
The tangent of an angle is the ratio of the length of the opposite side to the length
of the adjacent side in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
# Handle specific angles that would result in division by zero
if abs(math.cos(angle)) < 1e-10:
raise ValueError("Tangent is undefined at this angle (division by zero)")
return (math.tan(angle),)
class MathAsin(ComfyNodeABC):
"""
Calculates the arc sine (inverse sine) of a value.
This node takes a value between -1 and 1, and returns the arc sine in radians or degrees.
The arc sine is the inverse operation of sine, returning the angle whose sine is the input value.
This node takes a number and returns its absolute value (magnitude without sign).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
@@ -116,11 +22,8 @@ class MathAsin(ComfyNodeABC):
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
result = math.asin(value)
if unit == "degrees":
result = math.degrees(result)
return (result,)
def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]:
return (abs(value),)
class MathAcos(ComfyNodeABC):
@@ -151,6 +54,34 @@ class MathAcos(ComfyNodeABC):
return (result,)
class MathAsin(ComfyNodeABC):
"""
Calculates the arc sine (inverse sine) of a value.
This node takes a value between -1 and 1, and returns the arc sine in radians or degrees.
The arc sine is the inverse operation of sine, returning the angle whose sine is the input value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
result = math.asin(value)
if unit == "degrees":
result = math.degrees(result)
return (result,)
class MathAtan(ComfyNodeABC):
"""
Calculates the arc tangent (inverse tangent) of a value.
@@ -209,17 +140,43 @@ class MathAtan2(ComfyNodeABC):
return (result,)
class MathSqrt(ComfyNodeABC):
class MathCeil(ComfyNodeABC):
"""
Calculates the square root of a non-negative number.
Returns the ceiling of a number.
This node takes a non-negative number and returns its square root.
This node takes a number and returns the smallest integer greater than or equal to the input value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}),
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[int]:
return (math.ceil(value),)
class MathCos(ComfyNodeABC):
"""
Calculates the cosine of an angle in radians or degrees.
This node takes an angle value and unit type, and returns the cosine of that angle.
The cosine of an angle is the ratio of the length of the adjacent side to the length
of the hypotenuse in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
@@ -228,8 +185,53 @@ class MathSqrt(ComfyNodeABC):
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[float]:
return (math.sqrt(value),)
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
return (math.cos(angle),)
class MathDegrees(ComfyNodeABC):
"""
Converts an angle from radians to degrees.
This node takes an angle in radians and returns the equivalent angle in degrees.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, radians: float) -> tuple[float]:
return (math.degrees(radians),)
class MathE(ComfyNodeABC):
"""
Returns the mathematical constant e.
This node returns the value of e (Euler's number), which is approximately 2.71828.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self) -> tuple[float]:
return (math.e,)
class MathExp(ComfyNodeABC):
@@ -255,6 +257,29 @@ class MathExp(ComfyNodeABC):
return (math.exp(value),)
class MathFloor(ComfyNodeABC):
"""
Returns the floor of a number.
This node takes a number and returns the largest integer less than or equal to the input value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[int]:
return (math.floor(value),)
class MathLog(ComfyNodeABC):
"""
Calculates the natural logarithm (base e) of a number.
@@ -308,157 +333,29 @@ class MathLog10(ComfyNodeABC):
return (math.log10(value),)
class MathDegrees(ComfyNodeABC):
class MathMax(ComfyNodeABC):
"""
Converts an angle from radians to degrees.
Returns the maximum value among the inputs.
This node takes an angle in radians and returns the equivalent angle in degrees.
This node takes two or more values and returns the maximum value among them.
The function works with both FLOAT and INT types.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
RETURN_TYPES = ("*",)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, radians: float) -> tuple[float]:
return (math.degrees(radians),)
class MathRadians(ComfyNodeABC):
"""
Converts an angle from degrees to radians.
This node takes an angle in degrees and returns the equivalent angle in radians.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, degrees: float) -> tuple[float]:
return (math.radians(degrees),)
class MathFloor(ComfyNodeABC):
"""
Returns the floor of a number.
This node takes a number and returns the largest integer less than or equal to the input value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[int]:
return (math.floor(value),)
class MathCeil(ComfyNodeABC):
"""
Returns the ceiling of a number.
This node takes a number and returns the smallest integer greater than or equal to the input value.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[int]:
return (math.ceil(value),)
class MathAbs(ComfyNodeABC):
"""
Returns the absolute value of a number.
This node takes a number and returns its absolute value (magnitude without sign).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]:
return (abs(value),)
class MathPi(ComfyNodeABC):
"""
Returns the mathematical constant π (pi).
This node returns the value of π, which is approximately 3.14159.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self) -> tuple[float]:
return (math.pi,)
class MathE(ComfyNodeABC):
"""
Returns the mathematical constant e.
This node returns the value of e (Euler's number), which is approximately 2.71828.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self) -> tuple[float]:
return (math.e,)
def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]:
return (max(value1, value2),)
class MathMin(ComfyNodeABC):
@@ -486,73 +383,176 @@ class MathMin(ComfyNodeABC):
return (min(value1, value2),)
class MathMax(ComfyNodeABC):
class MathPi(ComfyNodeABC):
"""
Returns the maximum value among the inputs.
Returns the mathematical constant π (pi).
This node takes two or more values and returns the maximum value among them.
The function works with both FLOAT and INT types.
This node returns the value of π, which is approximately 3.14159.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self) -> tuple[float]:
return (math.pi,)
class MathRadians(ComfyNodeABC):
"""
Converts an angle from degrees to radians.
This node takes an angle in degrees and returns the equivalent angle in radians.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = ("*",)
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]:
return (max(value1, value2),)
def calculate(self, degrees: float) -> tuple[float]:
return (math.radians(degrees),)
class MathSin(ComfyNodeABC):
"""
Calculates the sine of an angle in radians or degrees.
This node takes an angle value and unit type, and returns the sine of that angle.
The sine of an angle is the ratio of the length of the opposite side to the length
of the hypotenuse in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
return (math.sin(angle),)
class MathSqrt(ComfyNodeABC):
"""
Calculates the square root of a non-negative number.
This node takes a non-negative number and returns its square root.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, value: float) -> tuple[float]:
return (math.sqrt(value),)
class MathTan(ComfyNodeABC):
"""
Calculates the tangent of an angle in radians or degrees.
This node takes an angle value and unit type, and returns the tangent of that angle.
The tangent of an angle is the ratio of the length of the opposite side to the length
of the adjacent side in a right-angled triangle.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
"unit": (["radians", "degrees"], {"default": "degrees"}),
}
}
RETURN_TYPES = (IO.FLOAT,)
CATEGORY = "Basic/maths"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "calculate"
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
if unit == "degrees":
# Convert degrees to radians
angle = math.radians(angle)
# Handle specific angles that would result in division by zero
if abs(math.cos(angle)) < 1e-10:
raise ValueError("Tangent is undefined at this angle (division by zero)")
return (math.tan(angle),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: MathSin": MathSin,
"Basic data handling: MathCos": MathCos,
"Basic data handling: MathTan": MathTan,
"Basic data handling: MathAsin": MathAsin,
"Basic data handling: MathAbs": MathAbs,
"Basic data handling: MathAcos": MathAcos,
"Basic data handling: MathAsin": MathAsin,
"Basic data handling: MathAtan": MathAtan,
"Basic data handling: MathAtan2": MathAtan2,
"Basic data handling: MathSqrt": MathSqrt,
"Basic data handling: MathCeil": MathCeil,
"Basic data handling: MathCos": MathCos,
"Basic data handling: MathDegrees": MathDegrees,
"Basic data handling: MathE": MathE,
"Basic data handling: MathExp": MathExp,
"Basic data handling: MathFloor": MathFloor,
"Basic data handling: MathLog": MathLog,
"Basic data handling: MathLog10": MathLog10,
"Basic data handling: MathDegrees": MathDegrees,
"Basic data handling: MathRadians": MathRadians,
"Basic data handling: MathFloor": MathFloor,
"Basic data handling: MathCeil": MathCeil,
"Basic data handling: MathAbs": MathAbs,
"Basic data handling: MathPi": MathPi,
"Basic data handling: MathE": MathE,
"Basic data handling: MathMin": MathMin,
"Basic data handling: MathMax": MathMax,
"Basic data handling: MathMin": MathMin,
"Basic data handling: MathPi": MathPi,
"Basic data handling: MathRadians": MathRadians,
"Basic data handling: MathSin": MathSin,
"Basic data handling: MathSqrt": MathSqrt,
"Basic data handling: MathTan": MathTan,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: MathSin": "sin",
"Basic data handling: MathCos": "cos",
"Basic data handling: MathTan": "tan",
"Basic data handling: MathAsin": "asin",
"Basic data handling: MathAbs": "abs",
"Basic data handling: MathAcos": "acos",
"Basic data handling: MathAsin": "asin",
"Basic data handling: MathAtan": "atan",
"Basic data handling: MathAtan2": "atan2",
"Basic data handling: MathSqrt": "sqrt",
"Basic data handling: MathCeil": "ceil",
"Basic data handling: MathCos": "cos",
"Basic data handling: MathDegrees": "degrees",
"Basic data handling: MathE": "e (2.71828)",
"Basic data handling: MathExp": "exp",
"Basic data handling: MathFloor": "floor",
"Basic data handling: MathLog": "log",
"Basic data handling: MathLog10": "log10",
"Basic data handling: MathDegrees": "degrees",
"Basic data handling: MathRadians": "radians",
"Basic data handling: MathFloor": "floor",
"Basic data handling: MathCeil": "ceil",
"Basic data handling: MathAbs": "abs",
"Basic data handling: MathPi": "pi",
"Basic data handling: MathE": "e",
"Basic data handling: MathMin": "min",
"Basic data handling: MathMax": "max",
"Basic data handling: MathMin": "min",
"Basic data handling: MathPi": "pi (3.14159)",
"Basic data handling: MathRadians": "radians",
"Basic data handling: MathSin": "sin",
"Basic data handling: MathSqrt": "sqrt",
"Basic data handling: MathTan": "tan",
}
+405 -405
View File
@@ -4,6 +4,341 @@ import glob
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
class PathAbspath(ComfyNodeABC):
"""
Returns the absolute path of a file or directory.
This node takes a path and returns its absolute (full) path
by resolving any relative path components and symbolic links.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("absolute_path",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_abspath"
def get_abspath(self, path: str) -> tuple[str]:
return (os.path.abspath(path),)
class PathBasename(ComfyNodeABC):
"""
Returns the base name of a path.
This node extracts the filename component from a path,
removing any directory information.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("basename",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_basename"
def get_basename(self, path: str) -> tuple[str]:
return (os.path.basename(path),)
class PathCommonPrefix(ComfyNodeABC):
"""
Finds the common prefix of multiple paths.
This node returns the longest common leading component of the given paths.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path1": (IO.STRING, {"default": ""}),
},
"optional": {
"path2": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("common_prefix",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_common_prefix"
def get_common_prefix(self, path1: str, path2: str = "") -> tuple[str]:
paths = [p for p in [path1, path2] if p]
return (os.path.commonprefix(paths),)
class PathDirname(ComfyNodeABC):
"""
Returns the directory name of a path.
This node extracts the directory component from a path,
removing the filename.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("dirname",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_dirname"
def get_dirname(self, path: str) -> tuple[str]:
return (os.path.dirname(path),)
class PathExists(ComfyNodeABC):
"""
Checks if a path exists in the filesystem.
This node returns True if the path exists (either as a file or a directory),
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("exists",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_exists"
def check_exists(self, path: str) -> tuple[bool]:
return (os.path.exists(path),)
class PathExpandVars(ComfyNodeABC):
"""
Expands environment variables in a path.
This node replaces environment variables in a path with their values.
For example, $HOME or ${HOME} on Unix, or %USERPROFILE% on Windows.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("expanded_path",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "expand_vars"
def expand_vars(self, path: str) -> tuple[str]:
return (os.path.expandvars(path),)
class PathGetCwd(ComfyNodeABC):
"""
Returns the current working directory.
This node returns the current working directory as an absolute path.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("current_directory",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_cwd"
def get_cwd(self) -> tuple[str]:
return (os.getcwd(),)
class PathGetExtension(ComfyNodeABC):
"""
Returns the extension of a file.
This node extracts the file extension from a path,
including the dot (e.g., '.txt').
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("extension",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_extension"
def get_extension(self, path: str) -> tuple[str]:
return (os.path.splitext(path)[1],)
class PathGetSize(ComfyNodeABC):
"""
Returns the size of a file in bytes.
This node returns the size in bytes of the file at the given path.
Raises an error if the path doesn't exist or isn't a file.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("size_bytes",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_size"
def get_size(self, path: str) -> tuple[int]:
if not os.path.exists(path):
raise FileNotFoundError(f"Basic data handling: Path does not exist: {path}")
if not os.path.isfile(path):
raise ValueError(f"Basic data handling: Path is not a file: {path}")
return (os.path.getsize(path),)
class PathGlob(ComfyNodeABC):
"""
Finds paths matching a pattern.
This node returns a list of paths matching the given pattern.
The pattern follows shell-style wildcard rules:
* - matches any number of characters
? - matches a single character
[seq] - matches any character in seq
[!seq] - matches any character not in seq
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": (IO.STRING, {"default": "*.txt"}),
},
"optional": {
"recursive": (IO.BOOLEAN, {"default": False}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("matching_paths",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "glob_paths"
OUTPUT_IS_LIST = (True,)
def glob_paths(self, pattern: str, recursive: bool = False) -> tuple[list[str]]:
return (glob.glob(pattern, recursive=recursive),)
class PathIsAbsolute(ComfyNodeABC):
"""
Checks if a path is absolute.
This node returns True if the path is absolute (begins at the root directory),
and False if it's relative.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_absolute",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_absolute"
def check_is_absolute(self, path: str) -> tuple[bool]:
return (os.path.isabs(path),)
class PathIsDir(ComfyNodeABC):
"""
Checks if a path points to a directory.
This node returns True if the path exists and is a directory,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_dir",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_dir"
def check_is_dir(self, path: str) -> tuple[bool]:
return (os.path.isdir(path),)
class PathIsFile(ComfyNodeABC):
"""
Checks if a path points to a file.
This node returns True if the path exists and is a regular file,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_file",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_file"
def check_is_file(self, path: str) -> tuple[bool]:
return (os.path.isfile(path),)
class PathJoin(ComfyNodeABC):
"""
Joins multiple path components into a single path.
@@ -34,258 +369,51 @@ class PathJoin(ComfyNodeABC):
return (str(os.path.join(*paths)),)
class PathAbspath(ComfyNodeABC):
class PathListDir(ComfyNodeABC):
"""
Returns the absolute path of a file or directory.
Lists the contents of a directory.
This node takes a path and returns its absolute (full) path
by resolving any relative path components and symbolic links.
This node returns a list of files and directories in the specified path.
If 'files_only' is True, it only returns files.
If 'dirs_only' is True, it only returns directories.
If both are False, it returns all contents.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
},
"optional": {
"files_only": (IO.BOOLEAN, {"default": False}),
"dirs_only": (IO.BOOLEAN, {"default": False}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("absolute_path",)
RETURN_NAMES = ("entries",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_abspath"
FUNCTION = "list_directory"
OUTPUT_IS_LIST = (True,)
def get_abspath(self, path: str) -> tuple[str]:
return (os.path.abspath(path),)
def list_directory(self, path: str, files_only: str = False, dirs_only: str = False) -> tuple[list[str]]:
if not path:
path = os.getcwd()
class PathExists(ComfyNodeABC):
"""
Checks if a path exists in the filesystem.
This node returns True if the path exists (either as a file or a directory),
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("exists",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_exists"
def check_exists(self, path: str) -> tuple[bool]:
return (os.path.exists(path),)
class PathIsFile(ComfyNodeABC):
"""
Checks if a path points to a file.
This node returns True if the path exists and is a regular file,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_file",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_file"
def check_is_file(self, path: str) -> tuple[bool]:
return (os.path.isfile(path),)
class PathIsDir(ComfyNodeABC):
"""
Checks if a path points to a directory.
This node returns True if the path exists and is a directory,
and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_dir",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_dir"
def check_is_dir(self, path: str) -> tuple[bool]:
return (os.path.isdir(path),)
class PathGetSize(ComfyNodeABC):
"""
Returns the size of a file in bytes.
This node returns the size in bytes of the file at the given path.
Raises an error if the path doesn't exist or isn't a file.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.INT,)
RETURN_NAMES = ("size_bytes",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_size"
def get_size(self, path: str) -> tuple[int]:
if not os.path.exists(path):
raise FileNotFoundError(f"Basic data handling: Path does not exist: {path}")
if not os.path.isfile(path):
raise ValueError(f"Basic data handling: Path is not a file: {path}")
return (os.path.getsize(path),)
raise FileNotFoundError(f"Directory does not exist: {path}")
if not os.path.isdir(path):
raise NotADirectoryError(f"Basic data handling: Path is not a directory: {path}")
entries = os.listdir(path)
class PathSplit(ComfyNodeABC):
"""
Splits a path into directory and filename components.
if files_only:
entries = [e for e in entries if os.path.isfile(os.path.join(path, e))]
elif dirs_only:
entries = [e for e in entries if os.path.isdir(os.path.join(path, e))]
This node takes a path and returns a tuple containing the directory path
and the filename.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING, IO.STRING)
RETURN_NAMES = ("directory", "filename")
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "split_path"
def split_path(self, path: str) -> tuple[str, str]:
return os.path.split(path)
class PathSplitExt(ComfyNodeABC):
"""
Splits a path into name and extension components.
This node takes a path and returns a tuple containing the path without
the extension and the extension (including the dot).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING, IO.STRING)
RETURN_NAMES = ("path_without_ext", "extension")
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "split_ext"
def split_ext(self, path: str) -> tuple[str, str]:
return os.path.splitext(path)
class PathBasename(ComfyNodeABC):
"""
Returns the base name of a path.
This node extracts the filename component from a path,
removing any directory information.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("basename",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_basename"
def get_basename(self, path: str) -> tuple[str]:
return (os.path.basename(path),)
class PathDirname(ComfyNodeABC):
"""
Returns the directory name of a path.
This node extracts the directory component from a path,
removing the filename.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("dirname",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_dirname"
def get_dirname(self, path: str) -> tuple[str]:
return (os.path.dirname(path),)
class PathGetExtension(ComfyNodeABC):
"""
Returns the extension of a file.
This node extracts the file extension from a path,
including the dot (e.g., '.txt').
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("extension",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_extension"
def get_extension(self, path: str) -> tuple[str]:
return (os.path.splitext(path)[1],)
return (entries,)
class PathNormalize(ComfyNodeABC):
@@ -344,45 +472,12 @@ class PathRelative(ComfyNodeABC):
return (os.path.relpath(path, start),)
class PathGlob(ComfyNodeABC):
class PathSplit(ComfyNodeABC):
"""
Finds paths matching a pattern.
Splits a path into directory and filename components.
This node returns a list of paths matching the given pattern.
The pattern follows shell-style wildcard rules:
* - matches any number of characters
? - matches a single character
[seq] - matches any character in seq
[!seq] - matches any character not in seq
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": (IO.STRING, {"default": "*.txt"}),
},
"optional": {
"recursive": (IO.BOOLEAN, {"default": False}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("matching_paths",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "glob_paths"
OUTPUT_IS_LIST = (True,)
def glob_paths(self, pattern: str, recursive: bool = False) -> tuple[list[str]]:
return (glob.glob(pattern, recursive=recursive),)
class PathExpandVars(ComfyNodeABC):
"""
Expands environment variables in a path.
This node replaces environment variables in a path with their values.
For example, $HOME or ${HOME} on Unix, or %USERPROFILE% on Windows.
This node takes a path and returns a tuple containing the directory path
and the filename.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -392,89 +487,22 @@ class PathExpandVars(ComfyNodeABC):
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("expanded_path",)
RETURN_TYPES = (IO.STRING, IO.STRING)
RETURN_NAMES = ("directory", "filename")
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "expand_vars"
FUNCTION = "split_path"
def expand_vars(self, path: str) -> tuple[str]:
return (os.path.expandvars(path),)
def split_path(self, path: str) -> tuple[str, str]:
return os.path.split(path)
class PathGetCwd(ComfyNodeABC):
class PathSplitExt(ComfyNodeABC):
"""
Returns the current working directory.
Splits a path into name and extension components.
This node returns the current working directory as an absolute path.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("current_directory",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_cwd"
def get_cwd(self) -> tuple[str]:
return (os.getcwd(),)
class PathListDir(ComfyNodeABC):
"""
Lists the contents of a directory.
This node returns a list of files and directories in the specified path.
If 'files_only' is True, it only returns files.
If 'dirs_only' is True, it only returns directories.
If both are False, it returns all contents.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path": (IO.STRING, {"default": ""}),
},
"optional": {
"files_only": (IO.BOOLEAN, {"default": False}),
"dirs_only": (IO.BOOLEAN, {"default": False}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("entries",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "list_directory"
OUTPUT_IS_LIST = (True,)
def list_directory(self, path: str, files_only: str = False, dirs_only: str = False) -> tuple[list[str]]:
if not path:
path = os.getcwd()
if not os.path.exists(path):
raise FileNotFoundError(f"Directory does not exist: {path}")
if not os.path.isdir(path):
raise NotADirectoryError(f"Basic data handling: Path is not a directory: {path}")
entries = os.listdir(path)
if files_only:
entries = [e for e in entries if os.path.isfile(os.path.join(path, e))]
elif dirs_only:
entries = [e for e in entries if os.path.isdir(os.path.join(path, e))]
return (entries,)
class PathIsAbsolute(ComfyNodeABC):
"""
Checks if a path is absolute.
This node returns True if the path is absolute (begins at the root directory),
and False if it's relative.
This node takes a path and returns a tuple containing the path without
the extension and the extension (including the dot).
"""
@classmethod
def INPUT_TYPES(cls):
@@ -484,84 +512,56 @@ class PathIsAbsolute(ComfyNodeABC):
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_absolute",)
RETURN_TYPES = (IO.STRING, IO.STRING)
RETURN_NAMES = ("path_without_ext", "extension")
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "check_is_absolute"
FUNCTION = "split_ext"
def check_is_absolute(self, path: str) -> tuple[bool]:
return (os.path.isabs(path),)
class PathCommonPrefix(ComfyNodeABC):
"""
Finds the common prefix of multiple paths.
This node returns the longest common leading component of the given paths.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"path1": (IO.STRING, {"default": ""}),
},
"optional": {
"path2": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.STRING,)
RETURN_NAMES = ("common_prefix",)
CATEGORY = "Basic/path"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_common_prefix"
def get_common_prefix(self, path1: str, path2: str = "") -> tuple[str]:
paths = [p for p in [path1, path2] if p]
return (os.path.commonprefix(paths),)
def split_ext(self, path: str) -> tuple[str, str]:
return os.path.splitext(path)
NODE_CLASS_MAPPINGS = {
"Basic data handling: PathJoin": PathJoin,
"Basic data handling: PathAbspath": PathAbspath,
"Basic data handling: PathExists": PathExists,
"Basic data handling: PathIsFile": PathIsFile,
"Basic data handling: PathIsDir": PathIsDir,
"Basic data handling: PathGetSize": PathGetSize,
"Basic data handling: PathSplit": PathSplit,
"Basic data handling: PathSplitExt": PathSplitExt,
"Basic data handling: PathBasename": PathBasename,
"Basic data handling: PathCommonPrefix": PathCommonPrefix,
"Basic data handling: PathDirname": PathDirname,
"Basic data handling: PathGetExtension": PathGetExtension,
"Basic data handling: PathNormalize": PathNormalize,
"Basic data handling: PathRelative": PathRelative,
"Basic data handling: PathGlob": PathGlob,
"Basic data handling: PathExists": PathExists,
"Basic data handling: PathExpandVars": PathExpandVars,
"Basic data handling: PathGetCwd": PathGetCwd,
"Basic data handling: PathListDir": PathListDir,
"Basic data handling: PathGetExtension": PathGetExtension,
"Basic data handling: PathGetSize": PathGetSize,
"Basic data handling: PathGlob": PathGlob,
"Basic data handling: PathIsAbsolute": PathIsAbsolute,
"Basic data handling: PathCommonPrefix": PathCommonPrefix,
"Basic data handling: PathIsDir": PathIsDir,
"Basic data handling: PathIsFile": PathIsFile,
"Basic data handling: PathJoin": PathJoin,
"Basic data handling: PathListDir": PathListDir,
"Basic data handling: PathNormalize": PathNormalize,
"Basic data handling: PathRelative": PathRelative,
"Basic data handling: PathSplit": PathSplit,
"Basic data handling: PathSplitExt": PathSplitExt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: PathJoin": "join",
"Basic data handling: PathAbspath": "abspath",
"Basic data handling: PathExists": "exists",
"Basic data handling: PathIsFile": "is_file",
"Basic data handling: PathIsDir": "is_dir",
"Basic data handling: PathGetSize": "get_size",
"Basic data handling: PathSplit": "split",
"Basic data handling: PathSplitExt": "splitext",
"Basic data handling: PathBasename": "basename",
"Basic data handling: PathCommonPrefix": "common prefix",
"Basic data handling: PathDirname": "dirname",
"Basic data handling: PathGetExtension": "get_extension",
"Basic data handling: PathExists": "exists",
"Basic data handling: PathExpandVars": "expand vars",
"Basic data handling: PathGetCwd": "get current working directory",
"Basic data handling: PathGetExtension": "get extension",
"Basic data handling: PathGetSize": "get size",
"Basic data handling: PathGlob": "glob",
"Basic data handling: PathIsAbsolute": "is absolute",
"Basic data handling: PathIsDir": "is dir",
"Basic data handling: PathIsFile": "is file",
"Basic data handling: PathJoin": "join",
"Basic data handling: PathListDir": "list dir",
"Basic data handling: PathNormalize": "normalize",
"Basic data handling: PathRelative": "relative",
"Basic data handling: PathGlob": "glob",
"Basic data handling: PathExpandVars": "expand_vars",
"Basic data handling: PathGetCwd": "get_cwd",
"Basic data handling: PathListDir": "list_dir",
"Basic data handling: PathIsAbsolute": "is_absolute",
"Basic data handling: PathCommonPrefix": "common_prefix",
"Basic data handling: PathSplit": "split",
"Basic data handling: PathSplitExt": "splitext",
}
+77 -77
View File
@@ -2,6 +2,77 @@ from inspect import cleandoc
import re
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
class RegexFindallDataList(ComfyNodeABC):
"""
Returns all non-overlapping matches of a pattern in the string as a list of strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "findall"
OUTPUT_IS_LIST = (True,)
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.findall(pattern, string),)
class RegexFindallList(ComfyNodeABC):
"""
Returns all non-overlapping matches of a pattern in the string as a list of strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "findall"
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.findall(pattern, string),)
class RegexGroupDict(ComfyNodeABC):
"""
Searches the string with the given pattern and returns a DICT of named groups.
If no match is found, it returns an empty DICT.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "groupdict"
def groupdict(self, pattern: str, string: str) -> tuple[dict]:
match = re.search(pattern, string)
if match:
return (match.groupdict(),)
return ({},)
class RegexSearchGroupsDataList(ComfyNodeABC):
"""
Searches the string for a match to the pattern and returns a LIST of match groups.
@@ -55,77 +126,6 @@ class RegexSearchGroupsList(ComfyNodeABC):
return ([],)
class RegexGroupDict(ComfyNodeABC):
"""
Searches the string with the given pattern and returns a DICT of named groups.
If no match is found, it returns an empty DICT.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "groupdict"
def groupdict(self, pattern: str, string: str) -> tuple[dict]:
match = re.search(pattern, string)
if match:
return (match.groupdict(),)
return ({},)
class RegexFindallDataList(ComfyNodeABC):
"""
Returns all non-overlapping matches of a pattern in the string as a list of strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "findall"
OUTPUT_IS_LIST = (True,)
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.findall(pattern, string),)
class RegexFindallList(ComfyNodeABC):
"""
Returns all non-overlapping matches of a pattern in the string as a list of strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {}),
"pattern": (IO.STRING, {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "findall"
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.findall(pattern, string),)
class RegexSplitDataList(ComfyNodeABC):
"""
Splits the string at each match of the pattern and returns a list of substrings.
@@ -220,11 +220,11 @@ class RegexTest(ComfyNodeABC):
NODE_CLASS_MAPPINGS = {
"Basic data handling: RegexSearchGroupsDataList": RegexSearchGroupsDataList,
"Basic data handling: RegexSearchGroupsList": RegexSearchGroupsList,
"Basic data handling: RegexGroupDict": RegexGroupDict,
"Basic data handling: RegexFindallDataList": RegexFindallDataList,
"Basic data handling: RegexFindallList": RegexFindallList,
"Basic data handling: RegexGroupDict": RegexGroupDict,
"Basic data handling: RegexSearchGroupsDataList": RegexSearchGroupsDataList,
"Basic data handling: RegexSearchGroupsList": RegexSearchGroupsList,
"Basic data handling: RegexSplitDataList": RegexSplitDataList,
"Basic data handling: RegexSplitList": RegexSplitList,
"Basic data handling: RegexSub": RegexSub,
@@ -232,11 +232,11 @@ NODE_CLASS_MAPPINGS = {
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: RegexSearchGroupsDataList": "search groups (data list)",
"Basic data handling: RegexSearchGroupsList": "search groups (LIST)",
"Basic data handling: RegexGroupDict": "search named groups",
"Basic data handling: RegexFindallDataList": "find all (data list)",
"Basic data handling: RegexFindallList": "find all (LIST)",
"Basic data handling: RegexGroupDict": "search named groups",
"Basic data handling: RegexSearchGroupsDataList": "search groups (data list)",
"Basic data handling: RegexSearchGroupsList": "search groups (LIST)",
"Basic data handling: RegexSplitDataList": "split (data list)",
"Basic data handling: RegexSplitList": "split (LIST)",
"Basic data handling: RegexSub": "substitute",
+175 -199
View File
@@ -29,36 +29,57 @@ class SetAdd(ComfyNodeABC):
return (result,)
class SetRemove(ComfyNodeABC):
class SetContains(ComfyNodeABC):
"""
Removes an item from a SET.
Checks if a SET contains a specified value.
This node takes a SET and any item as inputs, then returns a new SET
with the item removed and a success indicator. If the item is not present,
the original SET is returned with success set to False.
This node takes a SET and a value as inputs, then returns True if the value
is present in the SET, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set": ("SET", {}),
"item": (IO.ANY, {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = ("SET", IO.BOOLEAN)
RETURN_NAMES = ("set", "success")
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
FUNCTION = "contains"
def remove(self, set: set[Any], item: Any) -> tuple[set[Any], bool]:
result = set.copy()
try:
result.remove(item)
return result, True
except KeyError:
return result, False
def contains(self, set: set[Any], value: Any) -> tuple[bool]:
return (value in set,)
class SetDifference(ComfyNodeABC):
"""
Returns the difference between two SETs.
This node takes two SETs as input and returns a new SET containing
elements in the first SET but not in the second SET.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "difference"
def difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
result = set1.copy()
result.difference_update(set2)
return (result,)
class SetDiscard(ComfyNodeABC):
@@ -88,75 +109,6 @@ class SetDiscard(ComfyNodeABC):
return (result,)
class SetPop(ComfyNodeABC):
"""
Removes and returns an arbitrary item from a SET.
This node takes a SET as input and returns both the new SET
with an arbitrary item removed and the removed item.
When the SET is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set": ("SET", {}),
}
}
RETURN_TYPES = ("SET", IO.ANY)
RETURN_NAMES = ("set", "item")
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
def pop(self, set: set[Any]) -> tuple[set[Any], Any]:
result = set.copy()
try:
item = result.pop()
return result, item
except KeyError:
return result, None
class SetUnion(ComfyNodeABC):
"""
Returns the union of two or more SETs.
This node takes multiple SETs as input and returns a new SET containing
all elements from all the input SETs.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
},
"optional": {
"set3": ("SET", {}),
"set4": ("SET", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "union"
def union(self, set1: set[Any], set2: set[Any], set3=None, set4=None) -> tuple[set[Any]]:
result = set1.copy()
result.update(set2)
if set3 is not None:
result.update(set3)
if set4 is not None:
result.update(set4)
return (result,)
class SetIntersection(ComfyNodeABC):
"""
Returns the intersection of two or more SETs.
@@ -195,12 +147,11 @@ class SetIntersection(ComfyNodeABC):
return (result,)
class SetDifference(ComfyNodeABC):
class SetIsDisjoint(ComfyNodeABC):
"""
Returns the difference between two SETs.
Checks if two SETs have no elements in common.
This node takes two SETs as input and returns a new SET containing
elements in the first SET but not in the second SET.
This node takes two SETs as input and returns True if they have no elements in common.
"""
@classmethod
def INPUT_TYPES(cls):
@@ -211,42 +162,14 @@ class SetDifference(ComfyNodeABC):
}
}
RETURN_TYPES = ("SET",)
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_disjoint",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "difference"
FUNCTION = "is_disjoint"
def difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
result = set1.copy()
result.difference_update(set2)
return (result,)
class SetSymmetricDifference(ComfyNodeABC):
"""
Returns the symmetric difference between two SETs.
This node takes two SETs as input and returns a new SET containing
elements in either SET but not in both.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "symmetric_difference"
def symmetric_difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
result = set1.copy()
result.symmetric_difference_update(set2)
return (result,)
def is_disjoint(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
return (set1.isdisjoint(set2),)
class SetIsSubset(ComfyNodeABC):
@@ -301,57 +224,6 @@ class SetIsSuperset(ComfyNodeABC):
return (set1.issuperset(set2),)
class SetIsDisjoint(ComfyNodeABC):
"""
Checks if two SETs have no elements in common.
This node takes two SETs as input and returns True if they have no elements in common.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("is_disjoint",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "is_disjoint"
def is_disjoint(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
return (set1.isdisjoint(set2),)
class SetContains(ComfyNodeABC):
"""
Checks if a SET contains a specified value.
This node takes a SET and a value as inputs, then returns True if the value
is present in the SET, and False otherwise.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set": ("SET", {}),
"value": (IO.ANY, {}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
def contains(self, set: set[Any], value: Any) -> tuple[bool]:
return (value in set,)
class SetLength(ComfyNodeABC):
"""
Returns the number of items in a SET.
@@ -376,28 +248,132 @@ class SetLength(ComfyNodeABC):
return (len(set),)
class ListToSet(ComfyNodeABC):
class SetPop(ComfyNodeABC):
"""
Converts a LIST into a SET.
Removes and returns an arbitrary item from a SET.
This node takes a LIST input and creates a new SET containing all unique elements
from the LIST, removing any duplicates.
This node takes a SET as input and returns both the new SET
with an arbitrary item removed and the removed item.
When the SET is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"set": ("SET", {}),
}
}
RETURN_TYPES = ("SET", IO.ANY)
RETURN_NAMES = ("set", "item")
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
def pop(self, set: set[Any]) -> tuple[set[Any], Any]:
result = set.copy()
try:
item = result.pop()
return result, item
except KeyError:
return result, None
class SetRemove(ComfyNodeABC):
"""
Removes an item from a SET.
This node takes a SET and any item as inputs, then returns a new SET
with the item removed and a success indicator. If the item is not present,
the original SET is returned with success set to False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set": ("SET", {}),
"item": (IO.ANY, {}),
}
}
RETURN_TYPES = ("SET", IO.BOOLEAN)
RETURN_NAMES = ("set", "success")
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
def remove(self, set: set[Any], item: Any) -> tuple[set[Any], bool]:
result = set.copy()
try:
result.remove(item)
return result, True
except KeyError:
return result, False
class SetSymmetricDifference(ComfyNodeABC):
"""
Returns the symmetric difference between two SETs.
This node takes two SETs as input and returns a new SET containing
elements in either SET but not in both.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
FUNCTION = "symmetric_difference"
def convert(self, list: list[Any]) -> tuple[set[Any]]:
return (set(list),)
def symmetric_difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
result = set1.copy()
result.symmetric_difference_update(set2)
return (result,)
class SetUnion(ComfyNodeABC):
"""
Returns the union of two or more SETs.
This node takes multiple SETs as input and returns a new SET containing
all elements from all the input SETs.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"set1": ("SET", {}),
"set2": ("SET", {}),
},
"optional": {
"set3": ("SET", {}),
"set4": ("SET", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/SET"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "union"
def union(self, set1: set[Any], set2: set[Any], set3=None, set4=None) -> tuple[set[Any]]:
result = set1.copy()
result.update(set2)
if set3 is not None:
result.update(set3)
if set4 is not None:
result.update(set4)
return (result,)
class SetToDataList(ComfyNodeABC):
@@ -453,36 +429,36 @@ class SetToList(ComfyNodeABC):
NODE_CLASS_MAPPINGS = {
"Basic data handling: SetAdd": SetAdd,
"Basic data handling: SetRemove": SetRemove,
"Basic data handling: SetDiscard": SetDiscard,
"Basic data handling: SetPop": SetPop,
"Basic data handling: SetUnion": SetUnion,
"Basic data handling: SetIntersection": SetIntersection,
"Basic data handling: SetContains": SetContains,
"Basic data handling: SetDifference": SetDifference,
"Basic data handling: SetSymmetricDifference": SetSymmetricDifference,
"Basic data handling: SetDiscard": SetDiscard,
"Basic data handling: SetIntersection": SetIntersection,
"Basic data handling: SetIsDisjoint": SetIsDisjoint,
"Basic data handling: SetIsSubset": SetIsSubset,
"Basic data handling: SetIsSuperset": SetIsSuperset,
"Basic data handling: SetIsDisjoint": SetIsDisjoint,
"Basic data handling: SetContains": SetContains,
"Basic data handling: SetLength": SetLength,
"Basic data handling: SetPop": SetPop,
"Basic data handling: SetRemove": SetRemove,
"Basic data handling: SetSymmetricDifference": SetSymmetricDifference,
"Basic data handling: SetUnion": SetUnion,
"Basic data handling: SetToDataList": SetToDataList,
"Basic data handling: SetToList": SetToList,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: SetAdd": "add",
"Basic data handling: SetRemove": "remove",
"Basic data handling: SetDiscard": "discard",
"Basic data handling: SetPop": "pop",
"Basic data handling: SetUnion": "union",
"Basic data handling: SetIntersection": "intersection",
"Basic data handling: SetDifference": "difference",
"Basic data handling: SetSymmetricDifference": "symmetric_difference",
"Basic data handling: SetIsSubset": "is_subset",
"Basic data handling: SetIsSuperset": "is_superset",
"Basic data handling: SetIsDisjoint": "is_disjoint",
"Basic data handling: SetContains": "contains",
"Basic data handling: SetDifference": "difference",
"Basic data handling: SetDiscard": "discard",
"Basic data handling: SetIntersection": "intersection",
"Basic data handling: SetIsDisjoint": "is disjoint",
"Basic data handling: SetIsSubset": "is subset",
"Basic data handling: SetIsSuperset": "is superset",
"Basic data handling: SetLength": "length",
"Basic data handling: SetPop": "pop",
"Basic data handling: SetRemove": "remove",
"Basic data handling: SetSymmetricDifference": "symmetric difference",
"Basic data handling: SetUnion": "union",
"Basic data handling: SetToDataList": "convert to data list",
"Basic data handling: SetToList": "convert to LIST",
}
+228 -227
View File
@@ -314,32 +314,6 @@ class StringFind(ComfyNodeABC):
return (string.find(substring, start, end),)
class StringIn(ComfyNodeABC):
"""
Checks if a string contains a specified substring.
This node implements the 'in' operator for strings. It checks whether the given substring
exists within the input string. Returns True if the substring is found,
otherwise False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"substring": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
def contains(self, string, substring):
return (substring in string,)
class StringFormatMap(ComfyNodeABC):
"""
Formats a string using values from a dictionary.
@@ -373,6 +347,32 @@ class StringFormatMap(ComfyNodeABC):
return (f"Formatting error: {str(e)}",)
class StringIn(ComfyNodeABC):
"""
Checks if a string contains a specified substring.
This node implements the 'in' operator for strings. It checks whether the given substring
exists within the input string. Returns True if the substring is found,
otherwise False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"substring": (IO.STRING, {"default": ""}),
}
}
RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
def contains(self, string, substring):
return (substring in string,)
class StringIsAlnum(ComfyNodeABC):
"""
Checks if all characters in the string are alphanumeric.
@@ -666,6 +666,85 @@ class StringIsUpper(ComfyNodeABC):
return (string.isupper(),)
class StringLength(ComfyNodeABC):
"""
Returns the length of a string.
This node calculates and returns the number of characters in the input string.
It works the same way as Python's built-in len() function for strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, string):
return (len(string),)
class StringDataListJoin(ComfyNodeABC):
"""
Joins strings from a data list with a specified separator.
This node takes a data list of strings and concatenates them, with the specified
separator string between each element. The separator is inserted between the strings,
not at the beginning or end of the result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"strings": (IO.STRING, {"forceInput": True}),
"sep": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "join"
INPUT_IS_LIST = True # The "strings" input accepts a data list
def join(self, sep, strings):
separator = sep[0] # everything comes as a list, so sep is list[str]
return (separator.join(strings),)
class StringListJoin(ComfyNodeABC):
"""
Joins strings from a LIST with a specified separator.
This node takes a data list of strings and concatenates them, with the specified
separator string between each element. The separator is inserted between the strings,
not at the beginning or end of the result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"strings": ("LIST", {"forceInput": True}),
"sep": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "join"
def join(self, sep, strings):
separator = sep[0] # everything comes as a list, so sep is list[str]
return (separator.join(strings),)
class StringLjust(ComfyNodeABC):
"""
Left-aligns the string within a field of a given width.
@@ -697,6 +776,29 @@ class StringLjust(ComfyNodeABC):
return (string.ljust(width, fillchar),)
class StringLower(ComfyNodeABC):
"""
Converts the string to lowercase.
This node returns a copy of the string with all uppercase characters converted to lowercase.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lower"
def lower(self, string):
return (string.lower(),)
class StringLstrip(ComfyNodeABC):
"""
Removes leading characters from the string.
@@ -777,6 +879,101 @@ class StringRemovesuffix(ComfyNodeABC):
return (string.removesuffix(suffix),)
class StringReplace(ComfyNodeABC):
"""
Replaces occurrences of a substring with another substring.
This node returns a copy of the string with all occurrences of substring
old replaced by new. If the optional argument count is given, only the
first count occurrences are replaced.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"old": (IO.STRING, {"default": ""}),
"new": (IO.STRING, {"default": ""}),
},
"optional": {
"count": (IO.INT, {"default": -1, "min": -1}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "replace"
def replace(self, string, old, new, count=-1):
return (string.replace(old, new, count),)
class StringRfind(ComfyNodeABC):
"""
Finds the highest index of the substring in the string.
This node searches for the last occurrence of the specified substring within the input string.
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
Optional start and end parameters allow you to limit the search to a specific portion of the string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"substring": (IO.STRING, {"default": ""}),
},
"optional": {
"start": (IO.INT, {"default": 0, "min": 0}),
"end": (IO.INT, {"default": 0, "min": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rfind"
def rfind(self, string, substring, start=0, end=0):
# If end is 0 or negative, search to the end of the string
if end <= 0:
end = len(string)
return (string.rfind(substring, start, end),)
class StringRjust(ComfyNodeABC):
"""
Right-aligns the string within a field of a given width.
This node returns a string right-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"width": (IO.INT, {"default": 10, "min": 0}),
},
"optional": {
"fillchar": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rjust"
def rjust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.rjust(width, fillchar),)
class StringRsplitDataList(ComfyNodeABC):
"""
Splits the string at the specified separator from the right.
@@ -842,179 +1039,6 @@ class StringRsplitList(ComfyNodeABC):
return (string.rsplit(sep, maxsplit),)
class StringLower(ComfyNodeABC):
"""
Converts the string to lowercase.
This node returns a copy of the string with all uppercase characters converted to lowercase.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lower"
def lower(self, string):
return (string.lower(),)
class StringReplace(ComfyNodeABC):
"""
Replaces occurrences of a substring with another substring.
This node returns a copy of the string with all occurrences of substring
old replaced by new. If the optional argument count is given, only the
first count occurrences are replaced.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"old": (IO.STRING, {"default": ""}),
"new": (IO.STRING, {"default": ""}),
},
"optional": {
"count": (IO.INT, {"default": -1, "min": -1}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "replace"
def replace(self, string, old, new, count=-1):
return (string.replace(old, new, count),)
class StringRjust(ComfyNodeABC):
"""
Right-aligns the string within a field of a given width.
This node returns a string right-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"width": (IO.INT, {"default": 10, "min": 0}),
},
"optional": {
"fillchar": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rjust"
def rjust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.rjust(width, fillchar),)
class StringDataListJoin(ComfyNodeABC):
"""
Joins strings from a data list with a specified separator.
This node takes a data list of strings and concatenates them, with the specified
separator string between each element. The separator is inserted between the strings,
not at the beginning or end of the result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"strings": (IO.STRING, {"forceInput": True}),
"sep": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "join"
INPUT_IS_LIST = True # The "strings" input accepts a data list
def join(self, sep, strings):
separator = sep[0] # everything comes as a list, so sep is list[str]
return (separator.join(strings),)
class StringListJoin(ComfyNodeABC):
"""
Joins strings from a LIST with a specified separator.
This node takes a data list of strings and concatenates them, with the specified
separator string between each element. The separator is inserted between the strings,
not at the beginning or end of the result.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"strings": ("LIST", {"forceInput": True}),
"sep": (IO.STRING, {"default": " "}),
}
}
RETURN_TYPES = (IO.STRING,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "join"
def join(self, sep, strings):
separator = sep[0] # everything comes as a list, so sep is list[str]
return (separator.join(strings),)
class StringRfind(ComfyNodeABC):
"""
Finds the highest index of the substring in the string.
This node searches for the last occurrence of the specified substring within the input string.
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
Optional start and end parameters allow you to limit the search to a specific portion of the string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
"substring": (IO.STRING, {"default": ""}),
},
"optional": {
"start": (IO.INT, {"default": 0, "min": 0}),
"end": (IO.INT, {"default": 0, "min": 0}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rfind"
def rfind(self, string, substring, start=0, end=0):
# If end is 0 or negative, search to the end of the string
if end <= 0:
end = len(string)
return (string.rfind(substring, start, end),)
class StringRstrip(ComfyNodeABC):
"""
Removes trailing characters from the string.
@@ -1340,30 +1364,6 @@ class StringZfill(ComfyNodeABC):
return (string.zfill(width),)
class StringLength(ComfyNodeABC):
"""
Returns the length of a string.
This node calculates and returns the number of characters in the input string.
It works the same way as Python's built-in len() function for strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": (IO.STRING, {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = (IO.INT,)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
def length(self, string):
return (len(string),)
NODE_CLASS_MAPPINGS = {
"Basic data handling: StringCapitalize": StringCapitalize,
"Basic data handling: StringCasefold": StringCasefold,
@@ -1372,10 +1372,11 @@ NODE_CLASS_MAPPINGS = {
"Basic data handling: StringCount": StringCount,
"Basic data handling: StringDecode": StringDecode,
"Basic data handling: StringEncode": StringEncode,
"Basic data handling: StringExpandtabs": StringExpandtabs,
"Basic data handling: StringEndswith": StringEndswith,
"Basic data handling: StringExpandtabs": StringExpandtabs,
"Basic data handling: StringFind": StringFind,
"Basic data handling: StringFormatMap": StringFormatMap,
"Basic data handling: StringIn": StringIn,
"Basic data handling: StringIsAlnum": StringIsAlnum,
"Basic data handling: StringIsAlpha": StringIsAlpha,
"Basic data handling: StringIsAscii": StringIsAscii,
@@ -1388,6 +1389,7 @@ NODE_CLASS_MAPPINGS = {
"Basic data handling: StringIsSpace": StringIsSpace,
"Basic data handling: StringIsTitle": StringIsTitle,
"Basic data handling: StringIsUpper": StringIsUpper,
"Basic data handling: StringLength": StringLength,
"Basic data handling: StringDataListJoin": StringDataListJoin,
"Basic data handling: StringListJoin": StringListJoin,
"Basic data handling: StringLjust": StringLjust,
@@ -1411,7 +1413,6 @@ NODE_CLASS_MAPPINGS = {
"Basic data handling: StringTitle": StringTitle,
"Basic data handling: StringUpper": StringUpper,
"Basic data handling: StringZfill": StringZfill,
"Basic data handling: StringLength": StringLength,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -1422,8 +1423,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: StringCount": "count",
"Basic data handling: StringDecode": "decode",
"Basic data handling: StringEncode": "encode",
"Basic data handling: StringExpandtabs": "expandtabs",
"Basic data handling: StringEndswith": "endswith",
"Basic data handling: StringExpandtabs": "expandtabs",
"Basic data handling: StringFind": "find",
"Basic data handling: StringFormatMap": "format_map",
"Basic data handling: StringIn": "in (contains)",
@@ -1439,6 +1440,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: StringIsSpace": "isspace",
"Basic data handling: StringIsTitle": "istitle",
"Basic data handling: StringIsUpper": "isupper",
"Basic data handling: StringLength": "length",
"Basic data handling: StringDataListJoin": "join (from data list)",
"Basic data handling: StringListJoin": "join (from LIST)",
"Basic data handling: StringLjust": "ljust",
@@ -1462,5 +1464,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: StringTitle": "title",
"Basic data handling: StringUpper": "upper",
"Basic data handling: StringZfill": "zfill",
"Basic data handling: StringLength": "length",
}
+1 -16
View File
@@ -1,7 +1,6 @@
import pytest
from src.basic_data_handling.casting_nodes import (CastToString, CastToInt, CastToFloat, CastToBoolean,
CastToList, CastDataListToList, CastToSet, CastDataListToSet,
CastToDict)
CastToList, CastToSet, CastToDict)
def test_cast_to_string():
@@ -49,13 +48,6 @@ def test_cast_to_list():
assert node.convert_to_list(None) == ([None],)
def test_cast_data_list_to_list():
node = CastDataListToList()
assert node.convert(list=[1, 2, 3]) == ([1, 2, 3],)
assert node.convert(list=[]) == ([],)
assert node.convert(list=["a", "b", "c"]) == (["a", "b", "c"],)
def test_cast_to_set():
node = CastToSet()
assert node.convert_to_set(123) == ({123},)
@@ -64,13 +56,6 @@ def test_cast_to_set():
assert node.convert_to_set(None) == ({None},)
def test_cast_data_list_to_set():
node = CastDataListToSet()
assert node.convert(list=[1, 2, 3, 3]) == ({1, 2, 3},)
assert node.convert(list=["a", "b", "b"]) == ({"a", "b"},)
assert node.convert(list=[]) == (set(),)
def test_cast_to_dict():
node = CastToDict()
assert node.convert_to_dict({"key": "value"}) == ({"key": "value"},)
-14
View File
@@ -5,12 +5,10 @@ from src.basic_data_handling.data_list_nodes import (
DataListInsert,
DataListRemove,
DataListPop,
DataListClear,
DataListIndex,
DataListCount,
DataListSort,
DataListReverse,
DataListCopy,
DataListLength,
DataListSlice,
DataListGetItem,
@@ -59,12 +57,6 @@ def test_pop():
assert node.pop(list=[], index=[0]) == ([], None) # Pop from empty list
def test_clear():
node = DataListClear()
assert node.clear(list=[1, 2, 3]) == ([],)
assert node.clear(list=[]) == ([],)
def test_index():
node = DataListIndex()
assert node.list_index(list=["a", "b", "c"], value=["b"]) == (1,)
@@ -92,12 +84,6 @@ def test_reverse():
assert node.reverse(list=[]) == ([],)
def test_copy():
node = DataListCopy()
assert node.copy(list=[1, 2, 3]) == ([1, 2, 3],)
assert node.copy(list=[]) == ([],)
def test_length():
node = DataListLength()
assert node.length(list=[1, 2, 3]) == (3,)
-28
View File
@@ -1,15 +1,12 @@
#import pytest
from src.basic_data_handling.dict_nodes import (
DictCreate,
DictCreateFromItems,
DictGet,
DictSet,
DictKeys,
DictValues,
DictItems,
DictContainsKey,
DictClear,
DictCopy,
DictFromKeys,
DictPop,
DictPopItem,
@@ -25,7 +22,6 @@ from src.basic_data_handling.dict_nodes import (
DictInvert,
DictCreateFromLists,
DictCompare,
AnyToDict,
)
def test_dict_create():
@@ -33,12 +29,6 @@ def test_dict_create():
assert node.create() == ({},) # Creates an empty dictionary
def test_dict_create_from_items():
node = DictCreateFromItems()
items = [("key1", "value1"), ("key2", "value2")]
assert node.create_from_items(items) == ({"key1": "value1", "key2": "value2"},)
def test_dict_get():
node = DictGet()
my_dict = {"key1": "value1", "key2": "value2"}
@@ -77,18 +67,6 @@ def test_dict_contains_key():
assert node.contains_key(my_dict, "key2") == (False,)
def test_dict_clear():
node = DictClear()
my_dict = {"key1": "value1"}
assert node.clear(my_dict) == ({},)
def test_dict_copy():
node = DictCopy()
my_dict = {"key1": "value1"}
assert node.copy(my_dict) == ({"key1": "value1"},)
def test_dict_from_keys():
node = DictFromKeys()
keys = ["key1", "key2"]
@@ -185,9 +163,3 @@ def test_dict_compare():
dict1 = {"key1": "value1"}
dict2 = {"key1": "value1"}
assert node.compare(dict1, dict2) == (True, [], [], [])
def test_any_to_dict():
node = AnyToDict()
my_dict = {"key1": "value1"}
assert node.convert(my_dict) == ({"key1": "value1"},)
-7
View File
@@ -5,7 +5,6 @@ from src.basic_data_handling.list_nodes import (
ListInsert,
ListRemove,
ListPop,
ListClear,
ListIndex,
ListCount,
ListSort,
@@ -55,12 +54,6 @@ def test_list_pop():
assert node.pop([], 0) == ([], None) # Empty list pop
def test_list_clear():
node = ListClear()
assert node.clear([1, 2, 3]) == ([],)
assert node.clear([]) == ([],)
def test_list_index():
node = ListIndex()
assert node.index([1, 2, 3, 2], 2) == (1,)
+6 -6
View File
@@ -1,15 +1,15 @@
#import pytest
from src.basic_data_handling.regex_nodes import (
RegexSearchGroups,
RegexSearchGroupsDataList,
RegexGroupDict,
RegexFindall,
RegexSplit,
RegexFindallDataList,
RegexSplitDataList,
RegexSub,
RegexTest
)
def test_regex_search_groups():
node = RegexSearchGroups()
node = RegexSearchGroupsDataList()
assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],)
assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],)
assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match
@@ -26,7 +26,7 @@ def test_regex_group_dict():
def test_regex_findall():
node = RegexFindall()
node = RegexFindallDataList()
assert node.findall(r"\d+", "abc 123 def 456") == (["123", "456"],)
assert node.findall(r"\b[a-zA-Z]+\b", "Multiple words here") == (["Multiple", "words", "here"],)
assert node.findall(r"nonexistent", "no match") == ([],) # No match
@@ -34,7 +34,7 @@ def test_regex_findall():
def test_regex_split():
node = RegexSplit()
node = RegexSplitDataList()
pattern = r",\s*"
string = "one, two, three"
assert node.split(pattern, string) == (["one", "two", "three"],)
-14
View File
@@ -4,7 +4,6 @@ from src.basic_data_handling.set_nodes import (
SetRemove,
SetDiscard,
SetPop,
SetClear,
SetUnion,
SetIntersection,
SetDifference,
@@ -14,7 +13,6 @@ from src.basic_data_handling.set_nodes import (
SetIsDisjoint,
SetContains,
SetLength,
ListToSet,
SetToList
)
@@ -47,12 +45,6 @@ def test_set_pop():
assert node.pop(empty_set) == (set(), None) # Handle empty set
def test_set_clear():
node = SetClear()
assert node.clear({1, 2, 3}) == (set(),)
assert node.clear(set()) == (set(),) # Already empty set remains empty
def test_set_union():
node = SetUnion()
assert node.union({1, 2}, {3, 4}) == ({1, 2, 3, 4},)
@@ -111,12 +103,6 @@ def test_set_length():
assert node.length(set()) == (0,) # Empty set
def test_list_to_set():
node = ListToSet()
assert node.convert([1, 2, 3, 2]) == ({1, 2, 3},)
assert node.convert([]) == (set(),) # Empty list
def test_set_to_list():
node = SetToList()
result = node.convert({1, 2, 3})
+46 -10
View File
@@ -6,13 +6,14 @@ from src.basic_data_handling.string_nodes import (
StringCenter,
StringConcat,
StringCount,
StringDataListJoin,
StringDecode,
StringEncode,
StringEndswith,
StringExpandtabs,
StringFind,
StringIn,
StringFormatMap,
StringIn,
StringIsAlnum,
StringIsAlpha,
StringIsAscii,
@@ -25,7 +26,8 @@ from src.basic_data_handling.string_nodes import (
StringIsSpace,
StringIsTitle,
StringIsUpper,
StringJoin,
StringLength,
StringListJoin,
StringLjust,
StringLower,
StringLstrip,
@@ -34,10 +36,13 @@ from src.basic_data_handling.string_nodes import (
StringReplace,
StringRfind,
StringRjust,
StringRsplit,
StringRsplitDataList,
StringRsplitList,
StringRstrip,
StringSplit,
StringSplitlines,
StringSplitDataList,
StringSplitList,
StringSplitlinesDataList,
StringSplitlinesList,
StringStartswith,
StringStrip,
StringSwapcase,
@@ -211,12 +216,18 @@ def test_isupper():
assert node.isupper("") == (False,) # Empty string
def test_join():
node = StringJoin()
node = StringDataListJoin()
# join expects a data list as input, so we need to simulate this
assert node.join([", "], ["apple", "banana", "cherry"]) == ("apple, banana, cherry",)
assert node.join([""], ["a", "b", "c"]) == ("abc",)
assert node.join(["-"], []) == ("",) # Empty list
# Test ListJoin variant
node_list = StringListJoin()
assert node_list.join([", "], ["apple", "banana", "cherry"]) == ("apple, banana, cherry",)
assert node_list.join([""], ["a", "b", "c"]) == ("abc",)
assert node_list.join(["-"], []) == ("",) # Empty list
def test_ljust():
node = StringLjust()
assert node.ljust("test", 10) == ("test ",)
@@ -269,11 +280,17 @@ def test_rjust():
assert node.rjust("test", 2) == ("test",) # Width smaller than string length
def test_rsplit():
node = StringRsplit()
node = StringRsplitDataList()
assert node.rsplit("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
assert node.rsplit("apple,banana,cherry", ",", 1) == (["apple,banana", "cherry"],)
assert node.rsplit(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
# Test List variant
node_list = StringRsplitList()
assert node_list.rsplit("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
assert node_list.rsplit("apple,banana,cherry", ",", 1) == (["apple,banana", "cherry"],)
assert node_list.rsplit(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
def test_rstrip():
node = StringRstrip()
assert node.rstrip("hello ") == ("hello",)
@@ -282,18 +299,31 @@ def test_rstrip():
assert node.rstrip("") == ("",) # Empty string
def test_split():
node = StringSplit()
node = StringSplitDataList()
assert node.split("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
assert node.split("apple,banana,cherry", ",", 1) == (["apple", "banana,cherry"],)
assert node.split(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
# Test List variant
node_list = StringSplitList()
assert node_list.split("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
assert node_list.split("apple,banana,cherry", ",", 1) == (["apple", "banana,cherry"],)
assert node_list.split(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
def test_splitlines():
node = StringSplitlines()
node = StringSplitlinesDataList()
assert node.splitlines("line1\nline2\nline3") == (["line1", "line2", "line3"],)
assert node.splitlines("line1\r\nline2\rline3") == (["line1", "line2", "line3"],)
assert node.splitlines("line1\nline2\nline3", "True") == (["line1\n", "line2\n", "line3"],) # Keep line endings
assert node.splitlines("line1\nline2\nline3", True) == (["line1\n", "line2\n", "line3"],) # Keep line endings
assert node.splitlines("no newlines") == (["no newlines"],)
# Test List variant
node_list = StringSplitlinesList()
assert node_list.splitlines("line1\nline2\nline3") == (["line1", "line2", "line3"],)
assert node_list.splitlines("line1\r\nline2\rline3") == (["line1", "line2", "line3"],)
assert node_list.splitlines("line1\nline2\nline3", True) == (["line1\n", "line2\n", "line3"],) # Keep line endings
assert node_list.splitlines("no newlines") == (["no newlines"],)
def test_startswith():
node = StringStartswith()
assert node.startswith("function startswith", "function") == (True,)
@@ -333,3 +363,9 @@ def test_zfill():
assert node.zfill("-123", 5) == ("-0123",) # Preserves sign
assert node.zfill("123", 2) == ("123",) # Width smaller than string length
assert node.zfill("", 3) == ("000",) # Empty string
def test_length():
node = StringLength()
assert node.length("hello") == (5,)
assert node.length("") == (0,) # Empty string
assert node.length("hello world") == (11,) # String with spaces
-1
View File
@@ -1 +0,0 @@
console.log(app);