From b45a110de67e761380f5e0ac52d77b84aaa4ba10 Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Fri, 25 Jul 2025 14:00:47 -0700 Subject: [PATCH 1/4] Reorganize types a bit The input types, input impls, and utility types are now all available in the versioned API. See the change in `comfy_extras/nodes_video.py` for an example of their usage. --- comfy_api/input/__init__.py | 16 +++++++- comfy_api/input/basic_types.py | 14 ++++++- comfy_api/input/video_types.py | 6 ++- comfy_api/input_impl/__init__.py | 7 +++- comfy_api/input_impl/video_types.py | 2 +- comfy_api/latest/__init__.py | 38 ++++++++++++++----- .../latest/{input => _input}/__init__.py | 0 .../latest/{input => _input}/basic_types.py | 0 .../latest/{input => _input}/video_types.py | 0 .../{input_impl => _input_impl}/__init__.py | 0 .../video_types.py | 4 +- comfy_api/latest/{util => _util}/__init__.py | 0 .../latest/{util => _util}/video_types.py | 2 +- comfy_api/util.py | 8 +++- comfy_api/util/__init__.py | 8 +++- comfy_api/util/video_types.py | 12 +++++- comfy_api/v0_0_1/__init__.py | 20 +++++++++- comfy_api/v0_0_2/__init__.py | 24 +++++++++++- comfy_extras/nodes_video.py | 22 +++++------ 19 files changed, 146 insertions(+), 37 deletions(-) rename comfy_api/latest/{input => _input}/__init__.py (100%) rename comfy_api/latest/{input => _input}/basic_types.py (100%) rename comfy_api/latest/{input => _input}/video_types.py (100%) rename comfy_api/latest/{input_impl => _input_impl}/__init__.py (100%) rename comfy_api/latest/{input_impl => _input_impl}/video_types.py (98%) rename comfy_api/latest/{util => _util}/__init__.py (100%) rename comfy_api/latest/{util => _util}/video_types.py (95%) diff --git a/comfy_api/input/__init__.py b/comfy_api/input/__init__.py index 08e0eba11..68ff78270 100644 --- a/comfy_api/input/__init__.py +++ b/comfy_api/input/__init__.py @@ -1,2 +1,16 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.input import * # noqa: F403 +from comfy_api.latest._input import ( + ImageInput, + AudioInput, + MaskInput, + LatentInput, + VideoInput, +) + +__all__ = [ + "ImageInput", + "AudioInput", + "MaskInput", + "LatentInput", + "VideoInput", +] diff --git a/comfy_api/input/basic_types.py b/comfy_api/input/basic_types.py index 12e0af4a2..5eadce86a 100644 --- a/comfy_api/input/basic_types.py +++ b/comfy_api/input/basic_types.py @@ -1,2 +1,14 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.input.basic_types import * # noqa: F403 +from comfy_api.latest._input.basic_types import ( + ImageInput, + AudioInput, + MaskInput, + LatentInput, +) + +__all__ = [ + "ImageInput", + "AudioInput", + "MaskInput", + "LatentInput", +] diff --git a/comfy_api/input/video_types.py b/comfy_api/input/video_types.py index 089128e62..9ace78cbc 100644 --- a/comfy_api/input/video_types.py +++ b/comfy_api/input/video_types.py @@ -1,2 +1,6 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.input.video_types import * # noqa: F403 +from comfy_api.latest._input.video_types import VideoInput + +__all__ = [ + "VideoInput", +] diff --git a/comfy_api/input_impl/__init__.py b/comfy_api/input_impl/__init__.py index 07cd7b344..b78ff0c08 100644 --- a/comfy_api/input_impl/__init__.py +++ b/comfy_api/input_impl/__init__.py @@ -1,2 +1,7 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.input_impl import * # noqa: F403 +from comfy_api.latest._input_impl import VideoFromFile, VideoFromComponents + +__all__ = [ + "VideoFromFile", + "VideoFromComponents", +] diff --git a/comfy_api/input_impl/video_types.py b/comfy_api/input_impl/video_types.py index ac0a10124..bd2e56ad5 100644 --- a/comfy_api/input_impl/video_types.py +++ b/comfy_api/input_impl/video_types.py @@ -1,2 +1,2 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.input_impl.video_types import * # noqa: F403 +from comfy_api.latest._input_impl.video_types import * # noqa: F403 diff --git a/comfy_api/latest/__init__.py b/comfy_api/latest/__init__.py index 8738196de..bcf09ffbf 100644 --- a/comfy_api/latest/__init__.py +++ b/comfy_api/latest/__init__.py @@ -4,9 +4,11 @@ from typing import Type, TYPE_CHECKING from comfy_api.internal import ComfyAPIBase from comfy_api.internal.singleton import ProxiedSingleton from comfy_api.internal.async_to_sync import create_sync_class -from comfy_api.latest.input import ImageInput +from comfy_api.latest._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput +from comfy_api.latest._input_impl import VideoFromFile, VideoFromComponents +from comfy_api.latest._util import VideoCodec, VideoContainer, VideoComponents from comfy_execution.utils import get_executing_context -from comfy_execution.progress import get_progress_state +from comfy_execution.progress import get_progress_state, PreviewImageTuple from PIL import Image from comfy.cli_args import args import numpy as np @@ -40,35 +42,51 @@ class ComfyAPI_latest(ComfyAPIBase): raise ValueError("node_id must be provided if not in executing context") # Convert preview_image to PreviewImageTuple if needed - if preview_image is not None: + to_display: PreviewImageTuple | Image.Image | ImageInput | None = preview_image + if to_display is not None: # First convert to PIL Image if needed - if isinstance(preview_image, ImageInput): + 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 = preview_image + 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) - preview_image = Image.fromarray(image_np) + to_display = Image.fromarray(image_np) - if isinstance(preview_image, Image.Image): + if isinstance(to_display, Image.Image): # Detect image format from PIL Image - image_format = preview_image.format if preview_image.format else "JPEG" + 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 - preview_image = (image_format, preview_image, 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=preview_image, + image=to_display, ) execution: Execution +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 ComfyAPI = ComfyAPI_latest diff --git a/comfy_api/latest/input/__init__.py b/comfy_api/latest/_input/__init__.py similarity index 100% rename from comfy_api/latest/input/__init__.py rename to comfy_api/latest/_input/__init__.py diff --git a/comfy_api/latest/input/basic_types.py b/comfy_api/latest/_input/basic_types.py similarity index 100% rename from comfy_api/latest/input/basic_types.py rename to comfy_api/latest/_input/basic_types.py diff --git a/comfy_api/latest/input/video_types.py b/comfy_api/latest/_input/video_types.py similarity index 100% rename from comfy_api/latest/input/video_types.py rename to comfy_api/latest/_input/video_types.py diff --git a/comfy_api/latest/input_impl/__init__.py b/comfy_api/latest/_input_impl/__init__.py similarity index 100% rename from comfy_api/latest/input_impl/__init__.py rename to comfy_api/latest/_input_impl/__init__.py diff --git a/comfy_api/latest/input_impl/video_types.py b/comfy_api/latest/_input_impl/video_types.py similarity index 98% rename from comfy_api/latest/input_impl/video_types.py rename to comfy_api/latest/_input_impl/video_types.py index 86aa6e008..2089307df 100644 --- a/comfy_api/latest/input_impl/video_types.py +++ b/comfy_api/latest/_input_impl/video_types.py @@ -3,13 +3,13 @@ from av.container import InputContainer from av.subtitles.stream import SubtitleStream from fractions import Fraction from typing import Optional -from comfy_api.latest.input import AudioInput, VideoInput +from comfy_api.latest._input import AudioInput, VideoInput import av import io import json import numpy as np import torch -from comfy_api.latest.util import VideoContainer, VideoCodec, VideoComponents +from comfy_api.latest._util import VideoContainer, VideoCodec, VideoComponents def container_to_output_format(container_format: str | None) -> str | None: diff --git a/comfy_api/latest/util/__init__.py b/comfy_api/latest/_util/__init__.py similarity index 100% rename from comfy_api/latest/util/__init__.py rename to comfy_api/latest/_util/__init__.py diff --git a/comfy_api/latest/util/video_types.py b/comfy_api/latest/_util/video_types.py similarity index 95% rename from comfy_api/latest/util/video_types.py rename to comfy_api/latest/_util/video_types.py index ceb937dc2..c3e3d8e3a 100644 --- a/comfy_api/latest/util/video_types.py +++ b/comfy_api/latest/_util/video_types.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from enum import Enum from fractions import Fraction from typing import Optional -from comfy_api.latest.input import ImageInput, AudioInput +from comfy_api.latest._input import ImageInput, AudioInput class VideoCodec(str, Enum): AUTO = "auto" diff --git a/comfy_api/util.py b/comfy_api/util.py index 7d342905d..1aa9606d2 100644 --- a/comfy_api/util.py +++ b/comfy_api/util.py @@ -1,2 +1,8 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.util import * # noqa: F403 +from comfy_api.latest._util import VideoCodec, VideoContainer, VideoComponents + +__all__ = [ + "VideoCodec", + "VideoContainer", + "VideoComponents", +] diff --git a/comfy_api/util/__init__.py b/comfy_api/util/__init__.py index 7d342905d..4c8a89d1e 100644 --- a/comfy_api/util/__init__.py +++ b/comfy_api/util/__init__.py @@ -1,2 +1,8 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.util import * # noqa: F403 +from comfy_api.latest._util import VideoContainer, VideoCodec, VideoComponents + +__all__ = [ + "VideoContainer", + "VideoCodec", + "VideoComponents", +] diff --git a/comfy_api/util/video_types.py b/comfy_api/util/video_types.py index 49fad6ff7..68c780d64 100644 --- a/comfy_api/util/video_types.py +++ b/comfy_api/util/video_types.py @@ -1,2 +1,12 @@ # This file only exists for backwards compatibility. -from comfy_api.latest.util.video_types import * # noqa: F403 +from comfy_api.latest._util.video_types import ( + VideoContainer, + VideoCodec, + VideoComponents, +) + +__all__ = [ + "VideoContainer", + "VideoCodec", + "VideoComponents", +] diff --git a/comfy_api/v0_0_1/__init__.py b/comfy_api/v0_0_1/__init__.py index 94750e966..ab6dc2b42 100644 --- a/comfy_api/v0_0_1/__init__.py +++ b/comfy_api/v0_0_1/__init__.py @@ -1,18 +1,34 @@ -from comfy_api.v0_0_2 import ComfyAPIAdapter_v0_0_2 +from comfy_api.v0_0_2 import ( + ComfyAPIAdapter_v0_0_2, + Input as Input_v0_0_2, + InputImpl as InputImpl_v0_0_2, + Types as Types_v0_0_2, +) from typing import Type, TYPE_CHECKING from comfy_api.internal.async_to_sync import create_sync_class + # This version only exists to serve as a template for future version adapters. # There is no reason anyone should ever use it. class ComfyAPIAdapter_v0_0_1(ComfyAPIAdapter_v0_0_2): VERSION = "0.0.1" STABLE = True +class Input(Input_v0_0_2): + pass + +class InputImpl(InputImpl_v0_0_2): + pass + +class Types(Types_v0_0_2): + pass + ComfyAPI = ComfyAPIAdapter_v0_0_1 # Create a synchronous version of the API if TYPE_CHECKING: - from comfy_api.v0_0_1.generated.ComfyAPISyncStub import ComfyAPISyncStub # type: ignore + from comfy_api.v0_0_1.generated.ComfyAPISyncStub import ComfyAPISyncStub # type: ignore + ComfyAPISync: Type[ComfyAPISyncStub] ComfyAPISync = create_sync_class(ComfyAPIAdapter_v0_0_1) diff --git a/comfy_api/v0_0_2/__init__.py b/comfy_api/v0_0_2/__init__.py index 90ca77e42..1b68bcc97 100644 --- a/comfy_api/v0_0_2/__init__.py +++ b/comfy_api/v0_0_2/__init__.py @@ -1,15 +1,35 @@ -from comfy_api.latest import ComfyAPI_latest +from comfy_api.latest import ( + ComfyAPI_latest, + Input as Input_latest, + InputImpl as InputImpl_latest, + Types as Types_latest, +) from typing import Type, TYPE_CHECKING from comfy_api.internal.async_to_sync import create_sync_class + class ComfyAPIAdapter_v0_0_2(ComfyAPI_latest): VERSION = "0.0.2" STABLE = False + +class Input(Input_latest): + pass + + +class InputImpl(InputImpl_latest): + pass + + +class Types(Types_latest): + pass + + ComfyAPI = ComfyAPIAdapter_v0_0_2 # Create a synchronous version of the API if TYPE_CHECKING: - from comfy_api.v0_0_2.generated.ComfyAPISyncStub import ComfyAPISyncStub # type: ignore + from comfy_api.v0_0_2.generated.ComfyAPISyncStub import ComfyAPISyncStub # type: ignore + ComfyAPISync: Type[ComfyAPISyncStub] ComfyAPISync = create_sync_class(ComfyAPIAdapter_v0_0_2) diff --git a/comfy_extras/nodes_video.py b/comfy_extras/nodes_video.py index 9f87a57ba..969f888b9 100644 --- a/comfy_extras/nodes_video.py +++ b/comfy_extras/nodes_video.py @@ -8,9 +8,7 @@ import json from typing import Optional, Literal from fractions import Fraction from comfy.comfy_types import IO, FileLocator, ComfyNodeABC -from comfy_api.latest.input import ImageInput, AudioInput, VideoInput -from comfy_api.latest.util import VideoContainer, VideoCodec, VideoComponents -from comfy_api.latest.input_impl import VideoFromFile, VideoFromComponents +from comfy_api.latest import Input, InputImpl, Types from comfy.cli_args import args class SaveWEBM: @@ -91,8 +89,8 @@ class SaveVideo(ComfyNodeABC): "required": { "video": (IO.VIDEO, {"tooltip": "The video to save."}), "filename_prefix": ("STRING", {"default": "video/ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}), - "format": (VideoContainer.as_input(), {"default": "auto", "tooltip": "The format to save the video as."}), - "codec": (VideoCodec.as_input(), {"default": "auto", "tooltip": "The codec to use for the video."}), + "format": (Types.VideoContainer.as_input(), {"default": "auto", "tooltip": "The format to save the video as."}), + "codec": (Types.VideoCodec.as_input(), {"default": "auto", "tooltip": "The codec to use for the video."}), }, "hidden": { "prompt": "PROMPT", @@ -108,7 +106,7 @@ class SaveVideo(ComfyNodeABC): CATEGORY = "image/video" DESCRIPTION = "Saves the input images to your ComfyUI output directory." - def save_video(self, video: VideoInput, filename_prefix, format, codec, prompt=None, extra_pnginfo=None): + def save_video(self, video: Input.Video, filename_prefix, format, codec, prompt=None, extra_pnginfo=None): filename_prefix += self.prefix_append width, height = video.get_dimensions() full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( @@ -127,7 +125,7 @@ class SaveVideo(ComfyNodeABC): metadata["prompt"] = prompt if len(metadata) > 0: saved_metadata = metadata - file = f"{filename}_{counter:05}_.{VideoContainer.get_extension(format)}" + file = f"{filename}_{counter:05}_.{Types.VideoContainer.get_extension(format)}" video.save_to( os.path.join(full_output_folder, file), format=format, @@ -163,9 +161,9 @@ class CreateVideo(ComfyNodeABC): CATEGORY = "image/video" DESCRIPTION = "Create a video from images." - def create_video(self, images: ImageInput, fps: float, audio: Optional[AudioInput] = None): - return (VideoFromComponents( - VideoComponents( + def create_video(self, images: Input.Image, fps: float, audio: Optional[Input.Audio] = None): + return (InputImpl.VideoFromComponents( + Types.VideoComponents( images=images, audio=audio, frame_rate=Fraction(fps), @@ -187,7 +185,7 @@ class GetVideoComponents(ComfyNodeABC): CATEGORY = "image/video" DESCRIPTION = "Extracts all components from a video: frames, audio, and framerate." - def get_components(self, video: VideoInput): + def get_components(self, video: Input.Video): components = video.get_components() return (components.images, components.audio, float(components.frame_rate)) @@ -208,7 +206,7 @@ class LoadVideo(ComfyNodeABC): FUNCTION = "load_video" def load_video(self, file): video_path = folder_paths.get_annotated_filepath(file) - return (VideoFromFile(video_path),) + return (InputImpl.VideoFromFile(video_path),) @classmethod def IS_CHANGED(cls, file): From 689db36073ce562f576c5b7aed082aceb5f152bf Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Fri, 25 Jul 2025 14:32:27 -0700 Subject: [PATCH 2/4] Remove the need for `--generate-api-stubs` --- comfy/cli_args.py | 1 - comfy_api/generate_api_stubs.py | 2 +- main.py | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/comfy/cli_args.py b/comfy/cli_args.py index b3bf0fc62..7234a7ba0 100644 --- a/comfy/cli_args.py +++ b/comfy/cli_args.py @@ -153,7 +153,6 @@ parser.add_argument("--disable-metadata", action="store_true", help="Disable sav parser.add_argument("--disable-all-custom-nodes", action="store_true", help="Disable loading all custom nodes.") parser.add_argument("--whitelist-custom-nodes", type=str, nargs='+', default=[], help="Specify custom node folders to load even when --disable-all-custom-nodes is enabled.") parser.add_argument("--disable-api-nodes", action="store_true", help="Disable loading all api nodes.") -parser.add_argument("--generate-api-stubs", action="store_true", help="Generate .pyi stub files for API sync wrappers and exit.") parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.") diff --git a/comfy_api/generate_api_stubs.py b/comfy_api/generate_api_stubs.py index 9d300e2db..604a7eced 100644 --- a/comfy_api/generate_api_stubs.py +++ b/comfy_api/generate_api_stubs.py @@ -10,7 +10,7 @@ import logging import importlib # Add ComfyUI to path so we can import modules -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from comfy_api.internal.async_to_sync import AsyncToSyncConverter from comfy_api.version_list import supported_versions diff --git a/main.py b/main.py index 9217ea8f3..86319f1a7 100644 --- a/main.py +++ b/main.py @@ -22,12 +22,6 @@ if __name__ == "__main__": setup_logger(log_level=args.verbose, use_stdout=args.log_stdout) -# Handle --generate-api-stubs early -if args.generate_api_stubs: - from comfy_api.generate_api_stubs import main as generate_stubs_main - generate_stubs_main() - sys.exit(0) - def apply_custom_paths(): # extra model paths extra_model_paths_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "extra_model_paths.yaml") From b6754d935b314d54cbe3f3f515e5ce3d55052426 Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Fri, 25 Jul 2025 19:24:57 -0700 Subject: [PATCH 3/4] Fix generated stubs differing by Python version --- comfy_api/internal/async_to_sync.py | 79 +++++++++++++++---- comfy_api/latest/__init__.py | 7 ++ .../latest/generated/ComfyAPISyncStub.pyi | 2 +- comfy_api/v0_0_1/__init__.py | 8 ++ .../v0_0_1/generated/ComfyAPISyncStub.pyi | 2 +- comfy_api/v0_0_2/__init__.py | 8 ++ .../v0_0_2/generated/ComfyAPISyncStub.pyi | 2 +- 7 files changed, 88 insertions(+), 20 deletions(-) diff --git a/comfy_api/internal/async_to_sync.py b/comfy_api/internal/async_to_sync.py index f6bf04230..dbc62255c 100644 --- a/comfy_api/internal/async_to_sync.py +++ b/comfy_api/internal/async_to_sync.py @@ -398,14 +398,18 @@ class AsyncToSyncConverter: origin_name = origin_name.split(".")[-1] # Special handling for types.UnionType (Python 3.10+ pipe operator) - if origin_name == "UnionType": + # Convert to old-style Union for compatibility + if str(origin) == "" or origin_name == "UnionType": origin_name = "Union" # Format arguments recursively if args: - formatted_args = [ - cls._format_type_annotation(arg, type_tracker) for arg in args - ] + formatted_args = [] + for arg in args: + # Track each type in the union + if type_tracker: + type_tracker.track_type(arg) + formatted_args.append(cls._format_type_annotation(arg, type_tracker)) return f"{origin_name}[{', '.join(formatted_args)}]" else: return origin_name @@ -489,23 +493,27 @@ class AsyncToSyncConverter: cls, sig: inspect.Signature, skip_self: bool = True, + type_hints: Optional[dict] = None, type_tracker: Optional[TypeTracker] = None, ) -> str: """Format method parameters for stub files.""" params = [] + if type_hints is None: + type_hints = {} for i, (param_name, param) in enumerate(sig.parameters.items()): if i == 0 and param_name == "self" and skip_self: params.append("self") else: - # Get type annotation - type_str = cls._format_type_annotation(param.annotation, type_tracker) + # Get type annotation from type hints if available, otherwise from signature + annotation = type_hints.get(param_name, param.annotation) + type_str = cls._format_type_annotation(annotation, type_tracker) # Get default value default_str = cls._format_parameter_default(param.default) # Combine parameter parts - if param.annotation is inspect.Parameter.empty: + if annotation is inspect.Parameter.empty: params.append(f"{param_name}: Any{default_str}") else: params.append(f"{param_name}: {type_str}{default_str}") @@ -522,14 +530,22 @@ class AsyncToSyncConverter: ) -> str: """Generate a complete method signature for stub files.""" sig = inspect.signature(method) + + # Try to get evaluated type hints to resolve string annotations + try: + from typing import get_type_hints + type_hints = get_type_hints(method) + except Exception: + # Fallback to empty dict if we can't get type hints + type_hints = {} # For async methods, extract the actual return type - return_annotation = sig.return_annotation + return_annotation = type_hints.get('return', sig.return_annotation) if is_async and inspect.iscoroutinefunction(method): return_annotation = cls._extract_coroutine_return_type(return_annotation) - # Format parameters - params_str = cls._format_method_parameters(sig, type_tracker=type_tracker) + # Format parameters with type hints + params_str = cls._format_method_parameters(sig, type_hints=type_hints, type_tracker=type_tracker) # Format return type return_type = cls._format_type_annotation(return_annotation, type_tracker) @@ -556,8 +572,18 @@ class AsyncToSyncConverter: additional_types = [] if module: + # Check if module has __all__ defined + module_all = getattr(module, "__all__", None) + for name, obj in sorted(inspect.getmembers(module)): if isinstance(obj, type): + # Skip if __all__ is defined and this name isn't in it + # unless it's already been tracked as used in type annotations + if module_all is not None and name not in module_all: + # Check if this type was actually used in annotations + if name not in type_tracker.discovered_types: + continue + # Check for NamedTuple if issubclass(obj, tuple) and hasattr(obj, "_fields"): additional_types.append(name) @@ -636,9 +662,17 @@ class AsyncToSyncConverter: try: init_method = getattr(attr, "__init__") init_sig = inspect.signature(init_method) + + # Try to get type hints + try: + from typing import get_type_hints + init_hints = get_type_hints(init_method) + except Exception: + init_hints = {} + # Format parameters params_str = cls._format_method_parameters( - init_sig, type_tracker=type_tracker + init_sig, type_hints=init_hints, type_tracker=type_tracker ) # Add __init__ docstring if available (before the method) if hasattr(init_method, "__doc__") and init_method.__doc__: @@ -790,9 +824,17 @@ class AsyncToSyncConverter: try: init_method = async_class.__init__ init_signature = inspect.signature(init_method) + + # Try to get type hints for __init__ + try: + from typing import get_type_hints + init_hints = get_type_hints(init_method) + except Exception: + init_hints = {} + # Format parameters params_str = cls._format_method_parameters( - init_signature, type_tracker=type_tracker + init_signature, type_hints=init_hints, type_tracker=type_tracker ) # Add __init__ docstring if available (before the method) if hasattr(init_method, "__doc__") and init_method.__doc__: @@ -875,18 +917,21 @@ class AsyncToSyncConverter: for attr_name, attr_type in sorted(all_annotations.items()): for class_name, class_type in class_attributes: # If the class type matches the annotated type - if attr_type == class_type or ( - hasattr(attr_type, "__name__") - and attr_type.__name__ == class_name + if ( + attr_type == class_type + or (hasattr(attr_type, "__name__") and attr_type.__name__ == class_name) + or (isinstance(attr_type, str) and attr_type == class_name) ): attribute_mappings[class_name] = attr_name # Remove the extra checking - annotations should be sufficient # Add the attribute declarations with proper names - for class_name, _ in class_attributes: - # Use the attribute name if found in mappings, otherwise use class name + for class_name, class_type in class_attributes: + # Check if there's a mapping from annotation attr_name = attribute_mappings.get(class_name, class_name) + # Use the annotation name if it exists, even if the attribute doesn't exist yet + # This is because the attribute might be created at runtime stub_content.append(f" {attr_name}: {class_name}Sync") stub_content.append("") # Add a final newline diff --git a/comfy_api/latest/__init__.py b/comfy_api/latest/__init__.py index bcf09ffbf..e1f3a3655 100644 --- a/comfy_api/latest/__init__.py +++ b/comfy_api/latest/__init__.py @@ -97,3 +97,10 @@ if TYPE_CHECKING: ComfyAPISync: Type[comfy_api.latest.generated.ComfyAPISyncStub.ComfyAPISyncStub] ComfyAPISync = create_sync_class(ComfyAPI_latest) +__all__ = [ + "ComfyAPI", + "ComfyAPISync", + "Input", + "InputImpl", + "Types", +] diff --git a/comfy_api/latest/generated/ComfyAPISyncStub.pyi b/comfy_api/latest/generated/ComfyAPISyncStub.pyi index 280893ddb..525c074dd 100644 --- a/comfy_api/latest/generated/ComfyAPISyncStub.pyi +++ b/comfy_api/latest/generated/ComfyAPISyncStub.pyi @@ -15,6 +15,6 @@ class ComfyAPISyncStub: Migration from previous API: comfy.utils.PROGRESS_BAR_HOOK """ - def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[tuple[str, Image, Union[int, None]], Image, Tensor, None] = None) -> None: ... + def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[Image, Tensor, None] = None, ignore_size_limit: bool = False) -> None: ... execution: ExecutionSync diff --git a/comfy_api/v0_0_1/__init__.py b/comfy_api/v0_0_1/__init__.py index ab6dc2b42..93608771d 100644 --- a/comfy_api/v0_0_1/__init__.py +++ b/comfy_api/v0_0_1/__init__.py @@ -32,3 +32,11 @@ if TYPE_CHECKING: ComfyAPISync: Type[ComfyAPISyncStub] ComfyAPISync = create_sync_class(ComfyAPIAdapter_v0_0_1) + +__all__ = [ + "ComfyAPI", + "ComfyAPISync", + "Input", + "InputImpl", + "Types", +] diff --git a/comfy_api/v0_0_1/generated/ComfyAPISyncStub.pyi b/comfy_api/v0_0_1/generated/ComfyAPISyncStub.pyi index c31461f17..270030324 100644 --- a/comfy_api/v0_0_1/generated/ComfyAPISyncStub.pyi +++ b/comfy_api/v0_0_1/generated/ComfyAPISyncStub.pyi @@ -15,6 +15,6 @@ class ComfyAPISyncStub: Migration from previous API: comfy.utils.PROGRESS_BAR_HOOK """ - def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[tuple[str, Image, Union[int, None]], Image, Tensor, None] = None) -> None: ... + def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[Image, Tensor, None] = None, ignore_size_limit: bool = False) -> None: ... execution: ExecutionSync diff --git a/comfy_api/v0_0_2/__init__.py b/comfy_api/v0_0_2/__init__.py index 1b68bcc97..ea83833fb 100644 --- a/comfy_api/v0_0_2/__init__.py +++ b/comfy_api/v0_0_2/__init__.py @@ -33,3 +33,11 @@ if TYPE_CHECKING: ComfyAPISync: Type[ComfyAPISyncStub] ComfyAPISync = create_sync_class(ComfyAPIAdapter_v0_0_2) + +__all__ = [ + "ComfyAPI", + "ComfyAPISync", + "Input", + "InputImpl", + "Types", +] diff --git a/comfy_api/v0_0_2/generated/ComfyAPISyncStub.pyi b/comfy_api/v0_0_2/generated/ComfyAPISyncStub.pyi index b3ad0e3df..7fcec685e 100644 --- a/comfy_api/v0_0_2/generated/ComfyAPISyncStub.pyi +++ b/comfy_api/v0_0_2/generated/ComfyAPISyncStub.pyi @@ -15,6 +15,6 @@ class ComfyAPISyncStub: Migration from previous API: comfy.utils.PROGRESS_BAR_HOOK """ - def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[tuple[str, Image, Union[int, None]], Image, Tensor, None] = None) -> None: ... + def set_progress(self, value: float, max_value: float, node_id: Union[str, None] = None, preview_image: Union[Image, Tensor, None] = None, ignore_size_limit: bool = False) -> None: ... execution: ExecutionSync From 2f0cc45682508db2804a76985f269b1acb290caa Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Fri, 25 Jul 2025 19:38:23 -0700 Subject: [PATCH 4/4] Fix ruff formatting issues --- comfy_api/internal/async_to_sync.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/comfy_api/internal/async_to_sync.py b/comfy_api/internal/async_to_sync.py index dbc62255c..f5f805a62 100644 --- a/comfy_api/internal/async_to_sync.py +++ b/comfy_api/internal/async_to_sync.py @@ -530,7 +530,7 @@ class AsyncToSyncConverter: ) -> str: """Generate a complete method signature for stub files.""" sig = inspect.signature(method) - + # Try to get evaluated type hints to resolve string annotations try: from typing import get_type_hints @@ -574,7 +574,7 @@ class AsyncToSyncConverter: if module: # Check if module has __all__ defined module_all = getattr(module, "__all__", None) - + for name, obj in sorted(inspect.getmembers(module)): if isinstance(obj, type): # Skip if __all__ is defined and this name isn't in it @@ -583,7 +583,7 @@ class AsyncToSyncConverter: # Check if this type was actually used in annotations if name not in type_tracker.discovered_types: continue - + # Check for NamedTuple if issubclass(obj, tuple) and hasattr(obj, "_fields"): additional_types.append(name) @@ -662,14 +662,14 @@ class AsyncToSyncConverter: try: init_method = getattr(attr, "__init__") init_sig = inspect.signature(init_method) - + # Try to get type hints try: from typing import get_type_hints init_hints = get_type_hints(init_method) except Exception: init_hints = {} - + # Format parameters params_str = cls._format_method_parameters( init_sig, type_hints=init_hints, type_tracker=type_tracker @@ -824,14 +824,14 @@ class AsyncToSyncConverter: try: init_method = async_class.__init__ init_signature = inspect.signature(init_method) - + # Try to get type hints for __init__ try: from typing import get_type_hints init_hints = get_type_hints(init_method) except Exception: init_hints = {} - + # Format parameters params_str = cls._format_method_parameters( init_signature, type_hints=init_hints, type_tracker=type_tracker @@ -918,7 +918,7 @@ class AsyncToSyncConverter: for class_name, class_type in class_attributes: # If the class type matches the annotated type if ( - attr_type == class_type + attr_type == class_type or (hasattr(attr_type, "__name__") and attr_type.__name__ == class_name) or (isinstance(attr_type, str) and attr_type == class_name) ):