mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-11 08:57:22 +08:00
TypeResolver: trim verbose comments and docstrings
Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
parent
15f55f1b24
commit
004ac8820b
@ -1357,12 +1357,9 @@ class Range(ComfyTypeIO):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# Signature: (out_dict, live_inputs, value, input_type, curr_prefix, live_input_types)
|
# Signature: (out_dict, live_inputs, value, input_type, curr_prefix, live_input_types).
|
||||||
# live_input_types is an optional {input_id: resolved_io_type} dict produced
|
# live_input_types is {input_id: resolved_io_type} from TypeResolver; existing
|
||||||
# by comfy_execution.type_resolver.TypeResolver. Existing dynamic-input
|
# expanders ignore it, future type-discriminated ones use it as discriminator.
|
||||||
# implementations may ignore it; future type-discriminated dynamic inputs
|
|
||||||
# (e.g. a per-connected-type variant of DynamicCombo) use it as their
|
|
||||||
# discriminator instead of literal live_inputs values.
|
|
||||||
_DynamicInputFunc = Callable[
|
_DynamicInputFunc = Callable[
|
||||||
[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None, dict[str, str] | None],
|
[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None, dict[str, str] | None],
|
||||||
None,
|
None,
|
||||||
@ -1722,15 +1719,8 @@ class Schema:
|
|||||||
def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], include_hidden=False, live_input_types: dict[str, str] | None = None) -> tuple[dict[str, Any], V3Data]:
|
def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], include_hidden=False, live_input_types: dict[str, str] | None = None) -> tuple[dict[str, Any], V3Data]:
|
||||||
"""Expand a node's V3 schema against a concrete prompt.
|
"""Expand a node's V3 schema against a concrete prompt.
|
||||||
|
|
||||||
Args:
|
``live_input_types`` is an optional ``{input_id: resolved_io_type}`` map
|
||||||
d: ``INPUT_TYPES()``-shaped dict for the node.
|
(from ``TypeResolver``) used by future type-discriminated dynamic inputs.
|
||||||
live_inputs: Concrete ``{input_id: value}`` map from the prompt
|
|
||||||
(values may be links ``[node_id, slot_idx]`` or literals).
|
|
||||||
include_hidden: When True, retain hidden inputs in the returned dict.
|
|
||||||
live_input_types: Optional ``{input_id: resolved_io_type}`` map
|
|
||||||
produced by ``comfy_execution.type_resolver.TypeResolver``. Future
|
|
||||||
dynamic-input strategies that branch on connected type use this as
|
|
||||||
their discriminator. Existing dynamic types ignore it.
|
|
||||||
"""
|
"""
|
||||||
out_dict = {
|
out_dict = {
|
||||||
"required": {},
|
"required": {},
|
||||||
|
|||||||
@ -26,9 +26,7 @@ class DynamicPrompt:
|
|||||||
self.ephemeral_prompt = {}
|
self.ephemeral_prompt = {}
|
||||||
self.ephemeral_parents = {}
|
self.ephemeral_parents = {}
|
||||||
self.ephemeral_display = {}
|
self.ephemeral_display = {}
|
||||||
# Lazily-built type resolver, scoped to this DynamicPrompt's lifetime.
|
self._type_resolver = None # lazy; invalidated by add_ephemeral_node
|
||||||
# Invalidated whenever the graph mutates via add_ephemeral_node.
|
|
||||||
self._type_resolver = None
|
|
||||||
|
|
||||||
def get_node(self, node_id):
|
def get_node(self, node_id):
|
||||||
if node_id in self.ephemeral_prompt:
|
if node_id in self.ephemeral_prompt:
|
||||||
@ -44,9 +42,8 @@ class DynamicPrompt:
|
|||||||
self.ephemeral_prompt[node_id] = node_info
|
self.ephemeral_prompt[node_id] = node_info
|
||||||
self.ephemeral_parents[node_id] = parent_id
|
self.ephemeral_parents[node_id] = parent_id
|
||||||
self.ephemeral_display[node_id] = display_id
|
self.ephemeral_display[node_id] = display_id
|
||||||
# Conservatively invalidate the entire resolver cache. Selective
|
# Selective downstream invalidation would need topological info; the
|
||||||
# downstream invalidation would require topological info we don't have
|
# cache is small, so just wipe it.
|
||||||
# here cheaply; the resolver's cache is small and easy to rebuild.
|
|
||||||
if self._type_resolver is not None:
|
if self._type_resolver is not None:
|
||||||
self._type_resolver.invalidate()
|
self._type_resolver.invalidate()
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,12 @@
|
|||||||
"""Server-side type resolver for prompt graphs.
|
"""Server-side type resolver for prompt graphs.
|
||||||
|
|
||||||
Resolves the concrete io_type of an output slot or input slot by walking the
|
Resolves the concrete io_type of any output/input slot by walking the prompt
|
||||||
prompt graph. Handles:
|
graph. Handles V1/V3 ``RETURN_TYPES``, V3 ``MatchType`` template chains, and
|
||||||
|
falls back to ``AnyType`` (with a one-shot warning) on cycles, depth overflow,
|
||||||
|
or unresolvable wildcards.
|
||||||
|
|
||||||
* Static V1/V3 ``RETURN_TYPES`` (returned as-is).
|
Works against either a raw prompt dict or a ``DynamicPrompt``. All resolved
|
||||||
* V3 ``MatchType.Output`` (resolved by walking inputs that share the same
|
values are strings, so resolver state is cross-process serializable.
|
||||||
``template_id`` until a concrete type is found).
|
|
||||||
* Cycles and unbounded recursion (terminates at ``AnyType`` with a one-shot
|
|
||||||
warning).
|
|
||||||
* Unknown / unresolvable / wildcard outputs (fall back to ``AnyType`` with a
|
|
||||||
one-shot warning).
|
|
||||||
|
|
||||||
The resolver works against either a raw prompt dict
|
|
||||||
(``{node_id: {"class_type": str, "inputs": dict}}``) or a
|
|
||||||
``comfy_execution.graph.DynamicPrompt`` instance.
|
|
||||||
|
|
||||||
All resolved values are plain strings, so the resolver state is trivially
|
|
||||||
serializable across processes if needed.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@ -29,30 +19,23 @@ from comfy_api.internal import _ComfyNodeInternal
|
|||||||
|
|
||||||
|
|
||||||
def _parse_link(val: Any) -> tuple[str, int] | None:
|
def _parse_link(val: Any) -> tuple[str, int] | None:
|
||||||
"""Return (src_node_id, src_slot_idx) if ``val`` is a well-formed link.
|
"""Return ``(src_node_id, src_slot_idx)`` if ``val`` is a well-formed link, else ``None``.
|
||||||
|
|
||||||
A link in the prompt schema is a length-2 list/tuple ``[node_id, slot_idx]``
|
A link is ``[node_id: str, slot_idx: int]``. Malformed shapes return ``None``
|
||||||
where ``node_id`` is a string and ``slot_idx`` is a non-negative int.
|
so callers can fall back to AnyType rather than raise.
|
||||||
Anything else (including ``[node_id, "0"]`` from malformed API JSON) returns
|
|
||||||
``None`` so callers can fall back to AnyType instead of crashing.
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(val, (list, tuple)) or len(val) != 2:
|
if not isinstance(val, (list, tuple)) or len(val) != 2:
|
||||||
return None
|
return None
|
||||||
src_node, src_slot = val[0], val[1]
|
src_node, src_slot = val[0], val[1]
|
||||||
if not isinstance(src_node, str):
|
if not isinstance(src_node, str):
|
||||||
return None
|
return None
|
||||||
# bool is a subclass of int — reject it to avoid treating True/False as slot 1/0.
|
# bool is a subclass of int — reject so True/False aren't read as slot 1/0.
|
||||||
if isinstance(src_slot, bool) or not isinstance(src_slot, int):
|
if isinstance(src_slot, bool) or not isinstance(src_slot, int):
|
||||||
return None
|
return None
|
||||||
return src_node, src_slot
|
return src_node, src_slot
|
||||||
|
|
||||||
# Sentinel for "type is unknown / wildcard". Matches AnyType.io_type ("*").
|
|
||||||
ANY_TYPE: str = io.AnyType.io_type
|
ANY_TYPE: str = io.AnyType.io_type
|
||||||
|
MAX_RESOLVE_DEPTH: int = 64 # belt-and-suspenders cap; real MatchType chains stay tiny
|
||||||
# Hard cap on resolver recursion depth. MatchType chains should never be
|
|
||||||
# anywhere near this deep; this is a belt-and-suspenders guard against malformed
|
|
||||||
# graphs and pathological cycles.
|
|
||||||
MAX_RESOLVE_DEPTH: int = 64
|
|
||||||
|
|
||||||
|
|
||||||
class TypeResolver:
|
class TypeResolver:
|
||||||
@ -90,8 +73,7 @@ class TypeResolver:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_class_def(class_type: str):
|
def _get_class_def(class_type: str):
|
||||||
# Local import to avoid a hard import-cycle between nodes.py and
|
# Local import: nodes <-> comfy_execution would cycle at import time.
|
||||||
# comfy_execution at module-load time.
|
|
||||||
import nodes
|
import nodes
|
||||||
return nodes.NODE_CLASS_MAPPINGS.get(class_type)
|
return nodes.NODE_CLASS_MAPPINGS.get(class_type)
|
||||||
|
|
||||||
@ -112,8 +94,7 @@ class TypeResolver:
|
|||||||
"""Clear all cached resolutions. Cheap; call after any graph mutation."""
|
"""Clear all cached resolutions. Cheap; call after any graph mutation."""
|
||||||
self._output_cache.clear()
|
self._output_cache.clear()
|
||||||
self._is_output_list_cache.clear()
|
self._is_output_list_cache.clear()
|
||||||
# Intentionally do NOT clear self._warned: those messages are already
|
# Keep self._warned: re-emitting already-logged warnings would just spam.
|
||||||
# logged and re-warning would just spam the log.
|
|
||||||
|
|
||||||
def invalidate_node(self, node_id: str) -> None:
|
def invalidate_node(self, node_id: str) -> None:
|
||||||
"""Clear cached entries for a single node (e.g. after node-level expand)."""
|
"""Clear cached entries for a single node (e.g. after node-level expand)."""
|
||||||
@ -131,9 +112,7 @@ class TypeResolver:
|
|||||||
out-of-range slot, missing node, malformed link, or unresolved
|
out-of-range slot, missing node, malformed link, or unresolved
|
||||||
MatchType template.
|
MatchType template.
|
||||||
"""
|
"""
|
||||||
# Guard against malformed callers passing non-int slot indices (e.g.
|
# Degrade gracefully on non-int slot_idx (e.g. malformed API JSON).
|
||||||
# API JSON that sent a string). Falling back to AnyType is safer than
|
|
||||||
# raising TypeError mid-validation.
|
|
||||||
if isinstance(slot_idx, bool) or not isinstance(slot_idx, int):
|
if isinstance(slot_idx, bool) or not isinstance(slot_idx, int):
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
|
|
||||||
@ -165,15 +144,13 @@ class TypeResolver:
|
|||||||
|
|
||||||
declared = return_types[slot_idx]
|
declared = return_types[slot_idx]
|
||||||
|
|
||||||
# V3 nodes may have MatchType outputs that need to be traced through
|
# Only V3 schemas carry MatchType template info; V1 RETURN_TYPES are
|
||||||
# the schema. V1 nodes (and V3 nodes with plain outputs) just use the
|
# always concrete strings.
|
||||||
# declared RETURN_TYPES string.
|
|
||||||
resolved = declared
|
resolved = declared
|
||||||
if isinstance(class_def, type) and issubclass(class_def, _ComfyNodeInternal):
|
if isinstance(class_def, type) and issubclass(class_def, _ComfyNodeInternal):
|
||||||
schema = getattr(class_def, "SCHEMA", None)
|
schema = getattr(class_def, "SCHEMA", None)
|
||||||
if schema is None:
|
if schema is None:
|
||||||
# Trigger schema computation. RETURN_TYPES would have done this
|
# RETURN_TYPES access above usually populates SCHEMA — be defensive.
|
||||||
# already, but be defensive.
|
|
||||||
try:
|
try:
|
||||||
schema = class_def.GET_SCHEMA()
|
schema = class_def.GET_SCHEMA()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -185,9 +162,8 @@ class TypeResolver:
|
|||||||
node_id, schema, out.template.template_id, next_stack
|
node_id, schema, out.template.template_id, next_stack
|
||||||
)
|
)
|
||||||
|
|
||||||
# Treat the legacy wildcard literally as AnyType. We warn only when the
|
# Warn only for V1 wildcards declared as "*"; unresolved MatchType
|
||||||
# source node's *declared* type was already wildcard, so MatchType-style
|
# templates warn separately in _resolve_match_template, avoiding double-warns.
|
||||||
# "no upstream connected" cases (which warn elsewhere) don't double-warn.
|
|
||||||
if isinstance(resolved, str) and resolved == ANY_TYPE and declared == ANY_TYPE:
|
if isinstance(resolved, str) and resolved == ANY_TYPE and declared == ANY_TYPE:
|
||||||
self._warn(
|
self._warn(
|
||||||
node_id, slot_idx,
|
node_id, slot_idx,
|
||||||
@ -195,7 +171,7 @@ class TypeResolver:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(resolved, str):
|
if not isinstance(resolved, str):
|
||||||
# Non-string types (e.g., legacy combos passed as list) — bail to AnyType.
|
# e.g. legacy combo declared as a list of options.
|
||||||
self._warn(node_id, slot_idx,
|
self._warn(node_id, slot_idx,
|
||||||
f"node '{class_type}' output slot {slot_idx} has non-string return type {type(resolved).__name__}; defaulting to AnyType")
|
f"node '{class_type}' output slot {slot_idx} has non-string return type {type(resolved).__name__}; defaulting to AnyType")
|
||||||
resolved = ANY_TYPE
|
resolved = ANY_TYPE
|
||||||
@ -205,13 +181,7 @@ class TypeResolver:
|
|||||||
|
|
||||||
def _resolve_match_template(self, node_id: str, schema, template_id: str,
|
def _resolve_match_template(self, node_id: str, schema, template_id: str,
|
||||||
stack: frozenset[tuple[str, int]]) -> str:
|
stack: frozenset[tuple[str, int]]) -> str:
|
||||||
"""Resolve a MatchType.Output by inspecting the node's MatchType.Inputs
|
"""Walk MatchType.Inputs sharing ``template_id``; return first concrete resolution or ``ANY_TYPE``."""
|
||||||
with the same template_id.
|
|
||||||
|
|
||||||
Strategy (per design decision): walk inputs in schema order, pick the
|
|
||||||
FIRST concrete (non-AnyType) resolution. If none resolve, return
|
|
||||||
AnyType with a one-shot warning.
|
|
||||||
"""
|
|
||||||
node = self._get_node(node_id)
|
node = self._get_node(node_id)
|
||||||
inputs_dict = (node or {}).get("inputs", {}) or {}
|
inputs_dict = (node or {}).get("inputs", {}) or {}
|
||||||
any_input_seen = False
|
any_input_seen = False
|
||||||
@ -229,11 +199,9 @@ class TypeResolver:
|
|||||||
t = self.resolve_output_type(link[0], link[1], stack)
|
t = self.resolve_output_type(link[0], link[1], stack)
|
||||||
if t != ANY_TYPE:
|
if t != ANY_TYPE:
|
||||||
return t
|
return t
|
||||||
# Literal value (or malformed link): a MatchType slot has no
|
# Literal or malformed link: MatchType slots have no declared concrete type.
|
||||||
# concrete declared type, so we cannot infer anything useful here.
|
|
||||||
if not any_input_seen:
|
if not any_input_seen:
|
||||||
# Schema declared a template_id with no Input bearing it. This is a
|
# Node-author bug: output template has no matching Input.
|
||||||
# node-author bug; warn once.
|
|
||||||
self._warn(node_id, None,
|
self._warn(node_id, None,
|
||||||
f"MatchType output template '{template_id}' has no matching Input on the node; defaulting to AnyType")
|
f"MatchType output template '{template_id}' has no matching Input on the node; defaulting to AnyType")
|
||||||
else:
|
else:
|
||||||
@ -315,11 +283,11 @@ class TypeResolver:
|
|||||||
except Exception:
|
except Exception:
|
||||||
schema = None
|
schema = None
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
# First, try a top-level input id match.
|
# Top-level input id.
|
||||||
for inp in schema.inputs:
|
for inp in schema.inputs:
|
||||||
if inp.id == input_id:
|
if inp.id == input_id:
|
||||||
return self._effective_io_type(inp)
|
return self._effective_io_type(inp)
|
||||||
# Then a nested match (DynamicSlot / DynamicCombo prefix.child).
|
# Nested (DynamicSlot / DynamicCombo `parent.child`).
|
||||||
if "." in input_id:
|
if "." in input_id:
|
||||||
top, _, _ = input_id.partition(".")
|
top, _, _ = input_id.partition(".")
|
||||||
for inp in schema.inputs:
|
for inp in schema.inputs:
|
||||||
@ -330,9 +298,8 @@ class TypeResolver:
|
|||||||
continue
|
continue
|
||||||
if child.id == input_id.split(".", 1)[1]:
|
if child.id == input_id.split(".", 1)[1]:
|
||||||
return self._effective_io_type(child)
|
return self._effective_io_type(child)
|
||||||
# Fall through to V1 dict for hidden inputs etc.
|
# Fall through to V1 dict (hidden inputs, etc.).
|
||||||
|
|
||||||
# V1 fallback: look at INPUT_TYPES() dict.
|
|
||||||
try:
|
try:
|
||||||
inputs = class_def.INPUT_TYPES()
|
inputs = class_def.INPUT_TYPES()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -346,8 +313,7 @@ class TypeResolver:
|
|||||||
t = entry[0]
|
t = entry[0]
|
||||||
if isinstance(t, str):
|
if isinstance(t, str):
|
||||||
return t
|
return t
|
||||||
if isinstance(t, list):
|
if isinstance(t, list): # legacy combo declared as list of options
|
||||||
# legacy combo declared as a list of options.
|
|
||||||
return io.Combo.io_type
|
return io.Combo.io_type
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
@ -355,22 +321,20 @@ class TypeResolver:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _effective_io_type(inp) -> str:
|
def _effective_io_type(inp) -> str:
|
||||||
"""Return the consumer-facing io_type of a (possibly dynamic) input."""
|
"""Return the consumer-facing io_type of a (possibly dynamic) input."""
|
||||||
# Autogrow wraps a template input — the element type is what matters.
|
# Autogrow / DynamicSlot wrap a real element type; that's what consumers care about.
|
||||||
if isinstance(inp, io.Autogrow.Input):
|
if isinstance(inp, io.Autogrow.Input):
|
||||||
try:
|
try:
|
||||||
return inp.template.input.get_io_type()
|
return inp.template.input.get_io_type()
|
||||||
except Exception:
|
except Exception:
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
# DynamicSlot wraps an underlying slot input.
|
|
||||||
if isinstance(inp, io.DynamicSlot.Input):
|
if isinstance(inp, io.DynamicSlot.Input):
|
||||||
try:
|
try:
|
||||||
return inp.slot.get_io_type()
|
return inp.slot.get_io_type()
|
||||||
except Exception:
|
except Exception:
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
# DynamicCombo's "type" is a key value selector, not a connection type.
|
# DynamicCombo's "type" is a key selector, not a connection type.
|
||||||
if isinstance(inp, io.DynamicCombo.Input):
|
if isinstance(inp, io.DynamicCombo.Input):
|
||||||
return ANY_TYPE
|
return ANY_TYPE
|
||||||
# Everything else: trust the input's declared io_type.
|
|
||||||
try:
|
try:
|
||||||
return inp.get_io_type()
|
return inp.get_io_type()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -380,9 +344,8 @@ class TypeResolver:
|
|||||||
def compute_live_input_types(self, node_id: str) -> dict[str, str]:
|
def compute_live_input_types(self, node_id: str) -> dict[str, str]:
|
||||||
"""Build the ``{input_id: resolved_io_type}`` map for a node.
|
"""Build the ``{input_id: resolved_io_type}`` map for a node.
|
||||||
|
|
||||||
Used by :py:func:`comfy_api.latest._io.get_finalized_class_inputs` so
|
Consumed by ``_io.get_finalized_class_inputs`` so future per-type
|
||||||
future dynamic-input expansion strategies (per-type DynamicType, etc.)
|
dynamic-input expansion can branch on what was actually connected.
|
||||||
can branch on what was actually connected.
|
|
||||||
"""
|
"""
|
||||||
node = self._get_node(node_id)
|
node = self._get_node(node_id)
|
||||||
if node is None:
|
if node is None:
|
||||||
|
|||||||
22
execution.py
22
execution.py
@ -84,7 +84,6 @@ class IsChangedCache:
|
|||||||
return self.is_changed[node_id]
|
return self.is_changed[node_id]
|
||||||
|
|
||||||
# Intentionally do not use cached outputs here. We only want constants in IS_CHANGED.
|
# Intentionally do not use cached outputs here. We only want constants in IS_CHANGED.
|
||||||
# Pass dynprompt so the TypeResolver can resolve link types for V3 dynamic schemas.
|
|
||||||
input_data_all, _, v3_data = get_input_data(node["inputs"], class_def, node_id, None, self.dynprompt)
|
input_data_all, _, v3_data = get_input_data(node["inputs"], class_def, node_id, None, self.dynprompt)
|
||||||
try:
|
try:
|
||||||
is_changed = await _async_map_node_over_list(self.prompt_id, node_id, class_def, input_data_all, is_changed_name, v3_data=v3_data)
|
is_changed = await _async_map_node_over_list(self.prompt_id, node_id, class_def, input_data_all, is_changed_name, v3_data=v3_data)
|
||||||
@ -159,11 +158,7 @@ def get_input_data(inputs, class_def, unique_id, execution_list=None, dynprompt=
|
|||||||
hidden_inputs_v3 = {}
|
hidden_inputs_v3 = {}
|
||||||
valid_inputs = class_def.INPUT_TYPES()
|
valid_inputs = class_def.INPUT_TYPES()
|
||||||
if is_v3:
|
if is_v3:
|
||||||
# Build the type-resolution map for this node so dynamic schemas can
|
# Let dynamic schemas branch on resolved upstream types, not just literal values.
|
||||||
# branch on resolved upstream types (and not only on literal values).
|
|
||||||
# When no DynamicPrompt is available (e.g. some IsChangedCache paths
|
|
||||||
# in tests), live_input_types stays None and only literal-driven
|
|
||||||
# dynamic types continue to work.
|
|
||||||
live_input_types = None
|
live_input_types = None
|
||||||
if dynprompt is not None and hasattr(dynprompt, "get_type_resolver"):
|
if dynprompt is not None and hasattr(dynprompt, "get_type_resolver"):
|
||||||
live_input_types = dynprompt.get_type_resolver().compute_live_input_types(unique_id)
|
live_input_types = dynprompt.get_type_resolver().compute_live_input_types(unique_id)
|
||||||
@ -833,10 +828,8 @@ class PromptExecutor:
|
|||||||
async def validate_inputs(prompt_id, prompt, item, validated, visiting=None, type_resolver=None):
|
async def validate_inputs(prompt_id, prompt, item, validated, visiting=None, type_resolver=None):
|
||||||
"""Validate inputs for a single node, recursing into upstream nodes.
|
"""Validate inputs for a single node, recursing into upstream nodes.
|
||||||
|
|
||||||
``type_resolver`` (a ``comfy_execution.type_resolver.TypeResolver``) is
|
``type_resolver`` is built once at the top of recursion and shared so
|
||||||
built once at the top of the recursion and reused so MatchType chains are
|
MatchType chains are only walked once per prompt.
|
||||||
only walked once. It also gives V3 dynamic schemas an accurate map of
|
|
||||||
resolved upstream types for API-submitted workflows.
|
|
||||||
"""
|
"""
|
||||||
if visiting is None:
|
if visiting is None:
|
||||||
visiting = []
|
visiting = []
|
||||||
@ -929,10 +922,8 @@ async def validate_inputs(prompt_id, prompt, item, validated, visiting=None, typ
|
|||||||
|
|
||||||
o_id = val[0]
|
o_id = val[0]
|
||||||
o_class_type = prompt[o_id]['class_type']
|
o_class_type = prompt[o_id]['class_type']
|
||||||
# Resolve the upstream output's effective type through the
|
# Walks MatchType/template chains so API workflows without
|
||||||
# TypeResolver. This walks MatchType/template chains, so an API
|
# frontend-injected type metadata get the same answer as the UI.
|
||||||
# workflow without frontend-injected type metadata still gets the
|
|
||||||
# same answer the UI does.
|
|
||||||
received_type = type_resolver.resolve_output_type(o_id, val[1])
|
received_type = type_resolver.resolve_output_type(o_id, val[1])
|
||||||
received_types[x] = received_type
|
received_types[x] = received_type
|
||||||
if 'input_types' not in validate_function_inputs and not validate_node_input(received_type, input_type):
|
if 'input_types' not in validate_function_inputs and not validate_node_input(received_type, input_type):
|
||||||
@ -1178,8 +1169,7 @@ async def validate_prompt(prompt_id, prompt, partial_execution_list: Union[list[
|
|||||||
errors = []
|
errors = []
|
||||||
node_errors = {}
|
node_errors = {}
|
||||||
validated = {}
|
validated = {}
|
||||||
# Share one TypeResolver across all output validations so MatchType chains
|
# Shared across output validations so MatchType chains walk only once.
|
||||||
# are only walked once per prompt.
|
|
||||||
from comfy_execution.type_resolver import TypeResolver
|
from comfy_execution.type_resolver import TypeResolver
|
||||||
type_resolver = TypeResolver(prompt)
|
type_resolver = TypeResolver(prompt)
|
||||||
for o in outputs:
|
for o in outputs:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user