streamlitで爆速アプリ公開のススメ

# この記事の対象
- 自分、Python書けます
- でも人に渡すアプリってどう作るかわからない　アプリ化？サーバ立て？
- とりあえず渡すのに何か楽な方法ないの？

# streamlitって何？
Streamlit（ストリームリット）は、機械学習やデータサイエンス向けのグラフィカルなカスタムWebアプリケーションを簡単に作成して全世界に公開できる、オープンソースのWebアプリケーションのフレームワークです。（AIによる回答）

Pythonは特にデータ周りの取り扱いとディープラーニングを代表とする各種分析機能が取り揃った、もはや分析者にはデファクトスタンダードなプログラミング言語です。最近ではOpenAIなど文章生成AIと合わせてアプリ作成する機会も増えました。
でも、自前PCでの検証ならともかく、実際に動くアプリの受け渡しってなかなかハードル高いですよね。Python開発はできたとしても、どうやってソフトウェアとして受け渡すかで苦労することもあると思います。例えば渡す先にはPython入ってません、とか。
そこで、streamlitを利用してアプリ公開してみよう、のススメです。

# 準備
まずは何はなくともstreamlitをインストールしましょう。pipが使えます。
```terminal
pip install streamlit
```

streamlitは最新版の方が色々なUIテンプレートが取り揃っているので、特に制約なければPythonのバージョンは新しい方が良いと思います。
streamlitはPythonにて以下のような記述で作成できます。
```hello_streamlit.py
import streamlit as st

st.write('hello')
```
以上をpython形式で保存して、ターミナル（コマンドプロンプト）でとりあえず実行します。

```terminal
streamlit run hello_streamlit.py
```

こちらを実行すると自動でブラウザが立ち上がり、上記で作成した「hello」が表示されます。こんな感じで文字を表示するくらいであればわざわざ手間をかけてUIコンポーネントを準備する必要なく、検証も開発PC上でサクッとできるのがいいですね。
開発中はブラウザそのままに、コードを修正して右上のメニューからrerunするといちいちターミナル開かなくていいのも便利です。

閉じるときはターミナル側のプロセスキルしてブラウザを閉じましょう。

# 条件分岐について
ここでstreamlitの機能紹介は省略しますが、コードを作っていくうえで意識した方がいいことを軽く触れていきます。streamlitはPythonで記載しますが、Pythonとは異なる独特な癖のあるルールで動いているのでそこに慣れましょう。

streamlitはブラウザ上のクリック等、ユーザ操作を受けてインタラクティブに表示切り替えを行うことができます。

```pushme.py
import streamlit as st

def main():
  st.write('start')
  if st.button('push me'):
    st.write('you push me!')
  else:
    st.write('hello?')

if __name__ == "__main__":
    main()
```

こちらを実行すると、ブラウザには押せるボタンが表示されます。そしてボタンを押すと、文字列が表示されます。
![before](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/4acec2f5-362c-642f-280e-46f0cd756a9f.png)
押すと・・・↓
![after](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/c5d93e27-1b10-edfe-cc45-920c0557c69e.png)


ここで何が起こっているか。まず最初は実行されるとif条件に合致しないのでelse文のwriteが実行されてます（buttonというUIを宣言しながら、押されていないのでFalseを返す）。そしてこれ以降はコードはありませんので、一旦停止しています。

ここからボタンを押下すると、条件が変わったことを契機に**プログラムが最初から実行され直して**、if条件合致が実行されます（buttonというUIを宣言しながら、押されたとしてTrueを返す）。ここではelse文のwriteで書かれた文章は実行されず、Trueの時の文章に置き換わるわけです。そしてここでコードがまた完了となります。

そして、ここでボタンを離しても再描画されません。後述で説明しますが、button押された時、つまり、Trueになった時だけ再描画となり、離した時は再描画となりません。以降はbuttonを押そうが離そうが、True状態のままになるんですね。

このように、streamlitでは通過したif条件のところが変わると、**最初からやり直して再描画される**という動きをします。この再実行されることと再実行される条件は意識する必要があります。

# 引数の保持
この再実行は、**Python上で状態をリセットして実行する**のと同じ挙動を見せます。そのため、例えば変数を用意してボタンの押下数をカウントする、みたいなことをしようとしても、**count用変数が定義されてない状態となって初期化をもう一度踏んでしまう**ためカウント実装ができないという状況になってしまいます。
以下の実装では残念ながら、何度ボタンを押しても1を返す仕様になります。
```count_badcase.py
# ダメな例
import streamlit as st

def main():
  if 'count' not in locals():# 定義されているかの確認。再実行の時はここを実行したくないが...
    count=0 # 毎回踏んで初期化してしまう
  if st.button('push me'):
    count+=1
  st.write(str(count))

if __name__ == "__main__":
    main()
```
![1しか返さない](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/18bbc009-1dfe-8e7a-5b20-ee9f9a5d8985.png)

