Library / SDK
bytecodealliance/wasmtime-py avatar
bytecodealliance/wasmtime-py

wasmtime-py and running WebAssembly modules from Python through Wasmtime

Python WebAssembly runtime powered by Wasmtime

542 stars69 forksPythonApache-2.0

At a glance

What is it?
wasmtime-py is the Python embedding of the Wasmtime runtime, distributed on PyPI as wasmtime, and it exposes the runtime's store, module, instance and function types directly to Python code, with WebAssembly components supported alongside core modules. The interesting engineering is that it ships a custom in-tree build backend that produces its own wheels, and the interesting constraint is a monthly major version that can break code between releases.
Who is it for?
wasmtime-py is a good fit for a Python project that needs to run untrusted or portable WebAssembly without a separate service, because the Wasmtime runtime is embedded in the process rather than reached over a socket, and the API surface is the runtime's own store and instance model.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the package actually is

wasmtime-py describes itself as the Python embedding of Wasmtime, and it is a Bytecode Alliance project. The package description in the project metadata is A WebAssembly runtime powered by Wasmtime.

The name on PyPI is not the same as the repository name, and that is the first thing to get right. The install command in the README is pip install wasmtime, not pip install wasmtime-py, and the PyPI project page the badges point at is pypi.org/project/wasmtime. The repository is bytecodealliance/wasmtime-py, the distribution is wasmtime.

That naming split is the sort of thing that costs an afternoon when you search a dependency list, and it also means the package sits in the same PyPI namespace as the JavaScript and other-language Wasmtime bindings, so a dependency resolver sees one distribution family with one version scheme across it.

The audience is a Python developer who wants to execute WebAssembly in-process. The README's own example does exactly one thing: compile a module, instantiate it, and call an exported function that turns out to be a Python function. That last detail is the interesting part, and it is worth reading the example before deciding whether this is the right shape for your use.

The primary language is Python, the licence in the project metadata is Apache-2.0 WITH LLVM-exception, the homepage is bytecodealliance.github.io/wasmtime-py/, the default branch is main, and the last push was on 2026-09-24. The repository is not archived and publishes no GitHub releases, so the PyPI version is the release record.

The example: a Python function imported into a WebAssembly module

The usage example in the README is the whole API story in twenty lines, and it is a good one to read closely.

python
from wasmtime import Store, Module, Instance, Func, FuncType

store = Store()
module = Module(store.engine, """
  (module
    (func $hello (import "" "hello"))
    (func (export "run") (call $hello))
  )
""")

def say_hello():
    print("Hello from Python!")
hello = Func(store, FuncType([], []), say_hello)

instance = Instance(store, module, [hello])
run = instance.exports(store)["run"]
run(store)

The flow is the runtime's own object model. A Store is created first and it holds the state of the module instance. The Module is compiled from a WebAssembly text string, the triple-quoted literal here is the WebAssembly source written inline, and it is compiled against store.engine, the engine that belongs to the store.

The module declares an import, and the import is what makes this example interesting. The module imports a function named hello from an empty module name, and the caller satisfies that import with a Python callable wrapped in Func, constructed with a FuncType of no arguments and no results.

Then the instance is created with the store, the module and a list of import values, and the export is looked up by name from instance.exports(store). Calling it passes the store, because the store is the execution context.

So the direction of the bridge is worth naming: here WebAssembly calls Python. The module exposes a run function that does nothing except call the imported hello, and hello is a Python function that prints. That is the inverse of the arrangement people usually want, where Python calls into WebAssembly, and it is a fair demonstration that the embedding is bidirectional.

The examples directory in the repository carries the other patterns, and the file names suggest the range: hello, gcd, linking, memory and multi, each with a .wat source alongside a .py driver.

The build backend, and why the wheels are custom

The pyproject.toml has an unusual section that explains most of the packaging story.

The build-system requires setuptools and setuptools-git-versioning, but the build-backend is set to backend, with a backend-path pointing at ci/_custom_build. The comment above it says to use an in-tree build backend, to customise the wheels we generate.

