156 lines
5.7 KiB
Python
156 lines
5.7 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
"""
|
|
Graph helpers for Pick-not-Push: materialize typed DataRefs before executeGraph runs.
|
|
|
|
- ``materializeConnectionRefs``: empty ``connectionReference`` from upstream connection provenance.
|
|
- ``materializePrimaryTextHandover``: parameters whose static definition includes
|
|
``graphInherit.kind == "primaryTextRef"`` (canonical paths: ``PRIMARY_TEXT_HANDOVER_REF_PATH``).
|
|
|
|
Runtime: executeGraph deep-copies the version graph and applies these passes in order.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import logging
|
|
from typing import Any, Dict, List
|
|
|
|
from modules.features.graphicalEditor.nodeDefinitions import STATIC_NODE_TYPES
|
|
from modules.features.graphicalEditor.portTypes import (
|
|
PRIMARY_TEXT_HANDOVER_REF_PATH,
|
|
resolve_output_schema_name,
|
|
)
|
|
from modules.workflows.automation2.graphUtils import buildConnectionMap, getInputSources
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_NODE_DEF_BY_ID = {n["id"]: n for n in STATIC_NODE_TYPES}
|
|
|
|
_SCHEMAS_WITH_CONNECTION = frozenset(
|
|
{"FileList", "DocumentList", "EmailList", "TaskList", "EmailDraft", "UdmDocument"},
|
|
)
|
|
|
|
|
|
def _data_ref(node_id: str, path: List[Any]) -> Dict[str, Any]:
|
|
return {"type": "ref", "nodeId": node_id, "path": list(path)}
|
|
|
|
|
|
def materializeConnectionRefs(graph: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Deep-copy graph and set empty connectionReference (userConnection params) to
|
|
DataRef { nodeId: upstreamPort0, path: ['connection','id'] } when upstream
|
|
output schema carries connection provenance.
|
|
"""
|
|
g = copy.deepcopy(graph)
|
|
nodes: List[Dict[str, Any]] = g.get("nodes") or []
|
|
connections = g.get("connections") or []
|
|
if not nodes:
|
|
return g
|
|
|
|
conn_map = buildConnectionMap(connections)
|
|
node_by_id = {n["id"]: n for n in nodes if n.get("id")}
|
|
|
|
for node in nodes:
|
|
nid = node.get("id")
|
|
ntype = node.get("type")
|
|
if not nid or not ntype:
|
|
continue
|
|
node_def = _NODE_DEF_BY_ID.get(ntype)
|
|
if not node_def:
|
|
continue
|
|
pdefs = node_def.get("parameters") or []
|
|
has_conn = any(
|
|
p.get("name") == "connectionReference" and p.get("frontendType") == "userConnection"
|
|
for p in pdefs
|
|
)
|
|
if not has_conn:
|
|
continue
|
|
params = node.get("parameters")
|
|
if not isinstance(params, dict):
|
|
node["parameters"] = {}
|
|
params = node["parameters"]
|
|
cur = params.get("connectionReference")
|
|
if cur not in (None, "", {}):
|
|
continue
|
|
input_sources = getInputSources(nid, conn_map)
|
|
if 0 not in input_sources:
|
|
continue
|
|
src_id, _ = input_sources[0]
|
|
src_node = node_by_id.get(src_id) or {}
|
|
src_def = _NODE_DEF_BY_ID.get(src_node.get("type") or "")
|
|
if not src_def:
|
|
continue
|
|
out_port = (src_def.get("outputPorts") or {}).get(0, {}) or {}
|
|
out_schema = resolve_output_schema_name(src_node, out_port if isinstance(out_port, dict) else {})
|
|
if out_schema not in _SCHEMAS_WITH_CONNECTION:
|
|
continue
|
|
params["connectionReference"] = _data_ref(src_id, ["connection", "id"])
|
|
logger.debug("materializeConnectionRefs: %s.connectionReference -> ref %s.connection.id", nid, src_id)
|
|
|
|
return g
|
|
|
|
|
|
def _slot_empty_for_primary_text_inherit(val: Any) -> bool:
|
|
return val is None or val == "" or val == []
|
|
|
|
|
|
def materializePrimaryTextHandover(graph: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
For parameters declaring ``graphInherit.kind == "primaryTextRef"`` (optional ``port``, default 0) with an
|
|
empty value, set an explicit ``DataRef`` to the canonical text field of the producer on
|
|
that port (see ``PRIMARY_TEXT_HANDOVER_REF_PATH`` keyed by upstream output schema name).
|
|
"""
|
|
g = copy.deepcopy(graph)
|
|
nodes: List[Dict[str, Any]] = g.get("nodes") or []
|
|
connections = g.get("connections") or []
|
|
if not nodes:
|
|
return g
|
|
|
|
conn_map = buildConnectionMap(connections)
|
|
node_by_id = {n["id"]: n for n in nodes if n.get("id")}
|
|
|
|
for node in nodes:
|
|
nid = node.get("id")
|
|
ntype = node.get("type")
|
|
if not nid or not ntype:
|
|
continue
|
|
node_def = _NODE_DEF_BY_ID.get(ntype)
|
|
if not node_def:
|
|
continue
|
|
params = node.get("parameters")
|
|
if not isinstance(params, dict):
|
|
node["parameters"] = {}
|
|
params = node["parameters"]
|
|
|
|
for pdef in node_def.get("parameters") or []:
|
|
gi = pdef.get("graphInherit")
|
|
if not isinstance(gi, dict) or gi.get("kind") != "primaryTextRef":
|
|
continue
|
|
pname = pdef.get("name")
|
|
if not pname:
|
|
continue
|
|
port_ix = int(gi.get("port", 0))
|
|
if not _slot_empty_for_primary_text_inherit(params.get(pname)):
|
|
continue
|
|
input_sources = getInputSources(nid, conn_map)
|
|
if port_ix not in input_sources:
|
|
continue
|
|
src_id, _ = input_sources[port_ix]
|
|
src_node = node_by_id.get(src_id) or {}
|
|
src_def = _NODE_DEF_BY_ID.get(src_node.get("type") or "")
|
|
if not src_def:
|
|
continue
|
|
out_port = (src_def.get("outputPorts") or {}).get(0, {}) or {}
|
|
out_schema = resolve_output_schema_name(src_node, out_port if isinstance(out_port, dict) else {})
|
|
ref_path = PRIMARY_TEXT_HANDOVER_REF_PATH.get(out_schema)
|
|
if not ref_path:
|
|
continue
|
|
params[pname] = _data_ref(src_id, list(ref_path))
|
|
logger.debug(
|
|
"materializePrimaryTextHandover: %s.%s -> ref %s path=%s",
|
|
nid,
|
|
pname,
|
|
src_id,
|
|
ref_path,
|
|
)
|
|
|
|
return g
|