drafts system to get ts too

This commit is contained in:
lda
2026-05-23 22:25:47 +07:00 Verified
parent 10350e87f8
commit 998b1943ba
5 changed files with 175 additions and 10 deletions
+51 -1
View File
@@ -5,7 +5,7 @@ from pydantic import ValidationError
from wf_artifacts.drafts import WorkflowDraft
from wf_artifacts.drafts.api import compile_workflow_draft, validate_workflow_draft
from wf_artifacts.drafts.adapter import build_workflow_from_draft
from wf_core import ConditionNode, NodeUse
from wf_core import ConditionNode, ForeachNode, NodeUse
from wf_core.models.steps import InputValueBinding
@@ -279,3 +279,53 @@ def test_adapter_lowers_match_step_through_builder() -> None:
("match_status_2", "true", "waiting"),
("match_status_2", "false", "__end__"),
]
def test_adapter_lowers_foreach_policy_through_builder() -> None:
draft = WorkflowDraft.model_validate(
{
"name": "foreach_policy",
"input_schema": {},
"state_schema": {
"type": "object",
"properties": {
"items": {"type": "array"},
"item_errors": {"type": "array"},
},
},
"output_schema": {},
"start": "each_item",
"steps": {
"each_item": {
"foreach": {
"over": "state.items",
"as": "item",
"mode": "concurrent",
"concurrent": {"max_active": 2, "max_outstanding": 4},
"item_error": {
"action": "collect",
"collect_to": "state.item_errors",
},
}
}
},
"routes": {
"each_item": {
"loop": "__end__",
"done": "__end__",
"completed_with_errors": "__end__",
}
},
}
)
workflow = build_workflow_from_draft(draft)
foreach = workflow.nodes[0]
assert isinstance(foreach, ForeachNode)
assert foreach.mode == "concurrent"
assert foreach.concurrent is not None
assert foreach.concurrent.max_active == 2
assert foreach.concurrent.max_outstanding == 4
assert foreach.item_error.action == "collect"
assert str(foreach.item_error.collect_to) == "state.item_errors"
+77
View File
@@ -7,6 +7,7 @@ from pydantic import ValidationError
from wf_artifacts.drafts import (
DraftChooseStep,
DraftForeachStep,
DraftMatchStep,
DraftUseStep,
DraftWhenStep,
@@ -191,6 +192,82 @@ def test_workflow_draft_foreach_over_dumps_structural_path() -> None:
}
def test_workflow_draft_foreach_accepts_canonical_item_error_policy() -> None:
draft = WorkflowDraft.model_validate(
{
**_keyed_echo_draft(),
"start": "each_item",
"steps": {
"each_item": {
"foreach": {
"over": "state.items",
"as": "item",
"mode": "concurrent",
"concurrent": {"max_active": 2, "max_outstanding": 3},
"item_error": {
"action": "collect",
"collect_to": "state.item_errors",
},
}
}
},
"routes": {"each_item": {"loop": "__end__", "done": "__end__"}},
}
)
step = draft.steps["each_item"]
dumped = draft.model_dump(mode="json")
assert isinstance(step, DraftForeachStep)
assert dumped["steps"]["each_item"]["foreach"]["item_error"] == {
"action": "collect",
"collect_to": {"root": "state", "parts": ["item_errors"]},
}
assert "on_item_error" not in dumped["steps"]["each_item"]["foreach"]
def test_workflow_draft_foreach_accepts_item_error_action_string() -> None:
draft = WorkflowDraft.model_validate(
{
**_keyed_echo_draft(),
"start": "each_item",
"steps": {
"each_item": {
"foreach": {
"over": "state.items",
"as": "item",
"item_error": "skip",
}
}
},
"routes": {"each_item": {"loop": "__end__", "done": "__end__"}},
}
)
dumped = draft.model_dump(mode="json")
assert dumped["steps"]["each_item"]["foreach"]["item_error"]["action"] == "skip"
def test_workflow_draft_foreach_collect_string_requires_destination() -> None:
with pytest.raises(ValidationError, match="collect_to"):
WorkflowDraft.model_validate(
{
**_keyed_echo_draft(),
"start": "each_item",
"steps": {
"each_item": {
"foreach": {
"over": "state.items",
"as": "item",
"item_error": "collect",
}
}
},
}
)
def test_workflow_draft_accepts_match_step() -> None:
draft = WorkflowDraft.model_validate(
{