在 windows 上打包 streamlit 应用程序和 运行 可执行文件

Package streamlit app and run executable on windows

这是我在 Whosebug 上的第一个问题。我希望我的问题很清楚,否则请告诉我,不要犹豫,问我更多细节。

我正在尝试为个人项目打包一个 streamlit 应用程序。我在 linux 下开发,但我必须在 Windows 上部署应用程序。我希望它是一个独立的可执行文件,一旦 运行 打开浏览器选项卡以显示应用程序,并在选项卡关闭时退出。我想使用 pynsist 库来打包应用程序(已经用于另一个项目并且运行良好)。

我遵循了 discussion 中的建议。它在 ubuntu 上运行良好,并且在使用 pynsist 打包应用程序后显然也在 Windows 上运行。 “显然”是因为可执行文件 运行,但没有打开浏览器选项卡来显示该应用程序。

这是我的一些代码片段。

项目结构

|- 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编辑 2 在 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 是一个环境变量,使应用程序在 linux 和 windows 上工作(在 windows 上,它被设置为安装目录)。

pynsist installer.cfg

编辑: 包括通过 pip list

发现的 streamlit 的依赖项

编辑 2: 添加 MarkupSafe 作为 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

查看 Windows 上的可执行输出,当前工作目录已正确打印,但未打印其他输出(streamlit 应用程序初始化消息或错误消息)。我尝试打开浏览器并转到 localhost:8501,但出现连接错误。

关于如何使代码执行并自动打开浏览器选项卡的任何提示?非常感谢任何帮助!

编辑: 正如在 installer.cfg 中最后一个包的评论中指出的那样,应用程序(具有 Jinja2 依赖项)已正确安装在 windows ,但启动后,应用程序仍然找不到 Jinja2 依赖项。这是回溯:

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.

编辑 2: 感谢 Thomas K 的有用提示,我想出了一半的解决方案。应用 运行s 和 streamlit 已启动。

但是

这些是日志消息:

  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: 

似乎应用程序的执行已停止,因为它正在等待一些凭据。我发现 here 可以添加 .streamlit/credentials.toml,但我不确定 windows 上的确切位置。我还尝试在 subprocess.run 命令中显式添加 --server.headless=false,但同样没有效果。

为什么应用程序不像 Linux 那样自动启动?有没有一种方法可以在用户不进行额外配置的情况下启动应用程序?

编辑:在 pynsist 回购的示例中添加了一个流光示例。 Here 您可以找到一个最小的和改进的工作应用程序示例(其中还包括 plotly)。

原始答案

我终于让它工作了。在我最后一次尝试中,我错误地设置了 --server.headless=false,而它必须是 true。我发现 streamlit 运行 命令需要一个附加标志:--global.developmentMode=false。这使部署工作,即使我在 streamlit configurations.

中找不到对此配置的任何引用

工作代码如下。

项目结构

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

一些注意事项:

  1. webbrowser.open 是自动在浏览器中打开一个新选项卡以显示 streamlit 应用程序所必需的。 subprocess.run 行只启动一个新的 streamlit 服务器。
  2. 正如我在评论中指出的那样,一旦退出浏览器中的 streamlit 选项卡,streamlit 服务器仍然存在并处于活动状态。您只需在地址栏中键入 localhost:8501 即可再次访问仪表板。如果您多次单击 Windows 应用程序图标,将启动多个 streamlit 服务器。我试过同时只有两个活动的,他们没有表现出冲突的行为。例如,要停止它们,您必须通过任务管理器手动结束任务。

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

注意:blinker需要额外的轮子。