DreamsHunter's picture
Fix Python 3.9 compatibility: replace PEP 604 union types with Optional
daca5bd
Raw
History Blame Contribute Delete
2.2 kB
"""Tracing helpers shared by every node."""
import time
from functools import wraps
from typing import Callable, Optional
def trace_event(node: str, *, status: str = "ok", duration_ms: float = 0.0,
summary: str = "", payload: Optional[dict] = None) -> dict:
"""Build a single TraceEvent dict matching the state schema."""
return {
"node": node,
"status": status,
"duration_ms": round(duration_ms, 2),
"summary": summary,
"payload": payload or {},
}
def traced(node_name: str) -> Callable:
"""
Decorator: time a node, swallow exceptions into a trace event, and
guarantee the node always returns a dict containing a `trace` list.
The wrapped function should return a partial state dict WITHOUT a `trace`
field — this decorator injects the timing/status event automatically. If
the wrapped function returns its own `trace` list, those events are kept
and the timing event is appended.
"""
def decorator(fn):
@wraps(fn)
def wrapper(state, *args, **kwargs):
t0 = time.perf_counter()
try:
result = fn(state, *args, **kwargs) or {}
dt_ms = (time.perf_counter() - t0) * 1000
summary = result.pop("_summary", "")
payload = result.pop("_payload", {})
event = trace_event(
node_name,
status="ok",
duration_ms=dt_ms,
summary=summary,
payload=payload,
)
existing = result.get("trace", [])
result["trace"] = existing + [event]
return result
except Exception as e:
dt_ms = (time.perf_counter() - t0) * 1000
event = trace_event(
node_name,
status="error",
duration_ms=dt_ms,
summary=f"{type(e).__name__}: {e}",
payload={"error": str(e)},
)
return {"trace": [event], "error": f"{node_name}: {e}"}
return wrapper
return decorator