Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Bastian-Kuhn/2e668fd4af3c5986847a4e90b8adebb1 to your computer and use it in GitHub Desktop.

Select an option

Save Bastian-Kuhn/2e668fd4af3c5986847a4e90b8adebb1 to your computer and use it in GitHub Desktop.
Show last 7 Days of Workout from Hevy
#!/usr/bin/env python3
"""Read Hevy workouts from the last 7 days with full details."""
import sys
from datetime import datetime, timedelta, timezone
import requests
API_KEY = "xxxx"
BASE_URL = "https://api.hevyapp.com/v1"
HEADERS = {"api-key": API_KEY, "accept": "application/json"}
def fetch_workouts_since(since: datetime) -> list[dict]:
workouts: list[dict] = []
page = 1
while True:
resp = requests.get(
f"{BASE_URL}/workouts",
headers=HEADERS,
params={"page": page, "pageSize": 10},
timeout=30,
)
resp.raise_for_status()
data = resp.json()
batch = data.get("workouts", [])
if not batch:
break
stop = False
for w in batch:
start = datetime.fromisoformat(w["start_time"].replace("Z", "+00:00"))
if start < since:
stop = True
break
workouts.append(w)
if stop or page >= data.get("page_count", page):
break
page += 1
return workouts
def format_duration(start_iso: str, end_iso: str) -> str:
start = datetime.fromisoformat(start_iso.replace("Z", "+00:00"))
end = datetime.fromisoformat(end_iso.replace("Z", "+00:00"))
minutes = int((end - start).total_seconds() // 60)
h, m = divmod(minutes, 60)
return f"{h}h {m}m" if h else f"{m}m"
def print_workout(w: dict) -> None:
start = datetime.fromisoformat(w["start_time"].replace("Z", "+00:00"))
print("=" * 70)
print(f"{w['title']}")
print(f" Date: {start.astimezone().strftime('%Y-%m-%d %H:%M')}")
print(f" Duration: {format_duration(w['start_time'], w['end_time'])}")
if w.get("description"):
print(f" Note: {w['description']}")
print(f" Exercises: {len(w.get('exercises', []))}")
print()
for ex in w.get("exercises", []):
print(f" • {ex.get('title', 'Unknown')}")
if ex.get("notes"):
print(f" Note: {ex['notes']}")
for i, s in enumerate(ex.get("sets", []), 1):
parts = [f"Set {i}"]
if s.get("weight_kg") is not None:
parts.append(f"{s['weight_kg']} kg")
if s.get("reps") is not None:
parts.append(f"{s['reps']} reps")
if s.get("duration_seconds"):
parts.append(f"{s['duration_seconds']}s")
if s.get("distance_meters"):
parts.append(f"{s['distance_meters']}m")
if s.get("rpe") is not None:
parts.append(f"RPE {s['rpe']}")
if s.get("type") and s["type"] != "normal":
parts.append(f"[{s['type']}]")
print(" " + " · ".join(parts))
print()
def main() -> int:
since = datetime.now(timezone.utc) - timedelta(days=7)
try:
workouts = fetch_workouts_since(since)
except requests.HTTPError as e:
print(f"API error: {e}", file=sys.stderr)
return 1
if not workouts:
print("No workouts in the last 7 days.")
return 0
print(f"{len(workouts)} workout(s) in the last 7 days since {since.astimezone().strftime('%Y-%m-%d %H:%M')}:\n")
for w in sorted(workouts, key=lambda x: x["start_time"]):
print_workout(w)
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment