Skip to content
v0.18.0 Actively developed View modules

Python to
WebAssembly

Waspy compiles typed Python ahead of time into a standalone WebAssembly module, with no interpreter inside. Built in Rust for performance and reliability.

~/your-rust-project

cargo add waspy

Adding waspy v0.18.0 to dependencies

Waspy is a Rust library: add it to your project and compile Python to WebAssembly from your own code, or try it in the browser first.

  • AOT

    Compiled ahead of timeNo interpreter or VM in the module you ship.

  • =

    CPython's answerOr a located compile error, never a silent wrong one.

  • .w

    Standard .wasmRuns in browsers, Node.js, wasmtime, and at the edge.

Pipeline

How Waspy works

From Python source to optimized WebAssembly in four steps.

  1. 01

    Python AST

    Parsed with RustPython's parser

  2. 02

    Typed IR

    Lowered to Waspy's own intermediate representation

  3. 03

    WASM binary

    Emitted with wasm-encoder, then validated

  4. 04

    Optimized

    Shrunk by Binaryen, when you ask for it

The same pipeline, in code

Write Python, compile it from Rust, run it from JavaScript or TypeScript.

1 · Write Python
fibonacci.py
def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    a, b = 0, 1
    for i in range(2, n + 1):
        a, b = b, a + b
    return b

def factorial(n: int) -> int:
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

Ordinary, type-annotated Python is the compiler's input. Annotations pick the WASM value types: int and bool become i32, float becomes f64.

2 · Compile from Rust
main.rs
// in your Rust project,
// after `cargo add waspy`
use waspy::compile_python_file;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // parse → typed IR → codegen → Binaryen
    let wasm =
        compile_python_file("fibonacci.py", true)?;

    // a standalone module,
    // no Python interpreter inside
    std::fs::write("fibonacci.wasm", &wasm)?;
    Ok(())
}

cargo run turns fibonacci.py into fibonacci.wasm. Every top-level Python function becomes a WASM export, and imports of sibling .py files link in automatically.

3 · Run from JS or TS
run.mjs
// Node 18+
// (TypeScript works the same)
import { readFile } from "node:fs/promises";

const wasm = await readFile("./fibonacci.wasm");
const { instance } =
    await WebAssembly.instantiate(wasm);

console.log(instance.exports.fibonacci(10));
// 55
console.log(instance.exports.factorial(5));
// 120

node run.mjs prints 55 and 120: your Python, running as WebAssembly. In a browser, swap readFile for fetch and WebAssembly.instantiateStreaming.

From a source string let wasm = waspy::compile_python_to_wasm(&source)?;

Compile Python you already hold in memory, no files involved.

From a project directory let wasm = waspy::compile_python_project("./my_project", true)?;

Compile a project directory into one module, with __main__.py entry detection and pyproject.toml config.

More in the library guide and on docs.rs.

Waspy vs other Python implementations

How Waspy compares across performance, safety, and portability.

Waspy compared with CPython, MicroPython, PyPy, and HPy
Feature Waspy CPython MicroPython PyPy HPy
Target WebAssembly Native / VM Microcontrollers JIT compilation C extensions
Performance Compiled WASM (full support) Interpreted (partial support) Optimized for size (partial support) JIT optimized (full support) Native speed (full support)
Type safety Compile-time (full support) Runtime only (limited or none) Runtime only (limited or none) JIT inference (partial support) Runtime only (limited or none)
Universal runtime Browser, server, edge (full support) OS dependent (partial support) Hardware specific (limited or none) OS dependent (partial support) OS dependent (partial support)
Startup time Instant (full support) Module loading (partial support) Fast (full support) JIT warmup (limited or none) Import overhead (partial support)
Memory usage Minimal (full support) Standard (partial support) Ultra-low (full support) Higher (JIT) (limited or none) Standard (partial support)
Standard library Small subset (partial support) Complete (full support) Subset (partial support) Complete (full support) Complete (full support)
Security Sandboxed WASM (full support) Full system access (limited or none) Hardware limited (partial support) Full system access (limited or none) Full system access (limited or none)
Use case Web apps, serverless General purpose IoT, embedded CPU-intensive apps C extension API
  • Full support
  • Partial support
  • Limited or none

Features

What Waspy does

