Add native ComfyUI list output converter

This commit is contained in:
Hopping Mad Games
2026-08-27 08:21:36 -06:00
parent 36a6124ba9
commit e5cccb21a3
6 changed files with 74 additions and 5 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 1.1.0 - 2026-08-27
- Add `List To ComfyUI List`, which exposes an ETK `LIST` through ComfyUI's
native list-mapped output contract without changing the contained items.
- Preserve all nine historical `*FromListIdx` workflow class IDs and behavior.
## 1.0.0 - 2026-08-26
- Extract all historical `*FromListIdx` selectors into one standalone pack.
+9 -2
View File
@@ -1,6 +1,7 @@
# ETK From List Index
A standalone ComfyUI node pack for selecting one item from a list by index.
A standalone ComfyUI node pack for selecting one item from a list by index and
converting an ETK `LIST` value into a native ComfyUI list-mapped output.
The pack preserves the historical workflow class IDs, input socket names,
output order, categories, and selection behavior previously supplied by
@@ -18,13 +19,19 @@ replacement or rewiring.
- `BytesFromListIdx`
- `IntFromListIdx`
- `FloatFromListIdx`
- `ETKListToComfyList` (`List To ComfyUI List`)
`List To ComfyUI List` returns the contained items through ComfyUI's native
list-output contract. A downstream node that does not use `INPUT_IS_LIST` is
therefore executed once for every item, instead of receiving one Python list
object.
## Install
Install the immutable Registry release with Comfy CLI:
```bash
comfy node install etk-from-list-index@1.0.0
comfy node install etk-from-list-index@1.1.0
```
New Registry releases have a short pending security-scan window. During that
+4
View File
@@ -5,6 +5,7 @@ from .nodes import (
BytesFromListIdx,
ConditioningFromListIdx,
DictFromListIdx,
ETKListToComfyList,
FloatFromListIdx,
ImageFromListIdx,
IntFromListIdx,
@@ -23,10 +24,12 @@ NODE_CLASS_MAPPINGS = {
"BytesFromListIdx": BytesFromListIdx,
"IntFromListIdx": IntFromListIdx,
"FloatFromListIdx": FloatFromListIdx,
"ETKListToComfyList": ETKListToComfyList,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AudioFromListIdx": "Audio From List Index",
"ETKListToComfyList": "List To ComfyUI List",
}
__all__ = [
@@ -34,6 +37,7 @@ __all__ = [
"BytesFromListIdx",
"ConditioningFromListIdx",
"DictFromListIdx",
"ETKListToComfyList",
"FloatFromListIdx",
"ImageFromListIdx",
"IntFromListIdx",
+31
View File
@@ -6,6 +6,37 @@ from collections.abc import Mapping
from copy import deepcopy
class AnyType(str):
"""ComfyUI wildcard socket type for a list whose item type is not fixed."""
def __ne__(self, other):
return False
ANY_TYPE = AnyType("*")
class ETKListToComfyList:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"items": ("LIST", {"default": []}),
}
}
RETURN_TYPES = (ANY_TYPE,)
RETURN_NAMES = ("items",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "convert"
CATEGORY = "ETK/List"
def convert(self, items):
if not isinstance(items, list):
raise TypeError(f"items must be a LIST, got {type(items).__name__}")
return (items,)
class ImageFromListIdx:
@classmethod
def INPUT_TYPES(cls):
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "etk-from-list-index"
version = "1.0.0"
description = "Workflow-compatible list-index selector nodes for ComfyUI images, latents, conditioning, audio, and primitive values."
version = "1.1.0"
description = "Workflow-compatible ComfyUI list-index selectors and ETK LIST to native list-output conversion."
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = []
+22 -1
View File
@@ -29,16 +29,37 @@ EXPECTED_IDS = {
"BytesFromListIdx",
"IntFromListIdx",
"FloatFromListIdx",
"ETKListToComfyList",
}
def test_exact_workflow_ids_are_registered():
assert set(PACKAGE.NODE_CLASS_MAPPINGS) == EXPECTED_IDS
assert PACKAGE.NODE_DISPLAY_NAME_MAPPINGS == {
"AudioFromListIdx": "Audio From List Index"
"AudioFromListIdx": "Audio From List Index",
"ETKListToComfyList": "List To ComfyUI List",
}
def test_etk_list_to_comfy_list_uses_native_list_output_contract():
node = PACKAGE.ETKListToComfyList
source = ["first", {"second": 2}, 3]
assert node.INPUT_TYPES() == {
"required": {"items": ("LIST", {"default": []})}
}
assert node.RETURN_NAMES == ("items",)
assert node.OUTPUT_IS_LIST == (True,)
assert node.CATEGORY == "ETK/List"
assert node().convert(source) == (source,)
assert node().convert(source)[0] is source
assert node().convert([]) == ([],)
assert (node.RETURN_TYPES[0] != "IMAGE") is False
with pytest.raises(TypeError, match="items must be a LIST"):
node().convert("not-a-list")
def test_etk_schema_and_selection_contracts():
image = PACKAGE.ImageFromListIdx
latent = PACKAGE.LatentFromListIdx