feat: resolve composite step inputs
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from wf_core.conditions import safe_resolve_path
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
from wf_core.local_paths import LocalPathError, set_local_value
|
||||
from wf_core.models.input_bindings import (
|
||||
ArrayExpression,
|
||||
InputExpression,
|
||||
InputExpressionBinding,
|
||||
InputPathBinding,
|
||||
InputValueBinding,
|
||||
LiteralExpression,
|
||||
ObjectExpression,
|
||||
PathExpression,
|
||||
StepInputBinding,
|
||||
)
|
||||
from wf_core.models.json_values import JsonValue, validate_strict_json_value
|
||||
|
||||
|
||||
def resolve_input_expression(
|
||||
expression: InputExpression,
|
||||
*,
|
||||
state: Mapping[str, Any],
|
||||
workflow_input: Mapping[str, Any],
|
||||
context: Mapping[str, Any],
|
||||
label: str,
|
||||
location: str,
|
||||
) -> JsonValue:
|
||||
"""Resolve one composite expression while preserving its payload location."""
|
||||
|
||||
if isinstance(expression, LiteralExpression):
|
||||
return expression.value
|
||||
if isinstance(expression, PathExpression):
|
||||
try:
|
||||
value = safe_resolve_path(
|
||||
str(expression.path),
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
)
|
||||
return validate_strict_json_value(value)
|
||||
except (ValueError, WorkflowExecutionError) as exc:
|
||||
raise WorkflowExecutionError(f"{label} {location}: {exc}") from exc
|
||||
if isinstance(expression, ArrayExpression):
|
||||
return [
|
||||
resolve_input_expression(
|
||||
item,
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
label=label,
|
||||
location=f"{location}[{index}]",
|
||||
)
|
||||
for index, item in enumerate(expression.items)
|
||||
]
|
||||
if isinstance(expression, ObjectExpression):
|
||||
return {
|
||||
field: resolve_input_expression(
|
||||
value,
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
label=label,
|
||||
location=f"{location}.{field}",
|
||||
)
|
||||
for field, value in expression.fields.items()
|
||||
}
|
||||
raise WorkflowExecutionError(f"unsupported input expression for {label} {location}")
|
||||
|
||||
|
||||
def resolve_step_input_bindings(
|
||||
bindings: Sequence[StepInputBinding],
|
||||
*,
|
||||
state: Mapping[str, Any],
|
||||
workflow_input: Mapping[str, Any],
|
||||
context: Mapping[str, Any],
|
||||
label: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Build one node-local payload from simple or composite input bindings."""
|
||||
|
||||
payload: dict[str, Any] = {}
|
||||
for binding in bindings:
|
||||
location = str(binding.target)
|
||||
if isinstance(binding, InputValueBinding):
|
||||
value = binding.value
|
||||
elif isinstance(binding, InputPathBinding):
|
||||
try:
|
||||
value = safe_resolve_path(
|
||||
str(binding.path),
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
)
|
||||
value = validate_strict_json_value(value)
|
||||
except (ValueError, WorkflowExecutionError) as exc:
|
||||
raise WorkflowExecutionError(f"{label} {location}: {exc}") from exc
|
||||
elif isinstance(binding, InputExpressionBinding):
|
||||
value = resolve_input_expression(
|
||||
binding.expression,
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
label=label,
|
||||
location=location,
|
||||
)
|
||||
else:
|
||||
raise WorkflowExecutionError(f"unsupported input binding for {label}")
|
||||
try:
|
||||
set_local_value(payload, binding.target, value)
|
||||
except LocalPathError as exc:
|
||||
raise WorkflowExecutionError(f"{label} {location}: {exc}") from exc
|
||||
return payload
|
||||
@@ -3,10 +3,8 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from wf_core.conditions import safe_resolve_path
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
from wf_core.local_paths import LocalPathError, set_local_value
|
||||
from wf_core.models.steps import InputPathBinding, InputValueBinding, InterruptNode
|
||||
from wf_core.models.steps import InterruptNode
|
||||
from wf_core.models.workflow import Workflow
|
||||
from wf_core.run_state import (
|
||||
FrameStatus,
|
||||
@@ -15,6 +13,7 @@ from wf_core.run_state import (
|
||||
RunState,
|
||||
StepExecutionResult,
|
||||
)
|
||||
from wf_core.runtime.input_bindings import resolve_step_input_bindings
|
||||
from wf_core.runtime.lineage import commit_patch_for_frame
|
||||
from wf_core.runtime.ops.flow import advance_frame, append_step_result_trace
|
||||
from wf_core.runtime.ops.index import WorkflowIndex
|
||||
@@ -35,25 +34,13 @@ def build_interrupt_request(
|
||||
public_node_id: str | None = None,
|
||||
route: InterruptRoute | None = None,
|
||||
) -> InterruptRequest:
|
||||
payload: dict[str, Any] = {}
|
||||
for binding in node.request:
|
||||
if isinstance(binding, InputValueBinding):
|
||||
value = binding.value
|
||||
elif isinstance(binding, InputPathBinding):
|
||||
value = safe_resolve_path(
|
||||
str(binding.path),
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
)
|
||||
else:
|
||||
raise WorkflowExecutionError(
|
||||
f"unsupported request binding for interrupt {node.id!r}"
|
||||
)
|
||||
try:
|
||||
set_local_value(payload, binding.target, value)
|
||||
except LocalPathError as exc:
|
||||
raise WorkflowExecutionError(str(exc)) from exc
|
||||
payload = resolve_step_input_bindings(
|
||||
node.request,
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
label=f"interrupt {node.id!r} request",
|
||||
)
|
||||
validate_payload_against_schema(
|
||||
node.request_schema,
|
||||
payload,
|
||||
|
||||
@@ -4,12 +4,10 @@ from collections.abc import Awaitable, Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from wf_core.conditions import safe_resolve_path
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
from wf_core.local_paths import LocalPathError, set_local_value
|
||||
from wf_core.models.results import NodeResult
|
||||
from wf_core.models.schemas import NodeDef
|
||||
from wf_core.models.steps import InputPathBinding, InputValueBinding, NodeUse
|
||||
from wf_core.models.steps import NodeUse
|
||||
from wf_core.models.workflow import Workflow
|
||||
from wf_core.run_state import (
|
||||
ExecutionFrame,
|
||||
@@ -18,6 +16,7 @@ from wf_core.run_state import (
|
||||
StepExecutionResult,
|
||||
)
|
||||
from wf_core.runtime.foreach_state import ForeachBarrierState, item_frame_owner
|
||||
from wf_core.runtime.input_bindings import resolve_step_input_bindings
|
||||
from wf_core.runtime.lineage import (
|
||||
append_lineage_writes,
|
||||
commit_patch_for_frame,
|
||||
@@ -59,25 +58,13 @@ def _resolve_node_execution(
|
||||
) -> tuple[dict[str, Any], RuntimeContext, dict[str, Any]]:
|
||||
context_values = frame_context_values(frame)
|
||||
state_view = state_view_for_frame(run, frame)
|
||||
resolved_input: dict[str, Any] = {}
|
||||
for binding in node.input:
|
||||
if isinstance(binding, InputValueBinding):
|
||||
value = binding.value
|
||||
elif isinstance(binding, InputPathBinding):
|
||||
value = safe_resolve_path(
|
||||
str(binding.path),
|
||||
state=state_view,
|
||||
workflow_input=scope_input_for_frame(run, frame),
|
||||
context=context_values,
|
||||
)
|
||||
else:
|
||||
raise WorkflowExecutionError(
|
||||
f"unsupported input binding for node {node.id!r}"
|
||||
)
|
||||
try:
|
||||
set_local_value(resolved_input, binding.target, value)
|
||||
except LocalPathError as exc:
|
||||
raise WorkflowExecutionError(str(exc)) from exc
|
||||
resolved_input = resolve_step_input_bindings(
|
||||
node.input,
|
||||
state=state_view,
|
||||
workflow_input=scope_input_for_frame(run, frame),
|
||||
context=context_values,
|
||||
label=f"node {node.id!r} input",
|
||||
)
|
||||
validate_payload_against_schema(
|
||||
node_def.input_schema, resolved_input, f"node input for {node.id}"
|
||||
)
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from wf_core.conditions import safe_resolve_path
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
from wf_core.local_paths import LocalPathError, set_local_value
|
||||
from wf_core.models.steps import (
|
||||
InputBinding,
|
||||
InputPathBinding,
|
||||
InputValueBinding,
|
||||
SubgraphNode,
|
||||
)
|
||||
from wf_core.models.steps import SubgraphNode
|
||||
from wf_core.models.workflow import Workflow
|
||||
from wf_core.models.workflow_refs import WorkflowRef
|
||||
from wf_core.run_state import (
|
||||
@@ -23,6 +16,7 @@ from wf_core.run_state import (
|
||||
RuntimeScope,
|
||||
StepExecutionResult,
|
||||
)
|
||||
from wf_core.runtime.input_bindings import resolve_step_input_bindings
|
||||
from wf_core.runtime.lineage import commit_patch_for_frame
|
||||
from wf_core.runtime.ops.frames import frame_context_values
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
@@ -111,35 +105,6 @@ def resolve_prepared_subgraph(
|
||||
return prepared
|
||||
|
||||
|
||||
def resolve_input_bindings(
|
||||
bindings: Sequence[InputBinding],
|
||||
*,
|
||||
state: Mapping[str, Any],
|
||||
workflow_input: Mapping[str, Any],
|
||||
context: Mapping[str, Any],
|
||||
label: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a local input payload from canonical value/path bindings."""
|
||||
payload: dict[str, Any] = {}
|
||||
for binding in bindings:
|
||||
if isinstance(binding, InputValueBinding):
|
||||
value = binding.value
|
||||
elif isinstance(binding, InputPathBinding):
|
||||
value = safe_resolve_path(
|
||||
str(binding.path),
|
||||
state=state,
|
||||
workflow_input=workflow_input,
|
||||
context=context,
|
||||
)
|
||||
else:
|
||||
raise WorkflowExecutionError(f"unsupported input binding for {label}")
|
||||
try:
|
||||
set_local_value(payload, binding.target, value)
|
||||
except LocalPathError as exc:
|
||||
raise WorkflowExecutionError(str(exc)) from exc
|
||||
return payload
|
||||
|
||||
|
||||
def step_subgraph(
|
||||
workflow: Workflow,
|
||||
run: RunState,
|
||||
@@ -171,7 +136,7 @@ def _start_subgraph(
|
||||
) -> None:
|
||||
prepared.workflow.validate_structure().raise_for_errors()
|
||||
parent_scope = run.scopes[frame.scope_id]
|
||||
child_input = resolve_input_bindings(
|
||||
child_input = resolve_step_input_bindings(
|
||||
step.input,
|
||||
state=state_view_for_frame(run, frame),
|
||||
workflow_input=parent_scope.workflow_input,
|
||||
|
||||
Reference in New Issue
Block a user