Build a Streamlit app¶
A working Streamlit app on top of SweatStack in under fifteen minutes. It authenticates the user, lets them pick an activity, and renders a heart-rate chart from the timeseries.
What you'll build¶
A Streamlit app that:
- Authenticates with SweatStack through OAuth2
- Fetches the user's activities
- Renders a heart-rate chart for the selected activity
- Lets a coach switch between the athletes they have access to
In a hurry?
uv init
uv add "sweatstack[streamlit]"
Create a new app at app.sweatstack.no/applications/new with redirect URI http://localhost:8501. Save the credentials in a .env file:
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here
Create app.py:
import os
import streamlit as st
from sweatstack.streamlit import StreamlitAuth
auth = StreamlitAuth(
client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
redirect_uri="http://localhost:8501",
)
with st.sidebar:
auth.authenticate()
if auth.is_authenticated():
auth.select_user()
if not auth.is_authenticated():
st.write("# Heart-rate dashboard")
st.write("Please log in to view your activity data.")
st.stop()
st.write("# Heart-rate dashboard")
activity = auth.select_activity()
if not activity:
st.warning("No activities found.")
st.stop()
st.write(f"### {activity.sport.label} on {activity.start.date()}")
data = auth.client.activities.data(activity.id)
if data is None or data.empty or "heart_rate" not in data.columns:
st.info("No heart-rate data available for this activity.")
st.stop()
st.line_chart(data, x="timestamp", y="heart_rate")
Run it:
uv run --env-file .env streamlit run app.py
Prerequisites¶
- A SweatStack account (app.sweatstack.no)
- uv installed
Create a SweatStack application¶
Register your app to get OAuth2 credentials:
- Go to app.sweatstack.no/applications/new.
- Enter a name. Leave the public profile fields for later.
- Set the redirect URI to
http://localhost:8501. - Save.
- Click Create Secret, name it, and copy the secret immediately. SweatStack shows it only once.
Save the credentials in a .env file:
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here
Warning
Treat the client secret like a password. Add .env to .gitignore before you commit anything.
Set up the project¶
Create a project folder and install the dependencies:
uv init
uv add "sweatstack[streamlit]"
Add authentication¶
Create app.py:
import os
import streamlit as st
from sweatstack.streamlit import StreamlitAuth
auth = StreamlitAuth(
client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
redirect_uri="http://localhost:8501",
)
auth.authenticate()
if not auth.is_authenticated():
st.write("# Heart-rate dashboard")
st.write("Please log in to view your activity data.")
st.stop()
st.write("# Heart-rate dashboard")
st.success("Connected to SweatStack.")
What this does:
StreamlitAuthhandles the OAuth2 flow.auth.authenticate()renders a login or logout button.auth.is_authenticated()checks the session state.st.stop()stops the rest of the script for users who are not signed in.
Use auth.client for API calls
StreamlitAuth.client is a per-session client tied to the signed-in user. The module-level interface (import sweatstack) shares one client across every Streamlit session and can leak data between users. Always call auth.client.<method>().
Run the app¶
uv run --env-file .env streamlit run app.py
Streamlit opens the browser (or open http://localhost:8501 yourself). Click the login button and authorize on SweatStack. You're back in your app, signed in.
Add activity selection¶
Replace the st.success(...) line with an activity picker. auth.select_activity() renders a dropdown of the user's recent activities:
st.write("# Heart-rate dashboard")
activity = auth.select_activity()
if not activity:
st.warning("No activities found.")
st.stop()
st.write(f"### {activity.sport.label} on {activity.start.date()}")
Add a heart-rate chart¶
Fetch the activity's timeseries data and render it. Add this after the activity-selection block:
data = auth.client.activities.data(activity.id)
if data is None or data.empty or "heart_rate" not in data.columns:
st.info("No heart-rate data available for this activity.")
st.stop()
st.line_chart(data, x="timestamp", y="heart_rate")
activities.data returns a pandas DataFrame with a timestamp column and one column per metric the activity recorded (heart_rate, power, speed, and so on). Not every activity has every metric. The check above guards against missing heart-rate data.
st.line_chart is Streamlit's native chart for a single timeseries. For more control over styling, use matplotlib or altair.
Switch between users (coaches)¶
If your users are coaches with access to several athletes, add a user selector. Move the auth widget into the sidebar and add auth.select_user():
with st.sidebar:
auth.authenticate()
if auth.is_authenticated():
auth.select_user()
For single-user apps, skip this section. Without a selection, auth.client acts as the signed-in user.
What's next¶
- The data model lists every metric that can appear in
activities.data. - The Streamlit helper page covers the full
StreamlitAuthAPI:select_sport,select_metric,select_tag, and more. - For analysis across months of activities at once, see the Analyze activity data guide.