Skip to content

01 โ€“ Basic Linear Pipeline

A fixed sequence of steps: each step receives every state the previous step produced, and the last step's states are the terminal results.


๐ŸŽฏ Goal

Normalize and sum a list of numbers with a three-node chain: Clean โ†’ Sum โ†’ Announce.


๐Ÿงฑ Nodes

from dagpipe import Node, State

class Clean(Node):
    id = "math.clean"

    def resolve(self, state: State):
        numbers = [n for n in state.get("numbers", []) if isinstance(n, (int, float))]
        yield self.fork(state, payload_update={"numbers": numbers})

class Sum(Node):
    id = "math.sum"

    def resolve(self, state: State):
        yield self.fork(
            state,
            payload_update={"total": sum(state.get("numbers", []))},
        )

class Announce(Node):
    id = "math.announce"

    def resolve(self, state: State):
        print(f"total={state.get('total')}")
        yield self.fork(state, payload_update={"announced": True})

๐Ÿƒ Run it

from dagpipe import Engine, Payload, Schema, State

class CalcState(State):
    schema = Schema({
        "numbers": list,
        "total": int | float | None,
        "announced": bool | None,
    })

engine = Engine([Clean(), Sum(), Announce()])
results = engine.run(CalcState(payload=Payload({"numbers": [1, "x", 2, 3.5]})))

state = results[0]
assert state.get("numbers") == [1, 2, 3.5]
assert state.get("total") == 6.5
assert state.get("announced") is True
assert len(state.history) == 3      # clean โ†’ sum โ†’ announce
assert state.depth == 3

๐Ÿ” What just happened

  • The engine seeded states = [root].
  • Clean yielded one new state โ†’ states = [clean_state].
  • Sum consumed it โ†’ states = [sum_state].
  • Announce consumed it โ†’ states = [announce_state] (terminal).

If any node yields zero states, the engine breaks and returns an empty list โ€” that is how a pipeline can terminate early.


๐Ÿ’ก Tips

  • Order in the sequence is the execution order โ€” keep stateful order in mind.
  • Every node sees all states produced by the previous node. If a node forks twice, the next step runs once per fork.
  • Prefer Node.fork over State.fork inside resolve() so the node ID lands in history automatically.