Run Graphviz from Python via WebAssembly — no native binary required
Project description
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 14.1.5 compiled for
wasm32-wasi - Exposes a plain C ABI:
graphviz_render,graphviz_free,graphviz_last_error,graphviz_version - No Emscripten, no JS glue
- Graphviz 14.1.5 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-14.1.5
pixi run cmake -S build/src/graphviz-14.1.5 -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
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/14.1.5.
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wasi_graphviz-0.1.0.tar.gz.
File metadata
- Download URL: wasi_graphviz-0.1.0.tar.gz
- Upload date:
- Size: 404.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec8c856c576d6ddd7358ba098e42d352ef9ea6e75ec6046da125af2072d9f224
|
|
| MD5 |
005479a2d77761a5fa140ceb254458f3
|
|
| BLAKE2b-256 |
b1986105e2d49ee3053e91e3972880df0fc9ddac488ffd06f23e5e48af350b09
|
Provenance
The following attestation bundles were made for wasi_graphviz-0.1.0.tar.gz:
Publisher:
release.yml on pablormier/wasi-graphviz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wasi_graphviz-0.1.0.tar.gz -
Subject digest:
ec8c856c576d6ddd7358ba098e42d352ef9ea6e75ec6046da125af2072d9f224 - Sigstore transparency entry: 1397063320
- Sigstore integration time:
-
Permalink:
pablormier/wasi-graphviz@17c542dfb9ab5ed30f96d5f8b8315bb85fc661d4 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pablormier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17c542dfb9ab5ed30f96d5f8b8315bb85fc661d4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wasi_graphviz-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wasi_graphviz-0.1.0-py3-none-any.whl
- Upload date:
- Size: 411.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a666d79ebeb5319281d53db61c69232983ffb3e5c377119bd443ce36b08f548a
|
|
| MD5 |
8a4bf7ab077b6c6ffd1e9c375578977a
|
|
| BLAKE2b-256 |
53f7f1b109416e6051f1872d52cb5af244477bc9fc2fc11ad1acee17acc29543
|
Provenance
The following attestation bundles were made for wasi_graphviz-0.1.0-py3-none-any.whl:
Publisher:
release.yml on pablormier/wasi-graphviz
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wasi_graphviz-0.1.0-py3-none-any.whl -
Subject digest:
a666d79ebeb5319281d53db61c69232983ffb3e5c377119bd443ce36b08f548a - Sigstore transparency entry: 1397063324
- Sigstore integration time:
-
Permalink:
pablormier/wasi-graphviz@17c542dfb9ab5ed30f96d5f8b8315bb85fc661d4 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pablormier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17c542dfb9ab5ed30f96d5f8b8315bb85fc661d4 -
Trigger Event:
push
-
Statement type: