From 607093e9cd3da5eab7d4899a158808a3c1eddb07 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sat, 31 Jan 2026 19:05:41 +0100 Subject: [PATCH 1/2] Fix list generation --- src/basic_data_handling/data_list_nodes.py | 24 +++++++-------- src/basic_data_handling/list_nodes.py | 8 ++--- tests/test_data_list_nodes.py | 36 +++++++++++----------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index f7dc76d..7ec7f01 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -41,8 +41,8 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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): diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index b4ae7ae..966cd23 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -39,8 +39,8 @@ def INPUT_TYPES(cls): 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 @@ def INPUT_TYPES(cls): 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): diff --git a/tests/test_data_list_nodes.py b/tests/test_data_list_nodes.py index aa6d9e4..45e1894 100644 --- a/tests/test_data_list_nodes.py +++ b/tests/test_data_list_nodes.py @@ -183,75 +183,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(): From 443e75be09a4cd618033b83d8b189ec44bf15985 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sat, 31 Jan 2026 19:42:33 +0100 Subject: [PATCH 2/2] Add shuffle to Data List and LIST Bump version to 1.3.0 --- README.md | 4 +-- pyproject.toml | 2 +- src/basic_data_handling/data_list_nodes.py | 35 +++++++++++++++++++++ src/basic_data_handling/list_nodes.py | 31 +++++++++++++++++++ tests/test_data_list_nodes.py | 36 ++++++++++++++++++++++ tests/test_list_nodes.py | 36 ++++++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f524b7..6231c6e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index dbb2e4a..8c30f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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: diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index 7ec7f01..833efd9 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -920,6 +920,39 @@ def set_item(self, **kwargs: list[Any]) -> tuple[Any]: 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 @@ def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]: "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 @@ def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]: "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", diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index 966cd23..c2344ea 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -741,6 +741,35 @@ def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]: 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 @@ def convert(self, list: list[Any]) -> tuple[set[Any]]: "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 @@ def convert(self, list: list[Any]) -> tuple[set[Any]]: "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", diff --git a/tests/test_data_list_nodes.py b/tests/test_data_list_nodes.py index 45e1894..09a3eb0 100644 --- a/tests/test_data_list_nodes.py +++ b/tests/test_data_list_nodes.py @@ -29,6 +29,7 @@ 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,) diff --git a/tests/test_list_nodes.py b/tests/test_list_nodes.py index 1da2fbb..018ed71 100644 --- a/tests/test_list_nodes.py +++ b/tests/test_list_nodes.py @@ -26,6 +26,7 @@ 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,)