|
|
|
from .api_registry import ( |
|
ComfyAPIBase as ComfyAPIBase, |
|
ComfyAPIWithVersion as ComfyAPIWithVersion, |
|
register_versions as register_versions, |
|
get_all_versions as get_all_versions, |
|
) |
|
|
|
import asyncio |
|
from dataclasses import asdict |
|
from typing import Callable, Optional |
|
|
|
|
|
def first_real_override(cls: type, name: str, *, base: type=None) -> Optional[Callable]: |
|
"""Return the *callable* override of `name` visible on `cls`, or None if every |
|
implementation up to (and including) `base` is the placeholder defined on `base`. |
|
|
|
If base is not provided, it will assume cls has a GET_BASE_CLASS |
|
""" |
|
if base is None: |
|
if not hasattr(cls, "GET_BASE_CLASS"): |
|
raise ValueError("base is required if cls does not have a GET_BASE_CLASS; is this a valid ComfyNode subclass?") |
|
base = cls.GET_BASE_CLASS() |
|
base_attr = getattr(base, name, None) |
|
if base_attr is None: |
|
return None |
|
base_func = base_attr.__func__ |
|
for c in cls.mro(): |
|
if c is base: |
|
break |
|
if name in c.__dict__: |
|
func = getattr(c, name).__func__ |
|
if func is not base_func: |
|
return getattr(cls, name) |
|
return None |
|
|
|
|
|
class _ComfyNodeInternal: |
|
"""Class that all V3-based APIs inherit from for ComfyNode. |
|
|
|
This is intended to only be referenced within execution.py, as it has to handle all V3 APIs going forward.""" |
|
@classmethod |
|
def GET_NODE_INFO_V1(cls): |
|
... |
|
|
|
|
|
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)) |
|
|
|
def prune_dict(d: dict): |
|
return {k: v for k,v in d.items() if v is not None} |
|
|
|
|
|
def is_class(obj): |
|
''' |
|
Returns True if is a class type. |
|
Returns False if is a class instance. |
|
''' |
|
return isinstance(obj, type) |
|
|
|
|
|
def copy_class(cls: type) -> type: |
|
''' |
|
Copy a class and its attributes. |
|
''' |
|
if cls is None: |
|
return None |
|
cls_dict = { |
|
k: v for k, v in cls.__dict__.items() |
|
if k not in ('__dict__', '__weakref__', '__module__', '__doc__') |
|
} |
|
|
|
new_cls = type( |
|
cls.__name__, |
|
(cls,), |
|
cls_dict |
|
) |
|
|
|
new_cls.__module__ = cls.__module__ |
|
new_cls.__doc__ = cls.__doc__ |
|
return new_cls |
|
|
|
|
|
class classproperty(object): |
|
def __init__(self, f): |
|
self.f = f |
|
def __get__(self, obj, owner): |
|
return self.f(owner) |
|
|
|
|
|
|
|
def shallow_clone_class(cls, new_name=None): |
|
''' |
|
Shallow clone a class while preserving super() functionality. |
|
''' |
|
new_name = new_name or f"{cls.__name__}Clone" |
|
|
|
new_bases = (cls,) + cls.__bases__ |
|
return type(new_name, new_bases, dict(cls.__dict__)) |
|
|
|
|
|
def lock_class(cls): |
|
''' |
|
Lock a class so that its top-levelattributes cannot be modified. |
|
''' |
|
|
|
def locked_instance_setattr(self, name, value): |
|
raise AttributeError( |
|
f"Cannot set attribute '{name}' on immutable instance of {type(self).__name__}" |
|
) |
|
|
|
class LockedMeta(type(cls)): |
|
def __setattr__(cls_, name, value): |
|
raise AttributeError( |
|
f"Cannot modify class attribute '{name}' on locked class '{cls_.__name__}'" |
|
) |
|
|
|
locked_dict = dict(cls.__dict__) |
|
locked_dict['__setattr__'] = locked_instance_setattr |
|
|
|
return LockedMeta(cls.__name__, cls.__bases__, locked_dict) |
|
|
|
|
|
def make_locked_method_func(type_obj, func, class_clone): |
|
""" |
|
Returns a function that, when called with **inputs, will execute: |
|
getattr(type_obj, func).__func__(lock_class(class_clone), **inputs) |
|
|
|
Supports both synchronous and asynchronous methods. |
|
""" |
|
locked_class = lock_class(class_clone) |
|
method = getattr(type_obj, func).__func__ |
|
|
|
|
|
if asyncio.iscoroutinefunction(method): |
|
async def wrapped_async_func(**inputs): |
|
return await method(locked_class, **inputs) |
|
return wrapped_async_func |
|
else: |
|
def wrapped_func(**inputs): |
|
return method(locked_class, **inputs) |
|
return wrapped_func |
|
|