[Streamlit] ローカル環境か否かで処理を分岐
もっと良い方法求む!
背景
Streamlitによる開発では個人的に、ローカル環境でのデバッグ時とデプロイ先での本番動作時とで、処理を分岐したいシーンがよくあります。典型的には以下です。
- ローカルの開発環境では、データの微調整が多いので、キャッシュ (
@st.cache_data
) を無効にしたい - デプロイ先であるCommunity Cloudでは、速度向上のためキャッシュを有効にしたい
手法
- デコレータ (
@st.cache_data
) を効かせるかどうか条件分岐可能にする - ローカル環境と他の環境をコード上で判定する
雑な疑似コードとしては、こういうことがしたいわけです。
if not is_local_env():
@st.cache_data
def load_my_huge_dataframe() -> pd.DataFrame:
...
Conditionalデコレータ
以下が参考になります。そのまま拝借すれば実現できます。
型を付けてみました。
from collections.abc import Callable
from typing import Any
class conditional_decorator:
def __init__(self, decorator: Callable[[Callable[..., Any]], Callable[..., Any]], condition: bool) -> None:
self.decorator = decorator
self.condition = condition
def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
if self.condition:
return self.decorator(func)
return func
使い方です。
import streamlit as st
# デコレータが有効
@conditional_decorator(st.cache_data, True)
def load_my_huge_dataframe1() -> pd.DataFrame():
...
# st.cache_data は無視される
@conditional_decorator(st.cache_data, False)
def load_my_huge_dataframe2() -> pd.DataFrame():
...
あとはこのTrue/Falseをどう持ってきてあげるかの問題です。
ローカル環境かどうかを判定
以下が参考になりますが、あまり明解な方法は無いようですね。
示されている platform.processor()
以外の方法を以下考えました。Streamlitにおいてローカル環境と他で扱いを変えているといえば、シークレットです。
.streamlit/secrets.toml
ファイルに以下のように書きます。[1]
local = true
Secretsの手引きにあるように、このTOMLファイルはGitにコミットしないようにしてください。即.gitignoreに入れましょう。
Community Cloudにおいては、Secretsを以下のような画面にて入力することになっています。普通は、ローカル環境の.streamlit/secrets.toml
にある値と同じものを入力しておくわけですが、ここではあえてlocal
は登録しません。
これによって、ローカル環境とCommunity Cloudとで差を出すことができました。シークレットのlocal
エントリの有無で、キャッシュを効かせるかどうかを判定してゴールです。
import streamlit as st
@conditional_decorator(st.cache_data, 'local' not in st.secrets)
def load_my_huge_dataframe() -> pd.DataFrame():
...
この類の分岐は、一般には環境変数を使うのが定番の1つだと思います。今回も、ローカル環境での実行時に何らか環境変数をセットすることができるならば、それでも可能だと思います。
$ IS_LOCAL="true" streamlit run main.py
import os
if os.environ.get("IS_LOCAL"):
...
else:
...
備考
以下のコードはエラーになります。st.set_page_config
は他のst.xxx
呼び出しに先んじないといけないそうです。
import streamlit as st
st.write("Hello!")
st.set_page_config(page_title="Title")
そうすると、先ほど示した st.secrets
を使う作戦は、メソッド定義を st.set_page_config
の後ろに書かないといけない?と不安になるかもしれません。もしそうだとするとかなり不便で、回避のためには奇妙な実装になります。
しかし心配無用のようでした。st.secrets
は st.set_page_config
の前に使っても大丈夫です。
import streamlit as st
print(f"{st.secrets=}")
st.set_page_config(page_title="Title") # OK
Streamlitのメソッド呼び出しは禁じられているものの、st.secrets
は辞書的オブジェクトであり[2]、Streamlitをimportした瞬間にもう評価済みなので、難を逃れたと考えています。
Discussion