Compare commits

...

6 Commits

Author SHA1 Message Date
Jedrzej Kosinski
b1b1429b43 Make validate_inputs validate combo input correctly
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
2025-12-14 03:37:37 -08:00
Jedrzej Kosinski
308ae94c66 get_dynamic() on DynamicInput/Output was not doing anything anymore, so removed it 2025-12-14 03:23:46 -08:00
Jedrzej Kosinski
cb582ab299 Remove expand_schema_for_dynamic left over on DynamicInput class 2025-12-14 03:20:15 -08:00
Jedrzej Kosinski
364b9b673b Remove stray code that made it in 2025-12-14 03:12:48 -08:00
Jedrzej Kosinski
0d9364e942 Natively support AnyType (*) without __ne__ hacks 2025-12-14 02:41:45 -08:00
Jedrzej Kosinski
a4226dbfb0 Removed redundant code - dynamic stuff no longer happens in OOP way 2025-12-14 01:37:02 -08:00
3 changed files with 26 additions and 58 deletions

View File

@ -77,16 +77,6 @@ class NumberDisplay(str, Enum):
slider = "slider"
class _StringIOType(str):
def __ne__(self, value: object) -> bool:
if self == "*" or value == "*":
return False
if not isinstance(value, str):
return True
a = frozenset(self.split(","))
b = frozenset(value.split(","))
return not (b.issubset(a) or a.issubset(b))
class _ComfyType(ABC):
Type = Any
io_type: str = None
@ -126,8 +116,7 @@ def comfytype(io_type: str, **kwargs):
new_cls.__module__ = cls.__module__
new_cls.__doc__ = cls.__doc__
# assign ComfyType attributes, if needed
# NOTE: use __ne__ trick for io_type (see node_typing.IO.__ne__ for details)
new_cls.io_type = _StringIOType(io_type)
new_cls.io_type = io_type
if hasattr(new_cls, "Input") and new_cls.Input is not None:
new_cls.Input.Parent = new_cls
if hasattr(new_cls, "Output") and new_cls.Output is not None:
@ -186,7 +175,7 @@ class Input(_IO_V3):
}) | prune_dict(self.extra_dict)
def get_io_type(self):
return _StringIOType(self.io_type)
return self.io_type
def get_all(self) -> list[Input]:
return [self]
@ -879,25 +868,17 @@ class DynamicInput(Input, ABC):
'''
Abstract class for dynamic input registration.
'''
def get_dynamic(self) -> list[Input]:
return []
def expand_schema_for_dynamic(self, d: dict[str, Any], live_inputs: dict[str, Any], curr_prefix: list[str] | None=None):
pass
pass
class DynamicOutput(Output, ABC):
'''
Abstract class for dynamic output registration.
'''
def __init__(self, id: str=None, display_name: str=None, tooltip: str=None,
is_output_list=False):
super().__init__(id, display_name, tooltip, is_output_list)
pass
def get_dynamic(self) -> list[Output]:
return []
def handle_prefix(prefix_list: list | None, id: str | None = None) -> list[str]:
def handle_prefix(prefix_list: list[str] | None, id: str | None = None) -> list[str]:
if prefix_list is None:
prefix_list = []
if id is not None:
@ -991,9 +972,6 @@ class Autogrow(ComfyTypeI):
"template": self.template.as_dict(),
})
def get_dynamic(self) -> list[Input]:
return self.template.get_all()
def get_all(self) -> list[Input]:
return [self] + self.template.get_all()
@ -1055,9 +1033,6 @@ class DynamicCombo(ComfyTypeI):
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
self.options = options
def get_dynamic(self) -> list[Input]:
return [input for option in self.options for input in option.inputs]
def get_all(self) -> list[Input]:
return [self] + [input for option in self.options for input in option.inputs]
@ -1112,9 +1087,6 @@ class DynamicSlot(ComfyTypeI):
self.force_input = True
self.slot.force_input = True
def get_dynamic(self) -> list[Input]:
return [self.slot] + self.inputs
def get_all(self) -> list[Input]:
return [self.slot] + self.inputs
@ -1351,10 +1323,9 @@ class Schema:
if output.id is None:
output.id = f"_{i}_{output.io_type}_"
def get_v1_info(self, cls, live_inputs: dict[str, Any]=None) -> NodeInfoV1:
# NOTE: live_inputs will not be used anymore very soon and this will be done another way
def get_v1_info(self, cls) -> NodeInfoV1:
# get V1 inputs
input = create_input_dict_v1(self.inputs, live_inputs)
input = create_input_dict_v1(self.inputs)
if self.hidden:
for hidden in self.hidden:
input.setdefault("hidden", {})[hidden.name] = (hidden.value,)
@ -1468,33 +1439,20 @@ def parse_class_inputs(out_dict: dict[str, Any], live_inputs: dict[str, Any], cu
if curr_prefix:
out_dict["dynamic_paths"][finalized_id] = finalized_id
def create_input_dict_v1(inputs: list[Input], live_inputs: dict[str, Any]=None) -> dict:
def create_input_dict_v1(inputs: list[Input]) -> dict:
input = {
"required": {}
}
add_to_input_dict_v1(input, inputs, live_inputs)
for i in inputs:
add_to_dict_v1(i, input)
return input
def add_to_input_dict_v1(d: dict[str, Any], inputs: list[Input], live_inputs: dict[str, Any]=None, curr_prefix: list[str] | None=None):
for i in inputs:
if isinstance(i, DynamicInput):
add_to_dict_v1(i, d, curr_prefix=curr_prefix)
if live_inputs is not None:
i.expand_schema_for_dynamic(d, live_inputs, curr_prefix)
else:
add_to_dict_v1(i, d, curr_prefix=curr_prefix)
def add_to_dict_v1(i: Input, d: dict, dynamic_dict: dict=None, curr_prefix: list[str] | None=None):
def add_to_dict_v1(i: Input, d: dict):
key = "optional" if i.optional else "required"
as_dict = i.as_dict()
# for v1, we don't want to include the optional key
as_dict.pop("optional", None)
if dynamic_dict is None:
value = (i.get_io_type(), as_dict)
else:
value = (i.get_io_type(), as_dict, dynamic_dict)
actual_id = finalize_prefix(curr_prefix, i.id)
d.setdefault(key, {})[actual_id] = value
d.setdefault(key, {})[i.id] = (i.get_io_type(), as_dict)
def add_to_dict_v3(io: Input | Output, d: dict):
d[io.id] = (io.get_io_type(), io.as_dict())
@ -1600,8 +1558,6 @@ class _ComfyNodeBaseInternal(_ComfyNodeInternal):
@final
@classmethod
def EXECUTE_NORMALIZED(cls, *args, **kwargs) -> NodeOutput:
_args = args
_kwargs = kwargs
to_return = cls.execute(*args, **kwargs)
if to_return is None:
to_return = NodeOutput()

View File

@ -21,9 +21,14 @@ def validate_node_input(
"""
# If the types are exactly the same, we can return immediately
# Use pre-union behaviour: inverse of `__ne__`
# NOTE: this lets legacy '*' Any types work that override the __ne__ method of the str class.
if not received_type != input_type:
return True
# If one of the types is '*', we can return True immediately; this is the 'Any' type.
if received_type == IO.AnyType.io_type or input_type == IO.AnyType.io_type:
return True
# If the received type or input_type is a MatchType, we can return True immediately;
# validation for this is handled by the frontend
if received_type == IO.MatchType.io_type or input_type == IO.MatchType.io_type:
@ -42,6 +47,10 @@ def validate_node_input(
received_types = set(t.strip() for t in received_type.split(","))
input_types = set(t.strip() for t in input_type.split(","))
# If any of the types is '*', we can return True immediately; this is the 'Any' type.
if IO.AnyType.io_type in received_types or IO.AnyType.io_type in input_types:
return True
if strict:
# In strict mode, all received types must be in the input types
return received_types.issubset(input_types)

View File

@ -914,8 +914,11 @@ async def validate_inputs(prompt_id, prompt, item, validated):
errors.append(error)
continue
if isinstance(input_type, list):
combo_options = input_type
if isinstance(input_type, list) or input_type == io.Combo.io_type:
if input_type == io.Combo.io_type:
combo_options = extra_info.get("options", [])
else:
combo_options = input_type
if val not in combo_options:
input_config = info
list_info = ""