This release is a pre-release and may not be stable for production use.
wasmmod
v0.1.4-alpha — signed packs for Python (.wasm / .aot / .elf).
Experimental. This is a pre-release (
-alpha). APIs, pack layout, verify/HTTP surfaces, and build flags may change without a stable compatibility promise. Default-off on purpose (MICROPY_PY_WASM=0); treat enablement as an experiment until a non-alpha release. Engine matrix and demos are exercised often, but the surface is still moving fast.
One pack file can ship C + Rust + embedded Python, talk to peer packs, and call back into the host. Wasm/AOT run on WAMR; ELF (.elf / .<arch>.elf, ET_REL) uses an in-tree reloc loader (no dlopen). Same MicroPython wasm module either way (CPython path planned). Opt-in only (MICROPY_PY_WASM=0 by default); host trees stay unchanged unless they include the submodule and turn the flag on.
Drop-in submodule: extmod/wasmmod → #include "extmod/wasmmod/...".
Install (two ways)
| Role | How |
|---|---|
| In MicroPython / metalpython | Git submodule at extmod/wasmmod (upstream-compatible layout). Firmware via ports/micropython/micropython.mk. |
| Standalone host | pip install --pre pymergetic-wasmmod — ships a trimmed source tree + pulls pymergetic-wasmmod-tools (wasmmod CLI). No µPy checkout required for pack/sign/inspect/cdn. |
pip install --pre pymergetic-wasmmod
# optional: upy pack targets need mpy-cross on PATH (or MPY_CROSS=…)
wasmmod pack path/to/example -o hello.wasm
python -c "from pymergetic.wasmmod.rt import require_root; print(require_root())"
Override tree discovery with WASMMOD_ROOT= if you keep a git checkout beside the wheel.
Why it shows off
| Superpower | What you get |
|---|---|
| Multi-impl packs | C, Rust, and pack-local Python in one artifact |
| Containers | .wasm · .aotN · .elf / .<arch>.elf (same MPWI/sign/finder) |
| Guest → guest | bridge / ELF client import peers without host glue per call |
| Guest → host → Py | wasmmod.host slots, buffers, mem cookies, object handles |
| Engine matrix | Interp · AOT · Fast JIT · LLVM JIT — same Wasm packs; ELF skips WAMR |
caller\callee | Py | C | RS
--------------+-----+-----+----
Py | ✓ | ✓ | ✓
C | ✓ | ✓ | ✓
RS | ✓ | ✓ | ✓
+ guest→guest, guest→host, rich i64/f32/f64 …
make -C examples test-engines → 57 cases × 4 engines.
Taste in three languages
Pack Python calls a native export in the same .wasm:
# hello/src/__init__.py
def greet():
return "hello from pack py"
def answer():
return hello() # → C export in this pack
C guest imports peers + host with one macro:
#include "../../guest.h"
MP_WASM_IMPORT("hello", int, hello, void);
MP_WASM_IMPORT("mixed", int, mixed_answer, void);
MP_WASM_IMPORT("wasmmod.host", int, call_i32, int slot, int arg);
int via_rs(int x) { return rs_square(x); } /* same-pack Rust */
int via_hello(void) { return hello(); } /* peer pack */
int via_host(int x) { return call_i32(0, x); } /* host slot */
Rust freestanding export (linked into mixed / bridge):
#![no_std]
#[no_mangle]
pub extern "C" fn rs_answer() -> i32 { 7 }
#[no_mangle]
pub extern "C" fn rs_i64_answer(x: i64) -> i64 { x + 7 }
Host — real unix MicroPython REPL (wasm is a built-in; see help('modules')):
MicroPython … on 2026-08-03; linux [GCC …] version
Type "help()" for more information.
>>> import sys
>>> sys.implementation.name
'micropython'
>>> help("modules")
… requests/__init__ wasm
… select websocket
… socket
Plus any modules on the filesystem
>>> import wasm
>>> wasm.path.append("packs")
>>> wasm.install_hook()
>>> import hello, mixed, bridge # packs/hello.wasm etc.
>>> hello.greet()
'hello from pack py'
>>> bridge.via_rs(6)
36
HTTP roots use the same static layout (native C GET). With a baked root CA, signed packs verify over the wire:
make -C examples test-signed # → test-http-verify OK
Example without verify: wasm.verify(False); wasm.install_hook("http://host/packs/") then import hello.
See also make -C examples test-http.
FQN → file: import a.b.c searches wasm.path then sys.path. A pack can sit at any depth; if only the leaf file exists, missing parents are thin namespace packages (PEP 420-ish) so a.b.c stays attribute-reachable. VFS pack roots are scanned so import a / a.b work when children exist (flat a.b.c.wasm or tree a/b/…).
Path form (dots → /): a/b/c/__init__.wasm · a/b/c.wasm
Flat form (e.g. under packs/): a.b.c.wasm
Containers / preference: MICROPY_WASM_CONTAINERS (unix default elf,aot,wasm) picks
*.elf / *.<arch>.elf / AOT / Wasm (+ .zlib). Nested names inside one pack
(hello.util) still come from embedded pack-Python when no leaf file exists.
Explicit: wasm.load_pack("packs/foo.wasm", "foo") or …/foo.elf.
Browser builds stay wasm-only (MICROPY_PY_WASM_ELF=0).
Inspect (offline): wasm.symbols / addr2line / locations / disasm / mpy_disasm
(and wasmmod inspect, wasmmod-read symbols|…). CDN Inspect UI talks HTTP
only. See docs/PACK.md.
make -C examples demo # real REPL: micropython -i < demo_readme.py
make -C examples repl # interactive — type it yourself
make -C examples test-elf
Layout
wasmmod/
├── pack.* runtime.* forward.* verify.* portable core
├── fetch.* io.h wasmmod.c finder.* host.* MicroPython host
├── ports/ make / cmake / mpconfig / PORT.md
│ └── micropython/webassembly/ browser: js.fetch I/O + Emscripten WAMR
├── tools/wasmmod.py shim → pymergetic-wasmmod-tools (`wasmmod` CLI)
├── python/pymergetic/wasmmod/rt/ PyPI package (bundles share/ on build)
├── hatch_build.py stages cleaned share/ into the wheel
├── examples/ sources + packs/ + call matrix
│ └── packs/ built <name>.{wasm,elf,aotN} artifacts
├── screenshots/ README eye-catchers
├── docs/PACK.md section format
├── BRANCHES.md MetalPython host branch layout
├── VERSION → wasm.version (always when built)
└── third_party/wamr WAMR (Apache-2.0; not on PyPI — firmware only)
Pack sections are named wasmmod.pack / wasmmod.imports / wasmmod.host / wasmmod.sig — see docs/PACK.md. Host I/O replaceability (Metal async, browser js.fetch): ports/PORT.md.
CDN / channel publish (lead + @version pins, index schema, browser µPy shell): separate repo
metal-cdn.
One-shot release (pack → AOT → sign → zlib → upload):
pip install --pre pymergetic-wasmmod
wasmmod publish examples/hello --version 0.1.0 \
--key .keys/sign/leaf.key.pem --chain .keys/sign/chain.der \
--cdn-url https://cdn.example/cdn --token "$METAL_CDN_TOKEN" --claim
# Also upload a prebuilt ELF twin (staged under -o; --arch inserts CDN infix):
wasmmod publish examples/hello --version 0.1.0 \
--elf examples/packs/hello.elf --arch x86_64 \
--key .keys/sign/leaf.key.pem --chain .keys/sign/chain.der \
--cdn-url https://cdn.example/cdn --token "$METAL_CDN_TOKEN"
Remote index / lookup / download (pip-style):
python3 tools/wasmmod.py cdn list
python3 tools/wasmmod.py cdn search hello
python3 tools/wasmmod.py cdn show hello
python3 tools/wasmmod.py cdn get hello -o ./packs --unwrap
CI: .github/workflows/publish-pack.yml
(optional elf / arch inputs). Offline ELF section tests:
pytest tools/test_wasmmod_elf.py; device smoke: make -C examples test-elf.
flowchart LR
subgraph host [Host Python]
PY[import hello / bridge]
WM[wasm module]
HS[host slots / mem / handles]
end
subgraph packs [Signed .wasm packs]
H[hello<br/>Py+C]
M[mixed<br/>C+RS]
B[bridge<br/>Py+C+RS]
end
PY --> WM
WM --> H
WM --> M
WM --> B
B -->|guest→guest| H
B -->|guest→guest| M
B -->|wasmmod.host| HS
HS --> PY
MicroPython integration
git submodule add https://github.com/pymergetic/wasmmod.git extmod/wasmmod
git submodule update --init --recursive extmod/wasmmod
Host tree only needs thin includes (no py/mpconfig.h edits):
# extmod/extmod.mk
ifeq ($(MICROPY_PY_WASM),1)
include $(TOP)/extmod/wasmmod/ports/micropython/micropython.mk
endif
# extmod/extmod.cmake
if(MICROPY_PY_WASM)
include(${MICROPY_DIR}/extmod/wasmmod/ports/micropython/micropython.cmake)
endif()
Enable with make MICROPY_PY_WASM=1 (optional MICROPY_PY_WASM_{AOT,ELF,JIT,FAST_JIT,MATRIX},
MICROPY_WASM_CONTAINERS=elf,aot,wasm). Unix defaults ELF on when Wasm is on; browser forces wasm-only.
Optional C defaults: #include "extmod/wasmmod/ports/micropython/mpconfig_wasm.h".
wasm.version is always the package release string from VERSION (e.g. '0.1.4-alpha').
wasm.wamr_version() returns the linked WAMR major.minor.patch string.
wasm.AOT_VERSION is the AOT file-format N used in CDN artifact names.
Guests see the same loader surface as WAMR imports on module wasmmod: version, mode, verify, trust_count, call_i32 (dynamic pack export; peer of host wasm.c_call / rs_call). Callbacks/slots stay on wasmmod.host.
CDN loader without loading a pack:
wasm.cdn("https://cdn.example/cdn") # MetalCdnDriver; no pack fetch
wasm.install_hook() # import finder on; still no pack load
wasm.session_id("…") # optional correlation id (autoexec sets this)
# import hello # first HTTP / VFS pack load
names = wasm.catalog() # GET …/index/lead → package name list
# wasm.publish("pkg", "0.1.0", data) # NotImplemented until host request op
wasm.load(path_or_bytes) loads a raw .wasm module (not a metal pack). Prefer
wasm.load_pack(...) / import for CDN packs.
Browser (Emscripten): out-of-tree variant under
ports/micropython/webassembly/ — js.fetch I/O
(with optional Bearer + X-Shell-Session-Id when configured),
no pollution of vanilla ports/webassembly/. See that folder’s README for build +
sync into metal-cdn static/repl/.
Optional host trampoline (metalpython): tools/wasmmod.py → extmod/wasmmod/tools/wasmmod.py.
MetalPython host branch layout (wasmmod PR track under product master): BRANCHES.md.
Build & test
From the host MicroPython / metalpython tree:
make -C ports/unix submodules
make -C mpy-cross BUILD=build -j"$(nproc)" # pack freeze (.mpy)
make -C extmod/wasmmod/examples test # interp matrix
make -C extmod/wasmmod/examples test-elf # ELF containers (+ arch reject)
make -C extmod/wasmmod/examples test-engines # all engines
make -C extmod/wasmmod/examples test-http # static HTTP pack fetch
make -C extmod/wasmmod/examples test-verify # embed wasmmod.sig + baked root CA
make -C extmod/wasmmod/examples test-signed # PKI sign + VFS + HTTP verify (incl. ELF)
make -C extmod/wasmmod/examples demo # real micropython -i session
make -C extmod/wasmmod/examples repl # interactive unix REPL
# Browser µPy + wasmmod (needs emsdk on PATH):
source ~/emsdk/emsdk_env.sh
make -C extmod/wasmmod/ports/micropython/webassembly -j"$(nproc)"
# → ports/webassembly/build-wasmmod/micropython.{mjs,wasm}
Engine summary
================================================================
Engine Result
------------ ----------------
Interp PASS (57 cases OK)
AOT PASS (57 cases OK)
Fast JIT PASS (57 cases OK)
LLVM JIT PASS (57 cases OK)
----------------------------------------------------------------
4 passed, 0 failed, 0 skipped
ALL ENGINES OK
Details: examples/README.md · pack format: docs/PACK.md · browser port: ports/micropython/webassembly/README.md · CDN channels / shell: metal-cdn.
Preview
More shots (click through for full size):
pack-calls.png |
Cross-lang pack calls |
matrix-table.png |
Same-pack Py ↔ C ↔ RS matrix |
engine-summary.png |
Interp · AOT · Fast JIT · LLVM JIT |
repl-demo.png |
Full MicroPython REPL (hero above) |
http-verify.png |
Signed HTTP fetch + baked trust |
License
MIT — Copyright (c) 2026 Rouven Raudzus <raudzus@pymergetic.com>
Nested third_party/wamr is Apache-2.0 (Bytecode Alliance); see LICENSE.
Metadata
Release files for pymergetic-wasmmod 0.1.0a3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pymergetic_wasmmod-0.1.0a3.tar.gz | 166.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pymergetic_wasmmod-0.1.0a3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 309.2 kB
Release files / pymergetic_wasmmod-0.1.0a3.tar.gz
| Download URL | pymergetic_wasmmod-0.1.0a3.tar.gz |
|---|---|
| Size | 166.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bc192a198a158672a03dda380f3cb8c4b31ac681a89b0ca3f4f3dbc0786702de
|
|
BLAKE2b-256 checksum How to use checksums |
33fc3d8492ae2f5aafbe99e2efc5c437b4cc9c2728f803d9bb948f69794312e8
|
| 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 Aug 5, 2026.
Transparency logRelease files / pymergetic_wasmmod-0.1.0a3-py3-none-any.whl
| Download URL | pymergetic_wasmmod-0.1.0a3-py3-none-any.whl |
|---|---|
| Size | 143.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e90da17f2aa1a2190e2f5b0f16e1ba36df16463003c514202d71d4d91386cca9
|
|
BLAKE2b-256 checksum How to use checksums |
f33f1ecd569ebdab365e34549415afbf7fc06410c6b9fefe7a15603905be6c9f
|
| 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 Aug 5, 2026.
Transparency log