Skip to main content
  1. Posts/

Streamlit + PySide = standalone data app

·425 words·2 mins
ß

For one of my projects I wanted to be able to open a streamlit app in its own window. Streamlit is an excellent way to turn scripts into web apps that I like to use to showcase research data. By default, it starts a web server and attempts to open this url in a webbrowser.

I was curious if it would be possible to redirect this to some sort of Python webview, so that I could turn my dashboard into a standalone app.

Qt webview #

It turns out that Qt (using PySide2) has this capability via the QtWebEngineWidgets. The most straightforward way to install is via conda:

1
conda install -c conda-forge pyside2

With pyside installed, it is straightforward enough to make a little window pop up showing any url:

1
2
3
4
5
6
7
8
9
from PySide2 import QtCore, QtWebEngineWidgets, QtWidgets

URL = 'https://python.org'

app = QtWidgets.QApplication()
view = QtWebEngineWidgets.QWebEngineView()
view.load(QtCore.QUrl(URL))
view.show()
app.exec_()

This is what it looks like:

Python.org

Wrapping streamlit #

To start a streamlit server (and closing it!) is a bit more involved. Normally you would run streamlit run my_script.py, and then pop the url into your webbrowser. But somehow we must wrap this behaviour. This is what I ended up with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import atexit
import subprocess as sp
import os

from PySide2 import QtCore, QtWebEngineWidgets, QtWidgets

def kill_server(p):
    if os.name == 'nt':
        # p.kill is not adequate
        sp.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])
    elif os.name == 'posix':
        p.kill()
    else:
        pass

if __name__ == '__main__':
    cmd = f'streamlit hello --server.headless=True'

    p = sp.Popen(cmd.split(), stdout=sp.DEVNULL)
    atexit.register(kill_server, p)

    hostname = 'localhost'
    port = 8501

    app = QtWidgets.QApplication()
    view = QtWebEngineWidgets.QWebEngineView()

    view.load(QtCore.QUrl(f'https://{hostname}:{port}'))
    view.show()
    app.exec_()
  1. In this example I use streamlit hello. You can exchange this with the path to your script.
  2. For the streamlit command, we need --server.headless=True. This will prevent streamlit from trying to open the URL in your default browser.
  3. I run the streamlit command in subprocess. This is to isolate the event loop. I tried going with threads, but using a subprocess is so much easier.
  4. Therefore, we must kill the streamlit server when we close the webview. To do so we can use the atexit handler. On Linux we can use p.kill(), but on Windows we must send the taskkill command. From experience, p.kill() is not adequate on Windows.

This is the end result:

Streamlit webview

No browser required :-)