Skip to content

Commit f9b9a52

Browse files
authored
Merge pull request #12 from StableLlama/collections_enhancement
Collections enhancement
2 parents 1d72f3e + e856773 commit f9b9a52

File tree

10 files changed

+542
-50
lines changed

10 files changed

+542
-50
lines changed

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "basic_data_handling"
7-
version = "0.3.3"
7+
version = "0.3.4"
88
description = """NOTE: Still in development! Expect breaking changes!
99
Basic Python functions for manipulating data that every programmer is used to.
1010
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
@@ -52,9 +52,9 @@ pythonpath = [
5252
testpaths = [
5353
"tests",
5454
]
55-
#python_files = ["test_*.py"]
55+
python_files = ["test_*.py"]
5656
#python_files = ["conftest.py", "test_boolean_nodes.py"]
57-
python_files = ["test_boolean_nodes.py"]
57+
#python_files = ["test_boolean_nodes.py"]
5858

5959
[tool.mypy]
6060
files = "."

src/basic_data_handling/_dynamic_input.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def __contains__(self, key):
2424

2525
def __getitem__(self, key):
2626
# Dynamically return the value for keys matching a `prefix<number>` pattern
27-
print(f'_ dynamic prefixes: {self._dynamic_prefixes}; get key: {key}')
2827
for prefix, value in self._dynamic_prefixes.items():
2928
if key.startswith(prefix) and key[len(prefix):].isdigit():
3029
return value

src/basic_data_handling/data_list_nodes.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,33 @@ def select(self, **kwargs: list[Any]) -> tuple[list[Any]]:
348348
return result_true, result_false
349349

350350

351+
class DataListFirst(ComfyNodeABC):
352+
"""
353+
Returns the first element in a list.
354+
355+
This node takes a list as input and returns the first element of the list.
356+
If the list is empty, it returns None.
357+
"""
358+
@classmethod
359+
def INPUT_TYPES(cls):
360+
return {
361+
"required": {
362+
"list": (IO.ANY, {}),
363+
}
364+
}
365+
366+
RETURN_TYPES = (IO.ANY,)
367+
RETURN_NAMES = ("first_element",)
368+
CATEGORY = "Basic/Data List"
369+
DESCRIPTION = cleandoc(__doc__ or "")
370+
FUNCTION = "get_first_element"
371+
INPUT_IS_LIST = True
372+
373+
def get_first_element(self, **kwargs: list[Any]) -> tuple[Any]:
374+
input_list = kwargs.get('list', [])
375+
return (input_list[0] if input_list else None,)
376+
377+
351378
class DataListGetItem(ComfyNodeABC):
352379
"""
353380
Retrieves an item at a specified position in a list.
@@ -453,6 +480,33 @@ def insert(self, **kwargs: list[Any]) -> tuple[list[Any]]:
453480
return (result,)
454481

455482

