Streamlit component APIに入門する
「StreamlitってReactっぽいよな~~~」って思いつつ、書けば使えてしまうので何となくで使ってきたStreamlitですが、Streamlitの動作に対して理解を深めるべく Streamlit Componentに入門してみたいと思います。
Streamlitとは
Streamlitはpythonで簡単にフロントエンドを開発できるライブラリです。Pythonのコードを書くだけで、JavaScriptで構成されたフロントエンドとPythonで動くバックエンドを作れてしまう優れものです。
Webで動作するデモアプリを書きたいときにフロントとバックエンドの通信はどうするだとか、各々のサーバーは何で構成するだとか考えなくて済むのでとても重宝します。
Streamlit Component API
Streamlitは「コンポーネント」という単位で画面を構成し、「見出し」「マークダウン」「グラフ」のような感じで何を表示していくかを順に記述していきます。デフォルトでもグラフやDataFrameを表示できるので、十分なぐらいに実装されているのですが、オレオレなコンポーネントを実装したい人向けに「Streamlit Component API」というものが提供されています。
ドキュメントはこちら↓
Components API - Streamlit Docs
自作コンポーネントを作ってみよう
というわけで、自作コンポーネントを作っていきます。極力ステップバイステップで所感を交えつつ手順を紹介したいと思います。
公式から提供されているテンプレートを使う
いきなりテンプレートのコピペとなってしまって申し訳ないですが、公式からコンポーネントのプロジェクトを作るにあたって活用できるテンプレートが提供されているので、ありがたくそれを使っていきます。
画像の通り、「template(React Componentのように記述できる)」「template-reactless(React Componentを使用しないタイプ)」の2つのテンプレートがあります。今回の例ではReact Componentで試したいことがあったので、、前者を利用します。
というわけで、上記 component-template をクローンして、templateフォルダで作業をしていきます。
git clone https://github.com/streamlit/component-template cd component-template/template
フロントエンド用の資材を準備・サーバー起動
まずは、フロントエンドはTypeScriptで記述したものをトランスパイルして使用するので、依存しているライブラリの導入が必要になります。フロントエンドのフォルダに移動して npm install
を実行します。
pushd my_component/frontend npm install popd
ライブラリが揃ったら、npm start
でサーバーを起動します。Reactユーザーにはお馴染みの react-scripts
でWebサーバーが立ち上がります。
pushd my_component/frontend npm start
開発中は react-scripts
で適宜トランスパイルされてWebサーバーで公開されているものを読み取りに行く動きをするようですね。
Streamlitで動作確認する
Streamlit で my_component/__init__.py
を実行することで動作確認することができます。
streamlit run my_component/__init__.py
起動すると 8501 番ポートで立ち上がるので、そこにアクセスすると動作している様子を確認することができます。
vscode で devcontainer を使用している場合、vscodeのポートフォワーディングでlocalhostの8501にポートがバインドされるので、ブラウザで http://localhost:8051
を開けばOKです。
リリースする方法
ソースコードを見てもらえるとわかりますが、_RELEASE
変数の値によって動作が変わるようで、「_RELEASEがFalseの時はローカルサーバーを参照」「_RELEASE
がTrueの時はトランスパイル済みのjsファイルを参照」するように動作が変わるみたいです。
なぜわざわざローカルサーバーを参照?ってところですが、これはまぁコンポーネントのソースコートが更新された時にホットリロードされるように、ということなのかなと思います。
_RELEASE
をTrueにするとトランスパイル済み資材が参照されるので、これは別途 npm run build
でトランスパイルしてそれを含めてモジュール化するという流れになるみたいです。これは公式のワークフローを参照するといいと思います。(ソースコード内の_RELEASE変数書き換えからやってて勉強になります。)
component-template/.github/workflows/ci.yaml at master · streamlit/component-template · GitHub
devcontainer化しました
と、ここまでの内容(+これから以下で試す内容)をdevcontainer化しました。
とはいえ、求められるのはnodeとpythonだけなので、いうほど環境準備はハードル高くないかなと思いますが。ローカル環境汚したくないよって人はぜひ使ってみてください。
GitHub - yoh1496/learning-streamlit-component: Repository for learning streamlit component
気になったこと
個人的に試して気になったことを書きます。(順次書き足していく予定です)
気になったことその1: マウントアンマウントは検知できる?
Streamlit ComponentはReact Componentライクに書くことができるので、Reactのライフサイクル同様、componentDidMount
などの関数が呼ばれるかを試してみました。コンポーネントのアンマウント時にクリーンアップなどの処理ができると便利ですよね。
class MyComponent extends StreamlitComponentBase<State> { public state = { numClicks: 0, isFocused: false } public componentDidMount(): void { super.componentDidMount() console.log('mount'); } public componentWillUnmount(): void { if (super.componentWillUnmount) super.componentWillUnmount(); console.log('unmount'); } public componentDidUpdate(): void { console.log(this.props) } /** 略 **/
というわけでこんな感じに実装して、以下のようにマウント・アンマウントを試すコードを書いてみました。
if 'mounted' not in st.session_state: st.session_state.mounted = False def on_click_unmount(): st.session_state.mounted = False del st.session_state.hoge def on_click_mount(): st.session_state.mounted = True mounted = st.session_state.mounted st.button('Unmount Component', disabled=not mounted, on_click=on_click_unmount) st.button('Mount Component', disabled=mounted, on_click=on_click_mount) if mounted: my_component('Mount/Unmount Test', key="mount_unmount_test")
session_state
を用いてmountedフラグを管理し、mountedフラグの値によって表示/非表示を切り替えるコードです。
結果
これを実行した結果、componentDidMount
, componentDidUpdate
は呼ばれるが componentWillUnmount
は上記pythonスクリプトでは呼ばれないことがわかりました。
この結果では 「非表示にしただけではStreamlit Componentはアンマウントされない」のか「Streamlitではアンマウントに際して
componentWillUnmount` は呼ばれないのか」は不明ですが、ちょろっとコードを書くだけではアンマウント時の動作を記述することは難しいということは言えそうです。
どなたかアンマウントにあわせて処理を記述する方法をご存じの方がいらっしゃいましたら教えていただければ幸いです。
終わりに
今回はStreamlitをよりカスタマイズするために、Streamlit Componentを試してみました。
冒頭にも書きましたが、Streamlitはちょっとオタメシのフロントエンドを書くのにすごく便利で、Componentと組み合わせることで標準のStreamlitにはない機能を持たせることができることがわかりました。
個人的にはこれからも便利にStreamlitを使っていきたいなと思うところですが、いろいろ複雑なことをやると死ぬ(これについてはまたいつか書きたいです)というところも同時にわかってきたので、あんまりゴチャゴチャComponentで書こうとしてReactで書いた方が結局早くね?とならないように気を付けたいところです。