stlite: Serverless Streamlit
A port of Streamlit to WebAssembly, powered by Pyodide.
Streamlit is a Python web app framework for the fast development of data apps. This project is to make it run completely on web browsers.
Try it out
Visit stlite sharing.
Create a desktop app
See @stlite/desktop
.
Use stlite on your web page
You can use stlite on your web page loading the script and CSS files via <script>
and <link>
tags as below.
Here is a sample HTML file.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>stlite app</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@stlite/[email protected]/build/stlite.css"
/>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/@stlite/[email protected]/build/stlite.js"></script>
<script>
stlite.mount(
`
import streamlit as st
name = st.text_input('Your name')
st.write("Hello,", name or "world")
`,
document.getElementById("root")
);
</script>
</body>
</html>
In this sample,
- stlite library is imported with the first script tag, then the global
stlite
object becomes available. stlite.mount()
mounts the Streamlit app on the<div id="root" />
element as specified via the second argument. The app script is passed via the first argument.
More controls
If more controls are needed such as installing dependencies or mounting multiple files, use the following API instead.
stlite.mount(
{
requirements: ["matplotlib"], // Packages to install
entrypoint: "streamlit_app.py", // The target file of the `streamlit run` command
files: {
"streamlit_app.py": `
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np
size = st.slider("Sample size", 100, 1000)
arr = np.random.normal(1, 1, size=size)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
st.pyplot(fig)
`,
},
},
document.getElementById("root")
);
Various ways to load files
You can pass an object to the files
option to mount files, whose keys are file paths, and you can specify the values in various ways as below.
Passing string or binary data
You can pass the file content as a string or binary data.
This is what we did in the example above.
stlite.mount(
{
files: {
"path/to/text_file.txt": "file content",
"path/to/binary_file.bin": new Uint8Array([0x00, 0x01, 0x02, 0x03]),
},
// ... other options ...
},
document.getElementById("root")
);
Passing an object with a URL
You can use this way to load a file from a URL and mount it to the specified path on the virtual file system.
Either an absolute or relative URL is accepted. Consider as the same as the url
option of the fetch()
function.
stlite.mount(
{
files: {
"path/to/file": {
url: "https://example.com/path/to/file",
},
"path/to/file2": {
url: "./path/to/file",
},
},
// ... other options ...
},
document.getElementById("root")
);
Passing an object with options (advanced)
stlite runs on Pyodide, and it has a file system provided by Emscripten.
The files specified via the files
option are mounted on the file system, and Emscripten's FS.writeFile()
function is used internally for it.
You can specify the options (opts
) for the FS.writeFile(path, data, opts)
function as below.
stlite.mount(
{
files: {
"path/to/text_file.txt": {
data: "file content",
opts: {
encoding: "utf8",
},
},
"path/to/file": {
url: "https://example.com/path/to/file",
opts: {
encoding: "utf8",
},
},
},
// ... other options ...
},
document.getElementById("root")
);
Loading archive files
You can load archive files such as zip files, unpack them, and mount the unpacked files to the file system by using the archives
option.
The url
field of each item accepts either an absolute or relative URL. Consider as the same as the url
option of the fetch()
function.
The downloaded archive file is unpacked by the pyodide.unpackArchive(buffer, format, options)
function. You have to pass the rest of the arguments of the function, format
and options
as below.
mount(
{
archives: [
{
url: "./foo.zip",
// buffer: new Uint8Array([...archive file binary...]), // You can also pass the binary data directly
format: "zip",
options: {},
},
],
// ... other options ...
},
document.getElementById("root") as HTMLElement
);
Other stlite versions
In the example above, the stlite script is loaded via the <script>
tag with the versioned URL.
You can use another version by changing the version number in the URL.
The following URLs are also available, while our recommendation is to use the versioned one as above because the API may change without backward compatibility in future releases.
The latest release
<script src="https://cdn.jsdelivr.net/npm/@stlite/mountable/build/stlite.js"></script>
You can use the latest version of the published stlite package with this URL.
The head of the main branch
<script src="https://whitphx.github.io/stlite/lib/mountable/stlite.js"></script>
This URL points to the head of the main branch which is usually ahead of the released packages. However, we strongly recommend NOT to use this URL because this might be broken and there is no guarantee that this resource will be kept available in the future.
Multipage apps
You can pass the multiple files to the files
option as below to construct the multipage app structure, the entry point file and pages/*.py
files.
Read the Streamlit official document about the multipage apps.
stlite.mount(
{
entrypoint: "๐_Hello.py",
files: {
"๐_Hello.py": `
import streamlit as st
st.set_page_config(page_title="Hello")
st.title("Main page")
`,
"pages/1_โญ๏ธ_Page1.py": `
import streamlit as st
st.set_page_config(page_title="Page1")
st.title("Page 1")
`,
"pages/2_๐_Page2.py": `
import streamlit as st
st.set_page_config(page_title="Page2")
st.title("Page 2")
`,
},
},
document.getElementById("root")
);
Limitations
As stlite runs on the web browser environment (Pyodide runtime), there are things not working well. The known issues follow.
st.spinner()
does not work with blocking methods likepyodide.http.open_url()
because stlite runs on a single-threaded environment, sost.spinner()
can't execute its code to start showing the spinner during the blocking method occupies the only event loop.time.sleep()
is no-op. Useasyncio.sleep()
instead. This is a restriction from Pyodide runtime. See pyodide/pyodide#2354. The following section about top-level await may also help to know how to use async functions on stlite.st.experimental_data_editor
does not work as it relies on PyArrow, but it doesn't work on Pyodide. Track this issue on #509.- For URL access,
urllib
orrequests
don't work on Pyodide/stlite, so we have to use alternative methods provided by Pyodide, such aspyodide.http.pyfetch()
orpyodide.http.open_url()
. See https://pyodide.org/en/stable/usage/faq.html#how-can-i-load-external-files-in-pyodide for the details. Forpyodide.http.pyfetch()
, see also the following section about top-level await. - The C extension packages that are not built for Pyodide cannot be installed. See https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel for the details.
Other problems are tracked at GitHub Issues: https://github.com/whitphx/stlite/issues If you find a new problem, please report it.
Top-level await
TL;DR: use top-level await instead of asyncio.run()
on stlite.
Unlike the original Streamlit, stlite supports top-level await due to the differences in their execution models. Streamlit runs in a standard Python environment, allowing the use of asyncio.run()
when an async function needs to be executed within a script. In contrast, stlite runs in a web browser, operating in an environment where the only event loop is always in a running state. This makes it impossible to use asyncio.run()
within a script, necessitating the support for top-level await.
Top-level await can be useful in various situations.
asyncio.sleep()
Example 1: One of the most common use cases is asyncio.sleep()
. As mentioned in the previous section, time.sleep()
is no-op on stlite because its blocking nature is not compatible with the single-threaded event loop in the web browser environment. Instead, asyncio.sleep()
, which is non-blocking, can be used to pause the execution of a script for a specified amount of time.
You can use top-level await either for asyncio.sleep()
directly or for an async function that contains asyncio.sleep()
like the following:
import asyncio
import streamlit as st
st.write("Hello, world!")
await asyncio.sleep(3)
st.write("Goodbye, world!")
import asyncio
import streamlit as st
async def main():
st.write("Hello, world!")
await asyncio.sleep(3)
st.write("Goodbye, world!")
await main()
pyodide.http.pyfetch()
Example 2: Another common use case is accessing external resources. In the Pyodide environment, widely-used URL access methods in Python, like requests
, are not available. However, Pyodide provides pyodide.http.pyfetch()
as an alternative for accessing external resources. Since this method is async, top-level await becomes handy for utilizing pyodide.http.pyfetch()
.
Here's a sample code snippet demonstrating the usage of top-level await with pyodide.http.pyfetch()
:
import pyodide.http
url = "your_url_here"
response = await pyodide.http.pyfetch(url)
data_in_bytes = await response.bytes()
Resources
๐ Streamlit meets WebAssembly - stlite, by whitphx: A blog post covering from some technical surveys to the usages of the online editor stlite sharing, self-hosting apps, and the desktop app bundler.๐บ "Serverless Streamlit + OpenCV Python Web App Tutorial", by 1littlecoder, YouTube: A quick tutorial to develop an OpenCV image processing app with stlite that runs completely on browsers.๐ "New library: stlite, a port of Streamlit to Wasm, powered by Pyodide", Streamlit Community: The stlite thread at the Streamlit online forum.๐บ "Stlite - Streamlit without Server - powered by Pyodide (WebAssembly)", by 1littlecoder, YouTube: A quick tutorial with local app development and GitHub Pages deployment.๐บ "How to Convert a Streamlit App to an .EXE Executable", by Fanilo Andrianasolo, YouTube: A tutorial to convert a Streamlit app to an executable file with stlite and a demo to run it on an offline machine.๐ "Is This the Easiest Way to Build Your Streamlit App?", by Shantala Mukherjee๐ "The Best Python Desktop App Framework?", by Caleb Robey at Depot Analytics
Samples
โก๏ธ Serverless Image Processing App
Image processing with OpenCV works on the client-side.
- Repository
๐ : https://github.com/whitphx/stlite-image-processing-app - Online demo
๐ : https://whitphx.github.io/stlite-image-processing-app/
See the tutorial video
Serverless Streamlit + OpenCV Python Web App Tutorial, crafted by 1littlecoder.
Sponsors
Streamlit (Snowflake)
Databutton
They are sponsoring me on GitHub Sponsors!
Hal9
They are sponsoring me on GitHub Sponsors!