Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

wasmhost

WebAssembly from CPython, PyPy and Pythonista, with the JavaScript WebAssembly API. It runs in whichever JavaScript engine is available: JavaScriptCore's JSContext in Pythonista on iOS, WebKitGTK's JavaScriptCore on Linux, or Node. Plain Python, no dependencies, no C extension.

import wasmhost

module = wasmhost.Module(open("lib.wasm", "rb").read())  # WebAssembly.Module
instance = wasmhost.Instance(module)  # WebAssembly.Instance
print(instance.exports.add(2, 3))  # i32/i64 -> int, f32/f64 -> float
instance.exports.memory.write(ptr, b"data")  # WebAssembly.Memory
print(instance.exports.counter.value)  # WebAssembly.Global

See examples/basic.py.

  • Types. The JavaScript API can't tell a function's signature, and it matters (an i64 argument must reach JavaScript as a BigInt), so the binary's type, import, function, global and export sections are read in Python. Module.exports(module) and Module.imports(module) describe a module with them.
  • Errors are the API's: CompileError, LinkError and Trap (WebAssembly.RuntimeError, which is also a RuntimeError); an out-of-bounds memory access is an IndexError.
  • Memory is copied, not shared: memory.read(offset, n), memory.write(offset, data), memory[a:b], memory.grow(pages).

Batches

On a JavaScript engine every call into it has a fixed cost (a pipe to node, a bridged Objective-C call in Pythonista). A batch does several steps in one trip (on wasmtime and wasm3, in Python), and a step can use the results of the earlier ones:

batch = instance.batch()
ptr = batch.call(instance.exports.alloc, len(data))  # a Ref
batch.write(instance.exports.memory, ptr, data)
status = batch.call(instance.exports.run, ptr)
batch.stop_if_nonzero(status)  # leave the rest out on an error status
out = batch.read(instance.exports.memory, ptr, 16)
batch.run()
out.value  # bytes (`.done` says whether the step ran)

A failing step (a trap, an out-of-bounds access) raises from run(), after the earlier steps' results are set. Only i32 results can be used in arithmetic (ptr * 8, ptr + 4).

Backends

Backend Where How it is detected
jscontext iOS (Pythonista, PythonIDE) JavaScriptCore's JSContext through objc_util (both apps have it), or through rubicon-objc where that is missing (checked only against a fake bridge, not on a device)
wasmtime anywhere with the wasmtime package import wasmtime (pip install wasmtime)
wasm3 CPython 3.11+ with pywasm3 import wasm3; install it from git: uv add "pywasm3 @ git+https://github.com/wasm3/pywasm3" (its PyPI release predates the API used here)
gi-jsc Linux WebKitGTK's JavaScriptCore through PyGObject (apt install gir1.2-javascriptcoregtk-4.1 python3-gi)
node anywhere with Node.js node on PATH

With nothing configured, the first backend that starts wins, in the order shown. Each backend's constructor is its own probe: it fails when its runtime is missing. Choose one with WASMHOST_BACKEND=<name>, wasmhost.set_backend("<name>") or Module(..., backend="<name>"); wasmhost.get_backend().name says which is in use. wasmhost.close() closes the backends it started. (In WebAssembly's words the host is the embedder, the Python side that provides imports; what runs the module is the backend.)

Not every backend can do everything (backend.supports("memory.grow") and supports("table.length") say): wasm3 can't Memory.grow from Python (NotImplementedError; a module's own memory.grow works) and has no tables API.

Try it on a device

The package carries a self-test, since nothing else can be run in Pythonista to see whether this works there:

import wasmhost
wasmhost.selftest()                 # or, from a shell: python -m wasmhost [--backend NAME] [--all]

It prints one line per check (the Objective-C bridge in use, WebAssembly and BigInt in the engine, calls, i64, memory, globals, traps, batches, the cost of a call), then N/M passed. If something fails, send the whole output. On a computer, python -m wasmhost --all runs it on every backend that starts.

Where it has been run

Where Backend Result A call / a batch of 3
Pythonista 3 (StaSh 0.7.5), Python 3.10.4, iPhone17,3 jscontext (objc_util) 23/23 53 / 97 us
PythonIDE, Python 3.14.7, ios-13.0-arm64-iphoneos jscontext (objc_util) 23/23 37 / 76 us
Linux, CPython 3.14t gi-jsc 23/23 34 / 78 us
Linux, CPython 3.14t node 23/23 82 / 116 us
Linux, CPython 3.14t wasmtime 18/18 69 / 233 us
Linux, CPython 3.14t wasm3 18/18 4 / 46 us
Linux, CPython 3.10 and PyPy 3.10 node 23/23 (and the test suite on 3.10)

The times are one run of the self-test each, so read them as an order of magnitude. Not run on a device: the rubicon-objc bridge (both iOS apps above have objc_util, so it wasn't needed), and imports (see below).

Not yet

  • Imports. A module that imports host functions, memories, tables or globals can't be instantiated (NotImplementedError). Python callbacks need a synchronous bridge: native for wasmtime and wasm3, a JavaScript function made from Python for gi-jsc, an Objective-C block for jscontext, and for node a blocking read of the pipe.
  • Tables beyond their length, v128 and reference types, multi-value results in a batch.

Test

uv run pytest                            # every backend that starts here
uv run pytest --wasm-backend node        # one backend: it must start, or the run stops with an error
uv run pytest --wasm-backend wasmtime    # or wasm3
uv run pytest --wasm-backend gi-jsc      # needs PyGObject: run it with a system-site-packages venv (see the CI job)
uv run pyright && uv run ruff check

Release files for wasmhost 0.0.1a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wasmhost 0.0.1a1
File Size Uploaded
wasmhost-0.0.1a1.tar.gz 58.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wasmhost 0.0.1a1
File Interpreter ABI Platform
wasmhost-0.0.1a1-py3-none-any.whl Python 3 none any Details

Total release size: 88.2 kB

Release files / wasmhost-0.0.1a1.tar.gz

Download URL wasmhost-0.0.1a1.tar.gz
Size 58.9 kB
Tags Source
SHA-256 checksum
How to use checksums
438061e98d67771c705ea01bfabf91fa61f79c0cc5926430b2dd163330bbc21a
BLAKE2b-256 checksum
How to use checksums
5a2e02ba64c09d0935cc7b7856aeff8ca50ffc018fc0f15d5f45623bcd78c026
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 24, 2026.

Transparency log

Release files / wasmhost-0.0.1a1-py3-none-any.whl

Download URL wasmhost-0.0.1a1-py3-none-any.whl
Size 29.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d4cc74d06dcab55d31b937ed53755c35d952eb56d96c37c231e1b6280578455
BLAKE2b-256 checksum
How to use checksums
f62d52838df6adec20a1b7fe208fb34225484509bfe9c557b6f076549c59c50d
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 24, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page