Skip to main content

Reactive Python execution engine for Typst documents

Project description

typst_pyexec

typst_pyexec is a reactive Python execution engine for Typst documents.

It executes Python fences inside .typ files, captures outputs (stdout, figures, tables), and injects rendered Typst markup into an intermediate file (*.typst_pyexec.typ) that can be compiled directly.

Requirements

  • Python >= 3.10
  • Typst compiler for build and watch (default command typst)
  • Optional: tinymist for faster live preview (tinymist preview)

Core Capabilities

  • Reactive dependency graph based on Python AST analysis
  • Incremental execution with source-hash cache
  • Persistent Jupyter kernel between builds with cold-kernel hydration
  • Warm-session prerequisite reuse to avoid unnecessary replay in watch rebuilds
  • Matplotlib figure export to SVG with PNG fallback
  • Subfigure reconstruction via figure(grid(...))
  • DataFrame HTML rendering to Typst #table(...)
  • Watch mode with automatic rebuilds and live preview

Installation

pip install typst_pyexec

For development:

uv sync --extra dev

Use As A Git Submodule (Existing Project venv)

If typst_pyexec is checked out as a submodule and you want to run it with an existing project environment (for example a code/ project that pins numpy==1.26.4), you can execute it without creating a separate venv inside report/typst_pyexec.

From repository root:

uv --project code run --with-editable report/typst_pyexec typst_pyexec build report/report.typ --typst-compile-arg --root --typst-compile-arg .

On Windows, this repository also provides a wrapper script:

.\report\run_typst_pyexec.ps1 build

This wrapper:

  • runs in the code/ uv project environment
  • installs report/typst_pyexec as editable for that run
  • forwards Typst --root to the repository root
  • avoids any cd requirement

You can also run watch mode the same way:

.\report\run_typst_pyexec.ps1 watch -PreviewEngine typst

If you run from source manually, python -m typst_pyexec is supported:

PYTHONPATH=report/typst_pyexec python -m typst_pyexec build report/report.typ

CLI Usage

Build once:

typst_pyexec build document.typ

Watch and rebuild on save:

typst_pyexec watch document.typ

Watch with explicit preview backend:

typst_pyexec watch document.typ --preview-engine auto

Clean local state directory:

typst_pyexec clean

Common options:

  • --output-dir <dir>: write intermediate and state outputs to a custom folder (default: source file directory)
  • --no-cache: disable cache reads and writes, and execute all cells
  • --jobs <n>: reserved for future multi-kernel scheduling (-1 default)
  • --compiler <cmd>: Typst compiler binary name or path (default typst)
  • --typst-arg <arg>: pass an argument to both Typst compile and Typst watch (repeatable)
  • --typst-compile-arg <arg>: pass an argument only to Typst compile (repeatable)
  • --typst-watch-arg <arg>: pass an argument only to Typst watch (repeatable)
  • watch --preview-engine <auto|tinymist|typst|none>: select live preview backend

Environment variables:

  • TYPST_PYEXEC_CELL_TIMEOUT=<seconds>: timeout per executed Python cell (default: 600). Use a larger value for long-running analysis cells in watch mode.

Watch mode behavior

  • Watch mode regenerates *.typst_pyexec.typ on each save and delegates rendering to a live preview process.
  • --preview-engine auto tries tinymist preview first (if tinymist is on PATH), then falls back to typst watch.
  • --preview-engine tinymist prefers tinymist preview; if tinymist is unavailable it falls back to typst watch.
  • --preview-engine typst always runs typst watch.
  • --preview-engine none disables live preview and only refreshes the intermediate file.
  • Any --typst-watch-arg values are applied to typst watch (including fallback from tinymist to typst watch).
  • Any --typst-compile-arg values are also passed to tinymist preview (for example --root) to keep preview file access and project root behavior consistent with build mode.

Examples:

typst_pyexec build report.typ --typst-compile-arg --root --typst-compile-arg .
typst_pyexec watch report.typ --preview-engine typst --typst-watch-arg --root --typst-watch-arg .

Block Options (%|)

Place options at the top of a Python fence:

```python
%| echo: false
%| raw: true
%| figure: true
%| plt-lines.linewidth: 0.8
%| plt-axes.linewidth: 0.6
print("hello")
```