483+
class DataListLast(ComfyNodeABC):
484+
"""
485+
Returns the last element in a list.
486+
487+
This node takes a list as input and returns the last element of the list.
488+
If the list is empty, it returns None.
489+
"""
490+
@classmethod
491+
def INPUT_TYPES(cls):
492+
return {
493+
"required": {
494+
"list": (IO.ANY, {}),
495+
}
496+
}
497+
498+
RETURN_TYPES = (IO.ANY,)
499+
RETURN_NAMES = ("last_element",)
500+
CATEGORY = "Basic/Data List"
501+
DESCRIPTION = cleandoc(__doc__ or "")
502+
FUNCTION = "get_last_element"
503+
INPUT_IS_LIST = True
504+
505+
def get_last_element(self, **kwargs: list[Any]) -> tuple[Any]:
506+
input_list = kwargs.get('list', [])
507+
return (input_list[-1] if input_list else None,)
508+
509+
456510
class DataListLength(ComfyNodeABC):
457511
"""
458512
Counts the number of items in a list.
@@ -588,6 +642,38 @@ def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
588642
return result, None
589643

590644

645+
class DataListPopRandom(ComfyNodeABC):
646+
"""
647+
Removes and returns a random element from a list.
648+
649+
This node takes a list as input and returns the list with the random element removed
650+
and the removed element itself. If the list is empty, it returns None for the element.
651+
"""
652+
@classmethod
653+
def INPUT_TYPES(cls):
654+
return {
655+
"required": {
656+
"list": (IO.ANY, {}),
657+
}
658+
}
659+
660+
RETURN_TYPES = (IO.ANY, IO.ANY)
661+
RETURN_NAMES = ("list", "item")
662+
CATEGORY = "Basic/Data List"
663+
DESCRIPTION = cleandoc(__doc__ or "")
664+
FUNCTION = "pop_random_element"
665+
INPUT_IS_LIST = True
666+
OUTPUT_IS_LIST = (True, False)
667+
668+
def pop_random_element(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
669+
from random import randrange
670+
input_list = kwargs.get('list', []).copy()
671+
if input_list:
672+
random_element = input_list.pop(randrange(len(input_list)))
673+
return input_list, random_element
674+
return input_list, None
675+
676+
591677
class DataListRemove(ComfyNodeABC):
592678
"""
593679
Removes the first occurrence of a specified value from a list.
@@ -864,13 +950,16 @@ def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]:
864950
"Basic data handling: DataListExtend": DataListExtend,
865951
"Basic data handling: DataListFilter": DataListFilter,
866952
"Basic data handling: DataListFilterSelect": DataListFilterSelect,
953+
"Basic data handling: DataListFirst": DataListFirst,
867954
"Basic data handling: DataListGetItem": DataListGetItem,
868955
"Basic data handling: DataListIndex": DataListIndex,
869956
"Basic data handling: DataListInsert": DataListInsert,
957+
"Basic data handling: DataListLast": DataListLast,
870958
"Basic data handling: DataListLength": DataListLength,
871959
"Basic data handling: DataListMax": DataListMax,
872960
"Basic data handling: DataListMin": DataListMin,
873961
"Basic data handling: DataListPop": DataListPop,
962+
"Basic data handling: DataListPopRandom": DataListPopRandom,
874963
"Basic data handling: DataListRemove": DataListRemove,
875964
"Basic data handling: DataListReverse": DataListReverse,
876965
"Basic data handling: DataListSetItem": DataListSetItem,
@@ -893,13 +982,16 @@ def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]:
893982
"Basic data handling: DataListExtend": "extend",
894983
"Basic data handling: DataListFilter": "filter",
895984
"Basic data handling: DataListFilterSelect": "filter select",
985+
"Basic data handling: DataListFirst": "first",
896986
"Basic data handling: DataListGetItem": "get item",
897987
"Basic data handling: DataListIndex": "index",
898988
"Basic data handling: DataListInsert": "insert",
989+
"Basic data handling: DataListLast": "last",
899990
"Basic data handling: DataListLength": "length",
900991
"Basic data handling: DataListMax": "max",
901992
"Basic data handling: DataListMin": "min",
902993
"Basic data handling: DataListPop": "pop",
994+
"Basic data handling: DataListPopRandom": "pop random",
903995
"Basic data handling: DataListRemove": "remove",
904996
"Basic data handling: DataListReverse": "reverse",
905997
"Basic data handling: DataListSetItem": "set item",

src/basic_data_handling/dict_nodes.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,42 @@ def popitem(self, input_dict: dict) -> tuple[dict, str, Any, bool]:
719719
return result, "", None, False
720720

721721

