LoginSignup
8
9

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-11-14

背景

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 に記載し自動でパッケージされるようにする.

8
9
0

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
  3. You can use dark theme
What you can do with signing up
8
9