Skip to content

Instantly share code, notes, and snippets.

@ekerstein ekerstein/whoop.py
Last active Aug 9, 2019

Embed
What would you like to do?
Python script to export WHOOP Strap user data from whoop.com using a username and password.
import requests # for getting URL
import json # for parsing json
from datetime import datetime # datetime parsing
import pytz # timezone adjusting
import csv # for making csv files
#################################################################
# USER VARIABLES
username = "user@gmail.com"
password = "password123"
save_directory = "C:/Code/data/" # keep trailing slash
#################################################################
# GET ACCESS TOKEN
# Post credentials
r = requests.post("https://api-7.whoop.com/oauth/token", json={
"grant_type": "password",
"issueRefresh": False,
"password": password,
"username": username
})
# Exit if fail
if r.status_code != 200:
print("Fail - Credentials rejected.")
exit()
else:
print("Success - Credentials accepted")
# Set userid/token variables
userid = r.json()['user']['id']
access_token = r.json()['access_token']
#################################################################
# GET DATA
# Download data
url = 'https://api-7.whoop.com/users/{}/cycles'.format(userid)
params = {
'start': '2000-01-01T00:00:00.000Z',
'end': '2030-01-01T00:00:00.000Z'
}
headers = {
'Authorization': 'bearer {}'.format(access_token)
}
r = requests.get(url, params=params, headers=headers)
# Check if user/auth are accepted
if r.status_code != 200:
print("Fail - User ID / auth token rejected.")
exit()
else:
print("Success - User ID / auth token accepted")
#################################################################
# PARSE/TRANSFORM DATA
# Convert data to json
data_raw = r.json()
# Takes a time and offset string and returns a timezone-corrected datetime string
def time_parse(time_string, offset_string):
# Switch sign on offset
offset_string = offset_string.replace('-', '+') if offset_string.count('-') else offset_string.replace('+', '-')
# Remove tz from time and add offset, get to 19 characters
time_string = time_string[:-(len(time_string) - 19)] + offset_string
# Parse and format
oldformat = '%Y-%m-%dT%H:%M:%S%z'
newformat = '%Y-%m-%d %H:%M:%S'
return datetime.strptime(time_string, oldformat).astimezone(pytz.utc).strftime(newformat)
# Make data object
data_summary = []
# Iterate through data
for d in data_raw:
# Make record object with default values
record = {
'date': None,
'recovery_hrv': None,
'recovery_rhr': None,
'recovery_score': None,
'sleep_date_start': None,
'sleep_date_end': None,
'sleep_cycles': None,
'sleep_disturbances': None,
'sleep_respiratory_rate': None,
'sleep_consistency': None,
'sleep_efficiency': None,
'sleep_duration_inbed': None,
'sleep_duration_latent': None,
'sleep_duration_light': None,
'sleep_duration_rem': None,
'sleep_duration_deep': None,
'sleep_duration_wake': None,
'sleep_duration_total': None,
'sleep_score_total': None,
'sleep_naps': None,
'sleep_completion': None,
'strain_avghr': None,
'strain_kilojoules': None,
'strain_calories': None,
'strain_maxhr': None,
'strain_score': None,
'workouts_count': None,
'workouts_duration': None,
}
# Date
if d['days']:
record['date'] = d['days'][0] # date
# Recovery
if d['recovery']:
# HRV
if 'heartRateVariabilityRmssd' in d['recovery'] and isinstance(d['recovery']['heartRateVariabilityRmssd'], (int, float)):
record['recovery_hrv'] = d['recovery']['heartRateVariabilityRmssd'] * 1000 # convert to ms
# RHR
if 'restingHeartRate' in d['recovery'] and isinstance(d['recovery']['restingHeartRate'], (int, float)):
record['recovery_rhr'] = d['recovery']['restingHeartRate']
# Recovery score
if 'score' in d['recovery'] and isinstance(d['recovery']['score'], (int, float)):
record['recovery_score'] = d['recovery']['score']
# Sleep
if d['sleep']:
# If within 'sleeps' array
if d['sleep']['sleeps']:
# Start / end times
if d['sleep']['sleeps'][0]['during'] and d['sleep']['sleeps'][0]['timezoneOffset']:
record['sleep_date_start'] = time_parse(d['sleep']['sleeps'][0]['during']['lower'], d['sleep']['sleeps'][0]['timezoneOffset'])
record['sleep_date_end'] = time_parse(d['sleep']['sleeps'][0]['during']['upper'], d['sleep']['sleeps'][0]['timezoneOffset'])
# Cycles
if 'cyclesCount' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['cyclesCount'], (int, float)):
record['sleep_cycles'] = d['sleep']['sleeps'][0]['cyclesCount']
# Disturbances
if 'disturbanceCount' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['disturbanceCount'], (int, float)):
record['sleep_disturbances'] = d['sleep']['sleeps'][0]['disturbanceCount']
# Respiratory rate
if 'respiratoryRate' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['respiratoryRate'], (int, float)):
record['sleep_respiratory_rate'] = d['sleep']['sleeps'][0]['respiratoryRate']
# Consistency
if 'sleepConsistency' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['sleepConsistency'], (int, float)):
record['sleep_consistency'] = d['sleep']['sleeps'][0]['sleepConsistency']
# Efficiency
if 'sleepEfficiency' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['sleepEfficiency'], (int, float)):
record['sleep_efficiency'] = d['sleep']['sleeps'][0]['sleepEfficiency'] * 100 # to percent
# Duration in bed
if 'inBedDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['inBedDuration'], (int, float)):
record['sleep_duration_inbed'] = d['sleep']['sleeps'][0]['inBedDuration'] / 1000 / 60 / 60 # ms to hours
# Duration latent
if 'latencyDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['latencyDuration'], (int, float)):
record['sleep_duration_latent'] = d['sleep']['sleeps'][0]['latencyDuration'] / 1000 / 60 / 60 # ms to hours
# Duration light
if 'lightSleepDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['lightSleepDuration'], (int, float)):
record['sleep_duration_light'] = d['sleep']['sleeps'][0]['lightSleepDuration'] / 1000 / 60 / 60 # ms to hours
# Duration REM
if 'remSleepDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['remSleepDuration'], (int, float)):
record['sleep_duration_rem'] = d['sleep']['sleeps'][0]['remSleepDuration'] / 1000 / 60 / 60 # ms to hours
# Duration deep / SWS
if 'slowWaveSleepDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['slowWaveSleepDuration'], (int, float)):
record['sleep_duration_deep'] = d['sleep']['sleeps'][0]['slowWaveSleepDuration'] / 1000 / 60 / 60 # ms to hours
# Duration wake
if 'wakeDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['wakeDuration'], (int, float)):
record['sleep_duration_wake'] = d['sleep']['sleeps'][0]['wakeDuration'] / 1000 / 60 / 60 # ms to hours
# Duration total
if 'qualityDuration' in d['sleep']['sleeps'][0] and isinstance(d['sleep']['sleeps'][0]['qualityDuration'], (int, float)):
record['sleep_duration_total'] = d['sleep']['sleeps'][0]['qualityDuration'] / 1000 / 60 / 60 # ms to hours
# NOT within 'sleeps' array
# Score
if 'score' in d['sleep'] and isinstance(d['sleep']['score'], (int, float)):
record['sleep_score_total'] = d['sleep']['score']
# Naps
if 'naps' in d['sleep'] and isinstance(d['sleep']['naps'], list):
record['sleep_naps'] = len(d['sleep']['naps'])
# Complete / incomplete
if 'state' in d['sleep'] and isinstance(d['sleep']['state'], str):
record['sleep_completion'] = d['sleep']['state']
# Strain
if d['strain']:
# Avg HR
if 'averageHeartRate' in d['strain'] and isinstance(d['strain']['averageHeartRate'], (int, float)):
record['strain_avghr'] = d['strain']['averageHeartRate']
# Kilojoules
if 'kilojoules' in d['strain'] and isinstance(d['strain']['kilojoules'], (int, float)):
record['strain_kilojoules'] = d['strain']['kilojoules']
# Calories
if record['strain_kilojoules']:
record['strain_calories'] = record['strain_kilojoules'] / 4.184 # kj to calories
# Max HR
if 'maxHeartRate' in d['strain'] and isinstance(d['strain']['maxHeartRate'], (int, float)):
record['strain_maxhr'] = d['strain']['maxHeartRate']
# Score
if 'score' in d['strain'] and isinstance(d['strain']['score'], (int, float)):
record['strain_score'] = d['strain']['score']
# Workouts
if 'workouts' in d['strain'] and isinstance(d['strain']['workouts'], list):
# Number of workouts
record['workouts_count'] = len(d['strain']['workouts'])
# Workout length
workout_duration = 0
for workout in d['strain']['workouts']:
if workout['during'] and workout['timezoneOffset']:
# Format start/end dates
start = time_parse(workout['during']['lower'], workout['timezoneOffset'])
end = time_parse(workout['during']['upper'], workout['timezoneOffset'])
# Add to duration
time_format = '%Y-%m-%d %H:%M:%S'
time_delta = datetime.strptime(end, time_format) - datetime.strptime(start, time_format)
workout_duration += time_delta.seconds / 60 # seconds to minutes
record['workouts_duration'] = workout_duration
# Append record to data dictionary
data_summary.append(record)
#################################################################
# WRITE JSON RAW DATA FILE
'''
# Write json file
with open(save_directory + 'whoop_raw.json', 'w') as outfile:
json.dump(data_raw, outfile)
print("Success - JSON raw data saved.")
'''
#################################################################
# WRITE CSV SUMMARY DATA FILE
# Write to CSV file
with open(save_directory + 'whoop.csv', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=data_summary[0].keys())
# Write header
writer.writeheader()
# Write rows
for row in data_summary:
writer.writerow(row)
print("Success - CSV summary data saved.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.