expose itempolicy to the wf_authoring

This commit is contained in:
lda
2026-05-23 21:42:37 +07:00 Verified
parent 1c5cf15815
commit e09d714c72
6 changed files with 361 additions and 4 deletions
+65 -3
View File
@@ -10,6 +10,7 @@ from wf_authoring.ops.values import runtime_error
from wf_core import (
ConditionNode,
Edge,
ForeachItemErrorPolicy,
ForeachNode,
InterruptNode,
NodeUse,
@@ -32,7 +33,7 @@ from wf_core.models.steps import (
from wf_core.paths import GraphSourcePath, LocalPath, StatePath
from wf_core.runtime.ops.merges import ReducerDefinition
from ..dsl import Expr, PathArg, PathExpr, compile_condition
from ..dsl import Expr, GraphPath, PathArg, PathExpr, compile_condition
from ..nodes.callables import SyncRegistryHandler
from ..nodes.registry import build_registry
from ..reducers import ReducerCatalog
@@ -145,6 +146,28 @@ def _warn_deprecated_binding_sugar(
)
def _normalize_foreach_item_error(
item_error: ForeachItemErrorPolicy | Mapping[str, object] | str | None,
) -> ForeachItemErrorPolicy | dict[str, object] | str | None:
"""Coerce authoring path helpers inside the canonical item-error policy."""
if item_error is None or isinstance(item_error, ForeachItemErrorPolicy | str):
return item_error
normalized = dict(item_error)
collect_to = normalized.get("collect_to")
if isinstance(collect_to, PathExpr):
collect_to = collect_to.path
if isinstance(collect_to, GraphPath):
collect_to = collect_to.path
if isinstance(collect_to, GraphSourcePath):
if collect_to.root != "state":
raise ValueError("foreach item_error.collect_to must be a state path")
collect_to = StatePath(collect_to.parts)
if collect_to is not None:
normalized["collect_to"] = collect_to
return normalized
@dataclass(slots=True)
class WorkflowBuilder:
name: str
@@ -388,6 +411,20 @@ class WorkflowBuilder:
self.nodes.append(node)
return node
@overload
def foreach(
self,
*,
id: str | None = None,
over: PathArg,
as_: str,
mode: Literal["serial", "concurrent"] = "serial",
item_error: ForeachItemErrorPolicy | Mapping[str, object] | str | None = None,
concurrent: Mapping[str, object] | None = None,
) -> ForeachNode: ...
@overload
@deprecated("use item_error canonical policy instead")
def foreach(
self,
*,
@@ -397,13 +434,36 @@ class WorkflowBuilder:
mode: Literal["serial", "concurrent"] = "serial",
on_item_error: Literal["fail", "collect", "skip"] = "fail",
concurrent: Mapping[str, object] | None = None,
) -> ForeachNode: ...
def foreach(
self,
*,
id: str | None = None,
over: PathArg,
as_: str,
mode: Literal["serial", "concurrent"] = "serial",
item_error: ForeachItemErrorPolicy | Mapping[str, object] | str | None = None,
on_item_error: Literal["fail", "collect", "skip"] = "fail",
concurrent: Mapping[str, object] | None = None,
) -> ForeachNode:
"""Add a foreach step.
Concurrent mode is supported by the runtime with deterministic barrier
commits, item error policies, and async item-node batching. See ADR 0002
for the exact merge and interrupt semantics.
for the exact merge and interrupt semantics. Prefer the canonical
`item_error` policy object/mapping; `on_item_error` is compatibility
shorthand for older callers.
"""
if item_error is not None and on_item_error != "fail":
raise TypeError("cannot mix item_error with deprecated on_item_error")
if item_error is None and on_item_error != "fail":
warnings.warn(
"on_item_error is deprecated WorkflowBuilder foreach sugar; "
"use item_error={'action': ...} instead",
DeprecationWarning,
stacklevel=2,
)
node = ForeachNode.model_validate(
{
"id": id or self._next_step_id(f"foreach_{slug_id(as_)}"),
@@ -411,7 +471,9 @@ class WorkflowBuilder:
"over": coerce_path(over),
"as": as_,
"mode": mode,
"on_item_error": on_item_error,
"item_error": _normalize_foreach_item_error(item_error)
if item_error is not None
else {"action": on_item_error},
"concurrent": concurrent,
}
)