Skip to content
Frederik Beimgraben edited this page Jun 4, 2026 · 2 revisions

PyTeX

PyTeX generates LaTeX from Python. You build a document as a tree of typed TeX nodes and render it to a .tex file — or you drop inline Python into an existing .tex source and have it evaluated at render time. Either way the output is a plain .tex file, and --build hands that to tectonic to get a PDF.

The point of the node tree is that a document stays type-checkable: every node is an immutable dataclass with a .rendered property, and the public factories are named after the LaTeX control sequences they emit (Section, Bold, Frac, Title, …), so the Python reads like the LaTeX it produces. Nodes also carry their own package requirements, so the preamble is assembled from whatever the body actually uses — you never hand-maintain a \usepackage list.

from pytex.commands.builtin import Bold, Emph, Section, Title, MakeTitle
from pytex.model.concat import Concat
from pytex.model.document import Document
from pytex.model.math import DisplayMath, Frac

__pytex__ = Document(
    preamble=Title("PyTeX Example"),
    body=Concat(
        MakeTitle(),
        Section("Text"),
        "A paragraph with ", Bold("bold"), " and ", Emph("emphasised"), " words.",
        Section("Math"),
        DisplayMath(Concat("x = ", Frac("-b", "2a"))),
    ),
)
pytex example.tex.py          # -> build/example.out.tex
pytex example.tex.py --build  # -> build/example.out.pdf

Bare strings are coerced to text nodes and LaTeX-escaped, so you rarely have to think about escaping &, %, _ and friends.

Install

Requires Python 3.13+. Pick whichever fits:

  • Standalone binary — each release ships pytex binaries for Linux/macOS/Windows with no Python or pip needed. The binary bundles its own interpreter plus common data packages (numpy, pandas, openpyxl/calamine, Pillow, PyYAML), so a document can import those without installing anything. It's built on Python 3.14, so tex(t"...") template strings work even on a machine that only has 3.13 or no Python at all.
  • pipxpipx install pytex-preprocessor to get the pytex command on your PATH in an isolated environment.
  • pippip install pytex-preprocessor into whatever environment you like.
  • Dev — clone, then python -m venv venv && . venv/bin/activate and pip install -e . (add [dev] for pytest, ruff, basedpyright).

Things that trip people up

  • tectonic is fetched on first --build. You don't install it yourself. If it isn't on PATH, the build downloads a self-contained binary into a temp folder and reuses it. The first build is slow because tectonic also pulls its TeX bundle into a cache ($XDG_CACHE_HOME/Tectonic, or ~/.cache/Tectonic); later builds hit that cache and are fast. The cache survives across runs — wipe it only if a bundle gets corrupted.
  • 3.13 vs 3.14 for t-strings. The core library runs on 3.13. The PEP 750 tex(t"...") template-string entry point only exists on 3.14, because that's where t-strings landed. tex is simply not exported on 3.13. The standalone binary sidesteps this — it's a 3.14 build, so t-string documents work regardless of the host Python.
  • inkscape is needed only for SVG image conversion, and makeindex (from a TeX distribution) only to resolve glossaries/acronyms. Both are optional; you'll get a clear error if a document needs one and it's missing.

Where to go next

  • CLI and input modes — the pytex command, the three input kinds (.tex.py, .py.tex, .md), --build, --variant, the flags.
  • Markdown to PDF — variants, frontmatter, GitHub callouts, citations/bibliographies, the typographic extras.
  • Node and factory API — how the tree works and the factories you'll reach for most, including the size switches.
  • Blob API (pytex_api)render_blob for services: source bytes in, PDF bytes out, with trust levels and a Podman sandbox for untrusted input.

Clone this wiki locally