What the compiler supports today, from parser to binary. The Python subset lists it in full, and per-feature status lives on the modules board.

  • Ahead-of-time compilation

    Python compiles to a WASM binary before it runs, with no interpreter or VM in the output.

    no runtime VM
  • Typed code generation

    Type annotations map to WASM value types (i32 for ints and bools, f64 for floats), checked at compile time.

    annotations
  • CPython's answer, or an error

    A construct either gives the answer CPython gives or is refused at compile time with a located error. Nothing unsupported is silently accepted.

    correctness first
  • Binaryen optimization

    The binary can run through Binaryen passes: dead-code elimination, inlining, constant folding. The unoptimized module is already valid and correct.

    optimizer
  • Standard WASM output

    Emits plain .wasm modules that load in browsers, Node.js, wasmtime, and edge runtimes. A program with a __main__ block is a WASI command.

    .wasm
  • Language coverage

    Classes with multiple inheritance, closures, lambdas, generators, comprehensions, exceptions, f-strings, and decorators.

    python 3 syntax
  • Small Rust API

    compile_python_to_wasm() for one source, compile_python_file() for an entry file and its imports, compile_python_project() for a directory.

    rust crate
  • Project compilation

    Multi-file input with import resolution, __main__.py entry detection, and pyproject.toml config, linked into one .wasm. User modules resolve from disk, each compiled once.

    one .wasm out
  • Standard library

    Module constants, sys streams, argv and exit, re.sub and re.escape folded over constant arguments, and the functools decorators. Every other stdlib call is refused at compile time.

    stdlib subset

Under the hood

Architecture

How a project becomes one module.

  1. 1

    Project directory

    compile_python_project

    The input: a directory of Python modules. compile_python_file starts from a single entry file instead and follows its imports.

  2. 2

    Project config + file list

    core::config::load_project_config

    Reads pyproject.toml, setup.py, and __version__.py or __about__.py for the project's name and version, and collects the Python files to compile.

  3. 3

    Modules + entry point

    imports resolved, each module once

    Every module is gathered once, however many import paths reach it (from an entry file, sibling imports resolve from disk, transitively). The __main__ entry point is looked for in __main__.py first, then in the other files.

  4. 4

    Source → AST → IRModule

    parse_python, lower_ast_to_ir

    For each file: RustPython's parser turns the source into an AST, which is lowered to a typed IRModule. Module passes then desugar user decorators, metaclasses, nested functions, keyword arguments, with, and generators.

  5. 5

    Merged IRModule

    memory_layout.merge_from

    The per-file modules merge into one IRModule with one flat namespace: functions, classes, variables, and dispatch tables combine and the memory layouts merge. Two files defining the same function name is a compile error, never a silent pick.

  6. 6

    Decorators + entry points

    DecoratorRegistry, add_entry_point_to_module

    The built-in decorators are applied and a detected __main__ block becomes the entry point, which makes the program a WASI command exporting _start.

  7. 7

    Compilation context

    compile_ir_module

    Lays out classes (their MRO and fields), module globals, and the module init, then generates each function through the compilation context (type tables, function indices, memory layout) with wasm-encoder. Only the runtime helpers and WASI imports the program uses are added.

  8. 8

    WASM

    validated, functions exported

    A standalone WebAssembly module, validated before it is returned, with every top-level function exported. It is already correct: nothing depends on the optimizer.

  9. 9

    Optimized WASM

    optimize_wasm

    When optimization is on, Binaryen applies its passes (dead-code elimination, constant folding, inlining, and more) for a smaller, faster binary.

On stage

Talks

Where Waspy has been presented, with recordings and slides where they exist.

IICT 2026
talk

Direct Ahead-of-Time Compilation of Typed Python to Compact WebAssembly

Kumar Anirudha, Farhaan Bukhsh

Innovations in Compiler Technology, IISc Bengaluru. How Waspy maps typed Python onto WebAssembly's static types and linear memory with no embedded interpreter, producing modules of a few kilobytes.

PyCon India 2025
talk

Compiling Python to WASM

Kumar Anirudha, Farhaan Bukhsh

Why Python still lags behind in the browser, what Pyodide, PyScript, and RustPython trade away to get there, and how a native ahead-of-time compiler like Waspy closes the gap.

talk

WASM and Python: The Future of Serverless Computing

Kumar Anirudha, Farhaan Bukhsh

EuroPython 2025, Prague. WebAssembly against containers for serverless workloads, what makes Python hard to run in WASM, and a FastAPI function deployed in a WASM runtime and benchmarked against a traditional server.

Spoke about Waspy somewhere? Add your talk to this page.

The people

Contributors

Waspy is built in the open by everyone who has sent a patch, an issue, or an idea.

Loading contributors from GitHub…

Wanna contribute to Waspy?

Waspy is free and open source under the MIT license, and it gets better every time someone new pokes at it. A compiler is a big surface: the parser, the IR, codegen, the runtime, the docs you are reading right now. There is room for a first commit at almost any level of experience.

Pick up a good first issue, report a Python snippet that miscompiles, teach the compiler one more builtin, or just tell us what you tried to build and where it broke. Every one of those genuinely moves the project forward, and we would love to work on it together.