wf-core reorg 2 split the Models
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for runtime flow operations."""
|
||||
|
||||
from wf_core.runtime.ops.flow import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for foreach runtime operations."""
|
||||
|
||||
from wf_core.runtime.ops.foreach import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for frame runtime operations."""
|
||||
|
||||
from wf_core.runtime.ops.frames import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for interrupt runtime operations."""
|
||||
|
||||
from wf_core.runtime.ops.interrupts import * # noqa: F403
|
||||
|
||||
|
||||
+46
-153
@@ -1,161 +1,54 @@
|
||||
from __future__ import annotations
|
||||
"""Compatibility facade for core workflow model objects."""
|
||||
|
||||
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
|
||||
from wf_core.models import (
|
||||
BinaryCondition,
|
||||
Condition,
|
||||
ConditionNode,
|
||||
Edge,
|
||||
ExistsCondition,
|
||||
ForeachNode,
|
||||
InterruptNode,
|
||||
JoinNode,
|
||||
LiteralOperand,
|
||||
NodeDef,
|
||||
NodeResult,
|
||||
NodeUse,
|
||||
NotCondition,
|
||||
Operand,
|
||||
PathOperand,
|
||||
SchemaRef,
|
||||
StateField,
|
||||
StateSchema,
|
||||
Step,
|
||||
VariadicCondition,
|
||||
Workflow,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wf_core.validation.issues import ValidationReport
|
||||
|
||||
|
||||
class SchemaRef(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
title: str | None = None
|
||||
type: str | None = None
|
||||
properties: dict[str, Any] = Field(default_factory=dict)
|
||||
required: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class StateField(BaseModel):
|
||||
type: str
|
||||
merge_strategy: Literal["replace", "append", "merge_object"] = "replace"
|
||||
trace: bool = True
|
||||
default: Any = None
|
||||
|
||||
|
||||
class StateSchema(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
fields: dict[str, StateField] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class NodeDef(BaseModel):
|
||||
name: str
|
||||
input_schema: SchemaRef
|
||||
output_schema: SchemaRef
|
||||
outcomes: list[str] = Field(min_length=1)
|
||||
retry: int | None = Field(default=None, ge=0)
|
||||
timeout_seconds: int | None = Field(default=None, gt=0)
|
||||
|
||||
|
||||
class NodeUse(BaseModel):
|
||||
id: str
|
||||
type: Literal["node"]
|
||||
node: str
|
||||
desc: str | None = None
|
||||
in_map: dict[str, str] = Field(default_factory=dict)
|
||||
out_map: dict[str, str] = Field(default_factory=dict)
|
||||
retry: int | None = Field(default=None, ge=0)
|
||||
timeout_seconds: int | None = Field(default=None, gt=0)
|
||||
|
||||
|
||||
class PathOperand(BaseModel):
|
||||
path: str
|
||||
|
||||
|
||||
class LiteralOperand(BaseModel):
|
||||
value: Any
|
||||
|
||||
|
||||
Operand = Annotated[PathOperand | LiteralOperand, Field(discriminator=None)]
|
||||
|
||||
|
||||
class ExistsCondition(BaseModel):
|
||||
op: Literal["exists"]
|
||||
path: str
|
||||
|
||||
|
||||
class NotCondition(BaseModel):
|
||||
op: Literal["not"]
|
||||
arg: "Condition"
|
||||
|
||||
|
||||
class VariadicCondition(BaseModel):
|
||||
op: Literal["and", "or"]
|
||||
args: list["Condition"] = Field(min_length=1)
|
||||
|
||||
|
||||
class BinaryCondition(BaseModel):
|
||||
op: Literal["eq", "ne", "gt", "lt"]
|
||||
left: PathOperand | LiteralOperand
|
||||
right: PathOperand | LiteralOperand
|
||||
|
||||
|
||||
Condition = Annotated[
|
||||
ExistsCondition | NotCondition | VariadicCondition | BinaryCondition,
|
||||
Field(discriminator="op"),
|
||||
__all__ = [
|
||||
"BinaryCondition",
|
||||
"Condition",
|
||||
"ConditionNode",
|
||||
"Edge",
|
||||
"ExistsCondition",
|
||||
"ForeachNode",
|
||||
"InterruptNode",
|
||||
"JoinNode",
|
||||
"LiteralOperand",
|
||||
"NodeDef",
|
||||
"NodeResult",
|
||||
"NodeUse",
|
||||
"NotCondition",
|
||||
"Operand",
|
||||
"PathOperand",
|
||||
"SchemaRef",
|
||||
"StateField",
|
||||
"StateSchema",
|
||||
"Step",
|
||||
"VariadicCondition",
|
||||
"Workflow",
|
||||
]
|
||||
|
||||
|
||||
class ConditionNode(BaseModel):
|
||||
id: str
|
||||
type: Literal["condition"]
|
||||
check: Condition
|
||||
|
||||
|
||||
class ForeachNode(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
id: str
|
||||
type: Literal["foreach"]
|
||||
over: str
|
||||
as_: str = Field(alias="as")
|
||||
mode: Literal["serial", "parallel"] = "serial"
|
||||
on_item_error: Literal["fail", "collect", "skip"] = "fail"
|
||||
|
||||
|
||||
class JoinNode(BaseModel):
|
||||
id: str
|
||||
type: Literal["join"]
|
||||
|
||||
|
||||
class InterruptNode(BaseModel):
|
||||
id: str
|
||||
type: Literal["interrupt"]
|
||||
kind: str
|
||||
request_map: dict[str, str] = Field(default_factory=dict)
|
||||
out_map: dict[str, str] = Field(default_factory=dict)
|
||||
outcomes: list[str] = Field(default_factory=lambda: ["submitted"])
|
||||
|
||||
|
||||
Step = Annotated[
|
||||
NodeUse | ConditionNode | ForeachNode | JoinNode | InterruptNode,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
class Edge(BaseModel):
|
||||
from_: str = Field(alias="from")
|
||||
outcome: str
|
||||
to: str
|
||||
|
||||
|
||||
class Workflow(BaseModel):
|
||||
name: str
|
||||
input_schema: SchemaRef
|
||||
state_schema: StateSchema
|
||||
output_schema: SchemaRef
|
||||
node_defs: list[NodeDef] = Field(default_factory=list)
|
||||
start: str
|
||||
nodes: list[Step]
|
||||
edges: list[Edge]
|
||||
|
||||
def validate_structure(self) -> "ValidationReport":
|
||||
from importlib import import_module
|
||||
|
||||
validation = import_module("wf_core.validation.core")
|
||||
return cast("ValidationReport", validation.validate_workflow(self))
|
||||
|
||||
|
||||
class NodeResult(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
outcome: str
|
||||
output: dict[str, Any] = Field(default_factory=dict)
|
||||
meta: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
from wf_core.models.conditions import (
|
||||
BinaryCondition,
|
||||
Condition,
|
||||
ExistsCondition,
|
||||
LiteralOperand,
|
||||
NotCondition,
|
||||
Operand,
|
||||
PathOperand,
|
||||
VariadicCondition,
|
||||
)
|
||||
from wf_core.models.results import NodeResult
|
||||
from wf_core.models.schemas import NodeDef, SchemaRef, StateField, StateSchema
|
||||
from wf_core.models.steps import (
|
||||
ConditionNode,
|
||||
ForeachNode,
|
||||
InterruptNode,
|
||||
JoinNode,
|
||||
NodeUse,
|
||||
Step,
|
||||
)
|
||||
from wf_core.models.workflow import Edge, Workflow
|
||||
|
||||
__all__ = [
|
||||
"BinaryCondition",
|
||||
"Condition",
|
||||
"ConditionNode",
|
||||
"Edge",
|
||||
"ExistsCondition",
|
||||
"ForeachNode",
|
||||
"InterruptNode",
|
||||
"JoinNode",
|
||||
"LiteralOperand",
|
||||
"NodeDef",
|
||||
"NodeResult",
|
||||
"NodeUse",
|
||||
"NotCondition",
|
||||
"Operand",
|
||||
"PathOperand",
|
||||
"SchemaRef",
|
||||
"StateField",
|
||||
"StateSchema",
|
||||
"Step",
|
||||
"VariadicCondition",
|
||||
"Workflow",
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PathOperand(BaseModel):
|
||||
"""Condition operand resolved from a workflow graph path."""
|
||||
|
||||
path: str
|
||||
|
||||
|
||||
class LiteralOperand(BaseModel):
|
||||
"""Condition operand that carries a literal value."""
|
||||
|
||||
value: Any
|
||||
|
||||
|
||||
Operand = Annotated[PathOperand | LiteralOperand, Field(discriminator=None)]
|
||||
"""Condition operand accepted by binary condition expressions."""
|
||||
|
||||
|
||||
class ExistsCondition(BaseModel):
|
||||
"""Condition that is true when a graph path resolves to a present value."""
|
||||
|
||||
op: Literal["exists"]
|
||||
path: str
|
||||
|
||||
|
||||
class NotCondition(BaseModel):
|
||||
"""Condition that negates another condition expression."""
|
||||
|
||||
op: Literal["not"]
|
||||
arg: "Condition"
|
||||
|
||||
|
||||
class VariadicCondition(BaseModel):
|
||||
"""Condition that combines one or more child conditions."""
|
||||
|
||||
op: Literal["and", "or"]
|
||||
args: list["Condition"] = Field(min_length=1)
|
||||
|
||||
|
||||
class BinaryCondition(BaseModel):
|
||||
"""Condition that compares two operands."""
|
||||
|
||||
op: Literal["eq", "ne", "gt", "lt"]
|
||||
left: PathOperand | LiteralOperand
|
||||
right: PathOperand | LiteralOperand
|
||||
|
||||
|
||||
Condition = Annotated[
|
||||
ExistsCondition | NotCondition | VariadicCondition | BinaryCondition,
|
||||
Field(discriminator="op"),
|
||||
]
|
||||
"""Discriminated union of all supported condition expressions."""
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class NodeResult(BaseModel):
|
||||
"""Normalized result returned by a node handler after execution."""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
outcome: str
|
||||
output: dict[str, Any] = Field(default_factory=dict)
|
||||
meta: dict[str, Any] = Field(default_factory=dict)
|
||||
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class SchemaRef(BaseModel):
|
||||
"""JSON-schema-like shape used at workflow boundaries."""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
title: str | None = None
|
||||
type: str | None = None
|
||||
properties: dict[str, Any] = Field(default_factory=dict)
|
||||
required: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class StateField(BaseModel):
|
||||
"""Declared root state field plus its runtime merge behavior."""
|
||||
|
||||
type: str
|
||||
merge_strategy: Literal["replace", "append", "merge_object"] = "replace"
|
||||
trace: bool = True
|
||||
default: Any = None
|
||||
|
||||
|
||||
class StateSchema(BaseModel):
|
||||
"""Workflow state schema keyed by declared root field name."""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
fields: dict[str, StateField] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class NodeDef(BaseModel):
|
||||
"""Reusable node contract referenced by one or more node uses."""
|
||||
|
||||
name: str
|
||||
input_schema: SchemaRef
|
||||
output_schema: SchemaRef
|
||||
outcomes: list[str] = Field(min_length=1)
|
||||
retry: int | None = Field(default=None, ge=0)
|
||||
timeout_seconds: int | None = Field(default=None, gt=0)
|
||||
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from wf_core.models.conditions import Condition
|
||||
|
||||
|
||||
class NodeUse(BaseModel):
|
||||
"""Concrete use of a reusable node definition inside a workflow graph."""
|
||||
|
||||
id: str
|
||||
type: Literal["node"]
|
||||
node: str
|
||||
desc: str | None = None
|
||||
in_map: dict[str, str] = Field(default_factory=dict)
|
||||
out_map: dict[str, str] = Field(default_factory=dict)
|
||||
retry: int | None = Field(default=None, ge=0)
|
||||
timeout_seconds: int | None = Field(default=None, gt=0)
|
||||
|
||||
|
||||
class ConditionNode(BaseModel):
|
||||
"""Control-flow step that routes through `true` or `false` outcomes."""
|
||||
|
||||
id: str
|
||||
type: Literal["condition"]
|
||||
check: Condition
|
||||
|
||||
|
||||
class ForeachNode(BaseModel):
|
||||
"""Control-flow step that iterates over an input or state list."""
|
||||
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
id: str
|
||||
type: Literal["foreach"]
|
||||
over: str
|
||||
as_: str = Field(alias="as")
|
||||
mode: Literal["serial", "parallel"] = "serial"
|
||||
on_item_error: Literal["fail", "collect", "skip"] = "fail"
|
||||
|
||||
|
||||
class JoinNode(BaseModel):
|
||||
"""Control-flow step that marks a branch or frame as joined."""
|
||||
|
||||
id: str
|
||||
type: Literal["join"]
|
||||
|
||||
|
||||
class InterruptNode(BaseModel):
|
||||
"""Control-flow step that pauses a run and waits for resume input."""
|
||||
|
||||
id: str
|
||||
type: Literal["interrupt"]
|
||||
kind: str
|
||||
request_map: dict[str, str] = Field(default_factory=dict)
|
||||
out_map: dict[str, str] = Field(default_factory=dict)
|
||||
outcomes: list[str] = Field(default_factory=lambda: ["submitted"])
|
||||
|
||||
|
||||
Step = Annotated[
|
||||
NodeUse | ConditionNode | ForeachNode | JoinNode | InterruptNode,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
"""Discriminated union of all executable workflow graph steps."""
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from wf_core.models.schemas import NodeDef, SchemaRef, StateSchema
|
||||
from wf_core.models.steps import Step
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wf_core.validation.issues import ValidationReport
|
||||
|
||||
|
||||
class Edge(BaseModel):
|
||||
"""Outcome-specific transition from one workflow step to another."""
|
||||
|
||||
from_: str = Field(alias="from")
|
||||
outcome: str
|
||||
to: str
|
||||
|
||||
|
||||
class Workflow(BaseModel):
|
||||
"""Serializable workflow graph consumed by the core runtime."""
|
||||
|
||||
name: str
|
||||
input_schema: SchemaRef
|
||||
state_schema: StateSchema
|
||||
output_schema: SchemaRef
|
||||
node_defs: list[NodeDef] = Field(default_factory=list)
|
||||
start: str
|
||||
nodes: list[Step]
|
||||
edges: list[Edge]
|
||||
|
||||
def validate_structure(self) -> "ValidationReport":
|
||||
"""Return all structural validation issues for this workflow."""
|
||||
validation = import_module("wf_core.validation.core")
|
||||
return cast("ValidationReport", validation.validate_workflow(self))
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for node execution operations."""
|
||||
|
||||
from wf_core.runtime.ops.nodes import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for run-state construction."""
|
||||
|
||||
from wf_core.runtime.ops.runs import * # noqa: F403
|
||||
|
||||
|
||||
@@ -4,4 +4,3 @@ Root modules such as `wf_core.node_exec` remain as compatibility shims. New
|
||||
runtime internals should import from this package so the execution seam stays
|
||||
easy to navigate.
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for runtime schema validation helpers."""
|
||||
|
||||
from wf_core.runtime.ops.schemas import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for state mutation operations."""
|
||||
|
||||
from wf_core.runtime.ops.state import * # noqa: F403
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"""Compatibility shim for non-node step handlers."""
|
||||
|
||||
from wf_core.runtime.ops.handlers import * # noqa: F403
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@ __all__ = [
|
||||
"ValidationReport",
|
||||
"validate_workflow",
|
||||
]
|
||||
|
||||
|
||||
@@ -166,4 +166,3 @@ def _validate_reachable_outcomes(
|
||||
f"nodes[{node_id}]",
|
||||
f"reachable node is missing edges for outcomes {sorted(missing)!r}",
|
||||
)
|
||||
|
||||
|
||||
@@ -50,4 +50,3 @@ class ValidationReport:
|
||||
f"- [{issue.code}] {issue.path}: {issue.message}" for issue in self.errors
|
||||
)
|
||||
raise ValueError(f"Workflow validation failed:\n{rendered}")
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ def declared_outcomes_for_step(step: Step, node_defs: dict[str, NodeDef]) -> set
|
||||
return set()
|
||||
|
||||
|
||||
def reachable_node_ids(start: str, edges: list[Edge], nodes_by_id: dict[str, Step]) -> set[str]:
|
||||
def reachable_node_ids(
|
||||
start: str, edges: list[Edge], nodes_by_id: dict[str, Step]
|
||||
) -> set[str]:
|
||||
if start not in nodes_by_id:
|
||||
return set()
|
||||
|
||||
@@ -38,4 +40,3 @@ def reachable_node_ids(start: str, edges: list[Edge], nodes_by_id: dict[str, Ste
|
||||
seen.add(node_id)
|
||||
stack.extend(adjacency.get(node_id, []))
|
||||
return seen
|
||||
|
||||
|
||||
@@ -220,4 +220,3 @@ def validate_operand(
|
||||
path,
|
||||
f"invalid operand path {operand.path!r}",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user