Building Client Prototypes Fast: Streamlit Dashboards in an Afternoon
Part 3 of the Python for FDE track. Last updated: October 2026.
The FDE superpower is turning a Python script into something a client stakeholder can click — same afternoon. Streamlit (pip install streamlit, run with streamlit run app.py) does exactly that: no frontend code, no build step, just Python that renders as a web app. This post builds a client-ready dashboard pattern: widgets, caching, layout, state, and secrets.
Your first dashboard: file upload to insight
The classic FDE demo opener — let the stakeholder drop in their file and see it work on their data:
# app.py — run with: streamlit run app.py
import streamlit as st
import pandas as pd
st.title("Acme — Weekly Client Review")
uploaded = st.file_uploader("Drop the client CSV here", type="csv")
if uploaded:
df = pd.read_csv(uploaded)
st.metric("Rows", f"{len(df):,}")
st.dataframe(df.head(20))
else:
st.info("Upload a file to begin the demo.")
Widgets: the stakeholder control panel
Filters belong in the sidebar so the main area stays clean. Every widget reruns the script top-to-bottom when changed — that rerun model is the whole mental model of Streamlit:
import streamlit as st
client = st.sidebar.selectbox("Client", ["Acme", "Globex", "Initech"])
min_value = st.sidebar.slider("Minimum order value", 0, 1000, 100)
regions = st.sidebar.multiselect("Regions", ["NA", "EU", "APAC"], default=["NA"])
show_raw = st.sidebar.checkbox("Show raw data", value=False)
st.write(f"Showing **{client}**: orders over ${min_value} in {', '.join(regions)}")
if show_raw:
st.dataframe(filtered_df) # defined from your data step above
Caching: don't reload the world on every click
Because the script reruns on every widget change, expensive work needs st.cache_data. It caches by argument values and an optional TTL — the difference between a snappy demo and an awkward silence:
import streamlit as st
import pandas as pd
@st.cache_data(ttl=3600) # cache for one hour; keyed on `path`
def load_client_data(path: str) -> pd.DataFrame:
df = pd.read_csv(path, parse_dates=["order_date"])
return df.dropna(subset=["order_id"])
df = load_client_data("data/orders.csv")
st.metric("Orders loaded", f"{len(df):,}")
# Orders loaded: 1,231
Layout: tabs and columns
Stakeholders scan, they don't scroll. Put the headline numbers in columns and the details in tabs:
import streamlit as st
tab1, tab2 = st.tabs(["Overview", "Drill-down"])
with tab1:
c1, c2, c3 = st.columns(3)
c1.metric("Revenue", "$48.2k", "+6%")
c2.metric("Orders", "1,240", "+38")
c3.metric("Churn risk", "3", "-1")
with tab2:
st.bar_chart({"Week 1": [120, 95, 140], "Week 2": [150, 110, 125]})
State: multi-step flows
Some demos need steps — connect, then clean, then show. st.session_state survives reruns, so it holds your wizard's position:
import streamlit as st
if "step" not in st.session_state:
st.session_state.step = 0
if st.button("Next step"):
st.session_state.step += 1
steps = ["Connect", "Clean", "Demo"]
st.progress((st.session_state.step + 1) / len(steps))
st.subheader(steps[min(st.session_state.step, len(steps) - 1)])
Secrets: API keys without leaking them
Demos need real API keys, and real API keys must never appear on a shared screen or in a repo. Streamlit's secrets store keeps them out of the code — locally in .streamlit/secrets.toml, in the cloud via the app dashboard:
import streamlit as st
import requests
# .streamlit/secrets.toml (never committed):
# CLIENT_API_KEY = "sk-live-..."
api_key = st.secrets["CLIENT_API_KEY"]
r = requests.get("https://api.client.com/v1/health",
headers={"Authorization": f"Bearer {api_key}"}, timeout=10)
st.success("API reachable" if r.ok else f"API returned {r.status_code}")
Key takeaways
- Streamlit turns a script into a clickable app: pip install streamlit, then streamlit run app.py.
- The mental model is rerun-on-widget-change — design for it, and put filters in the sidebar.
- Wrap expensive loads in @st.cache_data so every click doesn't reload the world.
- Stakeholders scan: columns for headline metrics, tabs for detail.
- Multi-step demos need st.session_state; secrets need st.secrets, never hardcoded keys.
Next in this series: Data Wrangling for Demos: Turning Messy Client Data into Something Shippable.
Comments
Post a Comment