Supported options:

  • execute (default true): skip execution when false
  • refresh (default false): force this cell to run every build
  • timeout: per-cell execution timeout in seconds (overrides TYPST_PYEXEC_CELL_TIMEOUT when set)
  • echo (default true): show or hide source code
  • raw (default true): show or hide textual runtime output (stdout, tracebacks, text/plain bundles)
  • figure (default true): show or hide rendered figures
  • caption: explicit figure caption override
  • label: Typst label for cross-references
  • keep-subplots (default false): preserve multi-axis plot as one image
  • keep-colorbar / keep-colorbars (default true): keep colorbars attached in the exported figure; set to false to drop them
  • subfigure-caption-position (top or bottom): position subfigure captions for subplot grids
  • img-*: passthrough kwargs for Typst image(...)
  • fig-*: passthrough kwargs for the outer/global Typst figure(...)
  • subfig-*: passthrough kwargs for each generated Typst subfigure (kind: "subfigure")
  • grid-*: passthrough kwargs for Typst grid(...)
  • grid-columns: explicit columns value for the grid (overrides inferred default)
  • plt-*: per-cell matplotlib rcParams overrides (key after plt- maps to rcParam name)

Example for subplot grids with captions on top and separate outer/inner styling:

```python
%| keep-subplots: false
%| subfigure-caption-position: top
%| fig-supplement: "Figure"
%| fig-placement: top
%| subfig-supplement: "Figure"
%| subfig-stroke: rgb("#888")
```

In this configuration, fig-* applies only to the outer/global figure, and subfig-* applies to each child subfigure.

Boolean values are case-insensitive and accept: true/false, yes/no, on/off, 1/0.

plt-* values are parsed as JSON first, then Python literals. If parsing fails, the raw string is used.

Default plt-* values applied to every executed cell:

  • lines.linewidth: 0.8
  • axes.linewidth: 0.6
  • grid.linewidth: 0.2
  • axes.grid: true
  • lines.markersize: 4 (scatter points are about 2x smaller in area than matplotlib default)

plt-* examples:

  • %| plt-lines.linewidth: 0.8
  • %| plt-axes.linewidth: 0.6
  • %| plt-grid.linewidth: 0.4
  • %| plt-axes.grid: true

Precedence order for plot style values:

  1. Built-in defaults (values above)
  2. %| plt-* metadata options
  3. Any matplotlib.rcParams[...] = ... or plt.rcParams.update(...) in Python code

This means Python code always wins over %| plt-*, and %| plt-* wins over defaults.

Behavior notes:

  • cell_id is internal and generated automatically.
  • Python fences can be indented to fit paragraph or layout context; shared left padding is removed before execution.
  • In generated .typst_pyexec.typ, that original block padding is preserved for both rendered source and rendered outputs.
  • Option-only edits (including plt-*) do not invalidate cache because cache keys are based on Python source.
  • To re-run on option updates, set refresh: true.

Figure and Caption Behavior

  • Matplotlib figures are exported only when explicitly shown (plt.show() or fig.show()).
  • For single-axis plots, title or suptitle text is promoted into the Typst caption when caption is not set.
  • For subplot grids, each axis title becomes a child caption; suptitle becomes the outer caption.
  • Use %| subfigure-caption-position: top to place subplot captions above each subplot image.
  • In subplot grids, fig-* affects only the outer/global figure, while subfig-* affects each child subfigure.
  • Colorbars remain attached to their corresponding plot/subplot during export and are rendered together.
  • Exported figures use tight bounding boxes so colorbar tick labels are not clipped when kept.
  • With keep-subplots: true, multi-axis figures are kept as one image and subplot titles remain inside the image (the first subplot title is not promoted to caption).
  • Title and suptitle text are removed from exported images only when they are promoted to captions.
  • If SVG export fails and PNG is emitted, renderer auto-resolves PNG paths in final Typst output.

How It Works

  1. Parse Python fences from Typst source
  2. Build def/use DAG from AST
  3. Compute changed cells from source-hash cache
  4. Execute required cells in topological groups
  5. Capture stdout, display bundles, and figure artifacts
  6. Render Typst fragments per cell
  7. Inject into document.typst_pyexec.typ
  8. Build mode: compile with Typst compiler
  9. Watch mode: refresh intermediate file and stream live preview via tinymist preview or typst watch

Caching and Execution Model

  • Cache keys are SHA-256 hashes of the Python source for each cell.
  • Cache entries are stored under .typst_pyexec/cache.
  • --no-cache disables cache reads and writes and forces all executable cells to run.
  • refresh: true forces a cell to execute every build but does not cascade to dependents.
  • In watch mode, typst_pyexec keeps one executor for the whole watcher process. If prerequisite cells already ran in that warm kernel session, they are not replayed during later dependent executions.
  • When a kernel is cold or reconnected, typst_pyexec replays prerequisite cells to rebuild namespace state.

