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
  • 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

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)
  • watch --preview-engine <auto|tinymist|typst|none>: select live preview backend

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.

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
  • 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
  • subfigure-caption-position (top or bottom): position subfigure captions for subplot grids
  • img-*: passthrough kwargs for Typst image(...)
  • fig-*: passthrough kwargs for Typst figure(...)
  • 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)

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.
  • 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.
  • 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.5.tar.gz (191.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.5-py3-none-any.whl (40.8 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for typst_pyexec-0.0.5.tar.gz
Algorithm Hash digest
SHA256 534156afd3f6a2686bb32331ccc7a58ac96d0cf059bd3f0eca41262cd3096703
MD5 f430ccda7f0763e710dfb3eb15cf4044
BLAKE2b-256 379c427d5c9eba95693426cdda56f0d74faa80c5a122923f0691748d62a65bfc

See more details on using hashes here.

Provenance

The following attestation bundles were made for typst_pyexec-0.0.5.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.5-py3-none-any.whl.

File metadata

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

File hashes

Hashes for typst_pyexec-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 e1915adb6e0ed4045d018687e0c2b21f32736a104359b5b7f4efbed6b24884f5
MD5 88f733ccc1f7edd4cdcc514fadf086a1
BLAKE2b-256 9694f309d4efa698076e467dd0b8eb78c714ceb93eb3bb61d73dade2a88e1f3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for typst_pyexec-0.0.5-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