That is the whole reason for the custom backend. The version is dynamic rather than written in the manifest, because the project version comes from a VERSION file at the repository root together with the git history through setuptools-git-versioning, and the package is named wasmtime with the license field reading Apache-2.0 WITH LLVM-exception, which is the licence of the Wasmtime runtime itself rather than of this Python wrapper.

The dependency list is empty. There are no runtime dependencies, because the Wasmtime runtime is not a Python package you install separately; it is compiled into the extension module that this package ships. That is also why the wheels are custom, since the built artefact contains a compiled runtime rather than only Python code.

The packaging configuration includes the wasmtime package and its subpackages, and the package-data section carries a comment that says to coordinate changes with the gitignore, which is the practical note about which built artefacts are expected in the tree.

The classifiers are worth a glance for what they say about the support surface: Python 3.9 through 3.14, CPython and PyPy, and Rust as a language classifier, which reflects that the package is a compiled extension with Rust inside it rather than a pure Python project.

The development tooling is modern: uv.lock at the root for dependency management, pytest.ini and mypy.ini for the test and type-check configuration, a .flake8 file, and a ci/ directory that holds the custom build backend alongside the workflows. The optional testing extra pulls in coverage, pycparser, pytest-mypy and pytest.

Versioning: a major release every month, and why that hits your requirements file

The versioning section is the part of the README that deserves to be read twice, because it describes a dependency-management problem rather than a technical feature.

wasmtime-py follows the Wasmtime versioning scheme, with a new major version being released every month. As with Wasmtime itself, new major versions of wasmtime-py can contain changes that break code written against the previous major version.

So this is a package that publishes a breaking release every four weeks or so, and the reason is upstream cadence rather than a change in this wrapper's own API. That is a perfectly reasonable choice for the runtime project and an unusual one for a Python dependency.

The consequence is spelled out in the README. Since every installed Python package needs to agree on a single version of wasmtime-py, to use the upper bound on the major version in the dependency requirement should be bumped regularly, ideally as soon as a new wasmtime-py version is released.

That sentence describes the operational cost precisely. If your application depends on wasmtime-py and on some other library that also depends on it, then when the new major arrives both requirements have to move, and until they do the resolver cannot satisfy both. A monthly breaking release turns into a monthly chore for anyone downstream, and it is a chore that happens whether you use the new version or not.

The README suggests two ways to manage it. There is a script, whitequark/track-pypi-dependency-version, that automates bumping the upper bound. And there is an example of a project doing it properly, YoWASP/runtime, which automatically publishes releases on PyPI once a new version of wasmtime-py is released if it passes the testsuite.

That second example is the useful one, because it shows what a downstream project has to build to live with this cadence: a pipeline that notices the new release, bumps the requirement, runs the tests, and publishes. If you do not have that, the monthly major will arrive as an unplanned maintenance task.

Components, and the second API you have to learn

The components section is brief and it introduces a second surface that a user of core modules does not need at all.

Components are also supported in wasmtime-py. The documentation for wasmtime.component is linked, and the README says using a component is similar to using core wasm modules, with examples in the tests/component/ directory.

The distinction matters because components are a different programming model. A core WebAssembly module has a flat set of imports and exports with numeric types. A component is a higher-level unit with typed interfaces, and the two are not interchangeable at the API level even though the surrounding runtime is the same.

The documentation's guidance is to read the wasmtime.component pages separately, which is the right advice, because the phrase that using a component is similar to using core wasm modules is true of the workflow and not of the type system.

For a reader deciding whether to adopt the package, the practical point is that there are two things to learn rather than one, and the components path is the one with its own documentation tree. If your work is core modules, the components documentation is irrelevant to you, and if your work is components then the core module example above is the simpler of the two starting points.

The examples directory and the tests/component/ directory both exist in the repository, so the two usage styles have worked references in the same tree, which is useful when you want to see which one matches the problem you have.

Where wasmtime-py is the wrong choice

Three limits follow from what the package is.

