Skip to content
Taliesin User Guide

4 Executable content

Executable cells against a warm kernel: what a language label decides, how to set up Python, and how an output becomes a block.

A {python} cell runs against a warm Jupyter kernel; its outputs become their own blocks tied to the cell’s id. Everything on this page is produced from its own .tmd source.

4.1 Code cells

import numpy as np
np.arange(5).sum()
np.int64(10)

On save, only cells whose source changed (plus downstream cells) re-execute, and the kernel never cold-starts. Point the server at a Python with ipykernel via TALIESIN_PYTHON (one-time setup). {js} cells run in the browser (Live, browser-side content) and {mermaid} blocks render there as diagrams; a cell in any other language, or a {python} cell with no kernel available, renders as highlighted source rather than failing. The first cell of a session pays the interpreter’s own start-up (booting Python and importing what the cell reaches for); every edit after that reuses the kernel that boot produced.

Output streams into the preview as the cell produces it, beside a running-elapsed badge, so a training loop’s epoch lines appear as they are printed rather than all at once at the end. Progress bars that redraw with a carriage return (tqdm and friends) render as one updating line, not one line per frame.

If the kernel crashes mid-session, the server respawns it on the next run, and a wedged cell is interrupted instead of locking up the kernel. The cap is on silence, not on runtime: a cell that produces no output for TALIESIN_CELL_SILENCE seconds (default 600) is sent SIGINT, and the budget resets on every line the cell prints, so a long job that reports progress runs to completion however long it takes. The dev menu’s Restart kernel action drops the kernel, re-runs every cell against a fresh one, and reloads so define values re-bind. See Troubleshooting for the symptoms these answer.

4.1.1 echo, include, and cache: three different switches

These three boolean options look similar but control different stages:

OptionDoes the cell run?Source shown?Output shown?Cached in _freeze/?
(defaults)yesyesyesyes
#| echo: falseyesnoyesyes
#| include: falseyesnonoyes
#| cache: falseyesyesyesnever

echo: false hides the source listing only, for when the reader should see a plot or a table but not the code that made it.

include: false goes further and hides both source and output, but the cell still executes (kernel state is kept), so a later cell still sees its variables. This is the option for a setup cell whose result you never want to see but whose state you need downstream, and a define(...) in it still reaches your {js} cells, since that bridge is not output. If it fails, its traceback is hidden along with the rest of its output, so the failure is reported as an error diagnostic at the cell instead, and it fails build --strict. To skip execution entirely, run the whole document with --no-exec / TALIESIN_NO_EXEC.

cache: false is orthogonal to both: the cell runs and shows normally, but its output is never frozen, and everything downstream of it re-runs and is likewise never cached (a downstream output follows from this cell’s value, which the cache key cannot see: only its code).

#| include: false
#| cache: false
import pandas as pd
df = pd.read_csv("data.csv")
What the cache key covers

The _freeze/ key is built from cell code, every upstream cell’s code, and the interpreter’s identity, so it cannot see anything a cell reads that is not code: a data file, an environment variable, a URL it fetches, the clock, an installed library you upgraded in place.

Edit data.csv without touching a line of Python and the numbers on the page will not change, because the cache key has not changed. Mark any cell with an out-of-band input #| cache: false, as above. The preview does not watch data files either, so a cache: false cell re-runs on the next save of the .tmd, not when data.csv changes.

To force one fresh run without editing anything, use the dev-menu Restart kernel or run with TALIESIN_NO_CACHE.

cache (and only cache) also exists as a document-wide default under the execute: front-matter key, and a per-cell #| option always overrides it. See the cell-options reference for the full option table and the Configuration reference for execute:.

4.1.2 Captions: figures, listings, and tables

#| fig-cap: captions a cell’s output and numbers it as a figure; #| lst-cap: and #| tbl-cap: do the same for a code listing and for a table the cell publishes. Pair any of them with a matching #| label: fig-x / lst-x / tbl-x and the output is cross-referenceable as @fig-x, @lst-x or @tbl-x from anywhere in the book. A caption is inline Markdown, so *emphasis*, `code` and $x^2$ render in it the same way whichever kind of cell carries it. An executed image is published with an empty alt, since in a figure the caption describes it, so a cell whose output shows an image and has no fig-cap draws a warning. The cell-options reference has the routing rules.

4.1.3 Publishing a table, rather than printing one

#| tbl-cap: captions whatever the cell publishes, and the shortest way to write a table publishes something you did not mean. The page still builds and the lint stays clean; the defect is visible only in the rendered output.

A bare df on its own line emits pandas’ own repr: a table tagged border="1" and class="dataframe", a row-index column, and a <style scoped> block, which no browser scopes, so it leaks to the whole page. Publish the markup yourself instead: to_html(index=False, border=0) on a DataFrame, or any other HTML string handed to display(HTML(…)), emits plain markup that the page’s own table styling reaches. Keep the index when its labels carry meaning (a describe() table, where they name the statistic) and drop it when it is a bare row number. Table 4.1 below is a hand-built HTML string published that way.

import numpy as np
from IPython.display import HTML, display

rows = ""
for seed in (0, 1, 2):
    x = np.random.default_rng(seed).normal(size=1000)
    rows += f"<tr><td>{seed}</td><td>{x.mean():+.3f}</td><td>{x.std():.3f}</td></tr>"
display(HTML(f"<table><tr><th>seed</th><th>mean</th><th>sd</th></tr>{rows}</table>"))
Table 4.1: Mean and standard deviation of 1000 normal draws, by seed
seedmeansd
0-0.0480.977
1-0.0540.986
2-0.0221.013

4.1.4 Folded code

#| code-fold: true collapses a listing behind a summary (label it with code-summary), so long setup code stays out of the way until clicked, as Listing 4.1 does:

Listing 4.1: A setup cell, folded behind its summary.
Show the setup
import numpy as np
rng = np.random.default_rng(0)
draws = rng.normal(size=10_000)
(round(float(draws.mean()), 3),
 round(float(draws.std()), 3))
(0.006, 0.998)

4.1.5 Writing a figure to disk

Inline {python} figures are themed for the web (Figure 4.1 below has a transparent background and neutral grey axes that read on light and dark), but that theming never touches global rcParams, so a plain fig.savefig("x.pdf") in a cell already writes a clean, print-ready figure (black on white):

import matplotlib.pyplot as plt, numpy as np, os
fig, ax = plt.subplots()
ax.plot(np.arange(40), np.exp(-0.1 * np.arange(40)))
ax.set_xlabel("iteration"); ax.set_ylabel("loss")
os.makedirs("figures", exist_ok=True)
fig.savefig("figures/loss.pdf")
plt.show()
Figure 4.1: Loss curve

A cell runs with its document’s directory as the working directory, so a relative path lands beside the source rather than wherever you launched Taliesin.

4.2 Live, browser-side content

{js} cells run in your browser rather than against the kernel. See Interactive and explorable documents.