はじめに
StreamlitはPythonだけで、Webアプリケーションが作成できるフレームワークです。
あらかじめUIのコンポーネントが用意されていますが、必要に応じてコンポーネントを自作することもできます。
今回は簡単なコンポーネントの作成からPyPIへの公開までの手順をまとめました。
今回作成したコンポーネント
よくあるアバターアイコンを表示するものです。
Streamlitには何故かない。。データアプリには不要というとこですかね。
- PyPI
pip install streamlit-avatar
- Github
環境構築
PythonおよびStreamlit、Node.jsが必要となります。が、それぞれ数多くの導入記事があるのでそちらにお任せします。
テンプレートのクローン
公式のテンプレートがあるのですが、このままだと色々混ざっているので今回は最小構成のプロジェクトにします。
git clone https://github.com/ppspps824/streamlit_avatar.git
コンポーネント作成
バックエンド側
__init__.py
import os
import streamlit.components.v1 as components
_RELEASE = False # デバッグ時はFalseにしておく
if not _RELEASE:
_component_func = components.declare_component(
"streamlit_avatar",
url="http://localhost:3001",
)
else:
parent_dir = os.path.dirname(os.path.abspath(__file__))
build_dir = os.path.join(parent_dir, "frontend/build")
_component_func = components.declare_component("streamlit_avatar", path=build_dir)
# Pythonスクリプト内で呼び出す関数。通常通り引数もここで指定。
# defaultは初期状態での戻り値
def avatar(data_list):
component_value = _component_func(data_list=data_list, default=None)
return component_value
フロントエンド側
スタイリング部分でごちゃっとしてますが、基本はthis.props.args["xxx"]
でバックエンドから受け取ったパラメータを利用できます。(今回はdata_listを受け取っています。)
index.tsx
import React from "react"
import ReactDOM from "react-dom"
import Avatar from "./Avatar"
ReactDOM.render(
<React.StrictMode>
<Avatar />
</React.StrictMode>,
document.getElementById("root")
)
Avatar.tsx
import {
Streamlit,
StreamlitComponentBase,
withStreamlitConnection,
} from "streamlit-component-lib"
import React, { ReactNode, CSSProperties } from "react"
interface DataItem {
title: string;
url: string;
size: number;
caption: string;
key: string;
}
interface AvatarProps {
args: {
data_list: DataItem[];
};
}
class Avatar extends StreamlitComponentBase<AvatarProps> {
public render = (): ReactNode => {
const dataList: DataItem[] = this.props.args["data_list"] || [];
const container_styles: CSSProperties = {
display: "flex",
flexDirection: "column",
};
return (
<div style={container_styles}>
{dataList.map((data, index) => (
<AvatarComponent
key={index}
data={data}
onClicked={this.onClicked}
/>
))}
</div>
)
}
private onClicked = (title: string, caption: string, key: string): void => {
Streamlit.setComponentValue({ "title": title, "caption": caption, "key": key })
}
}
interface AvatarComponentProps {
data: DataItem;
onClicked: (title: string, caption: string, key: string) => void;
}
class AvatarComponent extends React.Component<AvatarComponentProps> {
render() {
const { data, onClicked } = this.props;
const container_styles: CSSProperties = {
display: "flex",
alignItems: "center",
margin: "0.5rem"
};
const img_styles: CSSProperties = {
width: data.size,
height: data.size,
borderRadius: "50%",
objectFit: "cover",
marginRight: "10px",
};
const text_container_styles: CSSProperties = {
display: "flex",
flexDirection: "column",
};
const title_styles: CSSProperties = {
fontWeight: "bold",
};
const caption_styles: CSSProperties = {
fontSize: "12px",
color: "gray",
marginBottom: 0,
};
return (
<div style={container_styles} onClick={() => onClicked(data.title, data.caption, data.key)}>
<img src={data.url} alt="avatar" style={img_styles} />
<div style={text_container_styles}>
<div style={title_styles}>{data.title}</div>
<p style={caption_styles}>{data.caption}</p>
</div>
</div>
)
}
}
export default withStreamlitConnection(Avatar)
テスト
フロントエンドサーバの起動
cd frontend
npm run start
バックエンドサーバの起動
テスト用のスクリプトを作って
app.py
import streamlit as st
from streamlit_avatar import avatar
result = avatar(
[
{
"url": "https://picsum.photos/id/237/300/300",
"size": 40,
"title": "Sam",
"caption": "hello",
"key": "avatar1",
},
{
"url": "https://picsum.photos/id/238/300/300",
"size": 40,
"title": "Bob",
"caption": "happy",
"key": "avatar2",
},
{
"url": "https://picsum.photos/id/23/300/300",
"size": 40,
"title": "Rick",
"caption": "Bye",
"key": "avatar3",
},
]
)
st.write(result)
起動します。
streamlit run app.py
PyPIへ公開
こちらの記事を参考にさせていただきましたが、いくつか特有の注意点があります。
フロントエンドのビルド
cd frontend
npm build
__init__.py
以下に変更しておきます。
__init__.py
_RELEASE = True
MANIFEST.in
以下のように指定してbuildにfrontendが含まれるようにします。
MANIFEST.in
recursive-include <コンポーネント名>/frontend/build *
おわりに
コンポーネントを使って公開するのは私自身も初めてでしたが思った以上に簡単でした。
皆さんもぜひ!