mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-07-27 08:16:44 +00:00
Moved helper functions into internal.__init__.py instead of in io.helpers.py as the functions will likely stay the same across different revisions of v3, move helper functions out of io.py to clean up the file a bit, remove Serialization class as not needed at the moment, fix ComfyNodeInternal inherting from ABC breaking lock_class function by removing ABC parent; will need better solution later
This commit is contained in:
parent
f8b7170103
commit
95289b3952
@ -1,10 +1,115 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import asdict
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
class ComfyNodeInternal(ABC):
|
|
||||||
|
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(): # NodeB, NodeA, ComfyNodeV3, object …
|
||||||
|
if c is base: # reached the placeholder – we're done
|
||||||
|
break
|
||||||
|
if name in c.__dict__: # first class that *defines* the attr
|
||||||
|
func = getattr(c, name).__func__
|
||||||
|
if func is not base_func: # real override
|
||||||
|
return getattr(cls, name) # bound to *cls*
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyNodeInternal:
|
||||||
"""Class that all V3-based APIs inherit from for ComfyNode.
|
"""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."""
|
This is intended to only be referenced within execution.py, as it has to handle all V3 APIs going forward."""
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
|
||||||
def GET_NODE_INFO_V1(cls):
|
def GET_NODE_INFO_V1(cls):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
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 class
|
||||||
|
new_cls = type(
|
||||||
|
cls.__name__,
|
||||||
|
(cls,),
|
||||||
|
cls_dict
|
||||||
|
)
|
||||||
|
# metadata preservation
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: this was ai generated and validated by hand
|
||||||
|
def shallow_clone_class(cls, new_name=None):
|
||||||
|
'''
|
||||||
|
Shallow clone a class.
|
||||||
|
'''
|
||||||
|
return type(
|
||||||
|
new_name or f"{cls.__name__}Clone",
|
||||||
|
cls.__bases__,
|
||||||
|
dict(cls.__dict__)
|
||||||
|
)
|
||||||
|
|
||||||
|
# NOTE: this was ai generated and validated by hand
|
||||||
|
def lock_class(cls):
|
||||||
|
'''
|
||||||
|
Lock a class so that its top-levelattributes cannot be modified.
|
||||||
|
'''
|
||||||
|
# Locked instance __setattr__
|
||||||
|
def locked_instance_setattr(self, name, value):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Cannot set attribute '{name}' on immutable instance of {type(self).__name__}"
|
||||||
|
)
|
||||||
|
# Locked metaclass
|
||||||
|
class LockedMeta(type(cls)):
|
||||||
|
def __setattr__(cls_, name, value):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Cannot modify class attribute '{name}' on locked class '{cls_.__name__}'"
|
||||||
|
)
|
||||||
|
# Rebuild class with locked behavior
|
||||||
|
locked_dict = dict(cls.__dict__)
|
||||||
|
locked_dict['__setattr__'] = locked_instance_setattr
|
||||||
|
|
||||||
|
return LockedMeta(cls.__name__, cls.__bases__, locked_dict)
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
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(): # NodeB, NodeA, ComfyNodeV3, object …
|
|
||||||
if c is base: # reached the placeholder – we're done
|
|
||||||
break
|
|
||||||
if name in c.__dict__: # first class that *defines* the attr
|
|
||||||
func = getattr(c, name).__func__
|
|
||||||
if func is not base_func: # real override
|
|
||||||
return getattr(cls, name) # bound to *cls*
|
|
||||||
return None
|
|
@ -6,7 +6,6 @@ from collections import Counter
|
|||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, Literal, TypedDict, TypeVar
|
from typing import Any, Callable, Literal, TypedDict, TypeVar
|
||||||
from comfy_api.v3.helpers import first_real_override
|
|
||||||
|
|
||||||
# used for type hinting
|
# used for type hinting
|
||||||
import torch
|
import torch
|
||||||
@ -22,7 +21,8 @@ from comfy.samplers import CFGGuider, Sampler
|
|||||||
from comfy.sd import CLIP, VAE
|
from comfy.sd import CLIP, VAE
|
||||||
from comfy.sd import StyleModel as StyleModel_
|
from comfy.sd import StyleModel as StyleModel_
|
||||||
from comfy_api.input import VideoInput
|
from comfy_api.input import VideoInput
|
||||||
from comfy_api.internal import ComfyNodeInternal
|
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_execution.graph import ExecutionBlocker
|
||||||
|
|
||||||
@ -69,36 +69,6 @@ class RemoteOptions:
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
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 class
|
|
||||||
new_cls = type(
|
|
||||||
cls.__name__,
|
|
||||||
(cls,),
|
|
||||||
cls_dict
|
|
||||||
)
|
|
||||||
# metadata preservation
|
|
||||||
new_cls.__module__ = cls.__module__
|
|
||||||
new_cls.__doc__ = cls.__doc__
|
|
||||||
return new_cls
|
|
||||||
|
|
||||||
|
|
||||||
class NumberDisplay(str, Enum):
|
class NumberDisplay(str, Enum):
|
||||||
number = "number"
|
number = "number"
|
||||||
slider = "slider"
|
slider = "slider"
|
||||||
@ -999,13 +969,6 @@ class NodeInfoV3:
|
|||||||
experimental: bool=None
|
experimental: bool=None
|
||||||
api_node: bool=None
|
api_node: bool=None
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SchemaV3:
|
class SchemaV3:
|
||||||
@ -1179,60 +1142,6 @@ class SchemaV3:
|
|||||||
)
|
)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
class Serializer:
|
|
||||||
def __init_subclass__(cls, io_type: str, **kwargs):
|
|
||||||
cls.io_type = io_type
|
|
||||||
super().__init_subclass__(**kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def serialize(cls, o: Any) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, s: str) -> Any:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class classproperty(object):
|
|
||||||
def __init__(self, f):
|
|
||||||
self.f = f
|
|
||||||
def __get__(self, obj, owner):
|
|
||||||
return self.f(owner)
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE: this was ai generated and validated by hand
|
|
||||||
def shallow_clone_class(cls, new_name=None):
|
|
||||||
'''
|
|
||||||
Shallow clone a class.
|
|
||||||
'''
|
|
||||||
return type(
|
|
||||||
new_name or f"{cls.__name__}Clone",
|
|
||||||
cls.__bases__,
|
|
||||||
dict(cls.__dict__)
|
|
||||||
)
|
|
||||||
|
|
||||||
# NOTE: this was ai generated and validated by hand
|
|
||||||
def lock_class(cls):
|
|
||||||
'''
|
|
||||||
Lock a class so that its top-levelattributes cannot be modified.
|
|
||||||
'''
|
|
||||||
# Locked instance __setattr__
|
|
||||||
def locked_instance_setattr(self, name, value):
|
|
||||||
raise AttributeError(
|
|
||||||
f"Cannot set attribute '{name}' on immutable instance of {type(self).__name__}"
|
|
||||||
)
|
|
||||||
# Locked metaclass
|
|
||||||
class LockedMeta(type(cls)):
|
|
||||||
def __setattr__(cls_, name, value):
|
|
||||||
raise AttributeError(
|
|
||||||
f"Cannot modify class attribute '{name}' on locked class '{cls_.__name__}'"
|
|
||||||
)
|
|
||||||
# Rebuild class with locked behavior
|
|
||||||
locked_dict = dict(cls.__dict__)
|
|
||||||
locked_dict['__setattr__'] = locked_instance_setattr
|
|
||||||
|
|
||||||
return LockedMeta(cls.__name__, cls.__bases__, locked_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_dict_v1(i: InputV3, input: dict):
|
def add_to_dict_v1(i: InputV3, input: dict):
|
||||||
key = "optional" if i.optional else "required"
|
key = "optional" if i.optional else "required"
|
||||||
@ -1300,10 +1209,6 @@ class _ComfyNodeBaseInternal(ComfyNodeInternal):
|
|||||||
self.local_resources: ResourcesLocal = None
|
self.local_resources: ResourcesLocal = None
|
||||||
self.__class__.VALIDATE_CLASS()
|
self.__class__.VALIDATE_CLASS()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def GET_SERIALIZERS(cls) -> list[Serializer]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def GET_BASE_CLASS(cls):
|
def GET_BASE_CLASS(cls):
|
||||||
return _ComfyNodeBaseInternal
|
return _ComfyNodeBaseInternal
|
||||||
|
12
execution.py
12
execution.py
@ -28,8 +28,8 @@ from comfy_execution.graph import (
|
|||||||
)
|
)
|
||||||
from comfy_execution.graph_utils import GraphBuilder, is_link
|
from comfy_execution.graph_utils import GraphBuilder, is_link
|
||||||
from comfy_execution.validation import validate_node_input
|
from comfy_execution.validation import validate_node_input
|
||||||
from comfy_api.internal import ComfyNodeInternal
|
from comfy_api.internal import ComfyNodeInternal, lock_class, first_real_override
|
||||||
from comfy_api.v3 import io, helpers
|
from comfy_api.v3 import io
|
||||||
|
|
||||||
|
|
||||||
class ExecutionResult(Enum):
|
class ExecutionResult(Enum):
|
||||||
@ -55,7 +55,7 @@ class IsChangedCache:
|
|||||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||||
has_is_changed = False
|
has_is_changed = False
|
||||||
is_changed_name = None
|
is_changed_name = None
|
||||||
if issubclass(class_def, ComfyNodeInternal) and helpers.first_real_override(class_def, "fingerprint_inputs") is not None:
|
if issubclass(class_def, ComfyNodeInternal) and first_real_override(class_def, "fingerprint_inputs") is not None:
|
||||||
has_is_changed = True
|
has_is_changed = True
|
||||||
is_changed_name = "fingerprint_inputs"
|
is_changed_name = "fingerprint_inputs"
|
||||||
elif hasattr(class_def, "IS_CHANGED"):
|
elif hasattr(class_def, "IS_CHANGED"):
|
||||||
@ -256,7 +256,7 @@ def _map_node_over_list(obj, input_data_all, func, allow_interrupt=False, execut
|
|||||||
dynamic_list.append(real_inputs.pop(d.id, None))
|
dynamic_list.append(real_inputs.pop(d.id, None))
|
||||||
dynamic_list = [x for x in dynamic_list if x is not None]
|
dynamic_list = [x for x in dynamic_list if x is not None]
|
||||||
inputs = {**real_inputs, add_key: dynamic_list}
|
inputs = {**real_inputs, add_key: dynamic_list}
|
||||||
results.append(getattr(type_obj, func).__func__(io.lock_class(class_clone), **inputs))
|
results.append(getattr(type_obj, func).__func__(lock_class(class_clone), **inputs))
|
||||||
# V1
|
# V1
|
||||||
else:
|
else:
|
||||||
results.append(getattr(obj, func)(**inputs))
|
results.append(getattr(obj, func)(**inputs))
|
||||||
@ -413,7 +413,7 @@ def execute(server, dynprompt, caches, current_item, extra_data, executed, promp
|
|||||||
caches.objects.set(unique_id, obj)
|
caches.objects.set(unique_id, obj)
|
||||||
|
|
||||||
if issubclass(class_def, ComfyNodeInternal):
|
if issubclass(class_def, ComfyNodeInternal):
|
||||||
lazy_status_present = helpers.first_real_override(class_def, "check_lazy_status") is not None
|
lazy_status_present = first_real_override(class_def, "check_lazy_status") is not None
|
||||||
else:
|
else:
|
||||||
lazy_status_present = getattr(obj, "check_lazy_status", None) is not None
|
lazy_status_present = getattr(obj, "check_lazy_status", None) is not None
|
||||||
if lazy_status_present:
|
if lazy_status_present:
|
||||||
@ -677,7 +677,7 @@ def validate_inputs(prompt, item, validated):
|
|||||||
validate_has_kwargs = False
|
validate_has_kwargs = False
|
||||||
if issubclass(obj_class, ComfyNodeInternal):
|
if issubclass(obj_class, ComfyNodeInternal):
|
||||||
validate_function_name = "validate_inputs"
|
validate_function_name = "validate_inputs"
|
||||||
validate_function = helpers.first_real_override(obj_class, validate_function_name)
|
validate_function = first_real_override(obj_class, validate_function_name)
|
||||||
else:
|
else:
|
||||||
validate_function_name = "VALIDATE_INPUTS"
|
validate_function_name = "VALIDATE_INPUTS"
|
||||||
validate_function = getattr(obj_class, validate_function_name, None)
|
validate_function = getattr(obj_class, validate_function_name, None)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user