722+
class DictPopRandom(ComfyNodeABC):
723+
"""
724+
Removes and returns a random key-value pair from a dictionary.
725+
726+
This node takes a dictionary as input, removes a random key-value pair,
727+
and returns the modified dictionary along with the removed key and value.
728+
If the dictionary is empty, it returns empty values.
729+
"""
730+
@classmethod
731+
def INPUT_TYPES(cls):
732+
return {
733+
"required": {
734+
"input_dict": ("DICT", {}),
735+
}
736+
}
737+
738+
RETURN_TYPES = ("DICT", IO.STRING, IO.ANY, IO.BOOLEAN)
739+
RETURN_NAMES = ("dict", "key", "value", "success")
740+
CATEGORY = "Basic/DICT"
741+
DESCRIPTION = cleandoc(__doc__ or "")
742+
FUNCTION = "pop_random"
743+
744+
def pop_random(self, input_dict: dict) -> tuple[dict, str, Any, bool]:
745+
import random
746+
result = input_dict.copy()
747+
try:
748+
if result:
749+
random_key = random.choice(list(result.keys()))
750+
random_value = result.pop(random_key)
751+
return result, random_key, random_value, True
752+
else:
753+
return result, "", None, False
754+
except:
755+
return result, "", None, False
756+
757+
722758
class DictRemove(ComfyNodeABC):
723759
"""
724760
Removes a key-value pair from a dictionary.
@@ -883,6 +919,7 @@ def values(self, input_dict: dict) -> tuple[list]:
883919
"Basic data handling: DictMerge": DictMerge,
884920
"Basic data handling: DictPop": DictPop,
885921
"Basic data handling: DictPopItem": DictPopItem,
922+
"Basic data handling: DictPopRandom": DictPopRandom,
886923
"Basic data handling: DictRemove": DictRemove,
887924
"Basic data handling: DictSet": DictSet,
888925
"Basic data handling: DictSetDefault": DictSetDefault,
@@ -913,7 +950,8 @@ def values(self, input_dict: dict) -> tuple[list]:
913950
"Basic data handling: DictLength": "length",
914951
"Basic data handling: DictMerge": "merge",
915952
"Basic data handling: DictPop": "pop",
916-
"Basic data handling: DictPopItem": "popitem",
953+
"Basic data handling: DictPopItem": "pop item",
954+
"Basic data handling: DictPopRandom": "pop random",
917955
"Basic data handling: DictRemove": "remove",
918956
"Basic data handling: DictSet": "set",
919957
"Basic data handling: DictSetDefault": "setdefault",

src/basic_data_handling/list_nodes.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,31 @@ def extend(self, list1: list[Any], list2: list[Any]) -> tuple[list[Any]]:
248248
return (result,)
249249

250250

251+
class ListFirst(ComfyNodeABC):
252+
"""
253+
Returns the first element in a LIST.
254+
255+
This node takes a LIST as input and returns the first element of the list.
256+
If the LIST is empty, it returns None.
257+
"""
258+
@classmethod
259+
def INPUT_TYPES(cls):
260+
return {
261+
"required": {
262+
"list": ("LIST", {}),
263+
}
264+
}
265+
266+
RETURN_TYPES = (IO.ANY,)
267+
RETURN_NAMES = ("first_element",)
268+
CATEGORY = "Basic/LIST"
269+
DESCRIPTION = cleandoc(__doc__ or "")
270+
FUNCTION = "get_first_element"
271+
272+
def get_first_element(self, list: list[Any]) -> tuple[Any]:
273+
return (list[0] if list else None,)
274+
275+
251276
class ListGetItem(ComfyNodeABC):
252277
"""
253278
Retrieves an item at a specified position in a LIST.
@@ -343,6 +368,31 @@ def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
343368
return (result,)
344369

345370

