Add variadic socket support to node decorator; fix Interact; add MakeSequence node

Agent-Logs-Url: https://github.com/ali1234/comfyui-job-iterator/sessions/c56c9e0b-f4b5-403d-889e-bd09244426b4

Co-authored-by: ali1234 <651970+ali1234@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-04 22:57:28 +00:00
committed by GitHub
co-authored by ali1234
parent 1abe898567
commit c761d43b7e
4 changed files with 69 additions and 66 deletions
+1 -1
View File
@@ -2,6 +2,6 @@ from .registry import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS, set_pack_
set_pack_options('jobiter', 'Job Iterator')
from . import sequence, job
from . import sequence, job, debug
+24 -56
View File
@@ -3,31 +3,12 @@ import os
import textwrap
import code
from . import register_node
@register_node
class Stringify:
"""Convert any input to str/repr."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"x": ("*", ),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("str", "repr")
FUNCTION = "go"
CATEGORY = "ali1234/debug"
def go(self, x):
return (str(x), repr(x))
from .registry import register_node
from .types import Any
class RestoreStdStreams(object):
# ComfyUI-Manager patches sys.stdout and sys.stder
# ComfyUI-Manager patches sys.stdout and sys.stderr
# which breaks GNU Readline support and makes the
# REPL annoying to use. This context manager temporarily
# puts back the originals.
@@ -58,39 +39,26 @@ class Quitter:
print(MESSAGE)
@register_node
class Interact:
@register_node(category='debug', output=True, display_name='Interact')
def Interact(*inputs: Any) -> ():
"""Opens an interactive REPL whenever the node is evaluated."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {x: "*" for x in ('a', 'b', 'c', 'd')},
}
if sys.__stdout__.isatty():
with RestoreStdStreams():
code.interact(
banner=textwrap.dedent(f"""
Interactive debugging started.
inputs = {inputs!r}
{MESSAGE}
"""),
exitmsg="Resuming workflow...",
local={
'inputs': list(inputs),
'quit': Quitter(),
'exit': Quitter(),
}
)
else:
# Don't block the server if there is no tty.
print("Skipping interactive prompt because there is no tty.")
return ()
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "interact"
CATEGORY = "ali1234/debug"
OUTPUT_NODE = True
def interact(self, **kwargs):
if sys.__stdout__.isatty():
with RestoreStdStreams():
code.interact(
banner=textwrap.dedent(f"""
Interactive debugging started.
Try `print(a)`.
{MESSAGE}
"""),
exitmsg="Resuming workflow...",
local={
**kwargs,
'quit': Quitter(),
'exit': Quitter()
}
)
else:
# Don't block the server if there is no tty.
print("Skipping interactive prompt because there is no tty.")
return ()
+36 -7
View File
@@ -29,7 +29,7 @@ def get_nodes():
return {k: v.exec.__doc__ for k, v in NODE_CLASS_MAPPINGS.items()}
def register_node(category=None, version=0, display_name=None, output=False):
def register_node(category=None, version=0, display_name=None, output=False, variadic_max=8):
def decorator(f):
node_attrs = {}
node_attrs['OUTPUT_NODE'] = output
@@ -39,10 +39,23 @@ def register_node(category=None, version=0, display_name=None, output=False):
node_attrs['RETURN_TYPES'] = tuple(x.type if isinstance(x, ComfyWidgetType) else x for x in sig.return_annotation)
variadic_params = set()
for k, v in sig.parameters.items():
t = v.annotation
opts = {}
req = 'required'
if v.kind == inspect.Parameter.VAR_POSITIONAL:
# Variadic *args parameter: emit numbered optional inputs.
variadic_params.add(k)
if isinstance(t, ComfyWidgetType):
opts = t.opts()
t = t.type
for i in range(variadic_max):
node_attrs['_INPUT_TYPES']['optional'][f'{k}_{i}'] = (t, opts)
continue
if isinstance(t, ComfyWidgetType):
opts = t.opts()
t = t.type
@@ -57,7 +70,6 @@ def register_node(category=None, version=0, display_name=None, output=False):
opts['default'] = v.default
node_attrs['_INPUT_TYPES'][req][k] = (t, opts)
#print(t, opts)
cat_list = []
if PACK_BASE_CATEGORY is not None:
@@ -71,11 +83,28 @@ def register_node(category=None, version=0, display_name=None, output=False):
@wraps(f)
def exec(**kwargs):
for k, v in kwargs.items():
if isinstance(sig.parameters[k].annotation, ComfyWidgetType):
# Look up Combo value from mapping
kwargs[k] = sig.parameters[k].annotation[v]
return f(**kwargs)
call_args = []
call_kwargs = {}
for param_name, param in sig.parameters.items():
if param.kind == inspect.Parameter.VAR_POSITIONAL:
# Collect all {param_name}_N kwargs, sorted by index.
prefix = param_name + '_'
numbered = {}
for k in kwargs:
if k.startswith(prefix) and k[len(prefix):].isdigit():
numbered[int(k[len(prefix):])] = kwargs[k]
values = [numbered[i] for i in sorted(numbered)]
if isinstance(param.annotation, ComfyWidgetType):
values = [param.annotation[v] for v in values]
call_args.extend(values)
else:
if param_name in kwargs:
v = kwargs[param_name]
if isinstance(param.annotation, ComfyWidgetType):
# Look up Combo value from mapping
v = param.annotation[v]
call_kwargs[param_name] = v
return f(*call_args, **call_kwargs)
node_attrs['exec'] = staticmethod(exec)
+8 -2
View File
@@ -46,9 +46,15 @@ def Join(sequence: Sequence(), sep: String() = ", ") -> (String(), ):
"""Joins an interable of strings into a single string."""
return (sep.join(str(x) for x in sequence), )
@register_node(display_name = "Mapped Join")
@register_node(display_name="Mapped Join")
def MappedJoin(sequence: Sequence(), sep: String() = ", ") -> (Sequence(), ):
"""Joins a sequence of interables of strings into a sequence of single strings."""
"""Joins a sequence of iterables of strings into a sequence of single strings."""
t = (sep.join(str(x) for x in s) for s in sequence)
return (list(t), )
@register_node(display_name="Make Sequence")
def MakeSequence(*inputs: Any) -> (Sequence(), ):
"""Collects variadic inputs into a single Sequence."""
return (list(inputs), )