gateway/modules/workflows/automation2/executors/dataExecutor.py
2026-03-22 15:25:29 +01:00

104 lines
3.5 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# Data transformation node executor (setFields, filter, parseJson, template).
import json
import logging
import re
from typing import Dict, Any, List
logger = logging.getLogger(__name__)
def _get_nested(obj: Any, path: str) -> Any:
"""Get nested key from obj, e.g. 'data.items'."""
for k in path.split("."):
if not k:
continue
if isinstance(obj, dict) and k in obj:
obj = obj[k]
elif isinstance(obj, (list, tuple)) and k.isdigit():
obj = obj[int(k)]
else:
return None
return obj
class DataExecutor:
"""Execute data transformation nodes."""
async def execute(
self,
node: Dict[str, Any],
context: Dict[str, Any],
) -> Any:
nodeType = node.get("type", "")
nodeOutputs = context.get("nodeOutputs", {})
nodeId = node.get("id", "")
inputSources = context.get("inputSources", {}).get(nodeId, {})
params = node.get("parameters") or {}
inp = None
if 0 in inputSources:
srcId, _ = inputSources[0]
inp = nodeOutputs.get(srcId)
from modules.workflows.automation2.graphUtils import resolveParameterReferences
resolvedParams = {k: resolveParameterReferences(v, nodeOutputs) for k, v in params.items()}
if nodeType == "data.setFields":
return self._setFields(inp, resolvedParams)
if nodeType == "data.filter":
return self._filter(inp, resolvedParams)
if nodeType == "data.parseJson":
return self._parseJson(inp, resolvedParams)
if nodeType == "data.template":
return self._template(inp, resolvedParams, nodeOutputs)
return inp
def _setFields(self, inp: Any, params: Dict) -> Any:
fields = params.get("fields", {})
if not isinstance(fields, dict):
return inp
base = dict(inp) if isinstance(inp, dict) else {}
base.update(fields)
return base
def _filter(self, inp: Any, params: Dict) -> Any:
itemsPath = (params.get("itemsPath") or "").strip()
condition = params.get("condition", "True")
items = inp
if itemsPath:
items = _get_nested(inp, itemsPath)
if not isinstance(items, list):
items = [inp] if inp is not None else []
out = []
for i, item in enumerate(items):
try:
local = {"item": item, "index": i, "input": inp}
ok = bool(eval(condition, {"__builtins__": {}}, local))
if ok:
out.append(item)
except Exception:
pass
return out
def _parseJson(self, inp: Any, params: Dict) -> Any:
jsonPath = (params.get("jsonPath") or "").strip()
raw = inp
if jsonPath:
raw = _get_nested(inp, jsonPath) if isinstance(inp, dict) else inp
if isinstance(raw, dict):
return raw
if isinstance(raw, str):
try:
return json.loads(raw)
except json.JSONDecodeError:
return {"error": "Invalid JSON", "raw": raw[:200]}
return inp
def _template(self, inp: Any, params: Dict, nodeOutputs: Dict) -> Any:
tpl = params.get("template", "")
from modules.workflows.automation2.graphUtils import resolveParameterReferences
result = resolveParameterReferences(tpl, nodeOutputs)
return {"text": result, "template": tpl}