そこで、streamlitのsession_stateを使うとstreamlitオブジェクト側で変数を定義することができます。こちらは条件分岐等の実行による再描画が行われても数値は保持されたままになります。
```count_and_reset.py
# 良い例
import streamlit as st

def main():
  if "count" not in st.session_state: #初期実行の時のみ定義と初期化を行う
    st.session_state.count=0 #ボタン押下で再実行されても一回しか踏まない
  if st.button('reset'):
    st.session_state.count=0
  if st.button('push me'):
    st.session_state.count+=1
  st.write(str(st.session_state.count))

if __name__ == "__main__":
    main()
```
![カウントアップされる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/209b0531-273e-01e1-04c4-d1ad99882723.png)

ついでにカウントを0に戻すリセットボタンも追加してみました。このように実装すると、カウントボタン、リセットボタン、いずれも想定通りの挙動になります。

さて、少し深掘り。前述でボタンを離したときは再描画されないと言いましたが、別ボタンのreset機能を押すとcountプラスは実行されず、ちゃんと0に戻っています（もしTrueのままだと、count+により1になるはず）。つまり、count側の文がFalseに戻っていて実行されてないわけです。
まとめますと、**buttonは押した時だけ再実行される（離した時は再実行されない）が、別の方法でコード再実行されればちゃんとFalseに戻っている**わけです。ここも若干のハマりどころなので注意です。

以上を押さえれば最低限のアプリは作れるかなと思います。少しお堅い話でしたが、実際のところstreamlitは各種UIパーツが充実しており、グラフを書けたり、チャットツールが作れたり、と色々作って楽しみながらやるのがよいかと思います。

# 公開準備

さて、アプリが出来たら今度は公開をサクッとやってみましょう。特段これを使って収益化だ、とか、大規模処理してやるぜ、といった目的がなく、まずは他の人が確認できればいいや、ということであれば、[streamlit Community Cloud](https://streamlit.io/cloud)を利用するのが最も楽でしょう。何らか自前サーバを用意することなく、パパッと公開できてしまいます。

sign upするとstreamlitのアカウント情報入力等の諸々必要な作業が順番に要求されますのでよしなに実施しましょう。ざっくりいうと、streamlit Communitiy Cloudへの公開は、githubで作成したpythonコードをpublicにする、streamlitでgithubアカウントを連携させる、の２段階の作業で可能となります。

sign up出来たら、同ページのCreate app>Deploy a public app from GitHubを選択し、作成したstreamlitのレポジトリ、ブランチ、streamlit runするpythonコードを選択しましょう。

![community cloud](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/44ff2cb1-0265-d97d-69c2-604e8a9a1155.png)

これで少し待てば作成完了、公開用URLが発行されますので見せたい人に渡しましょう。ね、簡単でしょ？

# レッツ公開、の前に
公開する上で、例えば外部のAPIを使いたい、そのためシークレットトークン情報を入れたい、ということがあると思います。でもpublic公開されるコード中に書くのは流石にNGですよね。
そんな時、ソースコードとは別に、公開されないシークレット情報を利用ができます。

まずローカル開発環境では、以下の位置にtomlファイルを配置します。
```.streamlit/secrets.toml
[secret]
secret_token=hogehoge
```
以上を準備しておき、以下のような記載をしておくと、pythonコード上でそのパラメータを引用できます。
```use_secrets.py
import streamlit as st

def main():
  secret = st.secrets('secret_token')
  
```
streamlit Community Cloudではこのtomlに当たる情報を別途配置して上記と同じくコードで読み出すことができます。作成する際にAdvance Settingから、または作成済みの場合はアプリ一覧からSetting>Secretsを開き、フォームにtomlをコピペしましょう。

![シークレットの管理](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/a9888a5b-d6fd-1230-5f62-59c32890d985.png)

また、以下の選択で公開を一般（検索可能）から、URLを知っている人だけに限定することも可能です。まあURLが漏れたら誰からも見えてしまうのであくまでお手軽制限です。ご注意を。

![スクリーンショット 2024-11-30 16.26.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510897/6d929b94-b617-f4ab-c20c-9f37334da41d.png)


# 結論
streamlit（＋Cloud機能）は、とりあえず他の人に提供することが簡易にできるツールとして使えます。手早く作れる人なら頭の中にある実装イメージを１日あればモノとして共有できるのではないでしょうか。
一方、上記で説明した独特なルールのため、if/while文をいくつも絡めた状態遷移管理が必要となるため高度なUI/UXの作り込みは難しく、プロト開発までに止めるのが無難でしょう。
とはいえ、Python少しわかる、環境構築苦手、な私でも一日あればサクッと提供できたので、とりあえず何かアプリ作りたいぞ、人に見せたいぞ、という方にはオススメですね。