371+
class ListLast(ComfyNodeABC):
372+
"""
373+
Returns the last element in a LIST.
374+
375+
This node takes a LIST as input and returns the last element of the list.
376+
If the LIST is empty, it returns None.
377+
"""
378+
@classmethod
379+
def INPUT_TYPES(cls):
380+
return {
381+
"required": {
382+
"list": ("LIST", {}),
383+
}
384+
}
385+
386+
RETURN_TYPES = (IO.ANY,)
387+
RETURN_NAMES = ("last_element",)
388+
CATEGORY = "Basic/LIST"
389+
DESCRIPTION = cleandoc(__doc__ or "")
390+
FUNCTION = "get_last_element"
391+
392+
def get_last_element(self, list: list[Any]) -> tuple[Any]:
393+
return (list[-1] if list else None,)
394+
395+
346396
class ListLength(ComfyNodeABC):
347397
"""
348398
Returns the number of items in a LIST.
@@ -466,6 +516,37 @@ def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
466516
return result, None
467517

468518

519+
class ListPopRandom(ComfyNodeABC):
520+
"""
521+
Removes and returns a random element from a LIST.
522+
523+
This node takes a LIST as input and returns the LIST with the random element removed
524+
and the removed element itself. If the LIST is empty, it returns None for the element.
525+
"""
526+
@classmethod
527+
def INPUT_TYPES(cls):
528+
return {
529+
"required": {
530+
"list": ("LIST", {}),
531+
}
532+
}
533+
534+
RETURN_TYPES = ("LIST", IO.ANY)
535+
RETURN_NAMES = ("list", "item")
536+
CATEGORY = "Basic/LIST"
537+
DESCRIPTION = cleandoc(__doc__ or "")
538+
FUNCTION = "pop_random_element"
539+
540+
def pop_random_element(self, list: list[Any]) -> tuple[list[Any], Any]:
541+
import random
542+
result = list.copy()
543+
if result:
544+
random_index = random.randrange(len(result))
545+
random_element = result.pop(random_index)
546+
return result, random_element
547+
return result, None
548+
549+
469550
class ListRemove(ComfyNodeABC):
470551
"""
471552
Removes the first occurrence of a specified value from a LIST.
@@ -680,13 +761,16 @@ def convert(self, list: list[Any]) -> tuple[set[Any]]:
680761
"Basic data handling: ListContains": ListContains,
681762
"Basic data handling: ListCount": ListCount,
682763
"Basic data handling: ListExtend": ListExtend,
764+
"Basic data handling: ListFirst": ListFirst,
683765
"Basic data handling: ListGetItem": ListGetItem,
684766
"Basic data handling: ListIndex": ListIndex,
685767
"Basic data handling: ListInsert": ListInsert,
768+
"Basic data handling: ListLast": ListLast,
686769
"Basic data handling: ListLength": ListLength,
687770
"Basic data handling: ListMax": ListMax,
688771
"Basic data handling: ListMin": ListMin,
689772
"Basic data handling: ListPop": ListPop,
773+
"Basic data handling: ListPopRandom": ListPopRandom,
690774
"Basic data handling: ListRemove": ListRemove,
691775
"Basic data handling: ListReverse": ListReverse,
692776
"Basic data handling: ListSetItem": ListSetItem,
@@ -706,13 +790,16 @@ def convert(self, list: list[Any]) -> tuple[set[Any]]:
706790
"Basic data handling: ListContains": "contains",
707791
"Basic data handling: ListCount": "count",
708792
"Basic data handling: ListExtend": "extend",
793+
"Basic data handling: ListFirst": "first",
709794
"Basic data handling: ListGetItem": "get item",
710795
"Basic data handling: ListIndex": "index",
711796
"Basic data handling: ListInsert": "insert",
797+
"Basic data handling: ListLast": "last",
712798
"Basic data handling: ListLength": "length",
713799
"Basic data handling: ListMax": "max",
714800
"Basic data handling: ListMin": "min",
715801
"Basic data handling: ListPop": "pop",
802+
"Basic data handling: ListPopRandom": "pop random",
716803
"Basic data handling: ListRemove": "remove",
717804
"Basic data handling: ListReverse": "reverse",
718805
"Basic data handling: ListSetItem": "set item",

0 commit comments

Comments
 (0)