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

  • 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.
  • Title and suptitle text are removed from exported images after promotion 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.3.tar.gz (189.9 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.3-py3-none-any.whl (39.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: typst_pyexec-0.0.3.tar.gz
  • Upload date:
  • Size: 189.9 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.3.tar.gz
Algorithm Hash digest
SHA256 406b6098d72561f2e216b29474b29cc7a3a09cb5bed9704dd17d4e5a082ea6f8
MD5 9eed5fca906804503d6902fb7494c44b
BLAKE2b-256 1ad5784546e2faebd67e72566cc04b604b62fde8588eef7e2ce92038dfe2a269

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: typst_pyexec-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 39.9 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ab7c7653a566df6c18cb41670a8c6a494335d9eea1a107e3053b82e99cac01ca
MD5 d109e7b5cc095fc27ebaab07ef8011fe
BLAKE2b-256 ef510c251abd2477fd00e73396aefd23a43d6739f0f1c228ed5eee7451a63a57

See more details on using hashes here.

Provenance

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