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

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")

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?

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:

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

1 Like

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.

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

Worked, thanks!

Beautiful :balloon:

1 Like

I think it’s possible.
Precisely, you can boot up stlite with any initial code.

Even with the current releases, you can do it although you have to host it on your own infrastructure and stlite is not stable yet. For that, please see the README, or 1littlecoder’s great video.

So I think if we create a code sharing service on top of stlite, it would be something like jsfiddle, and I would like to actually do that.

1 Like

Makes sense. Yes I think it would be a great use-case for stlite! Another naive question: would that be safe against code injection btw? Meaning can one run malicious code? My guess is there’s very little risk considering things are running locally + on the browser.

I can’t find additional risks on stlie now but I can’t either say it is safe as I’m not a security professional.

What kind of injection do you consider?
stlite itself is a purely client-side library. The attacker may be able to inject malicious code into the user scripts hosted on the server that will be loaded into the stlite runtime if there are server-side vulnerabilities, but it’s not stlite’s responsibility of course. Similarly, the stlite main script loads some remote resources including Python packages from PyPI or CDN (e.g. cdn.jsdelivr.net), so the attacker may inject these resources on server-side too.
I think knowing these attackable points is important to consider the potential risks, but I also think these are same as other normal web services and not special things for stlite.

My guess is there’s very little risk considering things are running locally + on the browser.

I think so. As far as I know, the runtime environment on a web browser is sandboxed.

2 Likes

stlite has been updated to support uploading files, so we can now use st.file_uploader().
I think it got closer to practical use cases, for example, where users upload CSV files and get some results.

import streamlit as st
import pandas as pd

import warnings
warnings.filterwarnings('ignore')  # https://discuss.streamlit.io/t/keyerror-warnings/2474/14?u=whitphx

uploaded_file = st.file_uploader("file")
if uploaded_file:
    df = pd.read_csv(uploaded_file)
    st.write(df)

(If you get some errors in the playground app, try force-reloading the page. See this comment.)

st.camera_input() is also supported now but st.image() is still not working with in-memory data, so it may be less practical right now (supporting st.image is planned, of course).

Hi, I made a huge progress; now the following components are unlocked!

  • Media components: st.image, st.video, st.audio
  • matplotlib: st.pyplot
  • File upload/download: st.file_uploader, st.download_button
  • Camera input: st.camera_input

And a new option requirements has been added to install external packages as below (though it’s not available on the playground app yet).

stlite.mount({
  requirements: ["matplotlib"],  // Install matplotlib!
  mainScriptData: `
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np

arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)

st.pyplot(fig)
  `
})

Moreover, Pyodide 0.21.0 has been released a couple days ago which added 34 new C-extensions including opencv-python and xgboost for client-side!

So I created this demo app: Serverless Image Processing App (Source code is stlite-image-processing-app/index.html at main · whitphx/stlite-image-processing-app · GitHub)
that demonstrates using OpenCV and various Streamlit components on stlite, Serverless Streamlit.

demo

7 Likes

This is genuinely awesome! Well done!

1 Like

Many thanks for the great work. I tried to open a “local” csv file using Pandas and it could not find it. Is there some specific protocol I need to follow? Here is the code:

import io
import streamlit as st
import numpy as np
import pyodide.http
import pandas as pd
import plotly.express as px

df = pd.read_csv('./sg-words.csv')

st.table(df)

, and the csf file is in the same folder as index.html.

Agian, thanks for the work.

That is not supported now.

In stlite, the Streamlit server is running inside the browser environment from which it cannot access the local file system. The browser runtime is sandboxed.
It is a reasonable restriction for the security sake.

When you use the file accessing methods such as open() on stlite, it accesses another file system, Emscripten File System, which acts like a normal file system but is a virtual one isolated from the file system on your host environment.
Imagine something like a docker container, which has its own directory structure and it is isolated from the host system and cannot be accessed from the host and vice versa.

FYI,

is a related topic, though it’s not a solution.


Thank you for trying it out! :slight_smile: