New library: stlite, a port of Streamlit to Wasm, powered by Pyodide

Hello everyone :balloon:,

I have started a new project, stlite ( GitHub - whitphx/stlite: A port of Streamlit to Wasm, powered by Pyodide. ) which aims to make Streamlit completely run on browsers by using Wasm, with no servers.
Itā€™s like JupyterLite for Jupyter, which inspired this project actually so much.

To try it out, I created a playground app with a code editor:

(~50MB will be downloaded including the Pyodide runtime and some Python packages)

Please visit the app and see Streamlit is running on the browser. I recommend to open the browser dev tools and watch the logs.

I also hosted a stlite package importable via <script /> tags that you can use on your web page like the following (Note that the URL can be changed in the future).

  <script src="https://whitphx.github.io/stlite/lib/application/stlite.js" ></script>
  <script>
    stlite.mount({
      mainScriptData: `
import streamlit as st

name = st.text_input("Name")
st.write("Hello, ", name or "world")
`
    })
  </script>

Please see the README for the details about it.


Playground app

The playground app is an SPA hosted on GitHub Pages. There is no server-side Python.

In addition, as itā€™s also built as a PWA, it works in an offline environment after the first loading and caching, and it can be installed to your local environment as well. I think this is one major possibility of Streamlit on WASM - it works as a desktop/smartphone app (loading runtime is still slow though).

Project status

stlite is at the very beginning of its development, where only a subset of Streamlit functionality is working and there are many things to implement and improve.
For example, there are still not working components such as st.table or st.line_chart that make use of C-extensions such as PyArrow, PyDeck or PyZMQ (or maybe others)[1].
Custom components are also not supported.

There are many TODOs including below:

Please also note that all the resources currently hosted such as the package file linked above above can be deleted or moved to elsewhere without any notice in the future, so do not use them in production for now.

The discussions before: Feature idea, Pyodide JupyterLite like Streamlit - #8 by whitphx


  1. Technically, these libraries have native code e.g. C-extensions that have to be compiled with Emscripten for Pyodide runtime, which is kind of tough. Supporting these components is one major task. ā†©ļøŽ

27 Likes

It seems like pyscript?

1 Like

This is so cool @whitphx! I ran quite a few of my gists successfully. Any reason why this one doesnā€™t runā€¦ it seems to comply with your rules.

Code
import streamlit as st
import time

st.set_page_config(
    page_title='Pomodoro',
    layout='centered',
    page_icon='šŸ…'
)

def count_down(ts):
    with st.empty():
        while True:
            mins, secs = divmod(ts, 60)
            time_now = '{:02d}:{:02d}'.format(mins, secs)
            st.header(f"{time_now}")
            time.sleep(1)
            ts -= 1
            if ts < 0:
                break
    st.write("Time Up!")
    st.balloons()

def main():
    st.title("Pomodoro")
    time_minutes = st.number_input('Enter the time in minutes ', min_value=0.1, value=25.0)
    time_in_seconds = time_minutes * 60
    if st.button("START"):
            count_down(int(time_in_seconds))

if __name__ == '__main__':
    main()
1 Like

From some perspectives, itā€™s yes. Both can be said as ā€œfrontend libraries that make it possible for developers to construct the frontend view with Pythonā€, but are also different in something such as how to control the view widgets or its abstraction level.

Also, in technical perspective, both are a wrapper of Pyodide but provide different APIs to control the frontend view.

From my understanding, PyScript provides its original Python interface to control DOM.
Instead, stlite runs Streamlit internally, or is kind of a wrapper of it too, so it directly exposes streamlit object (streamlit is importable in stlite runtime environment) and developers can use its API.

Thank you very much, I appreciate your trial and reporting.

I bookmarked your example and will investigate the problem!

Beautiful stuff @whitphx!

I can see this eventually becoming a favorite way to deploy streamlit applications either through something as simple as GitHub pages or providing a really simple portable executable.

2 Likes

Thank you very much!

I hope so, and would like to improve stlite so it will be as much compatible as possible with server-side Streamlit :smiley:

2 Likes