Local State

typst_pyexec writes runtime state into .typst_pyexec/ inside the output directory:

  • kernel_connection.json: reconnect data for persistent kernel
  • cache/: JSON entries keyed by SHA-256 of cell source
  • figures/: SVG/PNG artifacts
  • notebook.ipynb: synchronized notebook in normal execution mode (original user cells)
  • notebook_export.ipynb: synchronized notebook in export mode (figure capture preamble/postamble included)

Notebook Modes

Two notebooks are intentionally generated:

  • notebook.ipynb preserves the authored Python cells and attached outputs exactly as written (minimal overhead).
  • notebook_export.ipynb wraps each cell with figure-export helpers that invoke optimized functions.

Both notebooks include a setup cell that initializes the environment:

  • Normal mode: sets working directory for relative path consistency
  • Export mode: sets working directory and imports matplotlib plus save_figures_and_metadata, enabling the export notebook to execute standalone

Development Quality Gates

Run checks locally:

uv run ruff check .
uv run black --check typst_pyexec tests
uv run mypy typst_pyexec
uv run pytest

CI/CD (GitHub Actions)

  • CI: matrix on Ubuntu and Windows, Python 3.10-3.12, with lint + format + type-check + tests + coverage artifact
  • Release: tag-driven (v*.*.*) build and publish workflow for PyPI using trusted publishing

Workflows are in:

  • .github/workflows/ci.yml
  • .github/workflows/release.yml

Publish to PyPI

typst_pyexec is already configured for trusted publishing through GitHub Actions.

Release steps:

  1. Bump version in pyproject.toml and typst_pyexec/__init__.py.
  2. Commit and push to main.
  3. Create and push a version tag:
git tag v0.1.1
git push origin v0.1.1
  1. GitHub Actions runs .github/workflows/release.yml and publishes to PyPI.

Pre-tag checklist:

  1. python -m pytest -q is green.
  2. python -m build succeeds.
  3. python -m twine check dist/* passes.
  4. Version matches in pyproject.toml and typst_pyexec/__init__.py.
  5. Git tag matches that version (vX.Y.Z).

Local preflight checks before tagging:

python -m build
python -m twine check dist/*

If you prefer token-based manual publishing:

python -m twine upload dist/*

Use in Other Repositories

After publishing, install as a normal dependency.

With pip:

pip install typst_pyexec

With uv (project dependency):

uv add typst_pyexec

With uvx (run CLI without adding dependency):

uvx typst_pyexec build document.typ

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typst_pyexec-0.0.8.tar.gz (196.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typst_pyexec-0.0.8-py3-none-any.whl (44.7 kB view details)

Uploaded Python 3

File details

Details for the file typst_pyexec-0.0.8.tar.gz.

File metadata

  • Download URL: typst_pyexec-0.0.8.tar.gz
  • Upload date:
  • Size: 196.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for typst_pyexec-0.0.8.tar.gz
Algorithm Hash digest
SHA256 8cf2238599a89ede4c51512c0aff582e4994acfbc15dd65f9b43c0d18e642338
MD5 b19cb8cfecea007d29e9c5bc36cc1665
BLAKE2b-256 f623b35ffd917263779e9e8e3ed70b38e02a7137c1264df3e81078140b089c9c

See more details on using hashes here.

Provenance

The following attestation bundles were made for typst_pyexec-0.0.8.tar.gz:

Publisher: release.yml on Arkanthara/typst_pyexec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file typst_pyexec-0.0.8-py3-none-any.whl.

File metadata

  • Download URL: typst_pyexec-0.0.8-py3-none-any.whl
  • Upload date:
  • Size: 44.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for typst_pyexec-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 fb9dba34038426d4e01a943de20d295c62b9d57c9a44eed2152d0f2b764552f5
MD5 dbf922f7ad4f476c6df9cb6bb03d074f
BLAKE2b-256 7568a8cf3511462a74996985e765f146c1cd392f8844ef176e0e07ad3446aa48

See more details on using hashes here.

Provenance

The following attestation bundles were made for typst_pyexec-0.0.8-py3-none-any.whl:

Publisher: release.yml on Arkanthara/typst_pyexec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page