Skip to main content

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-graphviz ship 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 graphviz and pygraphviz shell out to a system-installed dot or link against libcgraph. 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

pywasm is pure-Python, but it imports fcntl (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 / pyodide backend (using the browser's native WebAssembly object + a WASI polyfill) is possible, but not yet implemented.

If you need Graphviz in a Pyodide or marimo notebook, use easydot instead — it wraps the browser's @hpcc-js/wasm renderer 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 model
  • circo — circular layout
  • fdp — force-directed placement
  • sfdp — scalable FDP
  • twopi — radial layouts
  • osage — array-based layouts
  • patchwork — treemaps

Supported output formats

The core plugin supports:

  • svg (default)
  • dot
  • json
  • ps
  • map
  • fig
  • tk

Architecture

The project consists of three layers:

  1. 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
  2. Python backends

    • PywasmBackend — pure-Python interpreter with built-in WASI support
    • WasmtimeBackend — fast native runtime with full WASI support
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wasi_graphviz-0.1.1.tar.gz (402.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wasi_graphviz-0.1.1-py3-none-any.whl (410.1 kB view details)

Uploaded Python 3

File details

Details for the file wasi_graphviz-0.1.1.tar.gz.

File metadata

  • Download URL: wasi_graphviz-0.1.1.tar.gz
  • Upload date:
  • Size: 402.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wasi_graphviz-0.1.1.tar.gz
Algorithm Hash digest
SHA256 34a8e43122fec3d890988e980c64cefb786adfc0b8310715809924c1b7d31287
MD5 204c90c6d8585fe02fa78d97327d426d
BLAKE2b-256 69fb701823aafe717991e3fb51e8203700ae58427a84aff77cef441657749bc9

See more details on using hashes here.

Provenance

The following attestation bundles were made for wasi_graphviz-0.1.1.tar.gz:

Publisher: release.yml on pablormier/wasi-graphviz

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wasi_graphviz-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: wasi_graphviz-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 410.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wasi_graphviz-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dc2aaf1884ae0e4b95601794b9a0fabc842f7f96dff18fe853caa1f045054a3c
MD5 2a062ba372e7ec191a8328b4acbb0e44
BLAKE2b-256 2ec0258fcc83e7e2eb4b48a63028a9ead84e45c8d7dc14b869e3dc7dc5f579d5

See more details on using hashes here.

Provenance

The following attestation bundles were made for wasi_graphviz-0.1.1-py3-none-any.whl:

Publisher: release.yml on pablormier/wasi-graphviz

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