Reactive execution
ipyflow’s headline feature is reactive execution: run a cell and let ipyflow re-run exactly the other cells that need it, so the notebook always looks as though it were run top to bottom. This behavior is governed by three orthogonal settings – the execution mode, the flow direction, and the execution schedule – plus an inline reactive-variable syntax. This guide covers each.
All four are also settable per session with the %flow magic (see
The %flow magic) or made the default in your IPython profile.
Execution mode: lazy vs. reactive
The execution mode is an ExecutionMode:
lazy(the default)Only the cell you execute runs. ipyflow still tracks dependencies and flags stale cells in the UI, but it does not act on them. This is the fully backwards-compatible
ipykernelbehavior.reactiveExecuting a cell also re-executes its stale upstream and downstream cells, in dependency order.
Switch modes at runtime with %flow mode; the change is reflected on
flow().mut_settings:
run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'mode reactive')")
run_cell("assert flow().mut_settings.exec_mode.value == 'reactive'")
run_cell("get_ipython().run_line_magic('flow', 'mode lazy')")
run_cell("assert flow().mut_settings.exec_mode.value == 'lazy'")
Even in lazy mode you can reactively execute a single cell on demand with ctrl+shift+enter (or cmd+shift+enter on macOS), which runs that cell plus its dependents without changing the default mode. When the default is reactive, the same shortcut inverts to a one-off non-reactive run.
To make reactive mode the default for every new kernel, add to your IPython
profile (usually ~/.ipython/profile_default/ipython_config.py):
c = get_config()
c.ipyflow.exec_mode = "reactive" # defaults to "lazy"
Flow direction: in-order vs. any-order
The flow direction is a FlowDirection that decides which
dataflow edges are eligible to drive reactive execution:
in_order(the default)A cell may only reactively trigger cells that appear after it in the notebook. ipyflow still tracks backward references (a cell using a symbol defined later), but omits those edges when scheduling reactivity.
any_orderSpatial position is ignored; any dependency edge can drive reactivity.
In-order semantics are less flexible but encourage cleaner, reproducible
notebooks that convert cleanly to top-to-bottom scripts. Toggle with %flow
direction (aliases: order, semantics; values also accept ordered /
unordered):
run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'direction any_order')")
run_cell("assert flow().mut_settings.flow_order.value == 'any_order'")
run_cell("get_ipython().run_line_magic('flow', 'direction in_order')")
run_cell("assert flow().mut_settings.flow_order.value == 'in_order'")
Or set the default in your profile:
c = get_config()
c.ipyflow.flow_direction = "any_order" # defaults to "in_order"
Execution schedule
The schedule is an ExecutionSchedule controlling how
the set of cells to re-run is computed:
dag_based(the default)Uses the dynamic dataflow DAG built from actual runtime dependencies. The most precise option.
liveness_basedUses static liveness analysis of cell source (which symbols each cell reads and writes). Useful when runtime edges are unavailable – for instance the JupyterLite/Pyodide build defaults to this.
hybrid_dag_liveness_basedCombines both; required for incremental reactivity (see below).
run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'schedule liveness_based')")
run_cell("assert flow().mut_settings.exec_schedule.value == 'liveness_based'")
A related knob, ReactivityMode (%flow reactivity
[batch|incremental]), chooses whether reactive updates are applied all at once
(batch, the default) or one cell at a time (incremental). Incremental
mode requires a liveness-aware schedule; selecting it while on dag_based
automatically upgrades the schedule to hybrid_dag_liveness_based.
Reactive-variable syntax
Reactivity is normally cell-level. The $ prefix opts an individual symbol
into reactivity so that updates propagate even in lazy mode:
# Cell 1
x = 1
# Cell 2 -- `$x` marks this usage as reactive
y = $x + 1
# Now re-running Cell 1 with a new value reactively refreshes Cell 2,
# even though the global execution mode is still lazy.
A double prefix, $$x, additionally makes the definition cascade: downstream
reactive readers update transitively. The $ syntax is applied by ipyflow’s
input transformer in the JupyterLab/Notebook frontend; because it is a
frontend-level source rewrite it is demonstrated here as prose rather than an
executed example.
See also
Configuration (ipyflow.config) for the full enum reference and
The %flow magic for every %flow subcommand that toggles
these settings.