The first is the monthly breaking release, discussed above, and it is the one most likely to decide the question. A package that can break every four weeks is fine when you own the whole process and can rebuild it, and awkward when the WebAssembly runtime is one dependency among many in a library that other people install.

The second is that the runtime is compiled in rather than installed separately. There are no runtime dependencies, which is convenient, but it also means the wheel carries a full WebAssembly runtime and the package's download size and platform coverage are tied to the set of platforms someone builds wheels for. The README states the supported matrix plainly: 64-bit builds of Python 3.9 or newer on Windows, macOS and Linux, for x86_64 and arm64 architectures. A 32-bit Python, a Python older than 3.9, or an architecture outside that list is simply not supported, and there is no source-distribution fallback described in the documentation.

The third is that embedding a runtime in your process gives you the isolation of WebAssembly without the isolation of a process. If the WebAssembly you are running is untrusted, a sandboxed WebAssembly runtime inside your own interpreter is still your interpreter's memory space, your file descriptors and your network stack. Whether that is sufficient depends entirely on where the module came from and what the runtime guarantees, and the documentation in this repository does not make security claims beyond pointing at the Wasmtime project itself.

The alternative worth comparing is running Wasmtime as a separate service and talking to it over a socket or a pipe. The difference in approach is that the embedded version removes a network hop, a protocol and a deployment, at the cost of putting the runtime's memory and file-descriptor access inside your process. A second alternative is a different WebAssembly runtime in Python with its own embedding model, where the version cadence and the API shape are yours to choose rather than inherited from an upstream project that ships monthly majors. For a plugin system inside one application, embedding is the natural answer. For code that must run at scale across many machines with a boundary between tenants, the process boundary is worth more than the saved hop.

Editorial conclusion

wasmtime-py is a good fit for a Python project that needs to run untrusted or portable WebAssembly without a separate service, because the Wasmtime runtime is embedded in the process rather than reached over a socket, and the API surface is the runtime's own store and instance model. It is a poor fit if your dependency tree cannot tolerate a monthly major release, because the package follows the Wasmtime versioning scheme and new major versions can break code written against the previous one. Before adopting, pin the upper bound of the major version in your requirements and bump it deliberately, since the README says every installed Python package has to agree on a single version of wasmtime-py, and read the component documentation separately if you intend to use components rather than core modules.

Frequently asked questions

How do I install wasmtime-py?

Run `pip install wasmtime`. The distribution on PyPI is named wasmtime, not wasmtime-py, and it supports 64-bit builds of Python 3.9 or newer on Windows, macOS and Linux for x86_64 and arm64.

How do I call a WebAssembly function from Python with wasmtime-py?

Create a Store, compile a Module from the WebAssembly source against store.engine, create an Instance with the store, module and any import values, then look the export up with instance.exports(store) and call it with the store. The README's example imports a Python callable into the module as a Func and calls it from WebAssembly.

Does wasmtime-py support WebAssembly components?

Yes. Components are supported alongside core modules, with the documentation at wasmtime.component in the wasmtime-py documentation and examples in the tests/component/ directory of the repository.

How often does wasmtime-py release a new version?

It follows the Wasmtime versioning scheme, with a new major version being released every month, and as with Wasmtime itself those major versions can contain changes that break code written against the previous one.

How should I handle wasmtime-py's monthly major releases?

The README says the upper bound on the major version in the dependency requirement should be bumped regularly, ideally as soon as a new version is released, because every installed Python package needs to agree on a single version of wasmtime-py. The track-pypi-dependency-version script can automate the bump.

What licence is wasmtime-py released under?

The project metadata declares the licence as Apache-2.0 WITH LLVM-exception, matching the licence of the Wasmtime runtime it embeds. The LICENSE file is at the repository root and the last push was on 2026-09-24.

Official sources

  1. bytecodealliance/wasmtime-py on GitHub
  2. Issues
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bytecodealliance-wasmtime-py.svg)](https://hysenlabs.com/projects/bytecodealliance-wasmtime-py)

Was this useful?