nested path support for in/out maps, merge strat refactor
validation + runtime for write targets
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
from wf_core.local_paths import LocalPathError, get_local_value, has_overlapping_paths
|
||||
from wf_core.models.steps import NodeUse
|
||||
from wf_core.models.workflow import Workflow
|
||||
from wf_core.paths import (
|
||||
@@ -11,6 +12,7 @@ from wf_core.paths import (
|
||||
set_nested_value,
|
||||
split_graph_path,
|
||||
)
|
||||
from wf_core.runtime.ops.merges import apply_builtin_merge
|
||||
|
||||
|
||||
def apply_output_map(
|
||||
@@ -36,16 +38,22 @@ def apply_mapped_state(
|
||||
*,
|
||||
missing_field_message: str,
|
||||
) -> dict[str, Any]:
|
||||
state_changes: dict[str, Any] = {}
|
||||
if has_overlapping_paths(mapping.values()):
|
||||
raise WorkflowExecutionError("mapped state patch has overlapping destination paths")
|
||||
|
||||
patch: dict[str, Any] = {}
|
||||
for source_field, destination_path in mapping.items():
|
||||
if source_field not in source_data:
|
||||
try:
|
||||
value = get_local_value(source_data, source_field)
|
||||
except LocalPathError:
|
||||
raise WorkflowExecutionError(
|
||||
missing_field_message.format(field=repr(source_field))
|
||||
)
|
||||
value = source_data[source_field]
|
||||
) from None
|
||||
patch[destination_path] = value
|
||||
|
||||
for destination_path, value in patch.items():
|
||||
write_state_value(workflow, state, destination_path, value)
|
||||
state_changes[destination_path] = value
|
||||
return state_changes
|
||||
return dict(patch)
|
||||
|
||||
|
||||
def write_state_value(
|
||||
@@ -65,44 +73,14 @@ def write_state_value(
|
||||
declared_field = workflow.state_schema.fields.get(field_name)
|
||||
merge_strategy = declared_field.merge_strategy if declared_field else "replace"
|
||||
key_path = parts
|
||||
|
||||
if merge_strategy == "replace":
|
||||
safe_set_nested_value(state, key_path, value)
|
||||
return
|
||||
|
||||
current_value = get_nested_value(state, key_path)
|
||||
if merge_strategy == "append":
|
||||
if current_value is None:
|
||||
safe_set_nested_value(
|
||||
state, key_path, [value] if not isinstance(value, list) else value
|
||||
)
|
||||
return
|
||||
if not isinstance(current_value, list):
|
||||
raise WorkflowExecutionError(
|
||||
f"cannot append into non-list state path {destination_path!r}"
|
||||
)
|
||||
if isinstance(value, list):
|
||||
current_value.extend(value)
|
||||
else:
|
||||
current_value.append(value)
|
||||
return
|
||||
|
||||
if merge_strategy == "merge_object":
|
||||
if current_value is None:
|
||||
if not isinstance(value, dict):
|
||||
raise WorkflowExecutionError(
|
||||
f"cannot merge non-object value into {destination_path!r}"
|
||||
)
|
||||
safe_set_nested_value(state, key_path, dict(value))
|
||||
return
|
||||
if not isinstance(current_value, dict) or not isinstance(value, dict):
|
||||
raise WorkflowExecutionError(
|
||||
f"merge_object requires dict values at {destination_path!r}"
|
||||
)
|
||||
current_value.update(value)
|
||||
return
|
||||
|
||||
raise WorkflowExecutionError(f"unknown merge strategy {merge_strategy!r}")
|
||||
merged_value = apply_builtin_merge(
|
||||
strategy=merge_strategy,
|
||||
current_value=current_value,
|
||||
incoming_value=value,
|
||||
destination_path=destination_path,
|
||||
)
|
||||
safe_set_nested_value(state, key_path, merged_value)
|
||||
|
||||
|
||||
def project_output(workflow: Workflow, state: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user