相关文章推荐
腼腆的柠檬  ·  python ...·  1 周前    · 
有情有义的大白菜  ·  python ...·  1 周前    · 
完美的馒头  ·  python QTreeWidget ...·  6 天前    · 
失眠的烤红薯  ·  python qt textBrowser ...·  4 天前    · 
迷茫的海龟  ·  openlayers ...·  1 年前    · 
俊秀的面包  ·  前端 - ...·  1 年前    · 
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?

If you haven't already, try running the installed application from the command prompt as described in the FAQ - you may see more information about what's failing that way. I might hazard a guess that it's not finding the streamlit command, though - launching the subprocess with {sys.executable} -m streamlit ... might get around that. – Thomas K Sep 28, 2021 at 13:59 Thank you, I was aware of the app log in the C:/Users/.../AppData/Roaming folder, but I missed the possibility to launch the app through command prompt. I discovered that the problem is some missing dependency, in particular packages that are dependencies of streamlit. I guess I have to add them all to the installer.cfg. It will take some time to discover all missing one with some trail and error. – mattiatantardini Sep 28, 2021 at 19:37 Yup, you do need to list all dependencies. If you want to shortcut the trial and error, you could try making an empty environment, installing streamlit into it with pip, and then using pip freeze to see what has been installed. If you get it working, it would be great to get a streamlit example in the examples folder for pynsist. :-) – Thomas K Sep 29, 2021 at 7:49 Well, you're right. I would like to try not to make the deployed app explode in size (in past projects I did not have to list all the sub-dependencies of certain packages), but this definitely will save me a lot of time. I think I'll go for that way. I'll keep you updated on my findings, and yeah, it will be great to share an example ;) – mattiatantardini Sep 29, 2021 at 12:59 Yup, I think you do. This is where the new FAQ entry on subprocesses comes in. I think you'll need something like {sys.executable} -m streamlit – Thomas K Oct 8, 2021 at 11:21

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]
                    Thank you Thomas for your help! I'll add a streamlit example to the pynsist repo as soon as possible
    – mattiatantardini
                    Oct 18, 2021 at 19:31
                    I would like to do this method, but I do not understand what the env variable about is, and the app.py when you are creating the path_to_main variable. It would be nice if you could formulate the question for a more general audience. Thanks
    – ananvodo
                    Feb 16 at 18:29
                    @anavodo I updated the answer with the relevant part of the code. EnvVar and get_env variables are just and helper class and function to deal with environmental variables, which I used to make the project configurable.
    – mattiatantardini
                    Feb 16 at 21:18
            

    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.