search
LoginSignup
3

posted at

updated at

Streamlit アプリを pyinstaller で .exe パッケージ化のメモ(2023/01 時点)

背景

streamlit のアプリを .exe にしてぺろっと配布できるようにしたい...

pyinstaller を使い, .py から steramlit run を動かすという形で対応します.

手順

ありがとうございます. 基本はこれを周到すればよいです. 以下は差分的な感じです.

2022 年 11 月時点で, streamlit.web.cli がすでに用意されているので, site-packages のほうに cli.py を作る必要がなくなりました.

にあるようにエントリとなる .py はこんな感じになります.

import streamlit.web.cli as stcli
import sys

def streamlit_run():
     sys.argv=["streamlit", "run", "main.py", "--global.developmentMode=false"]
     sys.exit(stcli.main())

if __name__ == "__main__":
     streamlit_run()

.streamlit/config.toml は不要かもしれません.

とりあえずなくても cli コマンド引数で対応できるためです.

spec ファイル

一度コマンドラインで pyinstaller 実行し, spec ファイルが作られるのでこれを編集します.

asset file

実行時に streamlit のサイトテンプレートのファイルなどが必要になります(streamlit/static/index.html など)

datas にパス(元ディレクトリと, コピー先)を指定します.
site.getsitepackages() で現在(pyinstaller 動かす環境)の python site-packages からコピるようにするとよいでしょう.

import site
import os

block_cipher = None

assert len(site.getsitepackages()) > 0

# Choose path containing "site-packages"
package_path = site.getsitepackages()[0]
for p in site.getsitepackages():
    if "site-package" in p:
        package_path = p
        break

a = Analysis(
    ['cli_main.py'],
    pathex=[],
    binaries=[],
    # copy streamlit files
    datas=[(os.path.join(package_path, "altair/vegalite/v4/schema/vega-lite-schema.json"),
        "./altair/vegalite/v4/schema/"),
       (os.path.join(package_path, "streamlit/static"),"./streamlit/static")
       ],
    hiddenimports=[],
    hookspath=['./hooks'],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

streamlit で利用する .py 関連(multipage 利用時は pages フォルダの .py なども)は datas に記載しましょう.

hooks

hooks スクリプトが必要になります. これを行わないと, 実行時 import streamlitstreamlit フォルダを import するようになり, しかしパッケージには static html のアセットファイルしかないため import error

importlib_metadata.PackageNotFoundError: No package metadata was found for streamlit

が生じてしまいます.

# hook-streamlit.py

from PyInstaller.utils.hooks import copy_metadata
datas = copy_metadata('streamlit')

な hoo-streamlit.py ファイルを作り,

↑での hookspath に hooks スクリプトのフォルダを記載します.

hook 用のファイル名は hook-IMPORTNAME.py である必要があります.

streamlit 1.14 あたりから, 実行時 `'No module named streamlit.runtime.scriptrunner.magic_funcs' エラーがでるるようになります.

streamlit.runtime は実行時に評価されるモジュールのため, .spec での datas に streamlit.runtime のフォルダもコピーするように追加するか, エントリポイントの .py で import して pyinstaller が見つけられるようにしておくとよいでしょう.

other python modules

cv2 など, streamlit に直接関係しない module は pyinstaller が自動では見つけてくれないので,
(.exe にしたあと .py を評価しないとわからないため)
適当に requirements.txt などの python module をエントリポイントの .py で import しておくか,
.spec に記述しておきましょう

パッケージング

あとは pyinstaller で .spec ファイルを指定してパッケージングするだけです!

お好みで --onefile などして.

問題点

streamlit が web サーバプロセスとして動くので, port 解放とか必要になります.
Electron で Standalone という手もありますが, 完全に処理部分を以下の stlite(python module が全部 WASM 化されている場合)に置き換えられる場合に限られます.
(ローカルファイル扱っていたり, FastAPI や pytorch つかっていたりしたら結局はサーバプロセス + Electron 等の GUI の組み合わせとなってしまう. FastAPI については OpenSSL 部分含め WASM モジュール化できればワンチャンあるかもしれませんが...)

.py がいじれる

起動させる .py はユーザーがいじれてしまいます.
ソースコード公開したくないやソースいじられたくない場合, なにか方法を考える必要があります(cli 起動側で main.py 部分を import させて .exe に取り込むなどできるかも?)

パッケージが大きい, 起動が遅い

pytorch とか使っているとパッケージが 4 GB とかになります. 大きめの python プロジェクトだと起動も遅くなります...

このあたりはどうしようもないですね...

python-build-standalone で Python 環境を self-contained で作り, PyOxidizer でバイナリ化(Python 環境 embed)がいいかもしれません.

stlite を使う(制約あり)

pyodide で WASM 化された Python で pure HTML(WASM)で動かす
C++ native module などを含んだものは対応できません(のはず)
(C++ -> wasm コンパイルすればいけるだろうがめんどい)

pytorch とかつかっておらず, numpy あたりだけ使っているのであれば, これと nativefier を組み合わせすれば行けそうなきもします!(未検証) ただ, 大きめのプロジェクトだと numpy とかの js のロードで起動遅くなるやも?

TODO

main.py など .spec に記載し自動でパッケージされるようにする.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3