From 7ef18d5afd59f6eda84aeedfdba5e36f8b1ce3d8 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Wed, 23 Jul 2025 20:48:12 -0700 Subject: [PATCH 1/9] Remove leftover v3 state code in execution.py --- execution.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/execution.py b/execution.py index 49536bda4..c6c853a99 100644 --- a/execution.py +++ b/execution.py @@ -256,11 +256,6 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f type_obj = type(obj) type_obj.VALIDATE_CLASS() class_clone = type_obj.PREPARE_CLASS_CLONE(hidden_inputs) - # NOTE: this is a mock of state management; for local, just stores NodeStateLocal on node instance - if hasattr(obj, "local_state"): - if obj.local_state is None: - obj.local_state = io.NodeStateLocal(class_clone.hidden.unique_id) - class_clone.state = obj.local_state # NOTE: this is a mock of resource management; for local, just stores ResourcesLocal on node instance if hasattr(obj, "local_resources"): if obj.local_resources is None: From 7d710727a9754622d3aaab5f0481c910c073a742 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Wed, 23 Jul 2025 20:52:05 -0700 Subject: [PATCH 2/9] Begin porting io, ui, and resources to be compatible with versioned Core API --- comfy_api/v3/{io.py => _io.py} | 4 +++- comfy_api/v3/{resources.py => _resources.py} | 0 comfy_api/v3/{ui.py => _ui.py} | 0 3 files changed, 3 insertions(+), 1 deletion(-) rename comfy_api/v3/{io.py => _io.py} (99%) rename comfy_api/v3/{resources.py => _resources.py} (100%) rename comfy_api/v3/{ui.py => _ui.py} (100%) diff --git a/comfy_api/v3/io.py b/comfy_api/v3/_io.py similarity index 99% rename from comfy_api/v3/io.py rename to comfy_api/v3/_io.py index a045123c7..ba6fe3c3a 100644 --- a/comfy_api/v3/io.py +++ b/comfy_api/v3/_io.py @@ -29,7 +29,6 @@ from comfy_execution.graph import ExecutionBlocker # from comfy_extras.nodes_images import SVG as SVG_ # NOTE: needs to be moved before can be imported due to circular reference - class FolderType(str, Enum): input = "input" output = "output" @@ -1502,3 +1501,6 @@ class _UIOutput(ABC): @abstractmethod def as_dict(self) -> dict: ... + +class IO: + FolderType = FolderType diff --git a/comfy_api/v3/resources.py b/comfy_api/v3/_resources.py similarity index 100% rename from comfy_api/v3/resources.py rename to comfy_api/v3/_resources.py diff --git a/comfy_api/v3/ui.py b/comfy_api/v3/_ui.py similarity index 100% rename from comfy_api/v3/ui.py rename to comfy_api/v3/_ui.py From 3a8286b034fcd3407fda1d93f512e5a27dd0b5f1 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 16:00:27 -0700 Subject: [PATCH 3/9] Refactored io.py, ui.py, and resources.py to expose themselves on v3/__init__.py on _IO, _UI, and _RESOURCES classes such that the v3 schema can be iterated upon on versioned Core API soon --- comfy_api/v3/__init__.py | 9 +++++ comfy_api/v3/_io.py | 75 ++++++++++++++++++++++++++++++++++- comfy_api/v3/_resources.py | 7 ++++ comfy_api/v3/_ui.py | 16 +++++++- comfy_api/v3_01/__init__.py | 21 ++++++++++ comfy_extras/nodes_v3_test.py | 1 - execution.py | 4 +- 7 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 comfy_api/v3_01/__init__.py diff --git a/comfy_api/v3/__init__.py b/comfy_api/v3/__init__.py index e69de29bb..b3a62e65a 100644 --- a/comfy_api/v3/__init__.py +++ b/comfy_api/v3/__init__.py @@ -0,0 +1,9 @@ +from comfy_api.v3._io import _IO +from comfy_api.v3._ui import _UI +from comfy_api.v3._resources import _RESOURCES + +io = _IO +ui = _UI +resources = _RESOURCES + +__all__ = ["io", "ui", "resources"] diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index ba6fe3c3a..0d7ac9974 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -24,7 +24,7 @@ from comfy.sd import StyleModel as StyleModel_ from comfy_api.input import VideoInput from comfy_api.internal import (_ComfyNodeInternal, classproperty, copy_class, first_real_override, is_class, prune_dict, shallow_clone_class) -from comfy_api.v3.resources import Resources, ResourcesLocal +from comfy_api.v3._resources import Resources, ResourcesLocal from comfy_execution.graph import ExecutionBlocker # from comfy_extras.nodes_images import SVG as SVG_ # NOTE: needs to be moved before can be imported due to circular reference @@ -1502,5 +1502,76 @@ class _UIOutput(ABC): def as_dict(self) -> dict: ... -class IO: + +class _IO: FolderType = FolderType + UploadType = UploadType + RemoteOptions = RemoteOptions + NumberDisplay = NumberDisplay + comfytype = staticmethod(comfytype) + Custom = staticmethod(Custom) + InputV3 = InputV3 + WidgetInputV3 = WidgetInputV3 + OutputV3 = OutputV3 + ComfyTypeI = ComfyTypeI + ComfyTypeIO = ComfyTypeIO + Boolean = Boolean + Int = Int + Float = Float + String = String + Combo = Combo + MultiCombo = MultiCombo + Image = Image + WanCameraEmbedding = WanCameraEmbedding + Webcam = Webcam + Mask = Mask + Latent = Latent + Conditioning = Conditioning + Sampler = Sampler + Sigmas = Sigmas + Noise = Noise + Guider = Guider + Clip = Clip + ControlNet = ControlNet + Vae = Vae + Model = Model + ClipVision = ClipVision + ClipVisionOutput = ClipVisionOutput + StyleModel = StyleModel + Gligen = Gligen + UpscaleModel = UpscaleModel + Audio = Audio + Video = Video + SVG = SVG + LoraModel = LoraModel + LossMap = LossMap + Voxel = Voxel + Mesh = Mesh + Hooks = Hooks + HookKeyframes = HookKeyframes + TimestepsRange = TimestepsRange + LatentOperation = LatentOperation + FlowControl = FlowControl + Accumulation = Accumulation + Load3DCamera = Load3DCamera + Photomaker = Photomaker + Point = Point + FaceAnalysis = FaceAnalysis + BBOX = BBOX + SEGS = SEGS + AnyType = AnyType + MultiType = MultiType + DynamicInput = DynamicInput + DynamicOutput = DynamicOutput + AutogrowDynamic = AutogrowDynamic + ComboDynamicInput = ComboDynamicInput + MatchType = MatchType + HiddenHolder = HiddenHolder + Hidden = Hidden + NodeInfoV1 = NodeInfoV1 + NodeInfoV3 = NodeInfoV3 + Schema = Schema + ComfyNode = ComfyNode + NodeOutput = NodeOutput + add_to_dict_v1 = staticmethod(add_to_dict_v1) + add_to_dict_v3 = staticmethod(add_to_dict_v3) diff --git a/comfy_api/v3/_resources.py b/comfy_api/v3/_resources.py index 12c751275..a6bdda972 100644 --- a/comfy_api/v3/_resources.py +++ b/comfy_api/v3/_resources.py @@ -63,3 +63,10 @@ class ResourcesLocal(Resources): if default is not ...: return default raise Exception(f"Unsupported resource key type: {type(key)}") + + +class _RESOURCES: + ResourceKey = ResourceKey + TorchDictFolderFilename = TorchDictFolderFilename + Resources = Resources + ResourcesLocal = ResourcesLocal diff --git a/comfy_api/v3/_ui.py b/comfy_api/v3/_ui.py index d41d758d6..04e889f97 100644 --- a/comfy_api/v3/_ui.py +++ b/comfy_api/v3/_ui.py @@ -17,7 +17,7 @@ import folder_paths # used for image preview from comfy.cli_args import args -from comfy_api.v3.io import ComfyNode, FolderType, Image, _UIOutput +from comfy_api.v3._io import ComfyNode, FolderType, Image, _UIOutput class SavedResult(dict): @@ -488,3 +488,17 @@ class PreviewText(_UIOutput): def as_dict(self): return {"text": (self.value,)} + + +class _UI: + SavedResult = SavedResult + SavedImages = SavedImages + SavedAudios = SavedAudios + ImageSaveHelper = ImageSaveHelper + AudioSaveHelper = AudioSaveHelper + PreviewImage = PreviewImage + PreviewMask = PreviewMask + PreviewAudio = PreviewAudio + PreviewVideo = PreviewVideo + PreviewUI3D = PreviewUI3D + PreviewText = PreviewText diff --git a/comfy_api/v3_01/__init__.py b/comfy_api/v3_01/__init__.py new file mode 100644 index 000000000..928f629a7 --- /dev/null +++ b/comfy_api/v3_01/__init__.py @@ -0,0 +1,21 @@ +from comfy_api.v3._io import _IO +from comfy_api.v3._ui import _UI +from comfy_api.v3._resources import _RESOURCES +import logging + +class Int(_IO.Int): + class Input(_IO.Int.Input): + def as_dict(self): + logging.info("I am in V3_01 def of Int 😎") + return super().as_dict() + + +class IO_01(_IO): + Int = Int + + +io = IO_01 +ui = _UI +resources = _RESOURCES + +__all__ = ["io", "ui", "resources"] diff --git a/comfy_extras/nodes_v3_test.py b/comfy_extras/nodes_v3_test.py index b8e5e2ae0..c0816ebe0 100644 --- a/comfy_extras/nodes_v3_test.py +++ b/comfy_extras/nodes_v3_test.py @@ -7,7 +7,6 @@ import comfy.utils import comfy.sd import asyncio - @io.comfytype(io_type="XYZ") class XYZ(io.ComfyTypeIO): Type = tuple[int,str] diff --git a/execution.py b/execution.py index c6c853a99..608006467 100644 --- a/execution.py +++ b/execution.py @@ -33,7 +33,7 @@ from comfy_execution.validation import validate_node_input from comfy_execution.progress import get_progress_state, reset_progress_state, add_progress_handler, WebUIProgressHandler from comfy_execution.utils import CurrentNodeContext from comfy_api.internal import _ComfyNodeInternal, first_real_override, is_class, make_locked_method_func -from comfy_api.v3 import io +from comfy_api.v3 import io, resources class ExecutionResult(Enum): @@ -259,7 +259,7 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f # NOTE: this is a mock of resource management; for local, just stores ResourcesLocal on node instance if hasattr(obj, "local_resources"): if obj.local_resources is None: - obj.local_resources = io.ResourcesLocal() + obj.local_resources = resources.ResourcesLocal() class_clone.resources = obj.local_resources # TODO: delete this when done testing mocking dynamic inputs for si in obj.SCHEMA.inputs: From dacd0e9a59ac76a0201dae8793b410d74b19d498 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 16:22:43 -0700 Subject: [PATCH 4/9] Complete merge - needed to expose some of the new classes in _io.py's _IO class --- comfy_api/v3/_io.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index 332a257d2..cc15a1b06 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -1533,6 +1533,7 @@ class _IO: UploadType = UploadType RemoteOptions = RemoteOptions NumberDisplay = NumberDisplay + comfytype = staticmethod(comfytype) Custom = staticmethod(Custom) InputV3 = InputV3 @@ -1540,6 +1541,8 @@ class _IO: OutputV3 = OutputV3 ComfyTypeI = ComfyTypeI ComfyTypeIO = ComfyTypeIO + #--------------------------------- + # Supported Types Boolean = Boolean Int = Int Float = Float @@ -1579,6 +1582,8 @@ class _IO: FlowControl = FlowControl Accumulation = Accumulation Load3DCamera = Load3DCamera + Load3D = Load3D + Load3DAnimation = Load3DAnimation Photomaker = Photomaker Point = Point FaceAnalysis = FaceAnalysis @@ -1586,6 +1591,7 @@ class _IO: SEGS = SEGS AnyType = AnyType MultiType = MultiType + #--------------------------------- DynamicInput = DynamicInput DynamicOutput = DynamicOutput AutogrowDynamic = AutogrowDynamic From 56aae3e2c819a85522947856ca047e5d5b445b87 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 16:24:59 -0700 Subject: [PATCH 5/9] Remove v3_01, didnt meant to commit that --- comfy_api/v3_01/__init__.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 comfy_api/v3_01/__init__.py diff --git a/comfy_api/v3_01/__init__.py b/comfy_api/v3_01/__init__.py deleted file mode 100644 index 928f629a7..000000000 --- a/comfy_api/v3_01/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from comfy_api.v3._io import _IO -from comfy_api.v3._ui import _UI -from comfy_api.v3._resources import _RESOURCES -import logging - -class Int(_IO.Int): - class Input(_IO.Int.Input): - def as_dict(self): - logging.info("I am in V3_01 def of Int 😎") - return super().as_dict() - - -class IO_01(_IO): - Int = Int - - -io = IO_01 -ui = _UI -resources = _RESOURCES - -__all__ = ["io", "ui", "resources"] From d3a62a440fb79ea715d627a4936411ac67dbbd80 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 16:29:26 -0700 Subject: [PATCH 6/9] Renamed InputV3, WidgetInputV3, OutputV3 to Input, WidgetInput, and Output --- comfy_api/v3/_io.py | 66 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index cc15a1b06..3ead59733 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -156,7 +156,7 @@ class _IO_V3: def Type(self): return self.Parent.Type -class InputV3(_IO_V3): +class Input(_IO_V3): ''' Base class for a V3 Input. ''' @@ -180,7 +180,7 @@ class InputV3(_IO_V3): def get_io_type(self): return _StringIOType(self.io_type) -class WidgetInputV3(InputV3): +class WidgetInput(Input): ''' Base class for a V3 Input with widget. ''' @@ -205,7 +205,7 @@ class WidgetInputV3(InputV3): return self.widget_type if self.widget_type is not None else super().get_io_type() -class OutputV3(_IO_V3): +class Output(_IO_V3): def __init__(self, id: str=None, display_name: str=None, tooltip: str=None, is_output_list=False): self.id = id @@ -226,12 +226,12 @@ class OutputV3(_IO_V3): class ComfyTypeI(_ComfyType): '''ComfyType subclass that only has a default Input class - intended for types that only have Inputs.''' - class Input(InputV3): + class Input(Input): ... class ComfyTypeIO(ComfyTypeI): '''ComfyType subclass that has default Input and Output classes; useful for types with both Inputs and Outputs.''' - class Output(OutputV3): + class Output(Output): ... @@ -239,7 +239,7 @@ class ComfyTypeIO(ComfyTypeI): class Boolean(ComfyTypeIO): Type = bool - class Input(WidgetInputV3): + class Input(WidgetInput): '''Boolean input.''' def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, default: bool=None, label_on: str=None, label_off: str=None, @@ -259,7 +259,7 @@ class Boolean(ComfyTypeIO): class Int(ComfyTypeIO): Type = int - class Input(WidgetInputV3): + class Input(WidgetInput): '''Integer input.''' def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, default: int=None, min: int=None, max: int=None, step: int=None, control_after_generate: bool=None, @@ -285,7 +285,7 @@ class Int(ComfyTypeIO): class Float(ComfyTypeIO): Type = float - class Input(WidgetInputV3): + class Input(WidgetInput): '''Float input.''' def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, default: float=None, min: float=None, max: float=None, step: float=None, round: float=None, @@ -311,7 +311,7 @@ class Float(ComfyTypeIO): class String(ComfyTypeIO): Type = str - class Input(WidgetInputV3): + class Input(WidgetInput): '''String input.''' def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, multiline=False, placeholder: str=None, default: str=None, dynamic_prompts: bool=None, @@ -332,7 +332,7 @@ class String(ComfyTypeIO): @comfytype(io_type="COMBO") class Combo(ComfyTypeI): Type = str - class Input(WidgetInputV3): + class Input(WidgetInput): """Combo input (dropdown).""" Type = str def __init__(self, id: str, options: list[str]=None, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, @@ -397,7 +397,7 @@ class WanCameraEmbedding(ComfyTypeIO): class Webcam(ComfyTypeIO): Type = str - class Input(WidgetInputV3): + class Input(WidgetInput): """Webcam input.""" Type = str def __init__( @@ -714,14 +714,14 @@ class AnyType(ComfyTypeIO): @comfytype(io_type="COMFY_MULTITYPED_V3") class MultiType: Type = Any - class Input(InputV3): + class Input(Input): ''' Input that permits more than one input type; if `id` is an instance of `ComfyType.Input`, then that input will be used to create a widget (if applicable) with overridden values. ''' - def __init__(self, id: str | InputV3, types: list[type[_ComfyType] | _ComfyType], display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, extra_dict=None): + def __init__(self, id: str | Input, types: list[type[_ComfyType] | _ComfyType], display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, extra_dict=None): # if id is an Input, then use that Input with overridden values self.input_override = None - if isinstance(id, InputV3): + if isinstance(id, Input): self.input_override = copy.copy(id) optional = id.optional if id.optional is True else optional tooltip = id.tooltip if id.tooltip is not None else tooltip @@ -729,13 +729,13 @@ class MultiType: lazy = id.lazy if id.lazy is not None else lazy id = id.id # if is a widget input, make sure widget_type is set appropriately - if isinstance(self.input_override, WidgetInputV3): + if isinstance(self.input_override, WidgetInput): self.input_override.widget_type = self.input_override.get_io_type() super().__init__(id, display_name, optional, tooltip, lazy, extra_dict) self._io_types = types @property - def io_types(self) -> list[type[InputV3]]: + def io_types(self) -> list[type[Input]]: ''' Returns list of InputV3 class types permitted. ''' @@ -760,15 +760,15 @@ class MultiType: else: return super().as_dict() -class DynamicInput(InputV3, ABC): +class DynamicInput(Input, ABC): ''' Abstract class for dynamic input registration. ''' @abstractmethod - def get_dynamic(self) -> list[InputV3]: + def get_dynamic(self) -> list[Input]: ... -class DynamicOutput(OutputV3, ABC): +class DynamicOutput(Output, ABC): ''' Abstract class for dynamic output registration. ''' @@ -777,7 +777,7 @@ class DynamicOutput(OutputV3, ABC): super().__init__(id, display_name, tooltip, is_output_list) @abstractmethod - def get_dynamic(self) -> list[OutputV3]: + def get_dynamic(self) -> list[Output]: ... @@ -785,7 +785,7 @@ class DynamicOutput(OutputV3, ABC): class AutogrowDynamic(ComfyTypeI): Type = list[Any] class Input(DynamicInput): - def __init__(self, id: str, template_input: InputV3, min: int=1, max: int=None, + def __init__(self, id: str, template_input: Input, min: int=1, max: int=None, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None, extra_dict=None): super().__init__(id, display_name, optional, tooltip, lazy, extra_dict) self.template_input = template_input @@ -796,7 +796,7 @@ class AutogrowDynamic(ComfyTypeI): self.min = min self.max = max - def get_dynamic(self) -> list[InputV3]: + def get_dynamic(self) -> list[Input]: curr_count = 1 new_inputs = [] for i in range(self.min): @@ -805,7 +805,7 @@ class AutogrowDynamic(ComfyTypeI): if new_input.display_name is not None: new_input.display_name = f"{new_input.display_name}{curr_count}" new_input.optional = self.optional or new_input.optional - if isinstance(self.template_input, WidgetInputV3): + if isinstance(self.template_input, WidgetInput): new_input.force_input = True new_inputs.append(new_input) curr_count += 1 @@ -816,7 +816,7 @@ class AutogrowDynamic(ComfyTypeI): if new_input.display_name is not None: new_input.display_name = f"{new_input.display_name}{curr_count}" new_input.optional = True - if isinstance(self.template_input, WidgetInputV3): + if isinstance(self.template_input, WidgetInput): new_input.force_input = True new_inputs.append(new_input) curr_count += 1 @@ -847,7 +847,7 @@ class MatchType(ComfyTypeIO): super().__init__(id, display_name, optional, tooltip, lazy, extra_dict) self.template = template - def get_dynamic(self) -> list[InputV3]: + def get_dynamic(self) -> list[Input]: return [self] def as_dict(self): @@ -861,7 +861,7 @@ class MatchType(ComfyTypeIO): super().__init__(id, display_name, tooltip, is_output_list) self.template = template - def get_dynamic(self) -> list[OutputV3]: + def get_dynamic(self) -> list[Output]: return [self] def as_dict(self): @@ -965,8 +965,8 @@ class Schema: """Display name of node.""" category: str = "sd" """The category of the node, as per the "Add Node" menu.""" - inputs: list[InputV3]=None - outputs: list[OutputV3]=None + inputs: list[Input]=None + outputs: list[Output]=None hidden: list[Hidden]=None description: str="" """Node description, shown as a tooltip when hovering over the node.""" @@ -1128,14 +1128,14 @@ class Schema: return info -def add_to_dict_v1(i: InputV3, input: dict): +def add_to_dict_v1(i: Input, input: dict): key = "optional" if i.optional else "required" as_dict = i.as_dict() # for v1, we don't want to include the optional key as_dict.pop("optional", None) input.setdefault(key, {})[i.id] = (i.get_io_type(), as_dict) -def add_to_dict_v3(io: InputV3 | OutputV3, d: dict): +def add_to_dict_v3(io: Input | Output, d: dict): d[io.id] = (io.get_io_type(), io.as_dict()) @@ -1536,9 +1536,9 @@ class _IO: comfytype = staticmethod(comfytype) Custom = staticmethod(Custom) - InputV3 = InputV3 - WidgetInputV3 = WidgetInputV3 - OutputV3 = OutputV3 + Input = Input + WidgetInput = WidgetInput + Output = Output ComfyTypeI = ComfyTypeI ComfyTypeIO = ComfyTypeIO #--------------------------------- From 44afeab12451f23700e2103d0ac8eb2d7451470f Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 16:58:25 -0700 Subject: [PATCH 7/9] Abstracted out NodeOutput into _NodeOutputInternal in execution.py --- comfy_api/internal/__init__.py | 7 +++++++ comfy_api/v3/_io.py | 4 ++-- execution.py | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/comfy_api/internal/__init__.py b/comfy_api/internal/__init__.py index 939d09b8d..7321b7845 100644 --- a/comfy_api/internal/__init__.py +++ b/comfy_api/internal/__init__.py @@ -36,6 +36,13 @@ class _ComfyNodeInternal: ... +class _NodeOutputInternal: + """Class that all V3-based APIs inherit from for NodeOutput. + + This is intended to only be referenced within execution.py, as it has to handle all V3 APIs going forward.""" + ... + + def as_pruned_dict(dataclass_obj): '''Return dict of dataclass object with pruned None values.''' return prune_dict(asdict(dataclass_obj)) diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index 3ead59733..88a88f867 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -22,7 +22,7 @@ from comfy.samplers import CFGGuider, Sampler from comfy.sd import CLIP, VAE from comfy.sd import StyleModel as StyleModel_ from comfy_api.input import VideoInput -from comfy_api.internal import (_ComfyNodeInternal, classproperty, copy_class, first_real_override, is_class, +from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class, prune_dict, shallow_clone_class) from comfy_api.v3._resources import Resources, ResourcesLocal from comfy_execution.graph import ExecutionBlocker @@ -1486,7 +1486,7 @@ class ComfyNode(_ComfyNodeBaseInternal): return ComfyNode -class NodeOutput: +class NodeOutput(_NodeOutputInternal): ''' Standardized output of a node; can pass in any number of args and/or a UIOutput into 'ui' kwarg. ''' diff --git a/execution.py b/execution.py index 608006467..3bea0f048 100644 --- a/execution.py +++ b/execution.py @@ -32,7 +32,7 @@ from comfy_execution.graph_utils import GraphBuilder, is_link from comfy_execution.validation import validate_node_input from comfy_execution.progress import get_progress_state, reset_progress_state, add_progress_handler, WebUIProgressHandler from comfy_execution.utils import CurrentNodeContext -from comfy_api.internal import _ComfyNodeInternal, first_real_override, is_class, make_locked_method_func +from comfy_api.internal import _ComfyNodeInternal, _NodeOutputInternal, first_real_override, is_class, make_locked_method_func from comfy_api.v3 import io, resources @@ -358,7 +358,7 @@ def get_output_from_returns(return_values, obj): result = tuple([result] * len(obj.RETURN_TYPES)) results.append(result) subgraph_results.append((None, result)) - elif isinstance(r, io.NodeOutput): + elif isinstance(r, _NodeOutputInternal): # V3 if r.ui is not None: if isinstance(r.ui, dict): From 9d44cbf7c86d7eafb442eb47b77c74337c5523d8 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 17:04:00 -0700 Subject: [PATCH 8/9] Removed dynamic type mocks from v3 definition, since were only used as tests up to this point --- comfy_api/v3/_io.py | 5 ----- comfy_extras/nodes_v3_test.py | 6 +++--- execution.py | 10 ---------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index 88a88f867..acd562d79 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -1592,11 +1592,6 @@ class _IO: AnyType = AnyType MultiType = MultiType #--------------------------------- - DynamicInput = DynamicInput - DynamicOutput = DynamicOutput - AutogrowDynamic = AutogrowDynamic - ComboDynamicInput = ComboDynamicInput - MatchType = MatchType HiddenHolder = HiddenHolder Hidden = Hidden NodeInfoV1 = NodeInfoV1 diff --git a/comfy_extras/nodes_v3_test.py b/comfy_extras/nodes_v3_test.py index c0816ebe0..d78fa9d4b 100644 --- a/comfy_extras/nodes_v3_test.py +++ b/comfy_extras/nodes_v3_test.py @@ -1,6 +1,6 @@ import torch import time -from comfy_api.v3 import io, ui, resources +from comfy_api.v3 import io, ui, resources, _io import logging # noqa import folder_paths import comfy.utils @@ -143,8 +143,8 @@ class NInputsTest(io.ComfyNode): node_id="V3_NInputsTest", display_name="V3 N Inputs Test", inputs=[ - io.AutogrowDynamic.Input("nmock", template_input=io.Image.Input("image"), min=1, max=3), - io.AutogrowDynamic.Input("nmock2", template_input=io.Int.Input("int"), optional=True, min=1, max=4), + _io.AutogrowDynamic.Input("nmock", template_input=io.Image.Input("image"), min=1, max=3), + _io.AutogrowDynamic.Input("nmock2", template_input=io.Int.Input("int"), optional=True, min=1, max=4), ], outputs=[ io.Image.Output(), diff --git a/execution.py b/execution.py index 3bea0f048..dae0d0390 100644 --- a/execution.py +++ b/execution.py @@ -261,16 +261,6 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f if obj.local_resources is None: obj.local_resources = resources.ResourcesLocal() class_clone.resources = obj.local_resources - # TODO: delete this when done testing mocking dynamic inputs - for si in obj.SCHEMA.inputs: - if isinstance(si, io.AutogrowDynamic.Input): - add_key = si.id - dynamic_list = [] - real_inputs = {k: v for k, v in inputs.items()} - for d in si.get_dynamic(): - dynamic_list.append(real_inputs.pop(d.id, None)) - dynamic_list = [x for x in dynamic_list if x is not None] - inputs = {**real_inputs, add_key: dynamic_list} f = make_locked_method_func(type_obj, func, class_clone) # V1 else: From a998a3ce4f4c44b568aaa1fd013116cbb5ce6526 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 24 Jul 2025 17:12:58 -0700 Subject: [PATCH 9/9] Prepare a mock ComboDynamic scaffolding for future --- comfy_api/v3/_io.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/comfy_api/v3/_io.py b/comfy_api/v3/_io.py index acd562d79..f9c7a8cc1 100644 --- a/comfy_api/v3/_io.py +++ b/comfy_api/v3/_io.py @@ -822,11 +822,11 @@ class AutogrowDynamic(ComfyTypeI): curr_count += 1 return new_inputs -# io_type="COMFY_COMBODYNAMIC_V3" -class ComboDynamicInput(DynamicInput): - def __init__(self, id: str): - pass - +@comfytype(io_type="COMFY_COMBODYNAMIC_V3") +class ComboDynamic(ComfyTypeI): + class Input(DynamicInput): + def __init__(self, id: str): + pass @comfytype(io_type="COMFY_MATCHTYPE_V3") class MatchType(ComfyTypeIO):