Version 0.0.5

Add Python list and dict as LIST and DICT custom data type
This commit is contained in:
StableLlama
2025-05-07 19:38:59 +02:00
parent b23c1bce7b
commit 5d47b3cc9f
6 changed files with 1772 additions and 376 deletions
+63 -4
View File
@@ -22,13 +22,30 @@ These nodes are very lightweight and require no additional dependencies.
- Case conversion: lower, upper, swapcase, title, capitalize
- Validation: isalnum, isalpha, isdigit, isnumeric, etc.
- **LIST**: List manipulation nodes
- Creation: create, create_empty
- **LIST**: Python list manipulation nodes (as a single variable)
- Conversion: convert to data list
- Creation: any to LIST
- Modification: append, extend, insert, remove, pop, clear, set_item
- Access: get_item, slice, index, contains
- Information: length, count
- Operations: sort, reverse, copy, join, zip, flatten
- Unpacking: unpack
- Operations: sort, reverse
- **data list**: ComfyUI list manipulation nodes (for processing individual items)
- Conversion: convert to LIST
- Creation: create_empty
- Modification: append, extend, insert, remove, pop, clear, set_item
- Access: get_item, slice, index, contains
- Information: length, count
- Operations: sort, reverse, copy, zip
- **DICT**: Dictionary manipulation nodes
- Creation: create, from_items, from_lists, fromkeys, any_to_DICT
- Access: get, get_multiple, keys, values, items
- Modification: set, update, setdefault, merge
- Removal: pop, popitem, remove, clear
- Information: length, contains_key
- Operations: copy, filter_by_keys, exclude_keys, invert, compare
- Conversion: get_keys_values
- **INT**: Integer operation nodes
- Basic operations: add, subtract, multiply, divide, modulus, power
@@ -41,6 +58,48 @@ These nodes are very lightweight and require no additional dependencies.
- **BOOLEAN**: Boolean logic nodes
- Logic operations: and, or, not, xor, nand, nor
## Understanding LIST vs. data list
ComfyUI has two different list data types that serve different purposes:
### 1. LIST datatype
- A Python list represented as a **single variable** in the workflow
- Treated as a self-contained object that can be passed between nodes
- Cannot directly connect to nodes that expect individual items
- Best for:
- Working with collections of data as a single unit
- Storing intermediate results that need to be processed as a whole
- Passing collections between different parts of your workflow
- Complex data storage that shouldn't be split apart
### 2. data list
- A native ComfyUI list where **items are processed individually**
- Acts like a standard array/list in most programming contexts
- Items can be accessed individually by compatible nodes
- Supports built-in ComfyUI iteration over each item
- Best for:
- Working directly with multiple items in parallel
- Batch processing scenarios
- When you need to apply the same operation to multiple inputs
- When your operation needs to work with individual items separately
### Converting between types
- Use `convert to data list` node to transform a LIST into a ComfyUI data list
- Use `convert to LIST` node to transform a ComfyUI data list into a LIST object
### When to use which type
- Use **LIST** when you need to:
- Keep items grouped together as a single entity
- Perform operations on the entire collection (sorting, reversing, etc.)
- Pass a collection between workflow sections
- Prevent automatic iteration over items
- Use **data list** when you need to:
- Process each item individually through ComfyUI nodes
- Allow nodes to iterate through your items automatically
- Connect to nodes that expect individual inputs
- Perform batch processing operations
## Tests
This repo contains unit tests written in Pytest in the `tests/` directory. It is recommended to unit test your custom node.
+3 -2
View File
@@ -4,9 +4,10 @@ build-backend = "setuptools.build_meta"
[project]
name = "basic_data_handling"
version = "0.0.4"
version = "0.0.5"
description = """Basic Python functions for manipulating data that every programmer is used to.
Currently supported: BOOLEAN, FLOAT, INT, STRING and data lists."""
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
Additionally supported Python data types as custom data types: DICT, LIST."""
authors = [
{name = "Basic data handling"}
]
+3 -1
View File
@@ -1,7 +1,8 @@
from . import boolean_nodes, dict_nodes, float_nodes, int_nodes, list_nodes, string_nodes
from . import boolean_nodes, data_list_nodes, dict_nodes, float_nodes, int_nodes, list_nodes, string_nodes
NODE_CLASS_MAPPINGS = {}
NODE_CLASS_MAPPINGS.update(boolean_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(data_list_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(dict_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(float_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(int_nodes.NODE_CLASS_MAPPINGS)
@@ -10,6 +11,7 @@ NODE_CLASS_MAPPINGS.update(string_nodes.NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS.update(boolean_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(data_list_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(dict_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(float_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(int_nodes.NODE_DISPLAY_NAME_MAPPINGS)
+722
View File
@@ -0,0 +1,722 @@
from typing import Any
from inspect import cleandoc
class DataListConvertToList:
"""
Converts a ComfyUI data list into a LIST object.
This node takes a data list input (which is typically a list of items with the same type)
and converts it to a LIST object (a Python list as a single variable).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
class DataListAppend:
"""
Adds an item to the end of a list.
This node takes a list and any item as inputs, then returns the modified
list with the new item appended.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list":("*",{}),
"item":("*",{}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "append"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def append(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
item = kwargs.get('item', [])
if len(item) > 0:
result.append(item[0])
return (result,)
class DataListExtend:
"""
Extends a list by appending elements from another list.
This node takes two lists as input and returns a new list that contains
all elements from both lists.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list_a": ("*",{}),
"list_b": ("*",{}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "extend"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def extend(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list_a', []) + kwargs.get('list_b', []),)
class DataListInsert:
"""
Inserts 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.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"index": ("INT", {"default": 0}),
"item": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def insert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0])
return (result,)
class DataListRemove:
"""
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": ("*",),
"value": ("*",),
}
}
RETURN_TYPES = ("*", "BOOLEAN",)
RETURN_NAMES = ("list", "success",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def remove(self, **kwargs: dict[str, list]) -> 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 DataListPop:
"""
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": ("*",),
},
"optional": {
"index": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("*", "*")
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str:
if input_types["index"] not in ("INT"):
return "index must be an INT type"
return True
def pop(self, **kwargs: dict[str, list]) -> 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 DataListClear:
"""
Removes all items from a list.
This node takes a list as input and returns a new empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "clear"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def clear(self, **kwargs: dict[str, list]) -> tuple[[]]:
# Return a new empty list rather than modifying the input
return ([],)
class DataListIndex:
"""
Returns the index of the first occurrence of a value in a list.
This node takes a list and a value as inputs, then returns the index of the first
occurrence of the value. Optional start and end parameters limit the search to a slice
of the list. Returns -1 if the value is not present.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"value": ("*",),
},
"optional": {
"start": ("INT", {"default": 0}),
"end": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("index",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "list_index"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def list_index(self, **kwargs: dict[str, list]) -> tuple[int]:
input_list = kwargs.get('list', [])
value = kwargs.get('value', [None])[0]
start = kwargs.get('start', [0])[0]
end = kwargs.get('end', [-1])[0]
if end == -1:
end = len(input_list)
try:
return (input_list.index(value, start, end),)
except ValueError:
return (-1,)
class DataListCount:
"""
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": ("*",),
"value": ("*",),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("count",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def count(self, **kwargs: dict[str, list]) -> tuple[int]:
value = kwargs.get('value', [None])[0]
return (kwargs.get('list', []).count(value),)
class DataListSort:
"""
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": ("*",),
},
"optional": {
"reverse": (["False", "True"], {"default": "False"}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "sort"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def sort(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
# Convert string to boolean
reverse = kwargs.get('reverse', ["False"])[0] == "True"
result = sorted(kwargs.get('list', []), reverse=reverse)
return (result,)
class DataListReverse:
"""
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": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "reverse"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def reverse(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
result.reverse()
return (result,)
class DataListCopy:
"""
Creates a shallow copy of a list.
This node takes a list as input and returns a new list that is a shallow copy of the original.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "copy"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def copy(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
class DataListLength:
"""
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": ("*",),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def length(self, **kwargs: dict[str, list]) -> tuple[int]:
return (len(kwargs.get('list', [])),)
class DataListSlice:
"""
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": ("*",),
},
"optional": {
"start": ("INT", {"default": 0}),
"stop": ("INT", {"default": -1}),
"step": ("INT", {"default": 1}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "slice"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def slice(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
input_list = kwargs.get('list', [])
start = kwargs.get('start', [0])[0]
stop = kwargs.get('stop', [-1])[0]
step = kwargs.get('step', [1])[0]
if stop == -1:
stop = len(input_list)
return (input_list[start:stop:step],)
class DataListGetItem:
"""
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": {
"input_list": ("*",),
"index": ("INT", {"default": 0}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_item"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def get_item(self, **kwargs: dict[str, list]) -> tuple[Any]:
index = kwargs.get('index', [0])[0]
try:
return (kwargs.get('list', [])[index],)
except IndexError:
return (None,)
class DataListSetItem:
"""
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": ("*",),
"index": ("INT", {"default": 0}),
"value": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set_item"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def set_item(self, **kwargs: dict[str, list]) -> 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 DataListContains:
"""
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": {
"input_list": ("*",),
"value": ("*",),
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def contains(self, **kwargs: dict[str, list]) -> tuple[bool]:
value = kwargs.get('value', [])
if len(value) == 0:
return (False,)
return (value[0] in kwargs.get('list', []),)
class DataListCreateEmpty:
"""
Creates a new empty list.
This node creates and returns an empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_empty"
OUTPUT_IS_LIST = (True,)
def create_empty(self) -> tuple[[]]:
return ([],)
class DataListZip:
"""
Combines multiple lists element-wise.
This node takes multiple data lists as input and returns a new data list
where each item is a list containing the corresponding elements from the input lists.
The length of the output list will be equal to the length of the shortest input list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list1": ("*",),
"list2": ("*",),
},
"optional": {
"list3": ("*",),
"list4": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "zip_lists"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def zip_lists(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
lists = [kwargs.get('list1', []), kwargs.get('list2', [])]
if 'list3' in kwargs:
lists.append(kwargs['list3'])
if 'list4' in kwargs:
lists.append(kwargs['list4'])
# Zip the lists together and convert each tuple to a list
result = [list(item) for item in zip(*lists)]
return (result,)
NODE_CLASS_MAPPINGS = {
"DataListConvertToList": DataListConvertToList,
"DataListAppend": DataListAppend,
"DataListExtend": DataListExtend,
"DataListInsert": DataListInsert,
"DataListRemove": DataListRemove,
"DataListPop": DataListPop,
"DataListClear": DataListClear,
"DataListIndex": DataListIndex,
"DataListCount": DataListCount,
"DataListSort": DataListSort,
"DataListReverse": DataListReverse,
"DataListCopy": DataListCopy,
"DataListLength": DataListLength,
"DataListSlice": DataListSlice,
"DataListGetItem": DataListGetItem,
"DataListSetItem": DataListSetItem,
"DataListContains": DataListContains,
"DataListCreateEmpty": DataListCreateEmpty,
"DataListZip": DataListZip,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DataListConvertToList": "convert to LIST",
"DataListAppend": "append",
"DataListExtend": "extend",
"DataListInsert": "insert",
"DataListRemove": "remove",
"DataListPop": "pop",
"DataListClear": "clear",
"DataListIndex": "index",
"DataListCount": "count",
"DataListSort": "sort",
"DataListReverse": "reverse",
"DataListCopy": "copy",
"DataListLength": "length",
"DataListSlice": "slice",
"DataListGetItem": "get_item",
"DataListSetItem": "set_item",
"DataListContains": "contains",
"DataListCreateEmpty": "create_empty",
"DataListZip": "zip",
}
+784 -13
View File
@@ -1,27 +1,85 @@
from typing import Any
from inspect import cleandoc
class DictGet:
class DictCreate:
"""
Retrieves a value from a dictionary using the specified key.
Creates a new empty dictionary.
If the key is not found, the node will return None.
This node creates and returns a new empty dictionary object.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create"
def create(self) -> tuple[dict]:
return ({},)
class DictCreateFromItems:
"""
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": {
"input_dict": ("DICT",),
"key": ("STRING", {"default": ""}),
"items": ("LIST", {}),
}
}
RETURN_TYPES = ("ANY",)
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:
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError("Each item must be a (key, value) pair")
return (dict(items),)
except Exception as e:
raise ValueError(f"Error creating dictionary from items: {str(e)}")
class DictGet:
"""
Retrieves a value from a dictionary using the specified key.
This node gets the value for the specified key from a dictionary.
If the key is not found and a default value is provided, that default is returned.
Otherwise, it returns None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": ("STRING", {"default": ""}),
},
"optional": {
"default_value": ("*", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("value",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get"
def get(self, input_dict, key):
return (input_dict.get(key, None),)
def get(self, input_dict: dict, key: str, default_value=None) -> tuple[Any]:
return (input_dict.get(key, default_value),)
class DictSet:
"""
@@ -34,9 +92,9 @@ class DictSet:
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT",),
"input_dict": ("DICT", {}),
"key": ("STRING", {"default": ""}),
"value": ("ANY",),
"value": ("*", {}),
}
}
@@ -45,16 +103,729 @@ class DictSet:
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set"
def set(self, input_dict, key, value):
input_dict[key] = value
return (input_dict,)
def set(self, input_dict: dict, key: str, value: Any) -> tuple[dict]:
result = input_dict.copy()
result[key] = value
return (result,)
class DictKeys:
"""
Returns all keys in a dictionary.
This node takes a dictionary and returns a list containing all of its keys.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "keys"
def keys(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.keys()),)
class DictValues:
"""
Returns all values in a dictionary.
This node takes a dictionary and returns a list containing all of its values.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "values"
def values(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.values()),)
class DictItems:
"""
Returns all key-value pairs in a dictionary.
This node takes a dictionary and returns a list of tuples, where each tuple
contains a key-value pair from the dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "items"
def items(self, input_dict: dict) -> tuple[list]:
return (list(input_dict.items()),)
class DictContainsKey:
"""
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": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("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 DictClear:
"""
Removes all items from a dictionary.
This node takes a dictionary as input and returns a new empty dictionary.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "clear"
def clear(self, input_dict: dict) -> tuple[dict]:
return ({},)
class DictCopy:
"""
Creates a shallow copy of a dictionary.
This node takes a dictionary as input and returns a new dictionary that is
a shallow copy of the original.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "copy"
def copy(self, input_dict: dict) -> tuple[dict]:
return (input_dict.copy(),)
class DictFromKeys:
"""
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": ("*", {}),
}
}
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 DictPop:
"""
Removes and returns a key-value pair from a dictionary.
This node takes a dictionary and a key as inputs, removes the specified key
from the dictionary, and returns both the modified dictionary and the value
associated with the key. If the key is not found and a default value is provided,
that default is returned. Otherwise, an error is raised.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": ("STRING", {"default": ""}),
},
"optional": {
"default_value": ("*", {}),
"has_default": (["False", "True"], {"default": "False"}),
}
}
RETURN_TYPES = ("DICT", "*", "BOOLEAN")
RETURN_NAMES = ("dict", "value", "key_found")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
def pop(self, input_dict: dict, key: str, default_value=None, has_default: str = "False") -> tuple[dict, Any, bool]:
result = input_dict.copy()
has_default_bool = (has_default == "True")
try:
if key in result:
value = result.pop(key)
return result, value, True
elif has_default_bool:
return result, default_value, False
else:
raise KeyError(f"Key '{key}' not found in dictionary")
except Exception as e:
raise ValueError(f"Error popping key from dictionary: {str(e)}")
class DictPopItem:
"""
Removes and returns an arbitrary key-value pair from a dictionary.
This node takes a dictionary as input, removes an arbitrary key-value pair,
and returns the modified dictionary along with the removed key and value.
If the dictionary is empty, returns an error.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT", "STRING", "*", "BOOLEAN")
RETURN_NAMES = ("dict", "key", "value", "success")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "popitem"
def popitem(self, input_dict: dict) -> tuple[dict, str, Any, bool]:
result = input_dict.copy()
try:
if result:
key, value = result.popitem()
return result, key, value, True
else:
return result, "", None, False
except Exception as e:
return result, "", None, False
class DictSetDefault:
"""
Returns the value for a key, setting a default if the key doesn't exist.
This node takes a dictionary, a key, and a default value. If the key exists
in the dictionary, the corresponding value is returned. If the key doesn't
exist, the default value is inserted for the key and returned.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_dict": ("DICT", {}),
"key": ("STRING", {"default": ""}),
"default_value": ("*", {}),
}
}
RETURN_TYPES = ("DICT", "*")
RETURN_NAMES = ("dict", "value")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "setdefault"
def setdefault(self, input_dict: dict, key: str, default_value=None) -> tuple[dict, Any]:
result = input_dict.copy()
value = result.setdefault(key, default_value)
return result, value
class DictUpdate:
"""
Updates a dictionary with key-value pairs from another dictionary.
This node takes two dictionaries as inputs and returns a new dictionary that
contains all key-value pairs from both dictionaries. If there are duplicate
keys, the values from the second dictionary take precedence.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"dict1": ("DICT", {}),
"dict2": ("DICT", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "update"
def update(self, dict1: dict, dict2: dict) -> tuple[dict]:
result = dict1.copy()
result.update(dict2)
return (result,)
class DictLength:
"""
Returns the number of key-value pairs in a dictionary.
This node takes a dictionary as input and returns its length (number of items).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"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:
"""
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:
"""
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:
"""
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": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("DICT", "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 AnyToDict:
"""
Converts compatible data structures to a dictionary.
This node attempts to convert an input value to a dictionary. Compatible
inputs include sequences of key-value pairs, mapping objects, and objects
with a to_dict() or as_dict() method.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {}),
}
}
RETURN_TYPES = ("DICT", "BOOLEAN")
RETURN_NAMES = ("dict", "success")
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
def convert(self, input: Any) -> tuple[dict, bool]:
try:
if isinstance(input, dict):
return input.copy(), True
# Try converting from items
if hasattr(input, "items"):
return dict(input.items()), True
# Try converting from sequence of pairs
if hasattr(input, "__iter__") and not isinstance(input, str):
try:
result = dict(input)
return result, True
except (TypeError, ValueError):
pass
# Check for to_dict or as_dict methods
if hasattr(input, "to_dict") and callable(getattr(input, "to_dict")):
return input.to_dict(), True
if hasattr(input, "as_dict") and callable(getattr(input, "as_dict")):
return input.as_dict(), True
# Failed to convert
return {}, False
except Exception:
return {}, False
class DictFilterByKeys:
"""
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:
"""
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:
"""
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_value": ("*", {}),
}
}
RETURN_TYPES = ("LIST",)
RETURN_NAMES = ("values",)
CATEGORY = "Basic/DICT"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_multiple"
def get_multiple(self, input_dict: dict, keys: list, default_value=None) -> tuple[list]:
values = [input_dict.get(key, default_value) for key in keys]
return (values,)
class DictInvert:
"""
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", "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:
"""
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:
"""
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 = ("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
NODE_CLASS_MAPPINGS = {
"DictCreate": DictCreate,
"DictCreateFromItems": DictCreateFromItems,
"DictGet": DictGet,
"DictSet": DictSet,
"DictKeys": DictKeys,
"DictValues": DictValues,
"DictItems": DictItems,
"DictContainsKey": DictContainsKey,
"DictClear": DictClear,
"DictCopy": DictCopy,
"DictFromKeys": DictFromKeys,
"DictPop": DictPop,
"DictPopItem": DictPopItem,
"DictSetDefault": DictSetDefault,
"DictUpdate": DictUpdate,
"DictLength": DictLength,
"DictMerge": DictMerge,
"DictGetKeysValues": DictGetKeysValues,
"DictRemove": DictRemove,
"AnyToDict": AnyToDict,
"DictFilterByKeys": DictFilterByKeys,
"DictExcludeKeys": DictExcludeKeys,
"DictGetMultiple": DictGetMultiple,
"DictInvert": DictInvert,
"DictCreateFromLists": DictCreateFromLists,
"DictCompare": DictCompare,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DictCreate": "create",
"DictCreateFromItems": "from_items",
"DictGet": "get",
"DictSet": "set",
"DictKeys": "keys",
"DictValues": "values",
"DictItems": "items",
"DictContainsKey": "contains_key",
"DictClear": "clear",
"DictCopy": "copy",
"DictFromKeys": "fromkeys",
"DictPop": "pop",
"DictPopItem": "popitem",
"DictSetDefault": "setdefault",
"DictUpdate": "update",
"DictLength": "length",
"DictMerge": "merge",
"DictGetKeysValues": "get_keys_values",
"DictRemove": "remove",
"AnyToDict": "any_to_DICT",
"DictFilterByKeys": "filter_by_keys",
"DictExcludeKeys": "exclude_keys",
"DictGetMultiple": "get_multiple",
"DictInvert": "invert",
"DictCreateFromLists": "from_lists",
"DictCompare": "compare",
}
+197 -356
View File
@@ -1,143 +1,141 @@
from typing import Any
from inspect import cleandoc
class ListAppend:
class ListConvertToDataList:
"""
Adds an item to the end of a list.
Converts a LIST object into a ComfyUI data list.
This node takes a list and any item as inputs, then returns the modified
list with the new item appended.
This node takes a LIST object (Python list as a single variable) and
converts it to a ComfyUI data list, allowing its items to be processed
individually by nodes that accept data lists.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list":("*",{}),
"item":("*",{}),
"required": {
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "append"
INPUT_IS_LIST = True
FUNCTION = "convert"
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, list) -> tuple[list[Any]]:
return (list,)
def append(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
item = kwargs.get('item', [])
if len(item) > 0:
result.append(item[0])
class ListAppend:
"""
Adds an item to the end of a LIST.
This node takes a LIST and any item as inputs, then returns a new LIST
with the item appended to the end.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"item": ("*", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "append"
def append(self, list: list[Any], item: Any) -> tuple[list[Any]]:
result = list.copy()
result.append(item)
return (result,)
class ListExtend:
"""
Extends a list by appending elements from another list.
Extends a LIST by appending elements from another LIST.
This node takes two lists as input and returns a new list that contains
This node takes two LIST objects as input and returns a new LIST that contains
all elements from both lists.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list_a": ("*",{}),
"list_b": ("*",{}),
"required": {
"list1": ("LIST", {}),
"list2": ("LIST", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "extend"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def extend(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list_a', []) + kwargs.get('list_b', []),)
def extend(self, list1: list[Any], list2: list[Any]) -> tuple[list[Any]]:
result = list1.copy()
result.extend(list2)
return (result,)
class ListInsert:
"""
Inserts an item at a specified position in a list.
Inserts 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, 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": ("LIST", {}),
"index": ("INT", {"default": 0}),
"item": ("*",),
"item": ("*", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "insert"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def insert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0])
def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
result = list.copy()
result.insert(index, item)
return (result,)
class ListRemove:
"""
Removes the first occurrence of a specified value from a list.
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.
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": ("*",),
"value": ("*",),
"list": ("LIST", {}),
"value": ("*", {}),
}
}
RETURN_TYPES = ("*", "BOOLEAN",)
RETURN_NAMES = ("list", "success",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST", "BOOLEAN")
RETURN_NAMES = ("list", "success")
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "remove"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def remove(self, **kwargs: dict[str, list]) -> tuple[list[Any], bool]:
result = kwargs.get('list', []).copy()
value = kwargs.get('value', [])
def remove(self, list: list[Any], value: Any) -> tuple[list[Any], bool]:
result = list.copy()
try:
result.remove(value[0])
result.remove(value)
return result, True
except ValueError:
return result, False
@@ -145,41 +143,32 @@ class ListRemove:
class ListPop:
"""
Removes and returns an item at a specified position in a list.
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
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.
When the LIST is empty, the item is None.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"list": ("LIST", {}),
},
"optional": {
"index": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("*", "*")
RETURN_TYPES = ("LIST", "*")
RETURN_NAMES = ("list", "item")
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "pop"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True, False)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
if input_types["index"] not in ("INT"):
return "index must be an INT type"
return True
def pop(self, **kwargs: dict[str, list]) -> tuple[list[Any], Any]:
result = kwargs.get('list', []).copy()
index = kwargs.get('index', [-1])[0]
def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
result = list.copy()
try:
item = result.pop(index)
return result, item
@@ -189,49 +178,41 @@ class ListPop:
class ListClear:
"""
Removes all items from a list.
Removes all items from a LIST.
This node takes a list as input and returns a new empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"list": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "clear"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def clear(self, **kwargs: dict[str, list]) -> tuple[[]]:
# Return a new empty list rather than modifying the input
return ([],)
class ListIndex:
"""
Returns the index of the first occurrence of a value in a list.
This node takes a list and a value as inputs, then returns the index of the first
occurrence of the value. Optional start and end parameters limit the search to a slice
of the list. Returns -1 if the value is not present.
This node takes a LIST as input and returns a new empty LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"value": ("*",),
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "clear"
def clear(self, list: list[Any]) -> tuple[list[Any]]:
return ([],)
class ListIndex:
"""
Returns the index of the first occurrence of a value in a LIST.
This node takes a LIST and a value as inputs, then returns the index of the first
occurrence of the value. Optional start and end parameters limit the search to a slice
of the LIST. Returns -1 if the value is not present.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {}),
"value": ("*", {}),
},
"optional": {
"start": ("INT", {"default": 0}),
@@ -241,202 +222,143 @@ class ListIndex:
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("index",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "list_index"
INPUT_IS_LIST = True
FUNCTION = "index"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def list_index(self, **kwargs: dict[str, list]) -> tuple[int]:
input_list = kwargs.get('list', [])
value = kwargs.get('value', [None])[0]
start = kwargs.get('start', [0])[0]
end = kwargs.get('end', [-1])[0]
def index(self, list: list[Any], value: Any, start: int = 0, end: int = -1) -> tuple[int]:
if end == -1:
end = len(input_list)
end = len(list)
try:
return (input_list.index(value, start, end),)
return (list.index(value, start, end),)
except ValueError:
return (-1,)
class ListCount:
"""
Counts the number of occurrences of a value in a list.
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.
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": ("*",),
"value": ("*",),
"list": ("LIST", {}),
"value": ("*", {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("count",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "count"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def count(self, **kwargs: dict[str, list]) -> tuple[int]:
value = kwargs.get('value', [None])[0]
return (kwargs.get('list', []).count(value),)
def count(self, list: list[Any], value: Any) -> tuple[int]:
return (list.count(value),)
class ListSort:
"""
Sorts the items in a list.
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.
This node takes a LIST as input and returns a new sorted LIST.
Option includes sorting in reverse order.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"list": ("LIST", {}),
},
"optional": {
"reverse": (["False", "True"], {"default": "False"}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "sort"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def sort(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def sort(self, list: list[Any], reverse: str = "False") -> tuple[list[Any]]:
# Convert string to boolean
reverse = kwargs.get('reverse', ["False"])[0] == "True"
reverse_bool = (reverse == "True")
result = sorted(kwargs.get('list', []), reverse=reverse)
return (result,)
# Use sorted to create a new sorted list
try:
result = sorted(list, reverse=reverse_bool)
return (result,)
except TypeError:
# If list contains mixed types that can't be compared, return original list
return (list.copy(),)
class ListReverse:
"""
Reverses the order of items in a list.
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.
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": ("LIST", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "reverse"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def reverse(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
def reverse(self, list: list[Any]) -> tuple[list[Any]]:
result = list.copy()
result.reverse()
return (result,)
class ListCopy:
"""
Creates a shallow copy of a list.
This node takes a list as input and returns a new list that is a shallow copy of the original.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "copy"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def copy(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
class ListLength:
"""
Counts the number of items in a list.
Returns the number of items in a LIST.
This node takes a list as input and returns its length as an integer.
This node takes a LIST as input and returns its length as an integer.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*",),
"list": ("LIST", {}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("length",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "length"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def length(self, **kwargs: dict[str, list]) -> tuple[int]:
return (len(kwargs.get('list', [])),)
def length(self, list: list[Any]) -> tuple[int]:
return (len(list),)
class ListSlice:
"""
Creates a slice of a list.
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.
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": ("LIST", {}),
},
"optional": {
"start": ("INT", {"default": 0}),
@@ -445,210 +367,132 @@ class ListSlice:
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "slice"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def slice(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
input_list = kwargs.get('list', [])
start = kwargs.get('start', [0])[0]
stop = kwargs.get('stop', [-1])[0]
step = kwargs.get('step', [1])[0]
def slice(self, list: list[Any], start: int = 0, stop: int = -1, step: int = 1) -> tuple[list[Any]]:
if stop == -1:
stop = len(input_list)
stop = len(list)
return (input_list[start:stop:step],)
return (list[start:stop:step],)
class ListGetItem:
"""
Retrieves an item at a specified position in a list.
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.
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": {
"input_list": ("*",),
"list": ("LIST", {}),
"index": ("INT", {"default": 0}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("item",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "get_item"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def get_item(self, **kwargs: dict[str, list]) -> tuple[Any]:
index = kwargs.get('index', [0])[0]
def get_item(self, list: list[Any], index: int) -> tuple[Any]:
try:
return (kwargs.get('list', [])[index],)
return (list[index],)
except IndexError:
return (None,)
class ListSetItem:
"""
Sets an item at a specified position in a list.
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
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": ("LIST", {}),
"index": ("INT", {"default": 0}),
"value": ("*",),
"value": ("*", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "set_item"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def set_item(self, **kwargs: dict[str, list]) -> tuple[Any]:
input_list = kwargs.get('list', [])
index = kwargs.get('index', [0])[0]
value = kwargs.get('value', [None])[0]
def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]:
result = list.copy()
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)}")
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
class ListContains:
"""
Checks if a list contains a specified value.
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.
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": {
"input_list": ("*",),
"value": ("*",),
"list": ("LIST", {}),
"value": ("*", {}),
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("contains",)
CATEGORY = "Basic/list"
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "contains"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def contains(self, **kwargs: dict[str, list]) -> tuple[bool]:
value = kwargs.get('value', [])
if len(value) == 0:
return (False,)
return (value[0] in kwargs.get('list', []),)
def contains(self, list: list[Any], value: Any) -> tuple[bool]:
return (value in list,)
class ListCreateEmpty:
class AnyToList:
"""
Creates a new empty list.
Converts any input datatype into a LIST.
This node creates and returns an empty list.
"""
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "create_empty"
OUTPUT_IS_LIST = (True,)
def create_empty(self) -> tuple[[]]:
return ([],)
class ListZip:
"""
Combines multiple lists element-wise.
This node takes multiple data lists as input and returns a new data list
where each item is a list containing the corresponding elements from the input lists.
The length of the output list will be equal to the length of the shortest input list.
This node takes any input value and wraps it in a LIST object,
allowing it to be processed by nodes that expect LIST inputs.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list1": ("*",),
"list2": ("*",),
},
"optional": {
"list3": ("*",),
"list4": ("*",),
"input": ("*", {}),
}
}
RETURN_TYPES = ("*",)
RETURN_NAMES = ("list",)
CATEGORY = "Basic/list"
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "zip_lists"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
FUNCTION = "convert"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def zip_lists(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
lists = [kwargs.get('list1', []), kwargs.get('list2', [])]
if 'list3' in kwargs:
lists.append(kwargs['list3'])
if 'list4' in kwargs:
lists.append(kwargs['list4'])
# Zip the lists together and convert each tuple to a list
result = [list(item) for item in zip(*lists)]
return (result,)
def convert(self, input: Any) -> tuple[list[Any]]:
# Create a new list containing the input value
return ([input],)
NODE_CLASS_MAPPINGS = {
"ListConvertToDataList": ListConvertToDataList,
"ListAppend": ListAppend,
"ListExtend": ListExtend,
"ListInsert": ListInsert,
@@ -659,17 +503,16 @@ NODE_CLASS_MAPPINGS = {
"ListCount": ListCount,
"ListSort": ListSort,
"ListReverse": ListReverse,
"ListCopy": ListCopy,
"ListLength": ListLength,
"ListSlice": ListSlice,
"ListGetItem": ListGetItem,
"ListSetItem": ListSetItem,
"ListContains": ListContains,
"ListCreateEmpty": ListCreateEmpty,
"ListZip": ListZip,
"AnyToList": AnyToList,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ListConvertToDataList": "convert to data list",
"ListAppend": "append",
"ListExtend": "extend",
"ListInsert": "insert",
@@ -680,12 +523,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ListCount": "count",
"ListSort": "sort",
"ListReverse": "reverse",
"ListCopy": "copy",
"ListLength": "length",
"ListSlice": "slice",
"ListGetItem": "get_item",
"ListSetItem": "set_item",
"ListGetItem": "get item",
"ListSetItem": "set item",
"ListContains": "contains",
"ListCreateEmpty": "create_empty",
"ListZip": "zip",
"AnyToList": "any to LIST",
}