From c761d43b7e656af4bfd8ec8d08b985779bbf9d15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 22:57:28 +0000 Subject: [PATCH] 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> --- __init__.py | 2 +- debug.py | 80 ++++++++++++++++------------------------------------- registry.py | 43 +++++++++++++++++++++++----- sequence.py | 10 +++++-- 4 files changed, 69 insertions(+), 66 deletions(-) diff --git a/__init__.py b/__init__.py index 6550eca..377fcc2 100644 --- a/__init__.py +++ b/__init__.py @@ -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 diff --git a/debug.py b/debug.py index c65cdfb..d98a8a7 100644 --- a/debug.py +++ b/debug.py @@ -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 () diff --git a/registry.py b/registry.py index a2e2ac4..c8dde5e 100644 --- a/registry.py +++ b/registry.py @@ -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) diff --git a/sequence.py b/sequence.py index db713d2..3385708 100644 --- a/sequence.py +++ b/sequence.py @@ -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), ) + +