- Data List and LIST: new "filter empty" node that removes every empty entry
  (`None`, empty STRING, empty LIST/DICT/SET, ...); `0` and `False` are kept.
  An optional boolean switch also treats a STRING consisting only of whitespace
  as empty.
This commit is contained in:
StableLlama
2026-10-16 17:22:16 +02:00
parent 678e48fcf6
commit 26878b18f1
7 changed files with 187 additions and 1 deletions
+39
View File
@@ -0,0 +1,39 @@
"""Small helpers shared by the node modules (this is not a ComfyUI node module)."""
from typing import Any
def as_bool(value: Any) -> bool:
"""Normalize a ComfyUI BOOLEAN widget/input value into a real bool.
ComfyUI hands over real booleans, but legacy combo widgets ("True" / "False"
strings) and nodes with ``INPUT_IS_LIST`` (values wrapped into a list) also
occur, so all of them are accepted here.
"""
if isinstance(value, (list, tuple)):
value = value[0] if value else False
if isinstance(value, str):
return value.strip().lower() in ("true", "1", "yes", "on")
return bool(value)
def is_empty_entry(value: Any, treat_whitespace_as_empty: bool = False) -> bool:
"""Tell whether an entry of a Data List / LIST has to be regarded as empty.
Empty are ``None``, an empty STRING and an empty collection (empty LIST,
DICT, SET, ...). Numbers and booleans are never empty, so ``0`` and ``False``
are kept.
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
well. The whitespace rule applies to STRINGs only; objects without a length
(e.g. plain numbers or 0-dimensional TENSORs) are never empty.
"""
if value is None:
return True
if isinstance(value, str):
return not value.strip() if treat_whitespace_as_empty else not value
try:
return len(value) == 0
except TypeError:
return False
@@ -16,6 +16,7 @@ except:
ComfyNodeABC = object
from ._dynamic_input import ContainsDynamicDict
from ._value_utils import as_bool, is_empty_entry
INT_MAX = 2**15 - 1 # the computer can do more but be nice to the eyes
@@ -501,6 +502,52 @@ class DataListFilter(ComfyNodeABC):
return (result,)
class DataListFilterEmpty(ComfyNodeABC):
"""
Removes every empty entry of a Data List.
This node takes a Data List and returns a new Data List without the entries
that are empty, i.e. ``None``, an empty STRING and an empty collection
(empty LIST, DICT, SET, ...). Numbers and booleans are never empty, so ``0``
and ``False`` are kept.
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
well and is removed, too.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": (IO.ANY, {"tooltip": "The Data List whose empty entries are removed."}),
},
"optional": {
"treat_whitespace_as_empty": (
IO.BOOLEAN,
{
"default": False,
"tooltip": "When enabled, STRINGs that consist only of whitespace (spaces, tabs, line breaks) are removed as well.",
},
),
},
}
RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("list",)
OUTPUT_TOOLTIPS = ("A new Data List without the empty entries.",)
CATEGORY = "Basic/Data List"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_empty"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def filter_empty(self, **kwargs: list[Any]) -> tuple[list[Any]]:
treat_whitespace_as_empty = as_bool(kwargs.get("treat_whitespace_as_empty"))
result = [value for value in (kwargs.get("list") or []) if not is_empty_entry(value, treat_whitespace_as_empty)]
return (result,)
class DataListFilterSelect(ComfyNodeABC):
"""
Filters a Data List using boolean values.
@@ -1334,6 +1381,7 @@ NODE_CLASS_MAPPINGS = {
"Basic data handling: DataListEnumerate": DataListEnumerate,
"Basic data handling: DataListExtend": DataListExtend,
"Basic data handling: DataListFilter": DataListFilter,
"Basic data handling: DataListFilterEmpty": DataListFilterEmpty,
"Basic data handling: DataListFilterSelect": DataListFilterSelect,
"Basic data handling: DataListFirst": DataListFirst,
"Basic data handling: DataListGetItem": DataListGetItem,
@@ -1373,6 +1421,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: DataListEnumerate": "enumerate",
"Basic data handling: DataListExtend": "extend",
"Basic data handling: DataListFilter": "filter",
"Basic data handling: DataListFilterEmpty": "filter empty",
"Basic data handling: DataListFilterSelect": "filter select",
"Basic data handling: DataListFirst": "first",
"Basic data handling: DataListGetItem": "get item",
+47
View File
@@ -17,6 +17,7 @@ except:
ComfyNodeABC = object
from ._dynamic_input import ContainsDynamicDict
from ._value_utils import as_bool, is_empty_entry
INT_MAX = 2**15 - 1 # the computer can do more but be nice to the eyes
@@ -404,6 +405,50 @@ class ListExtend(ComfyNodeABC):
return (result,)
class ListFilterEmpty(ComfyNodeABC):
"""
Removes every empty entry of a LIST.
This node takes a LIST and returns a new LIST without the entries that are
empty, i.e. ``None``, an empty STRING and an empty collection (empty LIST,
DICT, SET, ...). Numbers and booleans are never empty, so ``0`` and ``False``
are kept.
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
well and is removed, too.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("LIST", {"tooltip": "The LIST whose empty entries are removed."}),
},
"optional": {
"treat_whitespace_as_empty": (
IO.BOOLEAN,
{
"default": False,
"tooltip": "When enabled, STRINGs that consist only of whitespace (spaces, tabs, line breaks) are removed as well.",
},
),
},
}
RETURN_TYPES = ("LIST",)
RETURN_NAMES = ("list",)
OUTPUT_TOOLTIPS = ("A new LIST without the empty entries.",)
CATEGORY = "Basic/LIST"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "filter_empty"
def filter_empty(self, list: list[Any], treat_whitespace_as_empty: bool = False) -> tuple[list[Any]]:
list = list if list is not None else []
treat = as_bool(treat_whitespace_as_empty)
return ([value for value in list if not is_empty_entry(value, treat)],)
class ListFirst(ComfyNodeABC):
"""
Returns the first element in a LIST.
@@ -1135,6 +1180,7 @@ NODE_CLASS_MAPPINGS = {
"Basic data handling: ListCount": ListCount,
"Basic data handling: ListEnumerate": ListEnumerate,
"Basic data handling: ListExtend": ListExtend,
"Basic data handling: ListFilterEmpty": ListFilterEmpty,
"Basic data handling: ListFirst": ListFirst,
"Basic data handling: ListGetItem": ListGetItem,
"Basic data handling: ListIndex": ListIndex,
@@ -1171,6 +1217,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: ListCount": "count",
"Basic data handling: ListEnumerate": "enumerate",
"Basic data handling: ListExtend": "extend",
"Basic data handling: ListFilterEmpty": "filter empty",
"Basic data handling: ListFirst": "first",
"Basic data handling: ListGetItem": "get item",
"Basic data handling: ListIndex": "index",