interrupt to get the same treatment too!
This commit is contained in:
@@ -55,6 +55,46 @@ def test_workflow_draft_accepts_legacy_use_maps_but_dumps_canonical_bindings() -
|
||||
}
|
||||
|
||||
|
||||
def test_workflow_draft_accepts_legacy_interrupt_maps_but_dumps_canonical_bindings() -> (
|
||||
None
|
||||
):
|
||||
draft = WorkflowDraft.model_validate(
|
||||
{
|
||||
**_keyed_echo_draft(),
|
||||
"start": "approval",
|
||||
"steps": {
|
||||
"approval": {
|
||||
"interrupt": {
|
||||
"kind": "approval",
|
||||
"request": {"input.text": "message"},
|
||||
"resume": {"approved": "state.approved"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"routes": {"approval": {"submitted": "__end__"}},
|
||||
}
|
||||
)
|
||||
|
||||
dumped = draft.model_dump(mode="json")
|
||||
|
||||
assert dumped["steps"]["approval"]["interrupt"]["request"][0]["path"] == {
|
||||
"root": "input",
|
||||
"parts": ["text"],
|
||||
}
|
||||
assert dumped["steps"]["approval"]["interrupt"]["request"][0]["target"] == {
|
||||
"root": "local",
|
||||
"parts": ["message"],
|
||||
}
|
||||
assert dumped["steps"]["approval"]["interrupt"]["resume"][0]["source"] == {
|
||||
"root": "local",
|
||||
"parts": ["approved"],
|
||||
}
|
||||
assert dumped["steps"]["approval"]["interrupt"]["resume"][0]["target"] == {
|
||||
"root": "state",
|
||||
"parts": ["approved"],
|
||||
}
|
||||
|
||||
|
||||
def test_draft_step_requires_exactly_one_kind_key() -> None:
|
||||
draft = _keyed_echo_draft()
|
||||
steps = draft["steps"]
|
||||
|
||||
@@ -277,6 +277,27 @@ def test_builder_can_auto_id_condition_foreach_and_interrupt() -> None:
|
||||
assert interrupt.id == "interrupt_approval"
|
||||
|
||||
|
||||
def test_builder_interrupt_accepts_canonical_request_and_resume_bindings() -> None:
|
||||
builder = WorkflowBuilder(
|
||||
name="interrupt_bindings",
|
||||
input_schema=AutoBindInput,
|
||||
state_schema=AutoBindState,
|
||||
output_schema=AutoBindOutput,
|
||||
)
|
||||
|
||||
interrupt = builder.interrupt(
|
||||
kind="approval",
|
||||
request=[input_from(input_path("text"), "message")],
|
||||
resume=[output_to("text", state_path("text"))],
|
||||
)
|
||||
|
||||
assert isinstance(interrupt.request[0], InputPathBinding)
|
||||
assert interrupt.request[0].path == GraphSourcePath.input("text")
|
||||
assert interrupt.request[0].target == LocalPath.of("message")
|
||||
assert interrupt.resume[0].source == LocalPath.of("text")
|
||||
assert interrupt.resume[0].target == StatePath.of("text")
|
||||
|
||||
|
||||
def test_builder_connect_can_use_node_specs_and_returns_resolved_refs() -> None:
|
||||
builder = WorkflowBuilder(
|
||||
name="connect_specs_demo",
|
||||
|
||||
@@ -18,8 +18,6 @@ from examples.demo_workflow import build_demo_registry, build_demo_workflow
|
||||
from wf_authoring import (
|
||||
NodeReturn,
|
||||
WorkflowBuilder,
|
||||
bind_fields,
|
||||
bind_state,
|
||||
build_registry,
|
||||
input_from,
|
||||
state,
|
||||
@@ -192,14 +190,14 @@ def build_authoring_demo_workflow():
|
||||
approve_email = builder.interrupt(
|
||||
id="approve_email",
|
||||
kind="approval",
|
||||
request_map=bind_fields(
|
||||
summary=state_path("summary"),
|
||||
folder_id=input_path("folder_id"),
|
||||
),
|
||||
out_map=bind_state(
|
||||
approved=state_path("approved"),
|
||||
comment=state_path("approval_comment"),
|
||||
),
|
||||
request=[
|
||||
input_from(state_path("summary"), "summary"),
|
||||
input_from(input_path("folder_id"), "folder_id"),
|
||||
],
|
||||
resume=[
|
||||
output_to("approved", state_path("approved")),
|
||||
output_to("comment", state_path("approval_comment")),
|
||||
],
|
||||
outcomes=["submitted", "cancelled"],
|
||||
)
|
||||
skip_email = builder.use(
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from wf_core.models.steps import InputPathBinding, InputValueBinding, NodeUse
|
||||
from wf_core.models.steps import (
|
||||
InputPathBinding,
|
||||
InputValueBinding,
|
||||
InterruptNode,
|
||||
NodeUse,
|
||||
)
|
||||
from wf_core.paths import GraphSourcePath, LocalPath, StatePath
|
||||
|
||||
|
||||
@@ -220,3 +225,54 @@ def test_deprecated_input_value_preserves_explicit_null():
|
||||
dumped_input = node.model_dump(mode="json")["input"]
|
||||
assert dumped_input[0]["target"] == {"root": "local", "parts": ["maybe"]}
|
||||
assert dumped_input[0]["value"] is None
|
||||
|
||||
|
||||
def test_interrupt_node_accepts_canonical_request_and_resume_bindings():
|
||||
node = InterruptNode.model_validate(
|
||||
{
|
||||
"id": "approval",
|
||||
"type": "interrupt",
|
||||
"kind": "approval",
|
||||
"request": [{"target": "summary", "path": "state.summary"}],
|
||||
"resume": [{"source": "approved", "target": "state.approved"}],
|
||||
}
|
||||
)
|
||||
|
||||
assert isinstance(node.request[0], InputPathBinding)
|
||||
assert node.request[0].path == GraphSourcePath.state("summary")
|
||||
assert node.request[0].target == LocalPath.of("summary")
|
||||
assert node.resume[0].source == LocalPath.of("approved")
|
||||
assert node.resume[0].target == StatePath.of("approved")
|
||||
|
||||
|
||||
def test_interrupt_node_converts_old_maps_to_canonical_bindings():
|
||||
node = InterruptNode.model_validate(
|
||||
{
|
||||
"id": "approval",
|
||||
"type": "interrupt",
|
||||
"kind": "approval",
|
||||
"request_map": {"input.message": "message"},
|
||||
"out_map": {"approved": "state.approved"},
|
||||
}
|
||||
)
|
||||
|
||||
dumped = node.model_dump(mode="json")
|
||||
assert "request_map" not in dumped
|
||||
assert "out_map" not in dumped
|
||||
assert dumped["request"][0]["path"] == {"root": "input", "parts": ["message"]}
|
||||
assert dumped["request"][0]["target"] == {"root": "local", "parts": ["message"]}
|
||||
assert dumped["resume"][0]["source"] == {"root": "local", "parts": ["approved"]}
|
||||
assert dumped["resume"][0]["target"] == {"root": "state", "parts": ["approved"]}
|
||||
|
||||
|
||||
def test_interrupt_node_rejects_mixed_old_and_new_binding_styles():
|
||||
with pytest.raises(ValidationError):
|
||||
InterruptNode.model_validate(
|
||||
{
|
||||
"id": "approval",
|
||||
"type": "interrupt",
|
||||
"kind": "approval",
|
||||
"request": [{"target": "message", "path": "input.message"}],
|
||||
"request_map": {"input.other": "other"},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -564,8 +564,8 @@ def _interrupt_artifact() -> WorkflowArtifact:
|
||||
"id": "approval",
|
||||
"type": "interrupt",
|
||||
"kind": "approval",
|
||||
"request_map": {"input.message": "message"},
|
||||
"out_map": {},
|
||||
"request": [input_binding("input.message", "message")],
|
||||
"resume": [],
|
||||
"outcomes": ["submitted"],
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user