@@ -60,7 +60,7 @@ Mechanisms to direct workflow execution:
|
||||
ComfyUI list manipulation nodes (for processing individual items):
|
||||
|
||||
- **Creation**: create Data List (generic and type-specific versions)
|
||||
- **Modification**: append, extend, insert, set item, remove, pop, pop random
|
||||
- **Modification**: append, extend, insert, set item, shuffle, remove, pop, pop random
|
||||
- **Filtering**: filter, filter select
|
||||
- **Access**: get item, first, last, slice, index, contains
|
||||
- **Information**: length, count
|
||||
@@ -103,7 +103,7 @@ Integer operation nodes:
|
||||
Python list manipulation nodes (as a single variable):
|
||||
|
||||
- **Creation**: create LIST (generic and type-specific versions)
|
||||
- **Modification**: append, extend, insert, remove, pop, pop random, set_item
|
||||
- **Modification**: append, extend, insert, remove, pop, pop random, set_item, shuffle
|
||||
- **Access**: get_item, first, last, slice, index, contains
|
||||
- **Information**: length, count
|
||||
- **Operations**: sort, reverse, min, max
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "basic_data_handling"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = """Basic Python functions for manipulating data that every programmer is used to, lightweight with no additional dependencies.
|
||||
|
||||
Supported data types:
|
||||
|
||||
@@ -41,8 +41,8 @@ class DataListCreate(ComfyNodeABC):
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list]:
|
||||
values = list(kwargs.values())
|
||||
return (values[:-1],)
|
||||
values = list(kwargs.values())[:-1]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListListCreate(ComfyNodeABC):
|
||||
@@ -71,8 +71,8 @@ class DataListListCreate(ComfyNodeABC):
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list]:
|
||||
values = list(kwargs.values())
|
||||
return (values[:-1],)
|
||||
values = list(kwargs.values())[:-1]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListCreateFromBoolean(ComfyNodeABC):
|
||||
@@ -98,8 +98,8 @@ class DataListCreateFromBoolean(ComfyNodeABC):
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list]:
|
||||
values = [bool(value) for value in kwargs.values()]
|
||||
return (values[:-1],)
|
||||
values = [bool(value) for value in list(kwargs.values())[:-1]]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListCreateFromFloat(ComfyNodeABC):
|
||||
@@ -125,8 +125,8 @@ class DataListCreateFromFloat(ComfyNodeABC):
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list]:
|
||||
values = [float(value) for value in kwargs.values()]
|
||||
return (values[:-1],)
|
||||
values = [float(value) for value in list(kwargs.values())[:-1]]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListCreateFromInt(ComfyNodeABC):
|
||||
@@ -152,8 +152,8 @@ class DataListCreateFromInt(ComfyNodeABC):
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list]:
|
||||
values = [int(value) for value in kwargs.values()]
|
||||
return (values[:-1],)
|
||||
values = [int(value) for value in list(kwargs.values())[:-1]]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListCreateFromString(ComfyNodeABC):
|
||||
@@ -179,8 +179,8 @@ class DataListCreateFromString(ComfyNodeABC):
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
values = [str(value) for value in kwargs.values()]
|
||||
return (values[:-1],)
|
||||
values = [str(value) for value in list(kwargs.values())[:-1]]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DataListAll(ComfyNodeABC):
|
||||
@@ -920,6 +920,39 @@ class DataListSetItem(ComfyNodeABC):
|
||||
raise IndexError(f"Index {index} out of range for list of length {len(input_list)}")
|
||||
|
||||
|
||||
class DataListShuffle(ComfyNodeABC):
|
||||
"""
|
||||
Shuffles the items in a list using a seed for reproducibility.
|
||||
|
||||
This node takes a list and a seed as input and returns a new shuffled list.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY, {}),
|
||||
"seed": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
CATEGORY = "Basic/Data List"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "shuffle_list"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def shuffle_list(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
import random
|
||||
input_list = kwargs.get('list', [])
|
||||
seed = kwargs.get('seed', [0])[0]
|
||||
random.seed(seed)
|
||||
result = input_list.copy()
|
||||
random.shuffle(result)
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListSlice(ComfyNodeABC):
|
||||
"""
|
||||
Creates a slice of a list.
|
||||
@@ -1145,6 +1178,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: DataListRemove": DataListRemove,
|
||||
"Basic data handling: DataListReverse": DataListReverse,
|
||||
"Basic data handling: DataListSetItem": DataListSetItem,
|
||||
"Basic data handling: DataListShuffle": DataListShuffle,
|
||||
"Basic data handling: DataListSlice": DataListSlice,
|
||||
"Basic data handling: DataListSort": DataListSort,
|
||||
"Basic data handling: DataListSum": DataListSum,
|
||||
@@ -1183,6 +1217,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: DataListRemove": "remove",
|
||||
"Basic data handling: DataListReverse": "reverse",
|
||||
"Basic data handling: DataListSetItem": "set item",
|
||||
"Basic data handling: DataListShuffle": "shuffle",
|
||||
"Basic data handling: DataListSlice": "slice",
|
||||
"Basic data handling: DataListSort": "sort",
|
||||
"Basic data handling: DataListSum": "sum",
|
||||
|
||||
@@ -39,8 +39,8 @@ class ListCreate(ComfyNodeABC):
|
||||
FUNCTION = "create_list"
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
values = list(kwargs.values())
|
||||
return (values[:-1],)
|
||||
values = list(kwargs.values())[:-1]
|
||||
return (values,)
|
||||
|
||||
|
||||
class ListCreateFromBoolean(ComfyNodeABC):
|
||||
@@ -64,8 +64,8 @@ class ListCreateFromBoolean(ComfyNodeABC):
|
||||
FUNCTION = "create_list"
|
||||
|
||||
def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
values = [bool(value) for value in kwargs.values()]
|
||||
return (values[:-1],)
|
||||
values = [bool(value) for value in list(kwargs.values())[:-1]]
|
||||
return (values,)
|
||||
|
||||
|
||||
class ListCreateFromFloat(ComfyNodeABC):
|
||||
@@ -741,6 +741,35 @@ class ListSetItem(ComfyNodeABC):
|
||||
except IndexError:
|
||||
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
|
||||
|
||||
|
||||
class ListShuffle(ComfyNodeABC):
|
||||
"""
|
||||
Shuffles the items in a list using a seed for reproducibility.
|
||||
|
||||
This node takes a LIST and a seed as input and returns a new shuffled LIST.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"seed": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "shuffle_list"
|
||||
|
||||
def shuffle_list(self, list: list[Any], seed: int) -> tuple[list[Any]]:
|
||||
import random
|
||||
random.seed(seed)
|
||||
result = list.copy()
|
||||
random.shuffle(result)
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListSlice(ComfyNodeABC):
|
||||
"""
|
||||
Creates a slice of a LIST.
|
||||
@@ -910,6 +939,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: ListRemove": ListRemove,
|
||||
"Basic data handling: ListReverse": ListReverse,
|
||||
"Basic data handling: ListSetItem": ListSetItem,
|
||||
"Basic data handling: ListShuffle": ListShuffle,
|
||||
"Basic data handling: ListSlice": ListSlice,
|
||||
"Basic data handling: ListSort": ListSort,
|
||||
"Basic data handling: ListSum": ListSum,
|
||||
@@ -944,6 +974,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: ListRemove": "remove",
|
||||
"Basic data handling: ListReverse": "reverse",
|
||||
"Basic data handling: ListSetItem": "set item",
|
||||
"Basic data handling: ListShuffle": "shuffle",
|
||||
"Basic data handling: ListSlice": "slice",
|
||||
"Basic data handling: ListSort": "sort",
|
||||
"Basic data handling: ListSum": "sum",
|
||||
|
||||
@@ -29,6 +29,7 @@ from src.basic_data_handling.data_list_nodes import (
|
||||
DataListRemove,
|
||||
DataListReverse,
|
||||
DataListSetItem,
|
||||
DataListShuffle,
|
||||
DataListSlice,
|
||||
DataListSort,
|
||||
DataListSum,
|
||||
@@ -128,6 +129,41 @@ def test_set_item():
|
||||
node.set_item(list=[], index=[0], value=["test"]) # Out of range
|
||||
|
||||
|
||||
def test_shuffle():
|
||||
node = DataListShuffle()
|
||||
|
||||
# Test determinism: Same seed should produce the same shuffle
|
||||
original_list = [1, 2, 3, 4, 5]
|
||||
result1 = node.shuffle_list(list=original_list, seed=[42])
|
||||
result2 = node.shuffle_list(list=original_list, seed=[42])
|
||||
assert result1 == result2 # Same seed, same output
|
||||
|
||||
# Verify the output is a permutation of the input
|
||||
assert sorted(result1[0]) == sorted(original_list)
|
||||
assert len(result1[0]) == len(original_list)
|
||||
|
||||
# Test different seeds produce different shuffles (not guaranteed, but likely for this list)
|
||||
result3 = node.shuffle_list(list=original_list, seed=[123])
|
||||
assert result1 != result3 # Different seed, likely different output
|
||||
|
||||
# Test empty list
|
||||
assert node.shuffle_list(list=[], seed=[0]) == ([],)
|
||||
|
||||
# Test single-item list
|
||||
assert node.shuffle_list(list=[42], seed=[99]) == ([42],)
|
||||
|
||||
# Test mixed data types
|
||||
mixed_list = ["apple", 3.14, True, 42]
|
||||
result4 = node.shuffle_list(list=mixed_list, seed=[7])
|
||||
assert sorted(result4[0], key=str) == sorted(mixed_list, key=str) # Sort for comparison since types may not be directly comparable
|
||||
assert len(result4[0]) == len(mixed_list)
|
||||
|
||||
# Test that the original list is not modified (node should return a copy)
|
||||
original_copy = original_list.copy()
|
||||
node.shuffle_list(list=original_list, seed=[1])
|
||||
assert original_list == original_copy # Original should remain unchanged
|
||||
|
||||
|
||||
def test_contains():
|
||||
node = DataListContains()
|
||||
assert node.contains(list=[1, 2, 3], value=[2]) == (True,)
|
||||
@@ -183,75 +219,75 @@ def test_filter_select():
|
||||
def test_create():
|
||||
node = DataListCreate()
|
||||
# Testing with one item
|
||||
assert node.create_list(item_0="test", _dynamic_number=1) == (["test"],)
|
||||
assert node.create_list(item_0="test", item_1="") == (["test"],)
|
||||
|
||||
# Testing with multiple items of different types
|
||||
assert node.create_list(item_0=1, item_1="two", item_2=3.0, _dynamic_number=3) == ([1, "two", 3.0],)
|
||||
assert node.create_list(item_0=1, item_1="two", item_2=3.0, item_3="") == ([1, "two", 3.0],)
|
||||
|
||||
# Testing with empty list (no items)
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0="") == ([],)
|
||||
|
||||
|
||||
def test_create_from_boolean():
|
||||
node = DataListCreateFromBoolean()
|
||||
# Testing with boolean values
|
||||
assert node.create_list(item_0=True, item_1=False, _dynamic_number=2) == ([True, False],)
|
||||
assert node.create_list(item_0=True, item_1=False, item_2=False) == ([True, False],)
|
||||
|
||||
# Testing with boolean-convertible values
|
||||
assert node.create_list(item_0=1, item_1=0, _dynamic_number=2) == ([True, False],)
|
||||
assert node.create_list(item_0=1, item_1=0, item_2="") == ([True, False],)
|
||||
|
||||
# Testing with empty list
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0=0) == ([],)
|
||||
|
||||
|
||||
def test_create_from_float():
|
||||
node = DataListCreateFromFloat()
|
||||
# Testing with float values
|
||||
assert node.create_list(item_0=1.5, item_1=2.5, _dynamic_number=2) == ([1.5, 2.5],)
|
||||
assert node.create_list(item_0=1.5, item_1=2.5, item_2=2) == ([1.5, 2.5],)
|
||||
|
||||
# Testing with float-convertible values
|
||||
assert node.create_list(item_0=1, item_1="2.5", _dynamic_number=2) == ([1.0, 2.5],)
|
||||
assert node.create_list(item_0=1, item_1="2.5", item_2="") == ([1.0, 2.5],)
|
||||
|
||||
# Testing with empty list
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0="") == ([],)
|
||||
|
||||
|
||||
def test_create_from_int():
|
||||
node = DataListCreateFromInt()
|
||||
# Testing with integer values
|
||||
assert node.create_list(item_0=1, item_1=2, _dynamic_number=2) == ([1, 2],)
|
||||
assert node.create_list(item_0=1, item_1=2, item_2="") == ([1, 2],)
|
||||
|
||||
# Testing with int-convertible values
|
||||
assert node.create_list(item_0="1", item_1=2.0, _dynamic_number=2) == ([1, 2],)
|
||||
assert node.create_list(item_0="1", item_1=2.0, item_2="") == ([1, 2],)
|
||||
|
||||
# Testing with empty list
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0="") == ([],)
|
||||
|
||||
|
||||
def test_create_from_string():
|
||||
node = DataListCreateFromString()
|
||||
# Testing with string values
|
||||
assert node.create_list(item_0="hello", item_1="world", _dynamic_number=2) == (["hello", "world"],)
|
||||
assert node.create_list(item_0="hello", item_1="world", item_2="") == (["hello", "world"],)
|
||||
|
||||
# Testing with string-convertible values
|
||||
assert node.create_list(item_0=123, item_1=True, _dynamic_number=2) == (["123", "True"],)
|
||||
assert node.create_list(item_0=123, item_1=True, item_2="") == (["123", "True"],)
|
||||
|
||||
# Testing with empty list
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0="") == ([],)
|
||||
|
||||
|
||||
def test_list_create():
|
||||
node = DataListListCreate()
|
||||
# Testing with string values
|
||||
assert (node.create_list(item_0=["hello", "world"], item_1=["bye", "bye!"], _dynamic_number=2) ==
|
||||
assert (node.create_list(item_0=["hello", "world"], item_1=["bye", "bye!"], item_2="") ==
|
||||
([["hello", "world"], ["bye", "bye!"]],))
|
||||
|
||||
# Testing with mixed values
|
||||
assert (node.create_list(item_0=[123, 456], item_1=[True, False], _dynamic_number=2) ==
|
||||
assert (node.create_list(item_0=[123, 456], item_1=[True, False], item_2="") ==
|
||||
([[123, 456], [True, False]],))
|
||||
|
||||
# Testing with empty list
|
||||
assert node.create_list(_dynamic_number=0) == ([],)
|
||||
assert node.create_list(item_0="") == ([],)
|
||||
|
||||
|
||||
def test_pop_random():
|
||||
|
||||
@@ -26,6 +26,7 @@ from src.basic_data_handling.list_nodes import (
|
||||
ListRemove,
|
||||
ListReverse,
|
||||
ListSetItem,
|
||||
ListShuffle,
|
||||
ListSlice,
|
||||
ListSort,
|
||||
ListSum,
|
||||
@@ -158,6 +159,41 @@ def test_list_set_item():
|
||||
node.set_item([1, 2, 3], 3, 42) == ([1, 2, 3],) # Out of range
|
||||
|
||||
|
||||
def test_shuffle():
|
||||
node = ListShuffle()
|
||||
|
||||
# Test determinism: Same seed should produce the same shuffle
|
||||
original_list = [1, 2, 3, 4, 5]
|
||||
result1 = node.shuffle_list(list=original_list, seed=42)
|
||||
result2 = node.shuffle_list(list=original_list, seed=42)
|
||||
assert result1 == result2 # Same seed, same output
|
||||
|
||||
# Verify the output is a permutation of the input
|
||||
assert sorted(result1[0]) == sorted(original_list)
|
||||
assert len(result1[0]) == len(original_list)
|
||||
|
||||
# Test different seeds produce different shuffles (not guaranteed, but likely for this list)
|
||||
result3 = node.shuffle_list(list=original_list, seed=123)
|
||||
assert result1 != result3 # Different seed, likely different output
|
||||
|
||||
# Test empty list
|
||||
assert node.shuffle_list(list=[], seed=0) == ([],)
|
||||
|
||||
# Test single-item list
|
||||
assert node.shuffle_list(list=[42], seed=99) == ([42],)
|
||||
|
||||
# Test mixed data types
|
||||
mixed_list = ["apple", 3.14, True, 42]
|
||||
result4 = node.shuffle_list(list=mixed_list, seed=7)
|
||||
assert sorted(result4[0], key=str) == sorted(mixed_list, key=str) # Sort for comparison since types may not be directly comparable
|
||||
assert len(result4[0]) == len(mixed_list)
|
||||
|
||||
# Test that the original list is not modified (node should return a copy)
|
||||
original_copy = original_list.copy()
|
||||
node.shuffle_list(list=original_list, seed=1)
|
||||
assert original_list == original_copy # Original should remain unchanged
|
||||
|
||||
|
||||
def test_list_contains():
|
||||
node = ListContains()
|
||||
assert node.contains([1, 2, 3], 2) == (True,)
|
||||
|
||||
Reference in New Issue
Block a user