Quick demonstration of PyScript and Pyodide
python serve.pyThen open http://localhost:8000 in your browser.
pyscript is a wasm framework for running python in the browser
pyodide is a wasm runtime for python, essentially thecpython interpreter compiled to wasm
pyscript provides a framework for running python in the browser, pyodide provides the runtime
KeriWasm/
├─ index.html
├─ pyscript.toml
├─ python/
├─ static/
├─ workers/
└─ serve.py
index.html— entry point for the apppyscript.toml— config for pyscriptserve.py— simple http server so that I can test and see it in the browser on localhostpython/— python code that is loaded and run by pyscriptstatic/— This is where I am keeping the python wheels I have created- blake3
- liboqs
- pysodium
workers/— web workers -- this was from an earlier iteration before pyscript - however, I think that web workers will come back when we start integrating HTMX with pyscript - the file in here is not used in index.html
index.html— entry point for the apppyscript.toml— config for pyscriptpython/hio/— minimal hio scheduler package trimmed for Pyodidepython/hio_bridge.py— port of hio to run on the web (yields to the browser event loop)python/test_runner_doer.py— test runner as a hio Doer that executes a queued list of testspython/test_loaders.py— functions that build the test queue from liboqs test modulespython/run_liboqs_suite.py— PyScript entry point that wires the hio runner to the liboqs tests- the following are lifted from pyoqs:
python/test_kem.py— liboqs KEM correctness and edge-case testspython/test_sig.py— liboqs signature correctness tests (including context-string support)python/test_stfl_sig.py— stateful signature (XMSS/XMSSMT) tests filtered for browser-feasible algorithms
python/run_pysodium_suite.py— browser-friendly runner for the full pysodium unittest suitepython/pysodium_unittest.py— from libsodium library, the unittest definitions used by the pysodium runnerpython/package_tests.py— smoke tests to verify key packages import and run in PyScript
- Entry Point: The browser loads
index.html, served with serve.py - Framework:
index.htmlloads the PyScript core with<script type="module">(core.js), which bootstraps the Pyodide runtime (hosting Python in WebAssembly). - Configuration: PyScript reads
pyscript.tomlto determine which Python packages to install (including local wheels fromstatic/) and which files to mount into the virtual filesystem. This allows us to call the python functions from the browser using relative paths in the code as we would when running the code in a local python environment.
- Runtime: Pyodide runs partially on the main thread (blocking for heavy compute unless using workers).
- Scripts: The
<script type="py">tags inindex.htmlexecute the following Python initialization scripts:-
python/package_tests.py: Defines therun_tests()function. -
python/run_pysodium_suite.py: Defines therun_full_suite()function. -
python/run_liboqs_suite.py: Defines therun_liboqs_suite()function. -
all of these scripts are run in the browser! This puts the functions they have defined in the global scope of the browser.
-
the
type="py"attribute on the<script>tag tells PyScript to run python using pyodide.
-
- Trigger: When you click a button (e.g., "Run Package Tests"), the
py-clickattribute bridges the DOM event to the corresponding Python function. - Processing: The Python function executes within the WASM environment. It imports the necessary libraries (
pysodium,oqs,hio) and runs the test logic. - Output: The Python code writes output back to the browser's DOM (specifically the
<div id="output">) to display results to the user.
- HTML hooks:
py-click="run_tests"/py-click="run_full_suite"/py-click="run_liboqs_suite"map DOM events directly to Python functions defined in the loaded scripts.<script type="py" src="...">tells PyScript to load and execute that Python file in the browser.config="pyscript.toml"points PyScript at the package list and the files it should mount into the runtime.
- Python/DOM bridge:
from pyscript import documentexposes the browserdocumentobject to Python.document.querySelector("#output")fetches the output element for logging.output.innerHTML += ...writes results into the page, andoutput.scrollTop = output.scrollHeightkeeps the output pinned to the bottom.
A web worker would sit between the HTMX “frontend” and the Pyodide “backend,” intercept requests, route them to Pyodide, then return HTML fragments for HTMX to swap into the page.
PyScript exposes a small Pythonic wrapper over IndexedDB for simple persistence from Python.
Example calls (as used in PyScript):
from pyscript import storage, Storage
# open (or create) a named store
store = await storage("keriwasm-cache")
# dict-like reads/writes
store["key"] = "value"
value = store.get("key")
del store["key"]
# ensure pending writes are flushed
await store.sync()Notes:
- data persists per-origin (tab/page/domain) and is not the Pyodide virtual filesystem
- multiple PyScript workers on the same origin can access the same IndexedDB database
- supported values are basic types (bool/int/float/str/None), plus lists/dicts/tuples (tuples come back as lists)
- binary blobs are possible via
bytearray/memoryview, but must be top-level values (not nested) - you can subclass
Storageand passstorage_class=...for a custom interface
Each web worker would run its own Pyodide instance and hio scheduler, handling a slice of keripy. Workers would coordinate via message passing and shared IndexedDB state, while the main thread (or a router worker) dispatches requests and returns results to the UI.
Main thread or a “router” worker:
- Receives UI or HTMX requests, dispatches to the relevant workers, and aggregates results.
- Pulls the HTML fragment or data response and sends it back to the page.