Many thanks for the effort. I have been looking for such a thing for some time.

You mentioned using Pyodide. therefore, there should be a place in the code where I can specify third-party packages as we do with micropip in Pyodide. Is that right? I could not find a reference to Pyodide in your code :-/

1 Like

You mean something like py-env tag in PyScript?
Currently no specific API for specifying necessary packages is provided.
You can import and run micropip just inside the Streamlit script on stlite.

This may be covered with this ticket User script at initialization Ā· Issue #36 Ā· whitphx/stlite Ā· GitHub

Thanks. Do you have a working example? This code did not work on the playground (No module named ā€˜textblobā€™):

import micropip
async def install():
    await micropip.install('textblob')

install()
from textblob import TextBlob


import streamlit as st

name = st.text_input("Name")
st.write("Hello ", name or "world")
1 Like

Simply, the following code should work.

import micropip
micropip.install("textblob")
from textblob import TextBlob

Regardless of stlite, I think you misuse asyncio; coroutines, which are defined with async def like install() in your original code should be executed with asyncio.run(install()) or from another coroutine with await install(). Ref: Coroutines and Tasks ā€” Python 3.10.4 documentation
asyncio is not supported with current stlite anyway, though.

Your example doesnā€™t work either (in the playground). Is it because of not using asyncio?

1 Like

Ah, got it. I missed micropip.install() returns Future, so asyncio is necessary to handle it, but stlite currently does not support it.

So wait for User script at initialization Ā· Issue #36 Ā· whitphx/stlite Ā· GitHub to be implemented. There is no straightforward way to do it for now.

Only workaround is to run micropip.install() once and rerun the code a few seconds later that imports the package.

  1. The first run
    import micropip
    micropip.install("textblob")
    
  2. Wait a few seconds for the future to run and finish.
  3. The second run
    import textblob
    ...
    
1 Like

Memo:

asyncio.run or asyncio.get_running_loop does not work in Streamlit app scripts on stlite.

Still we can get the event loop with the following code.

def get_event_loop():
    from streamlit.server.server import Server
    current_server = Server.get_current()
    return current_server._ioloop.asyncio_loop


loop = get_event_loop()

This loop is an instance of webloop.WebLoop, which is a special event loop on Pyodide environment. This is different from the normal CPython env, where we usually get an instance of asyncio.AbstractEventLoop.
As the Pyodide document says, its run_until_complete() does not block the script, so we cannot write code that need to block async executions.
It means the following code never works.

loop.run_until_complete("textblob")

import textblob

We have to note that in Pyodide environment, the event loop is different from the usual Python environment.

There will likely also soon be a way for this ā€œstliteā€ to also access the local filesystem:

And, whatā€™s amazing, is it would be the client userā€™s deployed filesystem, not the server filesystem. Which, in many cases makes much more sense.

ā€¦I really think with time this stlite approach could and should be the default and preferred way to deploy and share streamlit applications :slight_smile:

1 Like

Thatā€™s interesting!
Iā€™m not so familiar with this technology (now I just know it is called as Native File System API), but just thought how it allows apps to access the local file systems without security concerns - if it is possible to access local Streamlit *.py files from browsers without any UX flaws, thatā€™s great for stlite and it could be a very new and useful way to run Streamlit apps as you said :open_mouth:.

2 Likes

great work

2 Likes

Update:

I added DataFrame support so the components using DataFrame such as st.dataframe() and st.line_chart() are now working on stlite.

1 Like

Such an exciting project @whitphx.

Canā€™t make st.dataframe() work though on Stlite Playground, hereā€™s my code:

import pandas as pd 
import streamlit as st 

df = pd.DataFrame(["hello", "world"])
st.dataframe(df)

And I get:

StreamlitAPIException: stlite is not supporting this method.

Maybe the playground wasnā€™t updated yet!

By the way, Iā€™m not familiar with those pyodide frameworks, but how feasible would it be to share a specific state of your playground? Think jsfiddle but for Streamlit.

1 Like

Try force-reloading to ignore the cache.
As the playground app is built as a PWA, it caches the old code on the client-side.

1 Like