wasi-graphviz
Run Graphviz from Python via WebAssembly — no native Graphviz binary, no Node.js, no browser required.
Why this exists
Rendering DOT graphs from Python sits in an awkward gap.
- Browser-based WASM renderers like
@hpcc-js/wasm-graphvizship Graphviz as a WebAssembly module — but it's an Emscripten build wrapped in JavaScript glue, designed to run in a browser or Node. Tools like easydot wrap that flow so you can call it from Python and display SVGs in a notebook, but the renderer itself still ultimately needs a JS runtime / browser surface to produce pixels — fine for interactive notebooks, awkward for headless static SVG generation in CI, batch jobs, or server-side pipelines. - System-binary Python wrappers like
graphvizandpygraphvizshell out to a system-installeddotor link againstlibcgraph. That means every deployment target — laptops, CI runners, Lambda functions, Docker base images — has to install Graphviz separately, and your code has to spawn subprocesses or manage shared-library loading.
wasi-graphviz plugs the gap: a wasm32-wasi build of upstream
Graphviz plus a thin Python wrapper. pip install is the entire
install story, the wheel is ~440 KB, and rendering happens entirely
in-process — no subprocesses, no system packages, no JS, no browser.
Works the same in a notebook, a CI job, a serverless function, or an
offline air-gapped environment.
The wheel itself does not include a WASM runtime — that's an explicit choice (see Installation below) so you can pick the runtime that matches your deployment constraints.
Installation
wasi-graphviz needs a runtime to execute the bundled graphviz.wasm.
Two are supported, picked via extras:
# Recommended for most users — fast native runtime
pip install wasi-graphviz[wasmtime]
# Pure-Python runtime — slow, but works anywhere CPython does (not Pyodide)
pip install wasi-graphviz[pywasm]
# Install both — `render(..., backend="auto")` will prefer wasmtime
pip install wasi-graphviz[all]
Choosing a backend
wasmtime |
pywasm |
|
|---|---|---|
| Implementation | Native runtime (Rust) with Python bindings | Pure-Python WASM interpreter |
| Speed | ~0.3–7 ms per render (see below) | ~3–130 s per render — 4–5 orders of magnitude slower |
| Install size | ~15 MB wheel (compiled extensions) | ~200 KB wheel |
| Platforms | Linux/macOS/Windows on x86_64 + arm64 | Anywhere CPython 3.11+ runs (see Pyodide note below) |
| Cold start | Slightly heavier instantiation | Fastest to import |
| Use when… | Production, CI, notebooks, anything performance-sensitive | Last-resort portability — exotic CPU/OS, no-native-deps environments |
backend="auto" (the default) prefers wasmtime when available and
silently falls back to pywasm, so most code can ignore the
distinction. Force one explicitly when you have a reason — see
Backend selection below.
Pyodide / marimo / WebAssembly-based Python environments
pywasmis pure-Python, but it importsfcntl(for stdin handling) which is not available in Pyodide because Pyodide itself runs inside a browser WebAssembly sandbox that lacks POSIX file-control APIs.Neither backend works in Pyodide today. A future
browser/pyodidebackend (using the browser's nativeWebAssemblyobject + a WASI polyfill) is possible, but not yet implemented.If you need Graphviz in a Pyodide or marimo notebook, use
easydotinstead — it wraps the browser's@hpcc-js/wasmrenderer and works out of the box in those environments.
Benchmarks
Median wall time per render on Apple M-series, Python 3.11, measured
via pytest-benchmark (run yourself with uv run pytest tests/test_benchmarks.py -m perf --benchmark-only):
| Graph | wasmtime |
pywasm |
wasmtime speedup |
|---|---|---|---|
| 10 edges | 0.29 ms | 3.7 s | ~12,800 × |
| 100 edges | 1.74 ms | 32.5 s | ~18,700 × |
| 400 edges | 6.86 ms | 133.3 s | ~19,400 × |
pywasm is a pure-Python WASM interpreter, so the ratio is roughly
"interpreted Python evaluating WASM bytecode" vs "native compiled
code" — expect orders of magnitude, not factors. Use wasmtime
unless your environment forbids native code.
Quick start
from wasi_graphviz import render
# Render a simple graph to SVG (uses wasmtime if available, falls back to pywasm)
svg = render("digraph G { a -> b; }")
print(svg.decode("utf-8"))
Usage
Basic rendering
from wasi_graphviz import render
# Render to SVG with default dot engine
svg = render("digraph G { a -> b; }")
# Use a different layout engine
svg = render("graph G { a -- b; }", engine="neato")
# Render to DOT format
output = render("digraph G { a -> b; }", format="dot")
Backend selection
See the trade-off table above for when to pick which.
from wasi_graphviz import render
# Auto-select (prefer wasmtime, fall back to pywasm)
svg = render("digraph G { a -> b; }", backend="auto")
# Force pywasm — pure Python, slow but maximally portable
svg = render("digraph G { a -> b; }", backend="pywasm")
# Force wasmtime — fast native runtime, requires compiled extension
svg = render("digraph G { a -> b; }", backend="wasmtime")
Error handling
from wasi_graphviz import render, RenderError
try:
svg = render("not valid dot {")
except RenderError as e:
print(f"Render failed: {e}")
Supported layout engines
All major Graphviz layout engines work:
dot— hierarchical layouts (default)neato— spring modelcirco— circular layoutfdp— force-directed placementsfdp— scalable FDPtwopi— radial layoutsosage— array-based layoutspatchwork— treemaps
Supported output formats
The core plugin supports:
svg(default)dotjsonpsmapfigtk
Architecture
The project consists of three layers:
-
WASM artifact (
graphviz.wasm)- Graphviz 15.0.0 compiled for
wasm32-wasi - Exposes a plain C ABI:
graphviz_render,graphviz_free,graphviz_last_error,graphviz_version - No Emscripten, no JS glue
- Graphviz 15.0.0 compiled for
-
Python backends
PywasmBackend— pure-Python interpreter with built-in WASI supportWasmtimeBackend— fast native runtime with full WASI support
-
Public API
render(dot, format="svg", engine="dot", backend="auto") -> bytes
Building from source
See BUILD.md for detailed build instructions.
Quick summary:
# Install build tools
pixi install
# Build the WASM artifact
python scripts/prepare_graphviz_wasi.py build/src/graphviz-15.0.0
pixi run cmake -S build/src/graphviz-15.0.0 -B build/graphviz-cmake \
-DCMAKE_TOOLCHAIN_FILE=$(pwd)/native/wasm32-wasi-toolchain.cmake \
...
pixi run cmake --build build/graphviz-cmake --parallel
# ... compile wrapper, link, validate
Development
# Run tests (perf benchmarks are skipped by default)
uv run pytest
# Format and lint
uv run ruff check .
uv run ruff format .
# Run benchmarks comparing wasmtime vs pywasm across graph sizes
uv run pytest tests/test_benchmarks.py -m perf --benchmark-only
Automated releases
The scheduled Graphviz update workflow creates a patch release only after the new WASM build, linting, tests, and package build succeed. It authenticates its commit and tag push with a short-lived GitHub App installation token.
To enable it for a repository, install a GitHub App with Contents: Read and write access and configure:
- repository variable
RELEASE_APP_CLIENT_IDwith the App's client ID; - repository secret
RELEASE_APP_PRIVATE_KEYwith the App's private key.
The App must be installed on this repository. No personal access token is required.
License & attribution
This package is licensed under the Eclipse Public License 2.0
(EPL-2.0). See LICENSE for the full text.
The wheel bundles a compiled build of Graphviz
(also EPL-2.0). The full EPL-2.0 text is also shipped inside the wheel at
wasi_graphviz/assets/GRAPHVIZ_LICENSE. Source for the bundled Graphviz
version is available upstream:
https://gitlab.com/graphviz/graphviz/-/tree/15.0.0.
Modifications applied to the Graphviz source before compilation are
described in
scripts/prepare_graphviz_wasi.py
and are themselves licensed under EPL-2.0. See NOTICE for the
full attribution.
wasi-graphviz is an unofficial repackaging and is not affiliated with or endorsed by the Graphviz project.
First functional v0.1.0 built with Kimi K2.6 in ~1h, single session (81% context used). Total cost: ~$1
Metadata
Release files for wasi-graphviz 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wasi_graphviz-0.1.3.tar.gz | 402.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wasi_graphviz-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 811.9 kB
Release files / wasi_graphviz-0.1.3.tar.gz
| Download URL | wasi_graphviz-0.1.3.tar.gz |
|---|---|
| Size | 402.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70b7df45ae357c647459ca8ffd12fb3d402176fdfd47c8f9cf7882444ba97d82
|
|
BLAKE2b-256 checksum How to use checksums |
2fe344190faf2eed3a54aec916b8a20c915c3e166363e19c423deab11a2d0864
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / wasi_graphviz-0.1.3-py3-none-any.whl
| Download URL | wasi_graphviz-0.1.3-py3-none-any.whl |
|---|---|
| Size | 409.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
db4b274d70308b617a289bb1460686c8a05e0f94cec75d86f0aa89d341584745
|
|
BLAKE2b-256 checksum How to use checksums |
e1225322b06183d34164cc9dc341927c22c50674403897b147cb5c173d816181
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log