Skip to content

How to run code on state changes

Hooks are plain functions called in the engine when a run or task run reaches a state. Use them for in-process reactions such as closing a connection, writing a marker file, or posting a metric. For reactions that should happen even when the process is gone, or that involve other flows, use rules instead.

Flow hooks

from cereyan import flow

events = []

def note(flow, run, state):
    events.append((flow.name, state["type"]))

@flow(on_completion=[note], on_failure=[note], on_crashed=[note], on_cancellation=[note])
def pipeline() -> str:
    return "done"

pipeline()
assert events == [("pipeline", "Completed")]

Each hook receives the Flow, the run as a dict (id, name, parameters, state, ...), and the state dict that triggered it. Hooks run after the state is recorded, in the order given. An exception in a hook is logged with the run and does not change the run's state.

Option Called when
on_completion The run ends Completed
on_failure The run ends Failed, including TimedOut
on_crashed The server marks the run Crashed; runs on the server side since the engine is gone
on_cancellation The run ends Cancelled

Task hooks

Tasks accept on_completion, on_failure, and on_cancellation with the same signature, receiving the Task instead of the flow:

from cereyan import flow, task

seen = []

def audit(task, run, state):
    seen.append((task.name, state["type"], state.get("message")))

@task(on_failure=[audit])
def load() -> None:
    raise ValueError("bad rows")

@flow
def etl() -> None:
    load()

try:
    etl()
except ValueError:
    pass
assert seen[0][:2] == ("load", "Failed")

Choosing between hooks and rules

Hooks Rules
Run where In the engine, with your code In the server
Survive a crash No, except on_crashed Yes
Can start other flows Through the client Yes, run_flow
Configurable at runtime No Data rules yes
Work offline Yes Code rules, once registered

Related: React to events with rules, Retry, time out and survive crashes.