mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
107 lines
3.1 KiB
Python
107 lines
3.1 KiB
Python
"""Execute one plan step: when-predicate, params render, handler dispatch, retries."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from datetime import UTC, datetime
|
|
from typing import Any
|
|
|
|
from app.automations.actions import get_action
|
|
from app.automations.actions.types import ActionContext
|
|
from app.automations.schemas.definition.plan_step import PlanStep
|
|
from app.automations.templating import evaluate_predicate, render_value
|
|
|
|
from .retries import with_retries
|
|
|
|
|
|
async def execute_step(
|
|
*,
|
|
step: PlanStep,
|
|
template_context: Mapping[str, Any],
|
|
action_context: ActionContext,
|
|
default_max_retries: int,
|
|
default_retry_backoff: str,
|
|
default_timeout_seconds: int,
|
|
) -> dict[str, Any]:
|
|
"""Run one step and return its structured result entry."""
|
|
started_at = datetime.now(UTC)
|
|
|
|
if step.when is not None:
|
|
try:
|
|
should_run = evaluate_predicate(step.when, template_context)
|
|
except Exception as exc:
|
|
return _result(
|
|
step, "failed", started_at, attempts=0, error=_error(exc, "when")
|
|
)
|
|
if not should_run:
|
|
return _result(step, "skipped", started_at, attempts=0)
|
|
|
|
try:
|
|
resolved_params = render_value(step.params, template_context)
|
|
except Exception as exc:
|
|
return _result(
|
|
step, "failed", started_at, attempts=0, error=_error(exc, "render")
|
|
)
|
|
|
|
action = get_action(step.action)
|
|
if action is None:
|
|
return _result(
|
|
step,
|
|
"failed",
|
|
started_at,
|
|
attempts=0,
|
|
error={
|
|
"message": f"action not registered: {step.action}",
|
|
"type": "ActionNotFound",
|
|
},
|
|
)
|
|
|
|
handler = action.build_handler(action_context)
|
|
|
|
max_retries = (
|
|
step.max_retries if step.max_retries is not None else default_max_retries
|
|
)
|
|
timeout = step.timeout_seconds or default_timeout_seconds
|
|
|
|
try:
|
|
result, attempts = await with_retries(
|
|
lambda: handler(resolved_params),
|
|
max_retries=max_retries,
|
|
backoff=default_retry_backoff,
|
|
timeout=timeout,
|
|
)
|
|
except Exception as exc:
|
|
return _result(
|
|
step, "failed", started_at, attempts=max_retries + 1, error=_error(exc)
|
|
)
|
|
|
|
return _result(step, "succeeded", started_at, attempts=attempts, result=result)
|
|
|
|
|
|
def _result(
|
|
step: PlanStep,
|
|
status: str,
|
|
started_at: datetime,
|
|
*,
|
|
attempts: int,
|
|
result: Any = None,
|
|
error: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
entry: dict[str, Any] = {
|
|
"step_id": step.step_id,
|
|
"action": step.action,
|
|
"status": status,
|
|
"started_at": started_at.isoformat(),
|
|
"finished_at": datetime.now(UTC).isoformat(),
|
|
"attempts": attempts,
|
|
}
|
|
if result is not None:
|
|
entry["result"] = result
|
|
if error is not None:
|
|
entry["error"] = error
|
|
return entry
|
|
|
|
|
|
def _error(exc: Exception, phase: str | None = None) -> dict[str, Any]:
|
|
msg = f"{phase}: {exc}" if phase else str(exc)
|
|
return {"message": msg, "type": type(exc).__name__}
|