add match!
This commit is contained in:
+29
-2
@@ -209,6 +209,33 @@ Creates one boolean decision step. The condition uses the same JSON shape as
|
||||
The draft adapter lowers this through `WorkflowBuilder.when()`. The draft step
|
||||
id becomes the generated condition entry id, so other routes can target it.
|
||||
|
||||
### `match`
|
||||
|
||||
Matches one graph value against ordered equality cases.
|
||||
|
||||
```json
|
||||
{
|
||||
"match": {
|
||||
"value": "state.status",
|
||||
"cases": [
|
||||
{
|
||||
"equals": "ready",
|
||||
"then": "run"
|
||||
},
|
||||
{
|
||||
"equals": "waiting",
|
||||
"then": "pause"
|
||||
}
|
||||
],
|
||||
"default": "__end__"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cases are a list rather than a JSON object so values such as `1`, `"1"`, and
|
||||
`true` are not silently coerced into object keys. The draft adapter lowers this
|
||||
through `WorkflowBuilder.match()`.
|
||||
|
||||
### `choose`
|
||||
|
||||
Creates an ordered first-true decision chain.
|
||||
@@ -243,8 +270,8 @@ Creates an ordered first-true decision chain.
|
||||
```
|
||||
|
||||
`choose` lowers through `WorkflowBuilder.choose()` and expands to generated
|
||||
condition nodes. It replaces the deprecated `route()` concept for draft JSON;
|
||||
there is intentionally no draft `route` step kind.
|
||||
condition nodes. `match`, `when`, and `choose` replace the deprecated `route()`
|
||||
concept for draft JSON; there is intentionally no draft `route` step kind.
|
||||
|
||||
## Draft Tools
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ from .models import (
|
||||
DraftForeachStep,
|
||||
DraftInterruptStep,
|
||||
DraftJoinStep,
|
||||
DraftMatchCase,
|
||||
DraftMatchStep,
|
||||
DraftWhenStep,
|
||||
DraftUseStep,
|
||||
WorkflowDraft,
|
||||
@@ -23,6 +25,8 @@ __all__ = [
|
||||
"DraftForeachStep",
|
||||
"DraftInterruptStep",
|
||||
"DraftJoinStep",
|
||||
"DraftMatchCase",
|
||||
"DraftMatchStep",
|
||||
"DraftWhenStep",
|
||||
"DraftUseStep",
|
||||
"WorkflowDraft",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from wf_authoring import WorkflowBuilder
|
||||
from wf_authoring.dsl import PathExpr
|
||||
from wf_core import JoinNode, Workflow
|
||||
|
||||
from .models import (
|
||||
DraftChooseStep,
|
||||
DraftForeachStep,
|
||||
DraftInterruptStep,
|
||||
DraftJoinStep,
|
||||
DraftMatchStep,
|
||||
DraftStep,
|
||||
DraftUseStep,
|
||||
DraftWhenStep,
|
||||
DraftChooseStep,
|
||||
WorkflowDraft,
|
||||
)
|
||||
|
||||
@@ -79,4 +81,11 @@ def _add_step(builder: WorkflowBuilder, step_id: str, step: DraftStep):
|
||||
id=step_id,
|
||||
default=step.choose.default,
|
||||
).entry
|
||||
if isinstance(step, DraftMatchStep):
|
||||
return builder.match(
|
||||
PathExpr(step.match.value),
|
||||
{case.equals: case.then for case in step.match.cases},
|
||||
id=step_id,
|
||||
default=step.match.default,
|
||||
).entry
|
||||
raise TypeError(f"unsupported draft step {type(step)!r}")
|
||||
|
||||
@@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, model_validator
|
||||
from wf_core.models.conditions import Condition
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
STEP_KIND_KEYS = frozenset({"use", "foreach", "interrupt", "join", "when", "choose"})
|
||||
STEP_KIND_KEYS = frozenset(
|
||||
{"use", "foreach", "interrupt", "join", "when", "choose", "match"}
|
||||
)
|
||||
|
||||
|
||||
class DraftUseStep(BaseModel):
|
||||
@@ -91,6 +93,27 @@ class DraftChooseStep(BaseModel):
|
||||
choose: DraftChoosePayload
|
||||
|
||||
|
||||
class DraftMatchCase(BaseModel):
|
||||
"""One ordered equality case in a draft match decision."""
|
||||
|
||||
equals: Any
|
||||
then: str
|
||||
|
||||
|
||||
class DraftMatchPayload(BaseModel):
|
||||
"""Payload for matching one graph value against ordered equality cases."""
|
||||
|
||||
value: str
|
||||
cases: list[DraftMatchCase] = Field(min_length=1)
|
||||
default: str = "__end__"
|
||||
|
||||
|
||||
class DraftMatchStep(BaseModel):
|
||||
"""Draft step that delegates equality decisions to `WorkflowBuilder.match`."""
|
||||
|
||||
match: DraftMatchPayload
|
||||
|
||||
|
||||
DraftStep = (
|
||||
DraftUseStep
|
||||
| DraftForeachStep
|
||||
@@ -98,6 +121,7 @@ DraftStep = (
|
||||
| DraftJoinStep
|
||||
| DraftWhenStep
|
||||
| DraftChooseStep
|
||||
| DraftMatchStep
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -121,3 +121,47 @@ def test_adapter_lowers_choose_step_through_builder() -> None:
|
||||
("pick_2", "true", "fallback"),
|
||||
("pick_2", "false", "__end__"),
|
||||
]
|
||||
|
||||
|
||||
def test_adapter_lowers_match_step_through_builder() -> None:
|
||||
draft = WorkflowDraft.model_validate(
|
||||
{
|
||||
"name": "match_example",
|
||||
"input_schema": {},
|
||||
"state_schema": {"fields": {}},
|
||||
"output_schema": {},
|
||||
"start": "match_status",
|
||||
"steps": {
|
||||
"match_status": {
|
||||
"match": {
|
||||
"value": "state.status",
|
||||
"cases": [
|
||||
{"equals": "ready", "then": "ready"},
|
||||
{"equals": "waiting", "then": "waiting"},
|
||||
],
|
||||
"default": "__end__",
|
||||
}
|
||||
},
|
||||
"ready": {"use": "demo.ready"},
|
||||
"waiting": {"use": "demo.waiting"},
|
||||
},
|
||||
"routes": {
|
||||
"ready": {"ok": "__end__"},
|
||||
"waiting": {"ok": "__end__"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
workflow = build_workflow_from_draft(draft)
|
||||
condition_ids = [
|
||||
node.id for node in workflow.nodes if isinstance(node, ConditionNode)
|
||||
]
|
||||
|
||||
assert condition_ids == ["match_status", "match_status_2"]
|
||||
assert workflow.start == "match_status"
|
||||
assert [(edge.from_, edge.outcome, edge.to) for edge in workflow.edges[:4]] == [
|
||||
("match_status", "true", "ready"),
|
||||
("match_status", "false", "match_status_2"),
|
||||
("match_status_2", "true", "waiting"),
|
||||
("match_status_2", "false", "__end__"),
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@ from pydantic import ValidationError
|
||||
|
||||
from wf_artifacts.drafts import (
|
||||
DraftChooseStep,
|
||||
DraftMatchStep,
|
||||
DraftUseStep,
|
||||
DraftWhenStep,
|
||||
WorkflowDraft,
|
||||
@@ -87,6 +88,30 @@ def test_workflow_draft_accepts_choose_step() -> None:
|
||||
assert isinstance(draft.steps["choose_next"], DraftChooseStep)
|
||||
|
||||
|
||||
def test_workflow_draft_accepts_match_step() -> None:
|
||||
draft = WorkflowDraft.model_validate(
|
||||
{
|
||||
**_keyed_echo_draft(),
|
||||
"start": "match_status",
|
||||
"steps": {
|
||||
**_keyed_echo_draft()["steps"],
|
||||
"match_status": {
|
||||
"match": {
|
||||
"value": "state.status",
|
||||
"cases": [
|
||||
{"equals": "ready", "then": "echo"},
|
||||
{"equals": "done", "then": "__end__"},
|
||||
],
|
||||
"default": "__end__",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert isinstance(draft.steps["match_status"], DraftMatchStep)
|
||||
|
||||
|
||||
def _keyed_echo_draft() -> dict[str, Any]:
|
||||
return {
|
||||
"name": "echo",
|
||||
|
||||
Reference in New Issue
Block a user