from __future__ import annotations from typing import Any def _format_node_description(node: Any) -> str: """Formats a human-readable node name and type label.""" if node is None: return "" name = getattr(node, "name", "") bl_label = getattr(node, "bl_label", "") bl_idname = getattr(node, "bl_idname", "") or type(node).__name__ label = bl_label or bl_idname if name and label and name != label: return f"'{name}' ({label})" elif name: return f"'{name}'" elif label: return f"'{label}'" return str(node) def get_node_input_names(node: Any, fallback_inputs: list[str] | None = None) -> list[str]: """Retrieves all input socket names for a node, preserving order without duplicates.""" names: list[str] = [] inputs = getattr(node, "inputs", None) if inputs: for s in inputs: s_name = getattr(s, "name", "") if s_name and s_name not in names: names.append(s_name) elif not s_name: ident = getattr(s, "identifier", "") if ident and ident not in names: names.append(ident) if not names and fallback_inputs: for s_name in fallback_inputs: if s_name and s_name not in names: names.append(s_name) if not names: mapping = getattr(node, "list_socket_mapping", None) if mapping: for item in mapping: m_in = item[0] if isinstance(item, (list, tuple)) and item else None if m_in and m_in not in names: names.append(m_in) return names def format_node_failure_message(node: Any, exc_or_msg: Any, fallback_inputs: list[str] | None = None) -> str: """Formats a concise, informative execution failure message specifying the node and input names.""" node_str = _format_node_description(node) or "Unknown Node" input_names = get_node_input_names(node, fallback_inputs=fallback_inputs) inputs_str = ", ".join(input_names) if input_names else "(None)" msg = str(exc_or_msg) if msg.startswith("Execution failed at node "): return msg return f"Execution failed at node {node_str} [Inputs: {inputs_str}]: {msg}"