diff --git a/app/node_replace_manager.py b/app/node_replace_manager.py index e805dee2f..db510cd94 100644 --- a/app/node_replace_manager.py +++ b/app/node_replace_manager.py @@ -4,7 +4,7 @@ from aiohttp import web from typing import TYPE_CHECKING, TypedDict if TYPE_CHECKING: - from comfy_api.latest._node_replace import NodeReplace + from comfy_api.latest._io import NodeReplace from comfy_execution.graph_utils import is_link import nodes diff --git a/comfy_api/latest/__init__.py b/comfy_api/latest/__init__.py index 1af0397bd..f2399422b 100644 --- a/comfy_api/latest/__init__.py +++ b/comfy_api/latest/__init__.py @@ -10,7 +10,6 @@ from ._input_impl import VideoFromFile, VideoFromComponents from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D from . import _io_public as io from . import _ui_public as ui -from . import _node_replace_public as node_replace from comfy_execution.utils import get_executing_context from comfy_execution.progress import get_progress_state, PreviewImageTuple from PIL import Image @@ -28,7 +27,7 @@ class ComfyAPI_latest(ComfyAPIBase): self.execution = self.Execution() class NodeReplacement(ProxiedSingleton): - async def register(self, node_replace: 'node_replace.NodeReplace') -> None: + async def register(self, node_replace: io.NodeReplace) -> None: """Register a node replacement mapping.""" from server import PromptServer PromptServer.instance.node_replace_manager.register(node_replace) @@ -141,5 +140,4 @@ __all__ = [ "IO", "ui", "UI", - "node_replace", ] diff --git a/comfy_api/latest/_io.py b/comfy_api/latest/_io.py index 93cf482ca..95d79c035 100644 --- a/comfy_api/latest/_io.py +++ b/comfy_api/latest/_io.py @@ -2030,6 +2030,68 @@ class _UIOutput(ABC): ... +class InputMapOldId(TypedDict): + """Map an old node input to a new node input by ID.""" + new_id: str + old_id: str + +class InputMapSetValue(TypedDict): + """Set a specific value for a new node input.""" + new_id: str + set_value: Any + +InputMap = InputMapOldId | InputMapSetValue +""" +Input mapping for node replacement. Type is inferred by dictionary keys: +- {"new_id": str, "old_id": str} - maps old input to new input +- {"new_id": str, "set_value": Any} - sets a specific value for new input +""" + +class OutputMap(TypedDict): + """Map outputs of node replacement via indexes.""" + new_idx: int + old_idx: int + +class NodeReplace: + """ + Defines a possible node replacement, mapping inputs and outputs of the old node to the new node. + + Also supports assigning specific values to the input widgets of the new node. + + Args: + new_node_id: The class name of the new replacement node. + old_node_id: The class name of the deprecated node. + old_widget_ids: Ordered list of input IDs for widgets that may not have an input slot + connected. The workflow JSON stores widget values by their relative position index, + not by ID. This list maps those positional indexes to input IDs, enabling the + replacement system to correctly identify widget values during node migration. + input_mapping: List of input mappings from old node to new node. + output_mapping: List of output mappings from old node to new node. + """ + def __init__(self, + new_node_id: str, + old_node_id: str, + old_widget_ids: list[str] | None=None, + input_mapping: list[InputMap] | None=None, + output_mapping: list[OutputMap] | None=None, + ): + self.new_node_id = new_node_id + self.old_node_id = old_node_id + self.old_widget_ids = old_widget_ids + self.input_mapping = input_mapping + self.output_mapping = output_mapping + + def as_dict(self): + """Create serializable representation of the node replacement.""" + return { + "new_node_id": self.new_node_id, + "old_node_id": self.old_node_id, + "old_widget_ids": self.old_widget_ids, + "input_mapping": list(self.input_mapping) if self.input_mapping else None, + "output_mapping": list(self.output_mapping) if self.output_mapping else None, + } + + __all__ = [ "FolderType", "UploadType", @@ -2121,4 +2183,5 @@ __all__ = [ "ImageCompare", "PriceBadgeDepends", "PriceBadge", + "NodeReplace", ] diff --git a/comfy_api/latest/_node_replace.py b/comfy_api/latest/_node_replace.py deleted file mode 100644 index c87b487c0..000000000 --- a/comfy_api/latest/_node_replace.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -from typing import Any, TypedDict - - -class InputMapOldId(TypedDict): - """Map an old node input to a new node input by ID.""" - new_id: str - old_id: str - - -class InputMapSetValue(TypedDict): - """Set a specific value for a new node input.""" - new_id: str - set_value: Any - - -InputMap = InputMapOldId | InputMapSetValue -""" -Input mapping for node replacement. Type is inferred by dictionary keys: -- {"new_id": str, "old_id": str} - maps old input to new input -- {"new_id": str, "set_value": Any} - sets a specific value for new input -""" - - -class OutputMap(TypedDict): - """Map outputs of node replacement via indexes.""" - new_idx: int - old_idx: int - - -class NodeReplace: - """ - Defines a possible node replacement, mapping inputs and outputs of the old node to the new node. - - Also supports assigning specific values to the input widgets of the new node. - - Args: - new_node_id: The class name of the new replacement node. - old_node_id: The class name of the deprecated node. - old_widget_ids: Ordered list of input IDs for widgets that may not have an input slot - connected. The workflow JSON stores widget values by their relative position index, - not by ID. This list maps those positional indexes to input IDs, enabling the - replacement system to correctly identify widget values during node migration. - input_mapping: List of input mappings from old node to new node. - output_mapping: List of output mappings from old node to new node. - """ - def __init__(self, - new_node_id: str, - old_node_id: str, - old_widget_ids: list[str] | None=None, - input_mapping: list[InputMap] | None=None, - output_mapping: list[OutputMap] | None=None, - ): - self.new_node_id = new_node_id - self.old_node_id = old_node_id - self.old_widget_ids = old_widget_ids - self.input_mapping = input_mapping - self.output_mapping = output_mapping - - def as_dict(self): - """Create serializable representation of the node replacement.""" - return { - "new_node_id": self.new_node_id, - "old_node_id": self.old_node_id, - "old_widget_ids": self.old_widget_ids, - "input_mapping": list(self.input_mapping) if self.input_mapping else None, - "output_mapping": list(self.output_mapping) if self.output_mapping else None, - } diff --git a/comfy_api/latest/_node_replace_public.py b/comfy_api/latest/_node_replace_public.py deleted file mode 100644 index cd18168ae..000000000 --- a/comfy_api/latest/_node_replace_public.py +++ /dev/null @@ -1 +0,0 @@ -from ._node_replace import * # noqa: F403 diff --git a/comfy_api/v0_0_2/__init__.py b/comfy_api/v0_0_2/__init__.py index 0d4d567da..c4fa1d971 100644 --- a/comfy_api/v0_0_2/__init__.py +++ b/comfy_api/v0_0_2/__init__.py @@ -6,7 +6,7 @@ from comfy_api.latest import ( ) from typing import Type, TYPE_CHECKING from comfy_api.internal.async_to_sync import create_sync_class -from comfy_api.latest import io, ui, IO, UI, ComfyExtension, node_replace #noqa: F401 +from comfy_api.latest import io, ui, IO, UI, ComfyExtension #noqa: F401 class ComfyAPIAdapter_v0_0_2(ComfyAPI_latest): @@ -46,5 +46,4 @@ __all__ = [ "IO", "ui", "UI", - "node_replace", ] diff --git a/comfy_extras/nodes_replacements.py b/comfy_extras/nodes_replacements.py index 3d33fd2f1..7684e854c 100644 --- a/comfy_extras/nodes_replacements.py +++ b/comfy_extras/nodes_replacements.py @@ -1,4 +1,4 @@ -from comfy_api.latest import ComfyExtension, io, node_replace, ComfyAPI +from comfy_api.latest import ComfyExtension, io, ComfyAPI api = ComfyAPI() @@ -16,7 +16,7 @@ async def register_replacements(): async def register_replacements_longeredge(): # No dynamic inputs here - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="ImageScaleToMaxDimension", old_node_id="ResizeImagesByLongerEdge", old_widget_ids=["longer_edge"], @@ -31,7 +31,7 @@ async def register_replacements_longeredge(): async def register_replacements_batchimages(): # BatchImages node uses Autogrow - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="BatchImagesNode", old_node_id="ImageBatch", input_mapping=[ @@ -42,7 +42,7 @@ async def register_replacements_batchimages(): async def register_replacements_upscaleimage(): # ResizeImageMaskNode uses DynamicCombo - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="ResizeImageMaskNode", old_node_id="ImageScaleBy", old_widget_ids=["upscale_method", "scale_by"], @@ -56,7 +56,7 @@ async def register_replacements_upscaleimage(): async def register_replacements_controlnet(): # T2IAdapterLoader → ControlNetLoader - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="ControlNetLoader", old_node_id="T2IAdapterLoader", input_mapping=[ @@ -66,28 +66,28 @@ async def register_replacements_controlnet(): async def register_replacements_load3d(): # Load3DAnimation merged into Load3D - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="Load3D", old_node_id="Load3DAnimation", )) async def register_replacements_preview3d(): # Preview3DAnimation merged into Preview3D - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="Preview3D", old_node_id="Preview3DAnimation", )) async def register_replacements_svdimg2vid(): # Typo fix: SDV → SVD - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="SVD_img2vid_Conditioning", old_node_id="SDV_img2vid_Conditioning", )) async def register_replacements_conditioningavg(): # Typo fix: trailing space in node name - await api.node_replacement.register(node_replace.NodeReplace( + await api.node_replacement.register(io.NodeReplace( new_node_id="ConditioningAverage", old_node_id="ConditioningAverage ", ))