Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
this is my first question on Stackoverflow. I hope my question is clear, otherwise let me know and don't hesitate to ask me more details.
I'm trying to package a streamlit app for a personal project. I'm developing under linux but I have to deploy the app on Windows. I want it to be a standalone executable, which once run opens the browser tab to display the app, and exits when the tab is closed. I would like to use
pynsist
library to package the app (already used for another project and it worked fine).
I followed the suggestion found in this
discussion
. It worked fine on ubuntu, and apparently also on Windows after packaging the app with pynsist. "Apparently" because the executable run, but no browser tab was open to display the app.
Here is some snippets of my code.
Project structure
|- installer.cfg
|- src
|- main.py
|- run_app.py
main.py
import streamlit as st
st.title("Test")
st.title("My first app deployed with Pynsist!")
run_app.py
(EDIT 2 after comment by Thomas K)
import os
import subprocess
import sys
from src.config import EnvironmentalVariableNames as EnvVar, get_env
def main():
executable = sys.executable
result = subprocess.run(
f"{executable} -m streamlit run {os.path.join(get_env(EnvVar.EMPORIO_VESTIARIO_DASHBOARD_WORKING_DIR), 'src', 'main.py')}",
shell=True,
capture_output=True,
text=True,
if __name__ == "__main__":
main()
EMPORIO_VESTIARIO_DASHBOARD_WORKING_DIR
is an environmental variable to make the app work on both linux and windows (on windows, it is set to the installation directory).
pynsist installer.cfg
EDIT: including dependencies of streamlit discovered through pip list
EDIT 2: added MarkupSafe as dependency of Jinja2
[Application]
name=Emporio Vestiario Dashboard
version=0.1.0
# How to lunch the app - this calls the 'main' function from the 'myapp' package:
entry_point=src.run_app:main
icon=resources/caritas-logo.ico
[Python]
version=3.8.10
bitness=64
[Include]
# Packages from PyPI that your application requires, one per line
# These must have wheels on PyPI:
pypi_wheels = altair==4.1.0
astor==0.8.1
attrs==21.2.0
backcall==0.2.0
backports.zoneinfo==0.2.1
base58==2.1.0
bleach==4.1.0
blinker==1.4
cachetools==4.2.2
certifi==2021.5.30
cffi==1.14.6
charset-normalizer==2.0.6
click==7.1.2
decorator==5.1.0
defusedxml==0.7.1
distlib==0.3.3
entrypoints==0.3
idna==3.2
jsonschema==3.2.0
mistune==0.8.4
mypy-extensions==0.4.3
numpy==1.21.1
packaging==21.0
pandas==1.3.3
pandocfilters==1.5.0
parso==0.8.2
pillow==8.3.2
platformdirs==2.4.0
prompt-toolkit==3.0.20
protobuf==3.18.0
pyarrow==5.0.0
pycparser==2.20
pydeck==0.7.0
pyparsing==2.4.7
pyrsistent==0.18.0
python-dateutil==2.8.2
pytz==2021.1
requests==2.26.0
requests-download==0.1.2
send2trash==1.8.0
setuptools==57.0.0
six==1.14.0
smmap==4.0.0
streamlit==0.89.0
terminado==0.12.1
testpath==0.5.0
toml==0.10.2
tomli==1.2.1
toolz==0.11.1
tornado==6.1
traitlets==5.1.0
typing-extensions==3.10.0.2
tzlocal==3.0
urllib3==1.26.7
validators==0.18.2
Jinja2==3.0.1
MarkupSafe==2.0.1
Looking at the executable output on Windows, the current working directory is correctly printed, but no other output (streamlit app initialization message, or error messages) is printed. I tried to open the browser and go to localhost:8501
, but I got connection error.
Any hints on how to make the code execute and automatically open the browser tab? Any help is greatly appreciated!
EDIT: as pointed out in the comment to the last package in installer.cfg
, the app (with Jinja2 dependency) is correctly installed on windows, but when launched, the app still cannot find Jinja2 dependency. This is the traceback:
Traceback (most recent call last):
File "Emporio_Vestiario_Dashboard.launch.pyw", line 34, in <module>
from src.run_app import main
File "C:\Users\tantardini\develop\caritas\pkgs\src\run_app.py", line 6, in <module>
import streamlit
File "C:\Users\tantardini\develop\caritas\pkgs\streamlit\__init__.py", line 75, in <module>
from streamlit.delta_generator import DeltaGenerator as _DeltaGenerator
File "C:\Users\tantardini\develop\caritas\pkgs\streamlit\delta_generator.py", line 70, in <module>
from streamlit.elements.arrow import ArrowMixin
File "C:\Users\tantardini\develop\caritas\pkgs\streamlit\elements\arrow.py", line 20, in <module>
from pandas.io.formats.style import Styler
File "C:\Users\tantardini\develop\caritas\pkgs\pandas\io\formats\style.py", line 49, in <module>
jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
File "C:\Users\tantardini\develop\caritas\pkgs\pandas\compat\_optional.py", line 118, in import_optional_dependency
raise ImportError(msg) from None
ImportError: Missing optional dependency 'Jinja2'. DataFrame.style requires jinja2. Use pip or conda to install Jinja2.
EDIT 2: thanks to the helpful hints by Thomas K, I came up with half a solution. The app runs and streamlit is started.
These are the log messages:
Welcome to Streamlit!
If you're one of our development partners or you're interested in getting
personal technical support or Streamlit updates, please enter your email
address below. Otherwise, you may leave the field blank.
Email:
2021-10-11 20:56:53.202 WARNING streamlit.config:
Warning: the config option 'server.enableCORS=false' is not compatible with 'server.enableXsrfProtection=true'.
As a result, 'server.enableCORS' is being overridden to 'true'.
More information:
In order to protect against CSRF attacks, we send a cookie with each request.
To do so, we must specify allowable origins, which places a restriction on
cross-origin resource sharing.
If cross origin resource sharing is required, please disable server.enableXsrfProtection.
2021-10-11 20:56:53.202 DEBUG streamlit.logger: Initialized tornado logs
2021-10-11 20:56:53.202 ERROR streamlit.credentials:
It seems that the execution of the app is stopped becuase it is waiting for some credentials. I found here that a .streamlit/credentials.toml
can be added, but I'm not sure on the exact location on windows. I've also tried to explicitly add --server.headless=false
in the subprocess.run
command, but again with no effect.
Why the app doesn't start automatically like on Linux? Is there a way to start the app without additional configurations by the user?
–
–
–
–
–
EDIT: a streamlit example was added to the examples of pynsist
repo. Here you can find a minimal and refined example of a working application (which also includes plotly).
ORIGINAL ANSWER
Finally I get it to work. In my last attempt, I made a mistake by setting --server.headless=false
, while it must be true
instead. I found that an additional flag to the streamlit run command is needed: --global.developmentMode=false
. This make the deploy work, even if I could not find any reference to this configuration in the streamlit configurations.
Working code follows.
Project structure
|- wheels/
|- installer.cfg
|- src
|- main.py
|- run_app.py
main.py
import streamlit as st
st.title("Test")
st.title("My first app deployed with Pynsist!")
run_app.py
import os
import subprocess
import sys
import webbrowser
from src.config import EnvironmentalVariableNames as EnvVar, get_env
def main():
# Getting path to python executable (full path of deployed python on Windows)
executable = sys.executable
# Open browser tab. May temporarily display error until streamlit server is started.
webbrowser.open("http://localhost:8501")
# Run streamlit server
path_to_main = os.path.join(
get_env(EnvVar.EMPORIO_VESTIARIO_DASHBOARD_WORKING_DIR), "src", "app.py"
result = subprocess.run(
f"{executable} -m streamlit run {path_to_main} --server.headless=true --global.developmentMode=false",
shell=True,
capture_output=True,
text=True,
# These are printed only when server is stopped.
# NOTE: you have to manually stop streamlit server killing process.
print(result.stdout)
print(result.stderr)
if __name__ == "__main__":
main()
Some notes:
webbrowser.open
is necessary to automatically open a new tab in the browser to show the streamlit app. The subprocess.run
lines only starts a new streamlit server.
As I pointed out in the comments, once exiting the streamlit tab in the browser, the streamlit server is still there and active. You may access again the dashboard by only typing localhost:8501
in the address bar. If you click multiple times on the Windows app icon, multiple streamlit servers are started. I've tried with only two active at the same time, and they do not show conflicting behaviour. To stop them you have to manually end tasks through task manager, for instance.
installer.cfg
[Application]
name=Emporio Vestiario Dashboard
version=0.1.0
# How to lunch the app - this calls the 'main' function from the 'myapp' package:
entry_point=src.run_app:main
icon=resources/caritas-logo.ico
[Python]
version=3.8.10
bitness=64
[Include]
# Packages from PyPI that your application requires, one per line
# These must have wheels on PyPI:
pypi_wheels = altair==4.1.0
astor==0.8.1
attrs==21.2.0
backcall==0.2.0
backports.zoneinfo==0.2.1
base58==2.1.0
bleach==4.1.0
blinker==1.4
cachetools==4.2.2
certifi==2021.5.30
cffi==1.14.6
charset-normalizer==2.0.6
click==7.1.2
decorator==5.1.0
defusedxml==0.7.1
distlib==0.3.3
entrypoints==0.3
idna==3.2
jsonschema==3.2.0
mistune==0.8.4
mypy-extensions==0.4.3
numpy==1.21.1
packaging==21.0
pandas==1.3.3
pandocfilters==1.5.0
parso==0.8.2
pillow==8.3.2
platformdirs==2.4.0
prompt-toolkit==3.0.20
protobuf==3.18.0
pyarrow==5.0.0
pycparser==2.20
pydeck==0.7.0
pyparsing==2.4.7
pyrsistent==0.18.0
python-dateutil==2.8.2
pytz==2021.1
requests==2.26.0
requests-download==0.1.2
send2trash==1.8.0
setuptools==57.0.0
six==1.14.0
smmap==4.0.0
streamlit==0.89.0
terminado==0.12.1
testpath==0.5.0
toml==0.10.2
tomli==1.2.1
toolz==0.11.1
tornado==6.1
traitlets==5.1.0
typing-extensions==3.10.0.2
tzlocal==3.0
urllib3==1.26.7
validators==0.18.2
Jinja2==3.0.1
MarkupSafe==2.0.1
extra_wheel_sources = ./wheels
Note: blinker
extra wheels is required.
EDIT 2
As @ananvodo pointed out, it may not be immediately clear what EnvVar
and get_env
are. I copy here the relevant parts of config.py for sake of completeness.
import os
class EnvironmentalVariableNames:
"""Defines the names of the environmental variables used in the code and useful shortcuts"""
# Environmental variables for Zaccheo app
EMPORIO_VESTIARIO_DASHBOARD_WORKING_DIR= "EMPORIO_VESTIARIO_DASHBOARD_WORKING_DIR"
# Other environmental variable names defined hereafter
def get_env(env_var):
"""Returns the value of the environment variable env_var"""
return os.environ[env_var]
–
–
–
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.