通过构建 Bored API 应用学习如何使用 API
Bored API 应用可以在你无聊的时候建议你做些有意思的事!
技术上来说,这也演示了如何在 Streamlit 应用内使用 API。
以下是如何实现上述 Bored API 应用:
import streamlit as st
import requests
#设置一个标题
st.title('🏀 Bored API app')
#设置一个侧边栏的抬头
st.sidebar.header('Input')
#添加一个选择框
selected_type = st.sidebar.selectbox('Select an activity type', ["education", "recreational", "social", "diy", "charity", "cooking", "relaxation", "music", "busywork"])
#获取制定链接数据
suggested_activity_url = f'http://www.boredapi.com/api/activity?type={selected_type}'
json_data = requests.get(suggested_activity_url)
suggested_activity = json_data.json()
#设定两列
c1, c2 = st.columns(2)
#第一个是扩展器
with c1:
with st.expander('About this app'):
st.write('Are you bored? The **Bored API app** provides suggestions on activities that you can do when you are bored. This app is powered by the Bored API.')
#设定第二个扩展器
with c2:
with st.expander('JSON data'):
st.write(suggested_activity)
#设定主面板的标题
st.header('Suggested activity')
st.info(suggested_activity['activity'])
#主面板设定三列
col1, col2, col3 = st.columns(3)
with col1:
st.metric(label='Number of Participants', value=suggested_activity['participants'], delta='')
with col2:
st.metric(label='Type of Activity', value=suggested_activity['type'].capitalize(), delta='')
with col3:
st.metric(label='Price', value=suggested_activity['price'], delta='')
创建 Streamlit 应用时要做的第一件事就是将 streamlit
库导入为 st
,并且导入要用到的 requests
库:
import streamlit as st
import requests
然后用 st.title
显示应用的标题:
st.title('🏀 Bored API app')
接下来我们将通过 st.selectbox
命令接收用户输入的活动类型:
st.sidebar.header('Input')
selected_type = st.sidebar.selectbox('Select an activity type', ["education", "recreational", "social", "diy", "charity", "cooking", "relaxation", "music", "busywork"])
上述选择的活动类型会通过 f
-字符串追加到请求链接之后,然后被用于请求 JSON 数据:
suggested_activity_url = f'http://www.boredapi.com/api/activity?type={selected_type}'
json_data = requests.get(suggested_activity_url)
suggested_activity = json_data.json()
以下我们将通过 st.expander
命令显示应用的说明以及获取到的 JSON 数据:
c1, c2 = st.columns(2)
with c1:
with st.expander('About this app'):
st.write('Are you bored? The **Bored API app** provides suggestions on activities that you can do. This app is powered by the Bored API.')
with c2:
with st.expander('JSON data'):
st.write(suggested_activity)
然后我们会像下面这样将建议的活动显示出来
st.header('Suggested activity')
st.info(suggested_activity['activity'])
最后,我们也会显示所建议活动随附的信息,比如参与人数、活动类型与价格。
col1, col2, col3 = st.columns(3)
with col1:
st.metric(label='Number of Participants', value=suggested_activity['participants'], delta='')
with col2:
st.metric(label='Type of Activity', value=suggested_activity['type'].capitalize(), delta='')
with col3:
st.metric(label='Price', value=suggested_activity['price'], delta='')
这里面的第二个参数是一个icon,也就是一个符号
在警报旁边显示的可选表情符号或图标。如果图标为 "无"(默认),则不显示图标。如果图标是字符串,则以下选项有效:
单字符表情符号。例如,可以设置 icon="🚨" 或 icon="🔥"。不支持表情符号简码。
材料符号库(轮廓样式)中的图标,格式为":material/icon_name:",其中 "icon_name "是蛇形图标的名称。
例如,icon=":material/thumb_up: "将显示拇指向上图标。在 Material Symbols 字体库中查找其他图标。
import streamlit as st
st.info('This is a purely informational message', icon="ℹ️")
Streamlit Elements 是一个由 okld 制作的第三方组件,能够让你用 Material UI 组件、Monaco 编辑器(Visual Studio Code)和 Nivo charts 等等搭建出精美的应用和仪表盘。
如何使用?
第一步要做的就是将 Streamlit Elements 安装到你的环境中:
pip install streamlit-elements==0.1.*
我们推荐你将其版本固定到 0.1.*
,因为此后的版本中可能引入变动破坏 API 向后兼容性。
你可以参考 Streamlit Elements README 中给出的深度用法指南。
今天挑战的目标是做一个包含三个 Material UI 卡片的仪表盘:
st.text_input
指定连接的 YouTube 视频你可以使用 Nivo Bump 示例中“data”标签页下生成的数据:Bump chart | nivo.
# 首先,我们需要给应用导入以下的库
import json
import streamlit as st
from pathlib import Path
# 然后我们需要 Streamlit Elements 中的这些对象
# 有关全部对象及其用法的说明请见:https://github.com/okld/streamlit-elements#getting-started
from streamlit_elements import elements, dashboard, mui, editor, media, lazy, sync, nivo
# 更改页面布局,让仪表盘占据整个页宽
st.set_page_config(layout="wide")
with st.sidebar:
st.title("🗓️ #30DaysOfStreamlit")
st.header("Day 27 - Streamlit Elements")
st.write("Build a draggable and resizable dashboard with Streamlit Elements.")
st.write("---")
# 媒体播放器所用的 URL
media_url = st.text_input("Media URL", value="https://www.youtube.com/watch?v=vIQQR_yq-8I")
# 初始化代码编辑器和图表的默认数据
#
# 在这篇教程中,我们会用到 Nivo Bump 图的数据
# 你能在“data”标签页下获取随机的数据:https://nivo.rocks/bump/
#
# 如下所示,当代码编辑器发生更改时,会话状态就会被更新
# 然后会被读入至 Nivo Bump 图并将其绘制出来
if "data" not in st.session_state:
st.session_state.data = Path("data.json").read_text()
# 定义默认的仪表盘布局
# 默认情况下仪表盘会分为 12 列
#
# 更多可用参数见:
# https://github.com/react-grid-layout/react-grid-layout#grid-item-props
layout = [
# 编辑器对象定位在坐标 x=0 且 y=0 处,占据 12 列中的 6 列以及 3 行
dashboard.Item("editor", 0, 0, 6, 3),
# 图表对象定位在坐标 x=6 且 y=0 处,占据 12 列中的 6 列以及 3 行
dashboard.Item("chart", 6, 0, 6, 3),
# 媒体播放器对象定位在坐标 x=0 且 y=3 处,占据 12 列中的 6 列以及 4 行
dashboard.Item("media", 0, 3, 12, 4),
]
# 创建显示各元素的框体
with elements("demo"):
# 使用以上指定的布局创建新仪表盘
#
# draggableHandle 是一个 CSS 查询选择器,定义了仪表盘中可拖拽的部分
# 以下为将带 'draggable' 类名的元素变为可拖拽对象
#
# 更多仪表盘网格相关的可用参数请见:
# https://github.com/react-grid-layout/react-grid-layout#grid-layout-props
# https://github.com/react-grid-layout/react-grid-layout#responsive-grid-layout-props
with dashboard.Grid(layout, draggableHandle=".draggable"):
# 第一个卡片,代码编辑器
#
# 我们使用 'key' 参数来选择正确的仪表盘对象
#
# 为了让卡片的内容自动填充占满全部高度,我们将使用 flexbox CSS 样式
# sx 是所有 Material UI 组件均可使用的参数,用于定义其 CSS 属性
#
# 有关卡片、flexbox 和 sx 的更多信息,请见:
# https://mui.com/components/cards/
# https://mui.com/system/flexbox/
# https://mui.com/system/the-sx-prop/
with mui.Card(key="editor", sx={"display": "flex", "flexDirection": "column"}):
# 为了让标题可拖拽,我们只需要将其类名设为 'draggable'
# 与 dashboard.Grid 当中 draggableHandle 的查询选择对应
mui.CardHeader(title="Editor", className="draggable")
# 要使卡片内容占满全高,我们需要将 CSS 样式中 flex 的值设为 1
# 同时我们也想要卡片内容随卡片缩放,因此将其 minHeight 设为 0
with mui.CardContent(sx={"flex": 1, "minHeight": 0}):
# 以下是我们的 Monaco 代码编辑器
#
# 首先,我们将其默认值设为之前初始化好的 st.session_state.data
# 其次,我们将设定所用的语言,这里我们设为 JSON
#
# 接下来,我们想要获取编辑器中内容的变动
# 查阅 Monaco 文档后,我们发现可以用 onChange 属性指定一个函数
# 这个函数会在每次变动发生后被调用,并且变更后的内容将被传入函数
# (参考 onChange: https://github.com/suren-atoyan/monaco-react#props)
#
# Streamlit Elements 提供了一个特殊的 sync() 函数
# 能够创建一个自动将其参数同步到 Streamlit 会话状态的回调函数
#
# 样例
# --------
# 创建一个自动将第一个参数同步至会话状态中 "data" 的回调函数:
# >>> editor.Monaco(onChange=sync("data"))
# >>> print(st.session_state.data)
#
# 创建一个自动将第二个参数同步至会话状态中 "ev" 的回调函数:
# >>> editor.Monaco(onChange=sync(None, "ev"))
# >>> print(st.session_state.ev)
#
# 创建一个自动将两个参数同步至会话状态的回调函数:
# >>> editor.Monaco(onChange=sync("data", "ev"))
# >>> print(st.session_state.data)
# >>> print(st.session_state.ev)
#
# 那么问题来了:onChange 会在每次发生变动时被调用
# 那么意味着每当你输入一个字符,整个 Streamlit 应用都会重新运行
#
# 为了避免这个问题,可以使用 lazy() 令 Streamlit Elements 等待其他事件发生
# (比如点击按钮)然后再将更新后的数据传给回调函数
#
# 有关 Monaco 其他可用参数的说明,请见:
# https://github.com/suren-atoyan/monaco-react
# https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html
editor.Monaco(
defaultValue=st.session_state.data,
language="json",
onChange=lazy(sync("data"))
)
with mui.CardActions:
# Monaco 编辑器已经将一个延迟回调函数绑定至 onChange 了,因此即便你更改了 Monaco 的内容
# Streamlit 也不会立刻接收到,因此不会每次都重新运行
# 因此我们需要另一个非延迟的事件来触发更新
#
# 解决方法就是创建一个在点击时回调的按钮
# 我们的回调函数实际上不需要做任何事
# 你可以创建一个空的函数,或者直接使用不带参数的 sync()
#
# 然后每当你点击按钮的时候,onClick 回调函数会被调用
# 而期间其他延迟调用了的回调函数也会被一并执行
mui.Button("Apply changes", onClick=sync())
# 第二个卡片,Nivo Bump 图
# 我们将使用和第一个卡片同样的 flexbox 配置来自动调整内容高度
with mui.Card(key="chart", sx={"display": "flex", "flexDirection": "column"}):
# 为了让标题可拖拽,我们只需要将其类名设为 'draggable'
# 与 dashboard.Grid 当中 draggableHandle 的查询选择对应
mui.CardHeader(title="Chart", className="draggable")
# 和前面一样,我们想要让我们的内容随着用户缩放卡片而缩放
# 因此将 flex 属性设为 1,minHeight 设为 0
with mui.CardContent(sx={"flex": 1, "minHeight": 0}):
# 以下我们将绘制 Bump 图