96 lines
3.1 KiB
Python
96 lines
3.1 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
"""
|
|
Workflow entry points (Starts) — configuration outside the flow editor.
|
|
|
|
Kinds align with run envelope trigger.type where applicable.
|
|
"""
|
|
|
|
import uuid
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
# On-demand (gear: Manueller Trigger, Formular)
|
|
KINDS_ON_DEMAND = frozenset({"manual", "form", "api"})
|
|
|
|
# Always-on (gear: Zeitplan, Immer aktiv, plus legacy listener kinds)
|
|
KINDS_ALWAYS_ON = frozenset({"schedule", "always_on", "email", "webhook", "event"})
|
|
|
|
ALL_KINDS = KINDS_ON_DEMAND | KINDS_ALWAYS_ON
|
|
|
|
|
|
def category_for_kind(kind: str) -> str:
|
|
if kind in KINDS_ALWAYS_ON:
|
|
return "always_on"
|
|
return "on_demand"
|
|
|
|
|
|
def default_manual_entry_point() -> Dict[str, Any]:
|
|
"""Single default manual start when a workflow has no invocations yet."""
|
|
return {
|
|
"id": str(uuid.uuid4()),
|
|
"kind": "manual",
|
|
"category": "on_demand",
|
|
"enabled": True,
|
|
"title": {
|
|
"de": "Jetzt ausführen",
|
|
"en": "Run now",
|
|
"fr": "Exécuter",
|
|
},
|
|
"description": {},
|
|
"config": {},
|
|
}
|
|
|
|
|
|
def _normalize_title(title: Any) -> Dict[str, str]:
|
|
if isinstance(title, dict):
|
|
return {k: str(v) for k, v in title.items() if v is not None}
|
|
if isinstance(title, str) and title.strip():
|
|
return {"de": title, "en": title, "fr": title}
|
|
return {"de": "Start", "en": "Start", "fr": "Départ"}
|
|
|
|
|
|
def normalize_invocation_entry(raw: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Validate and normalize a single entry point dict."""
|
|
kind = (raw.get("kind") or "manual").strip()
|
|
if kind not in ALL_KINDS:
|
|
kind = "manual"
|
|
cat = raw.get("category")
|
|
if cat not in ("on_demand", "always_on"):
|
|
cat = category_for_kind(kind)
|
|
eid = raw.get("id") or str(uuid.uuid4())
|
|
enabled = raw.get("enabled", True)
|
|
if not isinstance(enabled, bool):
|
|
enabled = bool(enabled)
|
|
config = raw.get("config") if isinstance(raw.get("config"), dict) else {}
|
|
desc = raw.get("description") if isinstance(raw.get("description"), dict) else {}
|
|
return {
|
|
"id": str(eid),
|
|
"kind": kind,
|
|
"category": cat,
|
|
"enabled": enabled,
|
|
"title": _normalize_title(raw.get("title")),
|
|
"description": desc,
|
|
"config": config,
|
|
}
|
|
|
|
|
|
def normalize_invocations_list(items: Optional[List[Any]]) -> List[Dict[str, Any]]:
|
|
if not items:
|
|
return [default_manual_entry_point()]
|
|
out: List[Dict[str, Any]] = []
|
|
for raw in items:
|
|
if isinstance(raw, dict):
|
|
out.append(normalize_invocation_entry(raw))
|
|
if not out:
|
|
return [default_manual_entry_point()]
|
|
return out
|
|
|
|
|
|
# Schedule / cron: wire an external job runner (APScheduler, Celery, system cron) to call
|
|
# POST .../execute with entryPointId set to a schedule entry — no separate in-process scheduler here yet.
|
|
|
|
|
|
def find_invocation(workflow: Dict[str, Any], entry_point_id: str) -> Optional[Dict[str, Any]]:
|
|
for inv in workflow.get("invocations") or []:
|
|
if isinstance(inv, dict) and inv.get("id") == entry_point_id:
|
|
return inv
|
|
return None
|