Analyze your athletes¶
As a coach, collect one number for every athlete who shared their data with you: here, weekly training time over the last four weeks, in one table.
1. List your athletes¶
users.list() returns everyone you can access: yourself, users you manage, and people who shared their data with you. Leave yourself out:
from sweatstack import Client
coach = Client()
coach.authenticate()
me = coach.whoami()
athletes = [user for user in coach.users.list() if user.id != me.id]
print([athlete.display_name for athlete in athletes])
To get the athletes who authorized one of your teams instead, use coach.teams.users(team_id), and pass team_id=team_id to delegated_client() in the next step.
2. Act as each athlete¶
delegated_client() returns a new client that acts as the athlete. The coach's client doesn't change.
from datetime import date, timedelta
from sweatstack import Client
coach = Client()
athlete = coach.users.list(include_managed=False)[0]
athlete_client = coach.delegated_client(athlete)
activities = athlete_client.activities.list(
start=date.today() - timedelta(weeks=4), output="polars"
)
A delegated client fails with SweatStackAuthError if the athlete revoked your access. Catch it per athlete, so one athlete doesn't stop the whole loop.
3. Combine the results¶
Add the athlete's name to each frame, then concatenate:
from datetime import date, timedelta
import polars as pl
from sweatstack import Client, SweatStackAuthError
coach = Client()
me = coach.whoami()
since = date.today() - timedelta(weeks=4)
frames = []
for athlete in coach.users.list():
if athlete.id == me.id:
continue
try:
activities = coach.delegated_client(athlete).activities.list(start=since, output="polars")
except SweatStackAuthError:
print(f"No access to {athlete.display_name}; skipping")
continue
frames.append(activities.with_columns(athlete=pl.lit(athlete.display_name)))
weekly = (
pl.concat(frames, how="diagonal_relaxed")
.with_columns(week=pl.col("start_local").dt.truncate("1w"))
.group_by("athlete", "week")
.agg(hours=pl.col("duration").dt.total_seconds().sum() / 3600)
.sort("athlete", "week")
)
print(weekly)
start_local is the athlete's local time, so a week is the athlete's week, not yours. See Timezones.
Complete script
# /// script
# requires-python = ">=3.10"
# dependencies = ["sweatstack[polars]"]
# ///
from datetime import date, timedelta
import polars as pl
from sweatstack import Client, SweatStackAuthError
coach = Client()
coach.authenticate()
me = coach.whoami()
since = date.today() - timedelta(weeks=4)
frames = []
for athlete in coach.users.list():
if athlete.id == me.id:
continue
try:
activities = coach.delegated_client(athlete).activities.list(
start=since, output="polars"
)
except SweatStackAuthError:
print(f"No access to {athlete.display_name}; skipping")
continue
if activities.height:
frames.append(activities.with_columns(athlete=pl.lit(athlete.display_name)))
if not frames:
raise SystemExit("No activities from any athlete in the last four weeks")
weekly = (
pl.concat(frames, how="diagonal_relaxed")
.with_columns(week=pl.col("start_local").dt.truncate("1w"))
.group_by("athlete", "week")
.agg(hours=pl.col("duration").dt.total_seconds().sum() / 3600)
.sort("athlete", "week")
)
print(weekly)