2025-07-31 15:02:12 -07:00
|
|
|
from abc import ABC, abstractmethod
|
2025-12-16 02:01:10 +02:00
|
|
|
from typing import TYPE_CHECKING
|
2025-07-29 19:17:22 -07:00
|
|
|
from comfy_api.internal import ComfyAPIBase
|
|
|
|
|
from comfy_api.internal.singleton import ProxiedSingleton
|
|
|
|
|
from comfy_api.internal.async_to_sync import create_sync_class
|
2025-12-08 11:21:41 +02:00
|
|
|
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
|
|
|
|
from ._input_impl import VideoFromFile, VideoFromComponents
|
2026-02-03 20:31:46 +02:00
|
|
|
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D
|
Add MatchType, DynamicCombo, and Autogrow support to V3 Schema (#10832)
* Added output_matchtypes to generated json for v3, initial backend support for MatchType, created nodes_logic.py and added SwitchNode
* Fixed providing list of allowed_types
* Add workaround in validation.py for V3 Combo outputs not working as Combo inputs
* Make match type receive_type pass validation
* Also add MatchType check to input_type in validation - will likely trigger when connecting to non-lazy stuff
* Make sure this PR only has MatchType stuff
* Initial work on DynamicCombo
* Add get_dynamic function, not yet filled out correctly
* Mark Switch node as Beta
* Make sure other unfinished dynamic types are not accidentally used
* Send DynamicCombo.Option inputs in the same format as normal v1 inputs
* add dynamic combo test node
* Support validation of inputs and outputs
* Add missing input params to DynamicCombo.Input
* Add get_all function to inputs for id validation purposes
* Fix imports for v3 returning everything when doing io/ui/IO/UI instead of what is in __all__ of _io.py and _ui.py
* Modifying behavior of get_dynamic in V3 + serialization so can be used in execution code
* Fix v3 schema validation code after changes
* Refactor hidden_values for v3 in execution.py to be more general v3_data, add helper functions for dynamic behavior, preparing for restructuring dynamic type into object (not finished yet)
* Add nesting of inputs on DynamicCombo during execution
* Work with latest frontend commits
* Fix cringe arrows
* frontend will no longer namespace dynamic inputs widgets so reflect that in code, refactor build_nested_inputs
* Prepare Autogrow support for the love of the game
* satisfy ruff
* Create test nodes for Autogrow to collab with frontend development
* Add nested combo to DCTestNode
* Remove array support from build_nested_inputs, properly handle missing expected values
* Make execution.validate_inputs properly validate required dynamic inputs, renamed dynamic_data to dynamic_paths for clarity
* MatchType does not need any DynamicInput/Output features on backend; will increase compatibility with dynamic types
* Probably need this for ruff check
* Change MatchType to have template be the first and only required param; output id's do nothing right now, so no need
* Fix merge regression with LatentUpscaleModel type not being put in __all__ for _io.py, fix invalid type hint for validate_inputs
* Make Switch node inputs optional, disallow both inputs from being missing, and still work properly with lazy; when one input is missing, use the other no matter what the switch is set to
* Satisfy ruff
* Move MatchType code above the types that inherit from DynamicInput
* Add DynamicSlot type, awaiting frontend support
* Make curr_prefix creation happen in Autogrow, move curr_prefix in DynamicCombo to only be created if input exists in live_inputs
* I was confused, fixing accidentally redundant curr_prefix addition in Autogrow
* Make sure Autogrow inputs are force_input = True when WidgetInput, fix runtime validation by removing original input from expected inputs, fix min/max bounds, change test nodes slightly
* Remove unnecessary id usage in Autogrow test node outputs
* Commented out Switch node + test nodes
* Remove commented out code from Autogrow
* Make TemplatePrefix max more clear, allow max == 1
* Replace all dict[str] with dict[str, Any]
* Renamed add_to_dict_live_inputs to expand_schema_for_dynamic
* Fixed typo in DynamicSlot input code
* note about live_inputs not being present soon in get_v1_info (internal function anyway)
* For now, hide DynamicCombo and Autogrow from public interface
* Removed comment
2025-12-02 21:17:13 -08:00
|
|
|
from . import _io_public as io
|
|
|
|
|
from . import _ui_public as ui
|
2025-07-29 19:17:22 -07:00
|
|
|
from comfy_execution.utils import get_executing_context
|
|
|
|
|
from comfy_execution.progress import get_progress_state, PreviewImageTuple
|
|
|
|
|
from PIL import Image
|
|
|
|
|
from comfy.cli_args import args
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ComfyAPI_latest(ComfyAPIBase):
|
|
|
|
|
VERSION = "latest"
|
|
|
|
|
STABLE = False
|
|
|
|
|
|
2026-02-15 02:12:30 -08:00
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.node_replacement = self.NodeReplacement()
|
|
|
|
|
self.execution = self.Execution()
|
2026-03-12 21:17:50 -07:00
|
|
|
self.caching = self.Caching()
|
2026-02-15 02:12:30 -08:00
|
|
|
|
|
|
|
|
class NodeReplacement(ProxiedSingleton):
|
|
|
|
|
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)
|
|
|
|
|
|
2025-07-29 19:17:22 -07:00
|
|
|
class Execution(ProxiedSingleton):
|
|
|
|
|
async def set_progress(
|
|
|
|
|
self,
|
|
|
|
|
value: float,
|
|
|
|
|
max_value: float,
|
|
|
|
|
node_id: str | None = None,
|
|
|
|
|
preview_image: Image.Image | ImageInput | None = None,
|
|
|
|
|
ignore_size_limit: bool = False,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Update the progress bar displayed in the ComfyUI interface.
|
|
|
|
|
|
|
|
|
|
This function allows custom nodes and API calls to report their progress
|
|
|
|
|
back to the user interface, providing visual feedback during long operations.
|
|
|
|
|
|
|
|
|
|
Migration from previous API: comfy.utils.PROGRESS_BAR_HOOK
|
|
|
|
|
"""
|
|
|
|
|
executing_context = get_executing_context()
|
|
|
|
|
if node_id is None and executing_context is not None:
|
|
|
|
|
node_id = executing_context.node_id
|
|
|
|
|
if node_id is None:
|
|
|
|
|
raise ValueError("node_id must be provided if not in executing context")
|
|
|
|
|
|
|
|
|
|
# Convert preview_image to PreviewImageTuple if needed
|
|
|
|
|
to_display: PreviewImageTuple | Image.Image | ImageInput | None = preview_image
|
|
|
|
|
if to_display is not None:
|
|
|
|
|
# First convert to PIL Image if needed
|
|
|
|
|
if isinstance(to_display, ImageInput):
|
|
|
|
|
# Convert ImageInput (torch.Tensor) to PIL Image
|
|
|
|
|
# Handle tensor shape [B, H, W, C] -> get first image if batch
|
|
|
|
|
tensor = to_display
|
|
|
|
|
if len(tensor.shape) == 4:
|
|
|
|
|
tensor = tensor[0]
|
|
|
|
|
|
|
|
|
|
# Convert to numpy array and scale to 0-255
|
|
|
|
|
image_np = (tensor.cpu().numpy() * 255).astype(np.uint8)
|
|
|
|
|
to_display = Image.fromarray(image_np)
|
|
|
|
|
|
|
|
|
|
if isinstance(to_display, Image.Image):
|
|
|
|
|
# Detect image format from PIL Image
|
|
|
|
|
image_format = to_display.format if to_display.format else "JPEG"
|
|
|
|
|
# Use None for preview_size if ignore_size_limit is True
|
|
|
|
|
preview_size = None if ignore_size_limit else args.preview_size
|
|
|
|
|
to_display = (image_format, to_display, preview_size)
|
|
|
|
|
|
|
|
|
|
get_progress_state().update_progress(
|
|
|
|
|
node_id=node_id,
|
|
|
|
|
value=value,
|
|
|
|
|
max_value=max_value,
|
|
|
|
|
image=to_display,
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-12 21:17:50 -07:00
|
|
|
class Caching(ProxiedSingleton):
|
|
|
|
|
"""
|
|
|
|
|
External cache provider API for sharing cached node outputs
|
|
|
|
|
across ComfyUI instances.
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
from comfy_api.latest import Caching
|
|
|
|
|
|
|
|
|
|
class MyCacheProvider(Caching.CacheProvider):
|
|
|
|
|
async def on_lookup(self, context):
|
|
|
|
|
... # check external storage
|
|
|
|
|
|
|
|
|
|
async def on_store(self, context, value):
|
|
|
|
|
... # store to external storage
|
|
|
|
|
|
|
|
|
|
Caching.register_provider(MyCacheProvider())
|
|
|
|
|
"""
|
|
|
|
|
from ._caching import CacheProvider, CacheContext, CacheValue
|
|
|
|
|
|
|
|
|
|
async def register_provider(self, provider: "ComfyAPI_latest.Caching.CacheProvider") -> None:
|
|
|
|
|
"""Register an external cache provider. Providers are called in registration order."""
|
|
|
|
|
from comfy_execution.cache_provider import register_cache_provider
|
|
|
|
|
register_cache_provider(provider)
|
|
|
|
|
|
|
|
|
|
async def unregister_provider(self, provider: "ComfyAPI_latest.Caching.CacheProvider") -> None:
|
|
|
|
|
"""Unregister a previously registered cache provider."""
|
|
|
|
|
from comfy_execution.cache_provider import unregister_cache_provider
|
|
|
|
|
unregister_cache_provider(provider)
|
|
|
|
|
|
2025-07-31 15:02:12 -07:00
|
|
|
class ComfyExtension(ABC):
|
|
|
|
|
async def on_load(self) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Called when an extension is loaded.
|
2025-12-08 11:21:41 +02:00
|
|
|
This should be used to initialize any global resources needed by the extension.
|
2025-07-31 15:02:12 -07:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
|
|
|
"""
|
|
|
|
|
Returns a list of nodes that this extension provides.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-07-29 19:17:22 -07:00
|
|
|
class Input:
|
|
|
|
|
Image = ImageInput
|
|
|
|
|
Audio = AudioInput
|
|
|
|
|
Mask = MaskInput
|
|
|
|
|
Latent = LatentInput
|
|
|
|
|
Video = VideoInput
|
|
|
|
|
|
|
|
|
|
class InputImpl:
|
|
|
|
|
VideoFromFile = VideoFromFile
|
|
|
|
|
VideoFromComponents = VideoFromComponents
|
|
|
|
|
|
|
|
|
|
class Types:
|
|
|
|
|
VideoCodec = VideoCodec
|
|
|
|
|
VideoContainer = VideoContainer
|
|
|
|
|
VideoComponents = VideoComponents
|
2025-11-20 00:49:01 +02:00
|
|
|
MESH = MESH
|
|
|
|
|
VOXEL = VOXEL
|
2026-02-03 20:31:46 +02:00
|
|
|
File3D = File3D
|
2025-07-29 19:17:22 -07:00
|
|
|
|
2026-03-12 21:17:50 -07:00
|
|
|
|
|
|
|
|
Caching = ComfyAPI_latest.Caching
|
|
|
|
|
|
2025-07-29 19:17:22 -07:00
|
|
|
ComfyAPI = ComfyAPI_latest
|
|
|
|
|
|
|
|
|
|
# Create a synchronous version of the API
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
import comfy_api.latest.generated.ComfyAPISyncStub # type: ignore
|
|
|
|
|
|
2025-12-16 02:01:10 +02:00
|
|
|
ComfyAPISync: type[comfy_api.latest.generated.ComfyAPISyncStub.ComfyAPISyncStub]
|
2025-07-29 19:17:22 -07:00
|
|
|
ComfyAPISync = create_sync_class(ComfyAPI_latest)
|
|
|
|
|
|
2025-10-14 09:55:56 +03:00
|
|
|
# create new aliases for io and ui
|
|
|
|
|
IO = io
|
|
|
|
|
UI = ui
|
2025-10-10 01:15:03 +03:00
|
|
|
|
2025-07-29 19:17:22 -07:00
|
|
|
__all__ = [
|
|
|
|
|
"ComfyAPI",
|
|
|
|
|
"ComfyAPISync",
|
|
|
|
|
"Input",
|
|
|
|
|
"InputImpl",
|
|
|
|
|
"Types",
|
2026-03-12 21:17:50 -07:00
|
|
|
"Caching",
|
2025-07-31 15:02:12 -07:00
|
|
|
"ComfyExtension",
|
2025-10-10 01:15:03 +03:00
|
|
|
"io",
|
2025-10-14 09:55:56 +03:00
|
|
|
"IO",
|
2025-10-10 01:15:03 +03:00
|
|
|
"ui",
|
2025-10-14 09:55:56 +03:00
|
|
|
"UI",
|
2025-07-29 19:17:22 -07:00
|
|
|
]
|