diff --git a/.gitignore b/.gitignore
index 1709fa6..964f6e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-session.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -104,6 +103,7 @@ celerybeat.pid
# Environments
.env
+.envrc
.venv
env/
venv/
@@ -128,3 +128,4 @@ dmypy.json
# Pyre type checker
.pyre/
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9b38853
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "python.testing.pytestArgs": [
+ "tests"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true
+}
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4277ddc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+PATH := ./venv/bin:${PATH}
+sources = garminconnect tests setup.py
+
+.PHONY: .venv ## Install virtual environment
+.venv:
+ python -m venv .venv
+ python -m pip install -qU pip
+
+.PHONY: install ## Install package
+install: .venv
+ pip install -qUe .
+
+.PHONY: install-test ## Install package in development mode
+install-test: .venv install
+ pip install -qU -r requirements-test.txt
+
+.PHONY: test ## Run tests
+test:
+ pytest --cov=garminconnect --cov-report=term-missing
\ No newline at end of file
diff --git a/README.md b/README.md
index 4b9b324..5279d66 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
-
# Python: Garmin Connect
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
+
Python 3 API wrapper for Garmin Connect to get your statistics.
## About
@@ -12,562 +12,22 @@ See
## Installation
```bash
-pip3 install garminconnect
+python -m pip install garminconnect
```
-## API Demo Program
-
-I wrote this for testing and playing with all available/known API calls.
-If you run it from the python-garmin connect directory it will use the library code beneath it, so you can develop without reinstalling the package.
+## Authentication
-The code also demonstrates how to implement session saving and re-using of the cookies.
+The library uses the same authentication method as the app using [Garth](https://github.com/matin/garth).
+The login credentials generated with Garth are valid for a year to avoid needing to login each time.
-You can set environment variables with your credentials like so, this is optional:
+## Testing
```bash
-export EMAIL=
-export PASSWORD=
-```
-
-Install the pre-requisites for the example program (not all are needed for using the library package):
-
-```bash
-pip3 install cloudscraper readchar requests pwinput
-```
-
-Or you can just run the program and enter your credentials when asked, it will create and save a session file and use that until it's outdated/invalid.
-
-```
-python3 ./example.py
-*** Garmin Connect API Demo by cyberjunky ***
-
-1 -- Get full name
-2 -- Get unit system
-3 -- Get activity data for '2023-03-10'
-4 -- Get activity data for '2023-03-10' (compatible with garminconnect-ha)
-5 -- Get body composition data for '2023-03-10' (compatible with garminconnect-ha)
-6 -- Get body composition data for from '2023-03-03' to '2023-03-10' (to be compatible with garminconnect-ha)
-7 -- Get stats and body composition data for '2023-03-10'
-8 -- Get steps data for '2023-03-10'
-9 -- Get heart rate data for '2023-03-10'
-0 -- Get training readiness data for '2023-03-10'
-- -- Get daily step data for '2023-03-03' to '2023-03-10'
-/ -- Get body battery data for '2023-03-03' to '2023-03-10'
-! -- Get floors data for '2023-03-03'
-? -- Get blood pressure data for '2023-03-03' to '2023-03-10'
-. -- Get training status data for '2023-03-10'
-a -- Get resting heart rate data for 2023-03-10'
-b -- Get hydration data for '2023-03-10'
-c -- Get sleep data for '2023-03-10'
-d -- Get stress data for '2023-03-10'
-e -- Get respiration data for '2023-03-10'
-f -- Get SpO2 data for '2023-03-10'
-g -- Get max metric data (like vo2MaxValue and fitnessAge) for '2023-03-10'
-h -- Get personal record for user
-i -- Get earned badges for user
-j -- Get adhoc challenges data from start '0' and limit '100'
-k -- Get available badge challenges data from '1' and limit '100'
-l -- Get badge challenges data from '1' and limit '100'
-m -- Get non completed badge challenges data from '1' and limit '100'
-n -- Get activities data from start '0' and limit '100'
-o -- Get last activity
-p -- Download activities data by date from '2023-03-03' to '2023-03-10'
-r -- Get all kinds of activities data from '0'
-s -- Upload activity data from file 'MY_ACTIVITY.fit'
-t -- Get all kinds of Garmin device info
-u -- Get active goals
-v -- Get future goals
-w -- Get past goals
-y -- Get all Garmin device alarms
-x -- Get Heart Rate Variability data (HRV) for '2023-03-10'
-z -- Get progress summary from '2023-03-03' to '2023-03-10' for all metrics
-A -- Get gear, the defaults, activity types and statistics
-Z -- Logout Garmin Connect portal
-q -- Exit
-
-Make your selection:
-
+make install-test
+make test
```
-This is some example code, and probably older than the latest code which can be found in 'example.py'.
-
-```python
-#!/usr/bin/env python3
-"""
-pip3 install cloudscraper requests readchar pwinput
-
-export EMAIL=
-export PASSWORD=
-
-"""
-import datetime
-import json
-import logging
-import os
-import sys
-
-import requests
-import pwinput
-import readchar
-
-from garminconnect import (
- Garmin,
- GarminConnectAuthenticationError,
- GarminConnectConnectionError,
- GarminConnectTooManyRequestsError,
-)
-
-# Configure debug logging
-# logging.basicConfig(level=logging.DEBUG)
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-# Load environment variables if defined
-email = os.getenv("EMAIL")
-password = os.getenv("PASSWORD")
-api = None
-
-# Example selections and settings
-today = datetime.date.today()
-startdate = today - datetime.timedelta(days=7) # Select past week
-start = 0
-limit = 100
-start_badge = 1 # Badge related calls calls start counting at 1
-activitytype = "" # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
-activityfile = "MY_ACTIVITY.fit" # Supported file types are: .fit .gpx .tcx
-
-menu_options = {
- "1": "Get full name",
- "2": "Get unit system",
- "3": f"Get activity data for '{today.isoformat()}'",
- "4": f"Get activity data for '{today.isoformat()}' (compatible with garminconnect-ha)",
- "5": f"Get body composition data for '{today.isoformat()}' (compatible with garminconnect-ha)",
- "6": f"Get body composition data for from '{startdate.isoformat()}' to '{today.isoformat()}' (to be compatible with garminconnect-ha)",
- "7": f"Get stats and body composition data for '{today.isoformat()}'",
- "8": f"Get steps data for '{today.isoformat()}'",
- "9": f"Get heart rate data for '{today.isoformat()}'",
- "0": f"Get training readiness data for '{today.isoformat()}'",
- "-": f"Get daily step data for '{startdate.isoformat()}' to '{today.isoformat()}'",
- "/": f"Get body battery data for '{startdate.isoformat()}' to '{today.isoformat()}'",
- "!": f"Get floors data for '{startdate.isoformat()}'",
- "?": f"Get blood pressure data for '{startdate.isoformat()}' to '{today.isoformat()}'",
- ".": f"Get training status data for '{today.isoformat()}'",
- "a": f"Get resting heart rate data for {today.isoformat()}'",
- "b": f"Get hydration data for '{today.isoformat()}'",
- "c": f"Get sleep data for '{today.isoformat()}'",
- "d": f"Get stress data for '{today.isoformat()}'",
- "e": f"Get respiration data for '{today.isoformat()}'",
- "f": f"Get SpO2 data for '{today.isoformat()}'",
- "g": f"Get max metric data (like vo2MaxValue and fitnessAge) for '{today.isoformat()}'",
- "h": "Get personal record for user",
- "i": "Get earned badges for user",
- "j": f"Get adhoc challenges data from start '{start}' and limit '{limit}'",
- "k": f"Get available badge challenges data from '{start_badge}' and limit '{limit}'",
- "l": f"Get badge challenges data from '{start_badge}' and limit '{limit}'",
- "m": f"Get non completed badge challenges data from '{start_badge}' and limit '{limit}'",
- "n": f"Get activities data from start '{start}' and limit '{limit}'",
- "o": "Get last activity",
- "p": f"Download activities data by date from '{startdate.isoformat()}' to '{today.isoformat()}'",
- "r": f"Get all kinds of activities data from '{start}'",
- "s": f"Upload activity data from file '{activityfile}'",
- "t": "Get all kinds of Garmin device info",
- "u": "Get active goals",
- "v": "Get future goals",
- "w": "Get past goals",
- "y": "Get all Garmin device alarms",
- "x": f"Get Heart Rate Variability data (HRV) for '{today.isoformat()}'",
- "z": f"Get progress summary from '{startdate.isoformat()}' to '{today.isoformat()}' for all metrics",
- "A": "Get gear, the defaults, activity types and statistics",
- "Z": "Logout Garmin Connect portal",
- "q": "Exit",
-}
-
-def display_json(api_call, output):
- """Format API output for better readability."""
-
- dashed = "-"*20
- header = f"{dashed} {api_call} {dashed}"
- footer = "-"*len(header)
-
- print(header)
- print(json.dumps(output, indent=4))
- print(footer)
-
-def display_text(output):
- """Format API output for better readability."""
-
- dashed = "-"*60
- header = f"{dashed}"
- footer = "-"*len(header)
-
- print(header)
- print(json.dumps(output, indent=4))
- print(footer)
-
-def get_credentials():
- """Get user credentials."""
- email = input("Login e-mail: ")
- password = pwinput.pwinput(prompt='Password: ')
-
- return email, password
-
-
-def init_api(email, password):
- """Initialize Garmin API with your credentials."""
-
- try:
- ## Try to load the previous session
- with open("session.json") as f:
- saved_session = json.load(f)
-
- print(
- "Login to Garmin Connect using session loaded from 'session.json'...\n"
- )
-
- # Use the loaded session for initializing the API (without need for credentials)
- api = Garmin(session_data=saved_session)
-
- # Login using the
- api.login()
-
- except (FileNotFoundError, GarminConnectAuthenticationError):
- # Login to Garmin Connect portal with credentials since session is invalid or not present.
- print(
- "Session file not present or turned invalid, login with your Garmin Connect credentials.\n"
- "NOTE: Credentials will not be stored, the session cookies will be stored in 'session.json' for future use.\n"
- )
- try:
- # Ask for credentials if not set as environment variables
- if not email or not password:
- email, password = get_credentials()
-
- api = Garmin(email, password)
- api.login()
-
- # Save session dictionary to json file for future use
- with open("session.json", "w", encoding="utf-8") as f:
- json.dump(api.session_data, f, ensure_ascii=False, indent=4)
- except (
- GarminConnectConnectionError,
- GarminConnectAuthenticationError,
- GarminConnectTooManyRequestsError,
- requests.exceptions.HTTPError,
- ) as err:
- logger.error("Error occurred during Garmin Connect communication: %s", err)
- return None
-
- return api
-
-
-def print_menu():
- """Print examples menu."""
- for key in menu_options.keys():
- print(f"{key} -- {menu_options[key]}")
- print("Make your selection: ", end="", flush=True)
-
-
-def switch(api, i):
- """Run selected API call."""
-
- # Exit example program
- if i == "q":
- print("Bye!")
- sys.exit()
-
- # Skip requests if login failed
- if api:
- try:
- print(f"\n\nExecuting: {menu_options[i]}\n")
-
- # USER BASICS
- if i == "1":
- # Get full name from profile
- display_json("api.get_full_name()", api.get_full_name())
- elif i == "2":
- # Get unit system from profile
- display_json("api.get_unit_system()", api.get_unit_system())
-
- # USER STATISTIC SUMMARIES
- elif i == "3":
- # Get activity data for 'YYYY-MM-DD'
- display_json(f"api.get_stats('{today.isoformat()}')", api.get_stats(today.isoformat()))
- elif i == "4":
- # Get activity data (to be compatible with garminconnect-ha)
- display_json(f"api.get_user_summary('{today.isoformat()}')", api.get_user_summary(today.isoformat()))
- elif i == "5":
- # Get body composition data for 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
- display_json(f"api.get_body_composition('{today.isoformat()}')", api.get_body_composition(today.isoformat()))
- elif i == "6":
- # Get body composition data for multiple days 'YYYY-MM-DD' (to be compatible with garminconnect-ha)
- display_json(f"api.get_body_composition('{startdate.isoformat()}', '{today.isoformat()}')",
- api.get_body_composition(startdate.isoformat(), today.isoformat())
- )
- elif i == "7":
- # Get stats and body composition data for 'YYYY-MM-DD'
- display_json(f"api.get_stats_and_body('{today.isoformat()}')", api.get_stats_and_body(today.isoformat()))
-
- # USER STATISTICS LOGGED
- elif i == "8":
- # Get steps data for 'YYYY-MM-DD'
- display_json(f"api.get_steps_data('{today.isoformat()}')", api.get_steps_data(today.isoformat()))
- elif i == "9":
- # Get heart rate data for 'YYYY-MM-DD'
- display_json(f"api.get_heart_rates('{today.isoformat()}')", api.get_heart_rates(today.isoformat()))
- elif i == "0":
- # Get training readiness data for 'YYYY-MM-DD'
- display_json(f"api.get_training_readiness('{today.isoformat()}')", api.get_training_readiness(today.isoformat()))
- elif i == "/":
- # Get daily body battery data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
- display_json(f"api.get_body_battery('{startdate.isoformat()}, {today.isoformat()}')", api.get_body_battery(startdate.isoformat(), today.isoformat()))
- elif i == "?":
- # Get daily blood pressure data for 'YYYY-MM-DD' to 'YYYY-MM-DD'
- display_json(f"api.get_blood_pressure('{startdate.isoformat()}, {today.isoformat()}')", api.get_blood_pressure(startdate.isoformat(), today.isoformat()))
- elif i == "-":
- # Get daily step data for 'YYYY-MM-DD'
- display_json(f"api.get_daily_steps('{startdate.isoformat()}, {today.isoformat()}')", api.get_daily_steps(startdate.isoformat(), today.isoformat()))
- elif i == "!":
- # Get daily floors data for 'YYYY-MM-DD'
- display_json(f"api.get_floors('{today.isoformat()}')", api.get_floors(today.isoformat()))
- elif i == ".":
- # Get training status data for 'YYYY-MM-DD'
- display_json(f"api.get_training_status('{today.isoformat()}')", api.get_training_status(today.isoformat()))
- elif i == "a":
- # Get resting heart rate data for 'YYYY-MM-DD'
- display_json(f"api.get_rhr_day('{today.isoformat()}')", api.get_rhr_day(today.isoformat()))
- elif i == "b":
- # Get hydration data 'YYYY-MM-DD'
- display_json(f"api.get_hydration_data('{today.isoformat()}')", api.get_hydration_data(today.isoformat()))
- elif i == "c":
- # Get sleep data for 'YYYY-MM-DD'
- display_json(f"api.get_sleep_data('{today.isoformat()}')", api.get_sleep_data(today.isoformat()))
- elif i == "d":
- # Get stress data for 'YYYY-MM-DD'
- display_json(f"api.get_stress_data('{today.isoformat()}')", api.get_stress_data(today.isoformat()))
- elif i == "e":
- # Get respiration data for 'YYYY-MM-DD'
- display_json(f"api.get_respiration_data('{today.isoformat()}')", api.get_respiration_data(today.isoformat()))
- elif i == "f":
- # Get SpO2 data for 'YYYY-MM-DD'
- display_json(f"api.get_spo2_data('{today.isoformat()}')", api.get_spo2_data(today.isoformat()))
- elif i == "g":
- # Get max metric data (like vo2MaxValue and fitnessAge) for 'YYYY-MM-DD'
- display_json(f"api.get_max_metrics('{today.isoformat()}')", api.get_max_metrics(today.isoformat()))
- elif i == "h":
- # Get personal record for user
- display_json("api.get_personal_record()", api.get_personal_record())
- elif i == "i":
- # Get earned badges for user
- display_json("api.get_earned_badges()", api.get_earned_badges())
- elif i == "j":
- # Get adhoc challenges data from start and limit
- display_json(
- f"api.get_adhoc_challenges({start},{limit})", api.get_adhoc_challenges(start, limit)
- ) # 1=start, 100=limit
- elif i == "k":
- # Get available badge challenges data from start and limit
- display_json(
- f"api.get_available_badge_challenges({start_badge}, {limit})", api.get_available_badge_challenges(start_badge, limit)
- ) # 1=start, 100=limit
- elif i == "l":
- # Get badge challenges data from start and limit
- display_json(
- f"api.get_badge_challenges({start_badge}, {limit})", api.get_badge_challenges(start_badge, limit)
- ) # 1=start, 100=limit
- elif i == "m":
- # Get non completed badge challenges data from start and limit
- display_json(
- f"api.get_non_completed_badge_challenges({start_badge}, {limit})", api.get_non_completed_badge_challenges(start_badge, limit)
- ) # 1=start, 100=limit
-
- # ACTIVITIES
- elif i == "n":
- # Get activities data from start and limit
- display_json(f"api.get_activities({start}, {limit})", api.get_activities(start, limit)) # 0=start, 1=limit
- elif i == "o":
- # Get last activity
- display_json("api.get_last_activity()", api.get_last_activity())
- elif i == "p":
- # Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD', with (optional) activitytype
- # Possible values are: cycling, running, swimming, multi_sport, fitness_equipment, hiking, walking, other
- activities = api.get_activities_by_date(
- startdate.isoformat(), today.isoformat(), activitytype
- )
-
- # Download activities
- for activity in activities:
-
- activity_id = activity["activityId"]
- display_text(activity)
-
- print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.GPX)")
- gpx_data = api.download_activity(
- activity_id, dl_fmt=api.ActivityDownloadFormat.GPX
- )
- output_file = f"./{str(activity_id)}.gpx"
- with open(output_file, "wb") as fb:
- fb.write(gpx_data)
- print(f"Activity data downloaded to file {output_file}")
-
- print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.TCX)")
- tcx_data = api.download_activity(
- activity_id, dl_fmt=api.ActivityDownloadFormat.TCX
- )
- output_file = f"./{str(activity_id)}.tcx"
- with open(output_file, "wb") as fb:
- fb.write(tcx_data)
- print(f"Activity data downloaded to file {output_file}")
-
- print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.ORIGINAL)")
- zip_data = api.download_activity(
- activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
- )
- output_file = f"./{str(activity_id)}.zip"
- with open(output_file, "wb") as fb:
- fb.write(zip_data)
- print(f"Activity data downloaded to file {output_file}")
-
- print(f"api.download_activity({activity_id}, dl_fmt=api.ActivityDownloadFormat.CSV)")
- csv_data = api.download_activity(
- activity_id, dl_fmt=api.ActivityDownloadFormat.CSV
- )
- output_file = f"./{str(activity_id)}.csv"
- with open(output_file, "wb") as fb:
- fb.write(csv_data)
- print(f"Activity data downloaded to file {output_file}")
-
- elif i == "r":
- # Get activities data from start and limit
- activities = api.get_activities(start, limit) # 0=start, 1=limit
-
- # Get activity splits
- first_activity_id = activities[0].get("activityId")
-
- display_json(f"api.get_activity_splits({first_activity_id})", api.get_activity_splits(first_activity_id))
-
- # Get activity split summaries for activity id
- display_json(f"api.get_activity_split_summaries({first_activity_id})", api.get_activity_split_summaries(first_activity_id))
-
- # Get activity weather data for activity
- display_json(f"api.get_activity_weather({first_activity_id})", api.get_activity_weather(first_activity_id))
-
- # Get activity hr timezones id
- display_json(f"api.get_activity_hr_in_timezones({first_activity_id})", api.get_activity_hr_in_timezones(first_activity_id))
-
- # Get activity details for activity id
- display_json(f"api.get_activity_details({first_activity_id})", api.get_activity_details(first_activity_id))
-
- # Get gear data for activity id
- display_json(f"api.get_activity_gear({first_activity_id})", api.get_activity_gear(first_activity_id))
-
- # Activity self evaluation data for activity id
- display_json(f"api.get_activity_evaluation({first_activity_id})", api.get_activity_evaluation(first_activity_id))
-
- # Get exercise sets in case the activity is a strength_training
- if activities[0]["activityType"]["typeKey"] == "strength_training":
- display_json(f"api.get_activity_exercise_sets({first_activity_id})", api.get_activity_exercise_sets(first_activity_id))
-
- elif i == "s":
- # Upload activity from file
- display_json(f"api.upload_activity({activityfile})", api.upload_activity(activityfile))
-
- # DEVICES
- elif i == "t":
- # Get Garmin devices
- devices = api.get_devices()
- display_json("api.get_devices()", devices)
-
- # Get device last used
- device_last_used = api.get_device_last_used()
- display_json("api.get_device_last_used()", device_last_used)
-
- # Get settings per device
- for device in devices:
- device_id = device["deviceId"]
- display_json(f"api.get_device_settings({device_id})", api.get_device_settings(device_id))
-
- # GOALS
- elif i == "u":
- # Get active goals
- goals = api.get_goals("active")
- display_json("api.get_goals(\"active\")", goals)
-
- elif i == "v":
- # Get future goals
- goals = api.get_goals("future")
- display_json("api.get_goals(\"future\")", goals)
-
- elif i == "w":
- # Get past goals
- goals = api.get_goals("past")
- display_json("api.get_goals(\"past\")", goals)
-
- # ALARMS
- elif i == "y":
- # Get Garmin device alarms
- alarms = api.get_device_alarms()
- for alarm in alarms:
- alarm_id = alarm["alarmId"]
- display_json(f"api.get_device_alarms({alarm_id})", alarm)
-
- elif i == "x":
- # Get Heart Rate Variability (hrv) data
- display_json(f"api.get_hrv_data({today.isoformat()})", api.get_hrv_data(today.isoformat()))
-
- elif i == "z":
- # Get progress summary
- for metric in ["elevationGain", "duration", "distance", "movingDuration"]:
- display_json(
- f"api.get_progress_summary_between_dates({today.isoformat()})", api.get_progress_summary_between_dates(
- startdate.isoformat(), today.isoformat(), metric
- ))
-
- # Gear
- elif i == "A":
- last_used_device = api.get_device_last_used()
- display_json(f"api.get_device_last_used()", last_used_device)
- userProfileNumber = last_used_device["userProfileNumber"]
- gear = api.get_gear(userProfileNumber)
- display_json(f"api.get_gear()", gear)
- display_json(f"api.get_gear_defaults()", api.get_gear_defaults(userProfileNumber))
- display_json(f"api.get()", api.get_activity_types())
- for gear in gear:
- uuid=gear["uuid"]
- name=gear["displayName"]
- display_json(f"api.get_gear_stats({uuid}) / {name}", api.get_gear_stats(uuid))
-
- elif i == "Z":
- # Logout Garmin Connect portal
- display_json("api.logout()", api.logout())
- api = None
-
- except (
- GarminConnectConnectionError,
- GarminConnectAuthenticationError,
- GarminConnectTooManyRequestsError,
- requests.exceptions.HTTPError,
- ) as err:
- logger.error("Error occurred: %s", err)
- except KeyError:
- # Invalid menu option chosen
- pass
- else:
- print("Could not login to Garmin Connect, try again later.")
-
-# Main program loop
-while True:
- # Display header and login
- print("\n*** Garmin Connect API Demo by cyberjunky ***\n")
-
- # Init API
- if not api:
- api = init_api(email, password)
-
- # Display menu
- print_menu()
- option = readchar.readkey()
- switch(api, option)
-
-```
+The tests provide examples of how to use the library.
## Donations
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
diff --git a/garminconnect/__init__.py b/garminconnect/__init__.py
index 4907d0b..3450b0d 100644
--- a/garminconnect/__init__.py
+++ b/garminconnect/__init__.py
@@ -1,426 +1,170 @@
-# -*- coding: utf-8 -*-
-
"""Python 3 API wrapper for Garmin Connect to get your statistics."""
-import json
import logging
-import re
-import requests
-from enum import Enum, auto
-from typing import Any, Dict, List
import os
+from enum import Enum, auto
+from typing import Any, Dict, List, Optional
-import cloudscraper
-
+import garth
logger = logging.getLogger(__name__)
-class ApiClient:
- """Class for a single API endpoint."""
-
- default_headers = {
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0"
- }
-
- def __init__(self, session, baseurl, headers=None, additional_headers=None):
- """Return a new Client instance."""
- self.session = session
- self.baseurl = baseurl
-
- if headers:
- self.headers = headers
- else:
- self.headers = self.default_headers.copy()
-
- if additional_headers:
- self.headers.update(additional_headers)
-
- def set_cookies(self, cookies):
- logger.debug("Restoring cookies for saved session")
- self.session.cookies.update(cookies)
-
- def get_cookies(self):
- return self.session.cookies
-
- def clear_cookies(self):
- self.session.cookies.clear()
-
- def url(self, addurl=None):
- """Return the url for the API endpoint."""
-
- path = f"https://{self.baseurl}"
- if addurl is not None:
- path += f"/{addurl}"
-
- return path
-
- def get(self, addurl, additional_headers=None, params=None):
- """Make an API call using the GET method."""
- total_headers = self.headers.copy()
- if additional_headers:
- total_headers.update(additional_headers)
- url = self.url(addurl)
-
- logger.debug("URL: %s", url)
- logger.debug("Headers: %s", total_headers)
-
- try:
- response = self.session.get(url, headers=total_headers, params=params)
- response.raise_for_status()
- return response
-
- except requests.exceptions.HTTPError as err:
- if response.status_code == 429:
- raise GarminConnectTooManyRequestsError("429 Too many requests: {url}") from err
- if response.status_code == 401:
- raise GarminConnectAuthenticationError("401 Authentication error: {url}") from err
- if response.status_code == 403:
- raise GarminConnectConnectionError(f"403 Forbidden error: {url}") from err
- except requests.exceptions.ConnectionError as err:
- raise GarminConnectConnectionError(f"Connection error: {url}") from err
- except requests.exceptions.Timeout as err:
- raise GarminConnectConnectionError(f"Timeout error: {url}") from err
- except requests.exceptions.RequestException as err:
- raise GarminConnectConnectionError(f"Request exception error: {url}") from err
-
-
- def post(self, addurl, additional_headers=None, params=None, data=None, files=None):
- """Make an API call using the POST method."""
- total_headers = self.headers.copy()
- if additional_headers:
- total_headers.update(additional_headers)
- url = self.url(addurl)
-
- logger.debug("URL: %s", url)
- logger.debug("Headers: %s", total_headers)
- logger.debug("Data: %s", data)
-
- try:
- response = self.session.post(
- url, headers=total_headers, params=params, data=data, files=files
- )
- response.raise_for_status()
- return response
-
- except requests.exceptions.HTTPError as err:
- if response.status_code == 429:
- raise GarminConnectTooManyRequestsError("429 Too many requests: {url}") from err
- if response.status_code == 401:
- raise GarminConnectAuthenticationError("401 Authentication error: {url}") from err
- if response.status_code == 403:
- raise GarminConnectConnectionError(f"403 Forbidden error: {url}") from err
- except requests.exceptions.ConnectionError as err:
- raise GarminConnectConnectionError(f"Connection error: {url}") from err
- except requests.exceptions.Timeout as err:
- raise GarminConnectConnectionError(f"Timeout error: {url}") from err
- except requests.exceptions.RequestException as err:
- raise GarminConnectConnectionError(f"Request exception error: {url}") from err
-
-
class Garmin:
"""Class for fetching data from Garmin Connect."""
- def __init__(self, email=None, password=None, is_cn=False, session_data=None):
+ def __init__(self, email=None, password=None, is_cn=False):
"""Create a new class instance."""
- self.session_data = session_data
-
self.username = email
self.password = password
self.is_cn = is_cn
- self.garmin_connect_base_url = "https://connect.garmin.com"
- self.garmin_connect_sso_url = "sso.garmin.com/sso"
- self.garmin_connect_modern_url = "connect.garmin.com/modern"
- self.garmin_connect_css_url = "https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css"
- self.garmin_connect_login_url = self.garmin_connect_base_url + "/en-US/signin"
-
- if self.is_cn:
- self.garmin_connect_base_url = "https://connect.garmin.cn"
- self.garmin_connect_sso_url = "sso.garmin.cn/sso"
- self.garmin_connect_modern_url = "connect.garmin.cn/modern"
- self.garmin_connect_css_url = "https://static.garmincdn.cn/cn.garmin.connect/ui/css/gauth-custom-v1.2-min.css"
- self.garmin_connect_login_url = self.garmin_connect_base_url + "/zh-CN/signin"
-
- self.garmin_connect_sso_login = "signin"
-
self.garmin_connect_devices_url = (
- "proxy/device-service/deviceregistration/devices"
+ "/device-service/deviceregistration/devices"
+ )
+ self.garmin_connect_device_url = "/device-service/deviceservice"
+ self.garmin_connect_weight_url = (
+ "/weight-service/weight/dateRange"
)
- self.garmin_connect_device_url = "proxy/device-service/deviceservice"
- self.garmin_connect_weight_url = "proxy/weight-service/weight/dateRange"
self.garmin_connect_daily_summary_url = (
- "proxy/usersummary-service/usersummary/daily"
+ "/usersummary-service/usersummary/daily"
+ )
+ self.garmin_connect_metrics_url = (
+ "/metrics-service/metrics/maxmet/daily"
)
- self.garmin_connect_metrics_url = "proxy/metrics-service/metrics/maxmet/daily"
self.garmin_connect_daily_hydration_url = (
- "proxy/usersummary-service/usersummary/hydration/daily"
+ "/usersummary-service/usersummary/hydration/daily"
)
self.garmin_connect_daily_stats_steps_url = (
- "proxy/usersummary-service/stats/steps/daily"
+ "/usersummary-service/stats/steps/daily"
)
self.garmin_connect_personal_record_url = (
- "proxy/personalrecord-service/personalrecord/prs"
+ "/personalrecord-service/personalrecord/prs"
+ )
+ self.garmin_connect_earned_badges_url = (
+ "/badge-service/badge/earned"
)
- self.garmin_connect_earned_badges_url = "proxy/badge-service/badge/earned"
self.garmin_connect_adhoc_challenges_url = (
- "proxy/adhocchallenge-service/adHocChallenge/historical"
+ "/adhocchallenge-service/adHocChallenge/historical"
)
self.garmin_connect_badge_challenges_url = (
- "proxy/badgechallenge-service/badgeChallenge/completed"
+ "/badgechallenge-service/badgeChallenge/completed"
)
self.garmin_connect_available_badge_challenges_url = (
- "proxy/badgechallenge-service/badgeChallenge/available"
+ "/badgechallenge-service/badgeChallenge/available"
)
self.garmin_connect_non_completed_badge_challenges_url = (
- "proxy/badgechallenge-service/badgeChallenge/non-completed"
+ "/badgechallenge-service/badgeChallenge/non-completed"
)
self.garmin_connect_daily_sleep_url = (
- "proxy/wellness-service/wellness/dailySleepData"
+ "/wellness-service/wellness/dailySleepData"
)
self.garmin_connect_daily_stress_url = (
- "proxy/wellness-service/wellness/dailyStress"
+ "/wellness-service/wellness/dailyStress"
)
self.garmin_connect_daily_body_battery_url = (
- "proxy/wellness-service/wellness/bodyBattery/reports/daily"
+ "/wellness-service/wellness/bodyBattery/reports/daily"
)
self.garmin_connect_blood_pressure_endpoint = (
- "proxy/bloodpressure-service/bloodpressure/range"
+ "/bloodpressure-service/bloodpressure/range"
)
- self.garmin_connect_goals_url = "proxy/goal-service/goal/goals"
+ self.garmin_connect_goals_url = "/goal-service/goal/goals"
- self.garmin_connect_rhr_url = "proxy/userstats-service/wellness/daily"
+ self.garmin_connect_rhr_url = "/userstats-service/wellness/daily"
- self.garmin_connect_hrv_url = "proxy/hrv-service/hrv"
+ self.garmin_connect_hrv_url = "/hrv-service/hrv"
- self.garmin_connect_training_readiness_url = "proxy/metrics-service/metrics/trainingreadiness"
+ self.garmin_connect_training_readiness_url = (
+ "/metrics-service/metrics/trainingreadiness"
+ )
- self.garmin_connect_training_status_url = "proxy/metrics-service/metrics/trainingstatus/aggregated"
+ self.garmin_connect_training_status_url = (
+ "/metrics-service/metrics/trainingstatus/aggregated"
+ )
self.garmin_connect_user_summary_chart = (
- "proxy/wellness-service/wellness/dailySummaryChart"
+ "/wellness-service/wellness/dailySummaryChart"
)
self.garmin_connect_floors_chart_daily_url = (
- "proxy/wellness-service/wellness/floorsChartData/daily"
+ "/wellness-service/wellness/floorsChartData/daily"
)
self.garmin_connect_heartrates_daily_url = (
- "proxy/wellness-service/wellness/dailyHeartRate"
+ "/wellness-service/wellness/dailyHeartRate"
)
self.garmin_connect_daily_respiration_url = (
- "proxy/wellness-service/wellness/daily/respiration"
+ "/wellness-service/wellness/daily/respiration"
)
self.garmin_connect_daily_spo2_url = (
- "proxy/wellness-service/wellness/daily/spo2"
+ "/wellness-service/wellness/daily/spo2"
)
self.garmin_connect_activities = (
- "proxy/activitylist-service/activities/search/activities"
+ "/activitylist-service/activities/search/activities"
+ )
+ self.garmin_connect_activity = "/activity-service/activity"
+ self.garmin_connect_activity_types = (
+ "/activity-service/activity/activityTypes"
)
- self.garmin_connect_activity = "proxy/activity-service/activity"
- self.garmin_connect_activity_types = "proxy/activity-service/activity/activityTypes"
- self.garmin_connect_fitnessstats = "proxy/fitnessstats-service/activity"
+ self.garmin_connect_fitnessstats = (
+ "/fitnessstats-service/activity"
+ )
- self.garmin_connect_fit_download = "proxy/download-service/files/activity"
- self.garmin_connect_tcx_download = "proxy/download-service/export/tcx/activity"
- self.garmin_connect_gpx_download = "proxy/download-service/export/gpx/activity"
- self.garmin_connect_kml_download = "proxy/download-service/export/kml/activity"
- self.garmin_connect_csv_download = "proxy/download-service/export/csv/activity"
+ self.garmin_connect_fit_download = (
+ "/download-service/files/activity"
+ )
+ self.garmin_connect_tcx_download = (
+ "/download-service/export/tcx/activity"
+ )
+ self.garmin_connect_gpx_download = (
+ "/download-service/export/gpx/activity"
+ )
+ self.garmin_connect_kml_download = (
+ "/download-service/export/kml/activity"
+ )
+ self.garmin_connect_csv_download = (
+ "/download-service/export/csv/activity"
+ )
- self.garmin_connect_upload = "proxy/upload-service/upload"
+ self.garmin_connect_upload = "/upload-service/upload"
- self.garmin_connect_gear = "proxy/gear-service/gear/filterGear"
- self.garmin_connect_gear_baseurl = "proxy/gear-service/gear/"
+ self.garmin_connect_gear = "/gear-service/gear/filterGear"
+ self.garmin_connect_gear_baseurl = "/gear-service/gear/"
self.garmin_connect_logout = "auth/logout/?url="
- self.garmin_headers = {"NK": "NT"}
-
- self.session = cloudscraper.CloudScraper()
- self.sso_rest_client = ApiClient(
- self.session,
- self.garmin_connect_sso_url,
- additional_headers=self.garmin_headers,
- )
- self.modern_rest_client = ApiClient(
- self.session,
- self.garmin_connect_modern_url,
- additional_headers=self.garmin_headers,
+ self.garth = garth.Client(
+ domain="garmin.cn" if is_cn else "garmin.com"
)
self.display_name = None
self.full_name = None
self.unit_system = None
- @staticmethod
- def __get_json(page_html, key):
- """Return json from text."""
-
- found = re.search(key + r" = (\{.*\});", page_html, re.M)
- if found:
- json_text = found.group(1).replace('\\"', '"')
- return json.loads(json_text)
-
- return None
-
- def login(self):
- if self.session_data is None:
- return self.authenticate()
- else:
- return self.login_session()
-
- def login_session(self):
- logger.debug("Login with cookies")
-
- session_display_name = self.session_data["display_name"]
- logger.debug("Set cookies in session")
- self.modern_rest_client.set_cookies(
- requests.utils.cookiejar_from_dict(self.session_data["session_cookies"])
- )
- self.sso_rest_client.set_cookies(
- requests.utils.cookiejar_from_dict(self.session_data["login_cookies"])
- )
-
- logger.debug("Get page data with cookies")
- params = {
- "service": "https://connect.garmin.com/modern/",
- "webhost": "https://connect.garmin.com",
- "gateway": "true",
- "generateExtraServiceTicket": "true",
- "generateTwoExtraServiceTickets": "true",
- }
- response = self.sso_rest_client.get("login", params=params)
- logger.debug("Session response %s", response.status_code)
- if response.status_code != 200:
- logger.debug("Session expired, authenticating again!")
- return self.authenticate()
-
- user_prefs = self.__get_json(response.text, "VIEWER_USERPREFERENCES")
- if user_prefs is None:
- logger.debug("Session expired, authenticating again!")
- return self.authenticate()
-
- self.display_name = user_prefs["displayName"]
- logger.debug("Display name is %s", self.display_name)
+ def connectapi(self, path, **kwargs):
+ return self.garth.connectapi(path, **kwargs)
+
+ def download(self, path, **kwargs):
+ return self.garth.download(path, **kwargs)
- self.unit_system = user_prefs["measurementSystem"]
- logger.debug("Unit system is %s", self.unit_system)
+ def login(self, /, garth_home: Optional[str] = None):
+ """Log in using Garth"""
+ garth_home = garth_home or os.getenv("GARTH_HOME")
- social_profile = self.__get_json(response.text, "VIEWER_SOCIAL_PROFILE")
- self.full_name = social_profile["fullName"]
- logger.debug("Fullname is %s", self.full_name)
-
- if self.display_name == session_display_name:
- return True
+ if garth_home:
+ self.garth.load(garth_home)
else:
- logger.debug("Session not valid for user %s", self.display_name)
- return self.authenticate()
-
- def authenticate(self):
- """Login to Garmin Connect."""
-
- logger.debug("login: %s %s", self.username, self.password)
- self.modern_rest_client.clear_cookies()
- self.sso_rest_client.clear_cookies()
-
- get_headers = {"Referer": self.garmin_connect_login_url}
- params = {
- "service": self.modern_rest_client.url(),
- "webhost": self.garmin_connect_base_url,
- "source": self.garmin_connect_login_url,
- "redirectAfterAccountLoginUrl": self.modern_rest_client.url(),
- "redirectAfterAccountCreationUrl": self.modern_rest_client.url(),
- "gauthHost": self.sso_rest_client.url(),
- "locale": "en_US",
- "id": "gauth-widget",
- "cssUrl": self.garmin_connect_css_url,
- "privacyStatementUrl": "//connect.garmin.com/en-US/privacy/",
- "clientId": "GarminConnect",
- "rememberMeShown": "true",
- "rememberMeChecked": "false",
- "createAccountShown": "true",
- "openCreateAccount": "false",
- "displayNameShown": "false",
- "consumeServiceTicket": "false",
- "initialFocus": "true",
- "embedWidget": "false",
- "generateExtraServiceTicket": "true",
- "generateTwoExtraServiceTickets": "false",
- "generateNoServiceTicket": "false",
- "globalOptInShown": "true",
- "globalOptInChecked": "false",
- "mobile": "false",
- "connectLegalTerms": "true",
- "locationPromptShown": "true",
- "showPassword": "true",
- }
+ self.garth.login(self.username, self.password)
- if self.is_cn:
- params[
- "cssUrl"
- ] = "https://static.garmincdn.cn/cn.garmin.connect/ui/css/gauth-custom-v1.2-min.css"
+ self.display_name = self.garth.profile["displayName"]
+ self.full_name = self.garth.profile["fullName"]
- response = self.sso_rest_client.get(
- self.garmin_connect_sso_login, get_headers, params
+ settings = self.garth.connectapi(
+ "/userprofile-service/userprofile/user-settings"
)
-
- found = re.search(r"name=\"_csrf\" value=\"(\w*)", response.text, re.M)
- if not found:
- logger.error("_csrf not found (%d)", response.status_code)
- return False
-
- csrf = found.group(1)
- referer = response.url
- logger.debug("_csrf found: %s", csrf)
- logger.debug("Referer: %s", referer)
-
- data = {
- "username": self.username,
- "password": self.password,
- "embed": "false",
- "_csrf": csrf,
- }
- post_headers = {
- "Referer": referer,
- "Content-Type": "application/x-www-form-urlencoded",
- }
-
- response = self.sso_rest_client.post(
- self.garmin_connect_sso_login, post_headers, params, data
- )
-
- found = re.search(r"\?ticket=([\w-]*)", response.text, re.M)
- if not found:
- logger.error("Login ticket not found (%d).", response.status_code)
- return False
- params = {"ticket": found.group(1)}
-
- response = self.modern_rest_client.get("", params=params)
-
- user_prefs = self.__get_json(response.text, "VIEWER_USERPREFERENCES")
- self.display_name = user_prefs["displayName"]
- logger.debug("Display name is %s", self.display_name)
-
- self.unit_system = user_prefs["measurementSystem"]
- logger.debug("Unit system is %s", self.unit_system)
-
- social_profile = self.__get_json(response.text, "VIEWER_SOCIAL_PROFILE")
- self.full_name = social_profile["fullName"]
- logger.debug("Fullname is %s", self.full_name)
-
- self.session_data = {
- "display_name": self.display_name,
- "session_cookies": requests.utils.dict_from_cookiejar(
- self.modern_rest_client.get_cookies()
- ),
- "login_cookies": requests.utils.dict_from_cookiejar(
- self.sso_rest_client.get_cookies()
- ),
- }
-
- logger.debug("Cookies saved")
+ self.unit_system = settings["userData"]["measurementSystem"]
return True
@@ -435,7 +179,10 @@ def get_unit_system(self):
return self.unit_system
def get_stats(self, cdate: str) -> Dict[str, Any]:
- """Return user activity summary for 'cdate' format 'YYYY-MM-DD' (compat for garminconnect)."""
+ """
+ Return user activity summary for 'cdate' format 'YYYY-MM-DD'
+ (compat for garminconnect).
+ """
return self.get_user_summary(cdate)
@@ -443,12 +190,10 @@ def get_user_summary(self, cdate: str) -> Dict[str, Any]:
"""Return user activity summary for 'cdate' format 'YYYY-MM-DD'."""
url = f"{self.garmin_connect_daily_summary_url}/{self.display_name}"
- params = {
- "calendarDate": str(cdate)
- }
+ params = {"calendarDate": str(cdate)}
logger.debug("Requesting user summary")
- response = self.modern_rest_client.get(url, params=params).json()
+ response = self.connectapi(url, params=params)
if response["privacyProtected"] is True:
raise GarminConnectAuthenticationError("Authentication error")
@@ -459,39 +204,35 @@ def get_steps_data(self, cdate):
"""Fetch available steps data 'cDate' format 'YYYY-MM-DD'."""
url = f"{self.garmin_connect_user_summary_chart}/{self.display_name}"
- params = {
- "date": str(cdate)
- }
+ params = {"date": str(cdate)}
logger.debug("Requesting steps data")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_floors(self, cdate):
"""Fetch available floors data 'cDate' format 'YYYY-MM-DD'."""
- url = f'{self.garmin_connect_floors_chart_daily_url}/{cdate}'
+ url = f"{self.garmin_connect_floors_chart_daily_url}/{cdate}"
logger.debug("Requesting floors data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_daily_steps(self, start, end):
"""Fetch available steps data 'start' and 'end' format 'YYYY-MM-DD'."""
- url = f'{self.garmin_connect_daily_stats_steps_url}/{start}/{end}'
+ url = f"{self.garmin_connect_daily_stats_steps_url}/{start}/{end}"
logger.debug("Requesting daily steps data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_heart_rates(self, cdate):
"""Fetch available heart rates data 'cDate' format 'YYYY-MM-DD'."""
url = f"{self.garmin_connect_heartrates_daily_url}/{self.display_name}"
- params = {
- "date": str(cdate)
- }
+ params = {"date": str(cdate)}
logger.debug("Requesting heart rates")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_stats_and_body(self, cdate):
"""Return activity data and body composition (compat for garminconnect)."""
@@ -501,8 +242,13 @@ def get_stats_and_body(self, cdate):
**self.get_body_composition(cdate)["totalAverage"],
}
- def get_body_composition(self, startdate: str, enddate=None) -> Dict[str, Any]:
- """Return available body composition data for 'startdate' format 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'."""
+ def get_body_composition(
+ self, startdate: str, enddate=None
+ ) -> Dict[str, Any]:
+ """
+ Return available body composition data for 'startdate' format
+ 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'.
+ """
if enddate is None:
enddate = startdate
@@ -510,10 +256,15 @@ def get_body_composition(self, startdate: str, enddate=None) -> Dict[str, Any]:
params = {"startDate": str(startdate), "endDate": str(enddate)}
logger.debug("Requesting body composition")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
- def get_body_battery(self, startdate: str, enddate=None) -> List[Dict[str, Any]]:
- """Return body battery values by day for 'startdate' format 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'"""
+ def get_body_battery(
+ self, startdate: str, enddate=None
+ ) -> List[Dict[str, Any]]:
+ """
+ Return body battery values by day for 'startdate' format
+ 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'
+ """
if enddate is None:
enddate = startdate
@@ -521,10 +272,15 @@ def get_body_battery(self, startdate: str, enddate=None) -> List[Dict[str, Any]]
params = {"startDate": str(startdate), "endDate": str(enddate)}
logger.debug("Requesting body battery data")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
- def get_blood_pressure(self, startdate: str, enddate=None) -> Dict[str, Any]:
- """Returns blood pressure by day for 'startdate' format 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'"""
+ def get_blood_pressure(
+ self, startdate: str, enddate=None
+ ) -> Dict[str, Any]:
+ """
+ Returns blood pressure by day for 'startdate' format
+ 'YYYY-MM-DD' through enddate 'YYYY-MM-DD'
+ """
if enddate is None:
enddate = startdate
@@ -532,7 +288,7 @@ def get_blood_pressure(self, startdate: str, enddate=None) -> Dict[str, Any]:
params = {"includeAll": True}
logger.debug("Requesting blood pressure data")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_max_metrics(self, cdate: str) -> Dict[str, Any]:
"""Return available max metric data for 'cdate' format 'YYYY-MM-DD'."""
@@ -540,7 +296,7 @@ def get_max_metrics(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_metrics_url}/{cdate}/{cdate}"
logger.debug("Requesting max metrics")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_hydration_data(self, cdate: str) -> Dict[str, Any]:
"""Return available hydration data 'cdate' format 'YYYY-MM-DD'."""
@@ -548,7 +304,7 @@ def get_hydration_data(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_daily_hydration_url}/{cdate}"
logger.debug("Requesting hydration data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_respiration_data(self, cdate: str) -> Dict[str, Any]:
"""Return available respiration data 'cdate' format 'YYYY-MM-DD'."""
@@ -556,7 +312,7 @@ def get_respiration_data(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_daily_respiration_url}/{cdate}"
logger.debug("Requesting respiration data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_spo2_data(self, cdate: str) -> Dict[str, Any]:
"""Return available SpO2 data 'cdate' format 'YYYY-MM-DD'."""
@@ -564,7 +320,7 @@ def get_spo2_data(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_daily_spo2_url}/{cdate}"
logger.debug("Requesting SpO2 data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_personal_record(self) -> Dict[str, Any]:
"""Return personal records for current user."""
@@ -572,7 +328,7 @@ def get_personal_record(self) -> Dict[str, Any]:
url = f"{self.garmin_connect_personal_record_url}/{self.display_name}"
logger.debug("Requesting personal records for user")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_earned_badges(self) -> Dict[str, Any]:
"""Return earned badges for current user."""
@@ -580,7 +336,7 @@ def get_earned_badges(self) -> Dict[str, Any]:
url = self.garmin_connect_earned_badges_url
logger.debug("Requesting earned badges for user")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_adhoc_challenges(self, start, limit) -> Dict[str, Any]:
"""Return adhoc challenges for current user."""
@@ -589,7 +345,7 @@ def get_adhoc_challenges(self, start, limit) -> Dict[str, Any]:
params = {"start": str(start), "limit": str(limit)}
logger.debug("Requesting adhoc challenges for user")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_badge_challenges(self, start, limit) -> Dict[str, Any]:
"""Return badge challenges for current user."""
@@ -598,7 +354,7 @@ def get_badge_challenges(self, start, limit) -> Dict[str, Any]:
params = {"start": str(start), "limit": str(limit)}
logger.debug("Requesting badge challenges for user")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_available_badge_challenges(self, start, limit) -> Dict[str, Any]:
"""Return available badge challenges."""
@@ -607,16 +363,18 @@ def get_available_badge_challenges(self, start, limit) -> Dict[str, Any]:
params = {"start": str(start), "limit": str(limit)}
logger.debug("Requesting available badge challenges")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
- def get_non_completed_badge_challenges(self, start, limit) -> Dict[str, Any]:
+ def get_non_completed_badge_challenges(
+ self, start, limit
+ ) -> Dict[str, Any]:
"""Return badge non-completed challenges for current user."""
url = self.garmin_connect_non_completed_badge_challenges_url
params = {"start": str(start), "limit": str(limit)}
logger.debug("Requesting badge challenges for user")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_sleep_data(self, cdate: str) -> Dict[str, Any]:
"""Return sleep data for current user."""
@@ -625,7 +383,7 @@ def get_sleep_data(self, cdate: str) -> Dict[str, Any]:
params = {"date": str(cdate), "nonSleepBufferMinutes": 60}
logger.debug("Requesting sleep data")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_stress_data(self, cdate: str) -> Dict[str, Any]:
"""Return stress data for current user."""
@@ -633,16 +391,20 @@ def get_stress_data(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_daily_stress_url}/{cdate}"
logger.debug("Requesting stress data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_rhr_day(self, cdate: str) -> Dict[str, Any]:
"""Return resting heartrate data for current user."""
url = f"{self.garmin_connect_rhr_url}/{self.display_name}"
- params = {"fromDate": str(cdate), "untilDate": str(cdate), "metricId": 60}
+ params = {
+ "fromDate": str(cdate),
+ "untilDate": str(cdate),
+ "metricId": 60,
+ }
logger.debug("Requesting resting heartrate data")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_hrv_data(self, cdate: str) -> Dict[str, Any]:
"""Return Heart Rate Variability (hrv) data for current user."""
@@ -650,7 +412,7 @@ def get_hrv_data(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_hrv_url}/{cdate}"
logger.debug("Requesting Heart Rate Variability (hrv) data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_training_readiness(self, cdate: str) -> Dict[str, Any]:
"""Return training readiness data for current user."""
@@ -658,7 +420,7 @@ def get_training_readiness(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_training_readiness_url}/{cdate}"
logger.debug("Requesting training readiness data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_training_status(self, cdate: str) -> Dict[str, Any]:
"""Return training status data for current user."""
@@ -666,7 +428,7 @@ def get_training_status(self, cdate: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_training_status_url}/{cdate}"
logger.debug("Requesting training status data")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_devices(self) -> Dict[str, Any]:
"""Return available devices for the current user account."""
@@ -674,7 +436,7 @@ def get_devices(self) -> Dict[str, Any]:
url = self.garmin_connect_devices_url
logger.debug("Requesting devices")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_device_settings(self, device_id: str) -> Dict[str, Any]:
"""Return device settings for device with 'device_id'."""
@@ -682,7 +444,7 @@ def get_device_settings(self, device_id: str) -> Dict[str, Any]:
url = f"{self.garmin_connect_device_url}/device-info/settings/{device_id}"
logger.debug("Requesting device settings")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_device_alarms(self) -> Dict[str, Any]:
"""Get list of active alarms from all devices."""
@@ -704,7 +466,7 @@ def get_device_last_used(self):
url = f"{self.garmin_connect_device_url}/mylastused"
logger.debug("Requesting device last used")
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activities(self, start, limit):
"""Return available activities."""
@@ -713,7 +475,7 @@ def get_activities(self, start, limit):
params = {"start": str(start), "limit": str(limit)}
logger.debug("Requesting activities")
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_last_activity(self):
"""Return last activity."""
@@ -730,16 +492,20 @@ def upload_activity(self, activity_path: str):
file_base_name = os.path.basename(activity_path)
file_extension = file_base_name.split(".")[-1]
- allowed_file_extension = file_extension.upper() in Garmin.ActivityUploadFormat.__members__
+ allowed_file_extension = (
+ file_extension.upper() in Garmin.ActivityUploadFormat.__members__
+ )
if allowed_file_extension:
files = {
"file": (file_base_name, open(activity_path, "rb" or "r")),
}
url = self.garmin_connect_upload
- return self.modern_rest_client.post(url, files=files).json()
+ return self.garth.post("connectapi", url, files=files)
else:
- raise GarminConnectInvalidFileFormatError(f"Could not upload {activity_path}")
+ raise GarminConnectInvalidFileFormatError(
+ f"Could not upload {activity_path}"
+ )
def get_activities_by_date(self, startdate, enddate, activitytype=None):
"""
@@ -755,7 +521,8 @@ def get_activities_by_date(self, startdate, enddate, activitytype=None):
activities = []
start = 0
limit = 20
- # mimicking the behavior of the web interface that fetches 20 activities at a time
+ # mimicking the behavior of the web interface that fetches
+ # 20 activities at a time
# and automatically loads more on scroll
url = self.garmin_connect_activities
params = {
@@ -767,11 +534,13 @@ def get_activities_by_date(self, startdate, enddate, activitytype=None):
if activitytype:
params["activityType"] = str(activitytype)
- logger.debug(f"Requesting activities by date from {startdate} to {enddate}")
+ logger.debug(
+ f"Requesting activities by date from {startdate} to {enddate}"
+ )
while True:
params["start"] = str(start)
logger.debug(f"Requesting activities {start} to {start+limit}")
- act = self.modern_rest_client.get(url, params=params).json()
+ act = self.connectapi(url, params=params)
if act:
activities.extend(act)
start = start + limit
@@ -780,7 +549,9 @@ def get_activities_by_date(self, startdate, enddate, activitytype=None):
return activities
- def get_progress_summary_between_dates(self, startdate, enddate, metric="distance"):
+ def get_progress_summary_between_dates(
+ self, startdate, enddate, metric="distance"
+ ):
"""
Fetch progress summary data between specific dates
:param startdate: String in the format YYYY-MM-DD
@@ -796,18 +567,18 @@ def get_progress_summary_between_dates(self, startdate, enddate, metric="distanc
"endDate": str(enddate),
"aggregation": "lifetime",
"groupByParentActivityType": "true",
- "metric": str(metric)
-
+ "metric": str(metric),
}
logger.debug(
- f"Requesting fitnessstats by date from {startdate} to {enddate}")
- return self.modern_rest_client.get(url, params=params).json()
+ f"Requesting fitnessstats by date from {startdate} to {enddate}"
+ )
+ return self.connectapi(url, params=params)
def get_activity_types(self):
url = self.garmin_connect_activity_types
- logger.debug(f"Requesting activy types")
- return self.modern_rest_client.get(url).json()
+ logger.debug("Requesting activy types")
+ return self.connectapi(url)
def get_goals(self, status="active", start=1, limit=30):
"""
@@ -827,14 +598,16 @@ def get_goals(self, status="active", start=1, limit=30):
"status": status,
"start": str(start),
"limit": str(limit),
- "sortOrder": "asc"
+ "sortOrder": "asc",
}
logger.debug(f"Requesting {status} goals")
while True:
params["start"] = str(start)
- logger.debug(f"Requesting {status} goals {start} to {start + limit - 1}")
- goals_json = self.modern_rest_client.get(url, params=params).json()
+ logger.debug(
+ f"Requesting {status} goals {start} to {start + limit - 1}"
+ )
+ goals_json = self.connectapi(url, params=params)
if goals_json:
goals.extend(goals_json)
start = start + limit
@@ -848,24 +621,32 @@ def get_gear(self, userProfileNumber):
url = f"{self.garmin_connect_gear}?userProfilePk={userProfileNumber}"
logger.debug("Requesting gear for user %s", userProfileNumber)
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_gear_stats(self, gearUUID):
url = f"{self.garmin_connect_gear_baseurl}stats/{gearUUID}"
logger.debug("Requesting gear stats for gearUUID %s", gearUUID)
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_gear_defaults(self, userProfileNumber):
- url = f"{self.garmin_connect_gear_baseurl}user/{userProfileNumber}/activityTypes"
+ url = (
+ f"{self.garmin_connect_gear_baseurl}user/"
+ f"{userProfileNumber}/activityTypes"
+ )
logger.debug("Requesting gear for user %s", userProfileNumber)
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def set_gear_default(self, activityType, gearUUID, defaultGear=True):
defaultGearString = "/default/true" if defaultGear else ""
method_override = "PUT" if defaultGear else "DELETE"
- url = f"{self.garmin_connect_gear_baseurl}{gearUUID}/activityType/{activityType}{defaultGearString}"
- return self.modern_rest_client.post(
- url, {"x-http-method-override": method_override}
+ url = (
+ f"{self.garmin_connect_gear_baseurl}{gearUUID}/"
+ f"activityType/{activityType}{defaultGearString}"
+ )
+ return self.garth.post(
+ "connectapi",
+ url,
+ {"x-http-method-override": method_override}
)
class ActivityDownloadFormat(Enum):
@@ -882,7 +663,9 @@ class ActivityUploadFormat(Enum):
GPX = auto()
TCX = auto()
- def download_activity(self, activity_id, dl_fmt=ActivityDownloadFormat.TCX):
+ def download_activity(
+ self, activity_id, dl_fmt=ActivityDownloadFormat.TCX
+ ):
"""
Downloads activity in requested format and returns the raw bytes. For
"Original" will return the zip file content, up to user to extract it.
@@ -902,7 +685,7 @@ def download_activity(self, activity_id, dl_fmt=ActivityDownloadFormat.TCX):
logger.debug("Downloading activities from %s", url)
- return self.modern_rest_client.get(url).content
+ return self.download(url)
def get_activity_splits(self, activity_id):
"""Return activity splits."""
@@ -911,16 +694,18 @@ def get_activity_splits(self, activity_id):
url = f"{self.garmin_connect_activity}/{activity_id}/splits"
logger.debug("Requesting splits for activity id %s", activity_id)
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_split_summaries(self, activity_id):
"""Return activity split summaries."""
activity_id = str(activity_id)
url = f"{self.garmin_connect_activity}/{activity_id}/split_summaries"
- logger.debug("Requesting split summaries for activity id %s", activity_id)
+ logger.debug(
+ "Requesting split summaries for activity id %s", activity_id
+ )
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_weather(self, activity_id):
"""Return activity weather."""
@@ -929,25 +714,29 @@ def get_activity_weather(self, activity_id):
url = f"{self.garmin_connect_activity}/{activity_id}/weather"
logger.debug("Requesting weather for activity id %s", activity_id)
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_hr_in_timezones(self, activity_id):
"""Return activity heartrate in timezones."""
activity_id = str(activity_id)
url = f"{self.garmin_connect_activity}/{activity_id}/hrTimeInZones"
- logger.debug("Requesting split summaries for activity id %s", activity_id)
+ logger.debug(
+ "Requesting split summaries for activity id %s", activity_id
+ )
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_evaluation(self, activity_id):
"""Return activity self evaluation details."""
activity_id = str(activity_id)
url = f"{self.garmin_connect_activity}/{activity_id}"
- logger.debug("Requesting self evaluation data for activity id %s", activity_id)
+ logger.debug(
+ "Requesting self evaluation data for activity id %s", activity_id
+ )
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_details(self, activity_id, maxchart=2000, maxpoly=4000):
"""Return activity details."""
@@ -960,16 +749,18 @@ def get_activity_details(self, activity_id, maxchart=2000, maxpoly=4000):
url = f"{self.garmin_connect_activity}/{activity_id}/details"
logger.debug("Requesting details for activity id %s", activity_id)
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def get_activity_exercise_sets(self, activity_id):
- """Return activity exercise sets."""
+ """Return activity exercise sets."""
- activity_id = str(activity_id)
- url = f"{self.garmin_connect_activity}/{activity_id}/exerciseSets"
- logger.debug("Requesting exercise sets for activity id %s", activity_id)
+ activity_id = str(activity_id)
+ url = f"{self.garmin_connect_activity}/{activity_id}/exerciseSets"
+ logger.debug(
+ "Requesting exercise sets for activity id %s", activity_id
+ )
- return self.modern_rest_client.get(url).json()
+ return self.connectapi(url)
def get_activity_gear(self, activity_id):
"""Return gears used for activity id."""
@@ -981,12 +772,12 @@ def get_activity_gear(self, activity_id):
url = self.garmin_connect_gear
logger.debug("Requesting gear for activity_id %s", activity_id)
- return self.modern_rest_client.get(url, params=params).json()
+ return self.connectapi(url, params=params)
def logout(self):
"""Log user out of session."""
- self.modern_rest_client.get(self.garmin_connect_logout)
+ self.connectapi(self.garmin_connect_logout)
class GarminConnectConnectionError(Exception):
diff --git a/reference.ipynb b/reference.ipynb
new file mode 100644
index 0000000..593dbe6
--- /dev/null
+++ b/reference.ipynb
@@ -0,0 +1,500 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Garth Migration"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import garminconnect"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Login"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Request email and password. If MFA is enabled, Garth will request it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'mtamizi'"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from getpass import getpass\n",
+ "\n",
+ "email = input(\"Enter email address: \")\n",
+ "password = getpass(\"Enter password: \")\n",
+ "\n",
+ "garmin = garminconnect.Garmin(email, password)\n",
+ "garmin.login()\n",
+ "\n",
+ "garmin.display_name"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Save session"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "GARTH_HOME = os.getenv(\"GARTH_HOME\", \"~/.garth\")\n",
+ "garmin.garth.dump(GARTH_HOME)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Get Connect stats"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'2023-08-05'"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from datetime import date, timedelta\n",
+ "\n",
+ "yesterday = date.today() - timedelta(days=1)\n",
+ "yesterday = yesterday.isoformat()\n",
+ "yesterday"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfileId', 'totalKilocalories', 'activeKilocalories', 'bmrKilocalories', 'wellnessKilocalories', 'burnedKilocalories', 'consumedKilocalories', 'remainingKilocalories', 'totalSteps', 'netCalorieGoal', 'totalDistanceMeters', 'wellnessDistanceMeters', 'wellnessActiveKilocalories', 'netRemainingKilocalories', 'userDailySummaryId', 'calendarDate', 'rule', 'uuid', 'dailyStepGoal', 'totalPushDistance', 'totalPushes', 'wellnessStartTimeGmt', 'wellnessStartTimeLocal', 'wellnessEndTimeGmt', 'wellnessEndTimeLocal', 'durationInMilliseconds', 'wellnessDescription', 'highlyActiveSeconds', 'activeSeconds', 'sedentarySeconds', 'sleepingSeconds', 'includesWellnessData', 'includesActivityData', 'includesCalorieConsumedData', 'privacyProtected', 'moderateIntensityMinutes', 'vigorousIntensityMinutes', 'floorsAscendedInMeters', 'floorsDescendedInMeters', 'floorsAscended', 'floorsDescended', 'intensityMinutesGoal', 'userFloorsAscendedGoal', 'minHeartRate', 'maxHeartRate', 'restingHeartRate', 'lastSevenDaysAvgRestingHeartRate', 'source', 'averageStressLevel', 'maxStressLevel', 'stressDuration', 'restStressDuration', 'activityStressDuration', 'uncategorizedStressDuration', 'totalStressDuration', 'lowStressDuration', 'mediumStressDuration', 'highStressDuration', 'stressPercentage', 'restStressPercentage', 'activityStressPercentage', 'uncategorizedStressPercentage', 'lowStressPercentage', 'mediumStressPercentage', 'highStressPercentage', 'stressQualifier', 'measurableAwakeDuration', 'measurableAsleepDuration', 'lastSyncTimestampGMT', 'minAvgHeartRate', 'maxAvgHeartRate', 'bodyBatteryChargedValue', 'bodyBatteryDrainedValue', 'bodyBatteryHighestValue', 'bodyBatteryLowestValue', 'bodyBatteryMostRecentValue', 'bodyBatteryVersion', 'abnormalHeartRateAlertsCount', 'averageSpo2', 'lowestSpo2', 'latestSpo2', 'latestSpo2ReadingTimeGmt', 'latestSpo2ReadingTimeLocal', 'averageMonitoringEnvironmentAltitude', 'restingCaloriesFromActivity', 'avgWakingRespirationValue', 'highestRespirationValue', 'lowestRespirationValue', 'latestRespirationValue', 'latestRespirationTimeGMT'])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_stats(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfileId', 'totalKilocalories', 'activeKilocalories', 'bmrKilocalories', 'wellnessKilocalories', 'burnedKilocalories', 'consumedKilocalories', 'remainingKilocalories', 'totalSteps', 'netCalorieGoal', 'totalDistanceMeters', 'wellnessDistanceMeters', 'wellnessActiveKilocalories', 'netRemainingKilocalories', 'userDailySummaryId', 'calendarDate', 'rule', 'uuid', 'dailyStepGoal', 'totalPushDistance', 'totalPushes', 'wellnessStartTimeGmt', 'wellnessStartTimeLocal', 'wellnessEndTimeGmt', 'wellnessEndTimeLocal', 'durationInMilliseconds', 'wellnessDescription', 'highlyActiveSeconds', 'activeSeconds', 'sedentarySeconds', 'sleepingSeconds', 'includesWellnessData', 'includesActivityData', 'includesCalorieConsumedData', 'privacyProtected', 'moderateIntensityMinutes', 'vigorousIntensityMinutes', 'floorsAscendedInMeters', 'floorsDescendedInMeters', 'floorsAscended', 'floorsDescended', 'intensityMinutesGoal', 'userFloorsAscendedGoal', 'minHeartRate', 'maxHeartRate', 'restingHeartRate', 'lastSevenDaysAvgRestingHeartRate', 'source', 'averageStressLevel', 'maxStressLevel', 'stressDuration', 'restStressDuration', 'activityStressDuration', 'uncategorizedStressDuration', 'totalStressDuration', 'lowStressDuration', 'mediumStressDuration', 'highStressDuration', 'stressPercentage', 'restStressPercentage', 'activityStressPercentage', 'uncategorizedStressPercentage', 'lowStressPercentage', 'mediumStressPercentage', 'highStressPercentage', 'stressQualifier', 'measurableAwakeDuration', 'measurableAsleepDuration', 'lastSyncTimestampGMT', 'minAvgHeartRate', 'maxAvgHeartRate', 'bodyBatteryChargedValue', 'bodyBatteryDrainedValue', 'bodyBatteryHighestValue', 'bodyBatteryLowestValue', 'bodyBatteryMostRecentValue', 'bodyBatteryVersion', 'abnormalHeartRateAlertsCount', 'averageSpo2', 'lowestSpo2', 'latestSpo2', 'latestSpo2ReadingTimeGmt', 'latestSpo2ReadingTimeLocal', 'averageMonitoringEnvironmentAltitude', 'restingCaloriesFromActivity', 'avgWakingRespirationValue', 'highestRespirationValue', 'lowestRespirationValue', 'latestRespirationValue', 'latestRespirationTimeGMT'])"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_user_summary(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'startGMT': '2023-08-05T06:00:00.0',\n",
+ " 'endGMT': '2023-08-05T06:15:00.0',\n",
+ " 'steps': 0,\n",
+ " 'pushes': 0,\n",
+ " 'primaryActivityLevel': 'sedentary',\n",
+ " 'activityLevelConstant': True},\n",
+ " {'startGMT': '2023-08-05T06:15:00.0',\n",
+ " 'endGMT': '2023-08-05T06:30:00.0',\n",
+ " 'steps': 0,\n",
+ " 'pushes': 0,\n",
+ " 'primaryActivityLevel': 'sleeping',\n",
+ " 'activityLevelConstant': False}]"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_steps_data(yesterday)[:2]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['startTimestampGMT', 'endTimestampGMT', 'startTimestampLocal', 'endTimestampLocal', 'floorsValueDescriptorDTOList', 'floorValuesArray'])"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_floors(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'calendarDate': '2023-08-05',\n",
+ " 'totalSteps': 17945,\n",
+ " 'totalDistance': 14352,\n",
+ " 'stepGoal': 8560}]"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_daily_steps(yesterday, yesterday)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfilePK', 'calendarDate', 'startTimestampGMT', 'endTimestampGMT', 'startTimestampLocal', 'endTimestampLocal', 'maxHeartRate', 'minHeartRate', 'restingHeartRate', 'lastSevenDaysAvgRestingHeartRate', 'heartRateValueDescriptors', 'heartRateValues'])"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_heart_rates(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfileId', 'totalKilocalories', 'activeKilocalories', 'bmrKilocalories', 'wellnessKilocalories', 'burnedKilocalories', 'consumedKilocalories', 'remainingKilocalories', 'totalSteps', 'netCalorieGoal', 'totalDistanceMeters', 'wellnessDistanceMeters', 'wellnessActiveKilocalories', 'netRemainingKilocalories', 'userDailySummaryId', 'calendarDate', 'rule', 'uuid', 'dailyStepGoal', 'totalPushDistance', 'totalPushes', 'wellnessStartTimeGmt', 'wellnessStartTimeLocal', 'wellnessEndTimeGmt', 'wellnessEndTimeLocal', 'durationInMilliseconds', 'wellnessDescription', 'highlyActiveSeconds', 'activeSeconds', 'sedentarySeconds', 'sleepingSeconds', 'includesWellnessData', 'includesActivityData', 'includesCalorieConsumedData', 'privacyProtected', 'moderateIntensityMinutes', 'vigorousIntensityMinutes', 'floorsAscendedInMeters', 'floorsDescendedInMeters', 'floorsAscended', 'floorsDescended', 'intensityMinutesGoal', 'userFloorsAscendedGoal', 'minHeartRate', 'maxHeartRate', 'restingHeartRate', 'lastSevenDaysAvgRestingHeartRate', 'source', 'averageStressLevel', 'maxStressLevel', 'stressDuration', 'restStressDuration', 'activityStressDuration', 'uncategorizedStressDuration', 'totalStressDuration', 'lowStressDuration', 'mediumStressDuration', 'highStressDuration', 'stressPercentage', 'restStressPercentage', 'activityStressPercentage', 'uncategorizedStressPercentage', 'lowStressPercentage', 'mediumStressPercentage', 'highStressPercentage', 'stressQualifier', 'measurableAwakeDuration', 'measurableAsleepDuration', 'lastSyncTimestampGMT', 'minAvgHeartRate', 'maxAvgHeartRate', 'bodyBatteryChargedValue', 'bodyBatteryDrainedValue', 'bodyBatteryHighestValue', 'bodyBatteryLowestValue', 'bodyBatteryMostRecentValue', 'bodyBatteryVersion', 'abnormalHeartRateAlertsCount', 'averageSpo2', 'lowestSpo2', 'latestSpo2', 'latestSpo2ReadingTimeGmt', 'latestSpo2ReadingTimeLocal', 'averageMonitoringEnvironmentAltitude', 'restingCaloriesFromActivity', 'avgWakingRespirationValue', 'highestRespirationValue', 'lowestRespirationValue', 'latestRespirationValue', 'latestRespirationTimeGMT', 'from', 'until', 'weight', 'bmi', 'bodyFat', 'bodyWater', 'boneMass', 'muscleMass', 'physiqueRating', 'visceralFat', 'metabolicAge'])"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_stats_and_body(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'startDate': '2023-08-05',\n",
+ " 'endDate': '2023-08-05',\n",
+ " 'dateWeightList': [],\n",
+ " 'totalAverage': {'from': 1691193600000,\n",
+ " 'until': 1691279999999,\n",
+ " 'weight': None,\n",
+ " 'bmi': None,\n",
+ " 'bodyFat': None,\n",
+ " 'bodyWater': None,\n",
+ " 'boneMass': None,\n",
+ " 'muscleMass': None,\n",
+ " 'physiqueRating': None,\n",
+ " 'visceralFat': None,\n",
+ " 'metabolicAge': None}}"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_body_composition(yesterday)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['date', 'charged', 'drained', 'startTimestampGMT', 'endTimestampGMT', 'startTimestampLocal', 'endTimestampLocal', 'bodyBatteryValuesArray', 'bodyBatteryValueDescriptorDTOList'])"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_body_battery(yesterday)[0].keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'from': '2023-08-05',\n",
+ " 'until': '2023-08-05',\n",
+ " 'measurementSummaries': [],\n",
+ " 'categoryStats': None}"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_blood_pressure(yesterday)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_max_metrics(yesterday)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'userId': 2591602,\n",
+ " 'calendarDate': '2023-08-05',\n",
+ " 'valueInML': 0.0,\n",
+ " 'goalInML': 3437.0,\n",
+ " 'dailyAverageinML': None,\n",
+ " 'lastEntryTimestampLocal': '2023-08-05T12:25:27.0',\n",
+ " 'sweatLossInML': 637.0,\n",
+ " 'activityIntakeInML': 0.0}"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_hydration_data(yesterday)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfilePK', 'calendarDate', 'startTimestampGMT', 'endTimestampGMT', 'startTimestampLocal', 'endTimestampLocal', 'sleepStartTimestampGMT', 'sleepEndTimestampGMT', 'sleepStartTimestampLocal', 'sleepEndTimestampLocal', 'tomorrowSleepStartTimestampGMT', 'tomorrowSleepEndTimestampGMT', 'tomorrowSleepStartTimestampLocal', 'tomorrowSleepEndTimestampLocal', 'lowestRespirationValue', 'highestRespirationValue', 'avgWakingRespirationValue', 'avgSleepRespirationValue', 'avgTomorrowSleepRespirationValue', 'respirationValueDescriptorsDTOList', 'respirationValuesArray'])"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_respiration_data(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['userProfilePK', 'calendarDate', 'startTimestampGMT', 'endTimestampGMT', 'startTimestampLocal', 'endTimestampLocal', 'sleepStartTimestampGMT', 'sleepEndTimestampGMT', 'sleepStartTimestampLocal', 'sleepEndTimestampLocal', 'tomorrowSleepStartTimestampGMT', 'tomorrowSleepEndTimestampGMT', 'tomorrowSleepStartTimestampLocal', 'tomorrowSleepEndTimestampLocal', 'averageSpO2', 'lowestSpO2', 'lastSevenDaysAvgSpO2', 'latestSpO2', 'latestSpO2TimestampGMT', 'latestSpO2TimestampLocal', 'avgSleepSpO2', 'avgTomorrowSleepSpO2', 'spO2ValueDescriptorsDTOList', 'spO2SingleValues', 'continuousReadingDTOList', 'spO2HourlyAverages'])"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_spo2_data(yesterday).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'id': 1944943000,\n",
+ " 'typeId': 16,\n",
+ " 'activityId': 0,\n",
+ " 'activityName': None,\n",
+ " 'activityStartDateTimeInGMT': None,\n",
+ " 'actStartDateTimeInGMTFormatted': None,\n",
+ " 'activityStartDateTimeLocal': None,\n",
+ " 'activityStartDateTimeLocalFormatted': None,\n",
+ " 'value': 2.0,\n",
+ " 'prStartTimeGmt': 1691215200000,\n",
+ " 'prStartTimeGmtFormatted': '2023-08-05T06:00:00.0',\n",
+ " 'prStartTimeLocal': 1691193600000,\n",
+ " 'prStartTimeLocalFormatted': '2023-08-05T00:00:00.0',\n",
+ " 'prTypeLabelKey': None,\n",
+ " 'poolLengthUnit': None},\n",
+ " {'id': 2184086093,\n",
+ " 'typeId': 3,\n",
+ " 'activityId': 10161959373,\n",
+ " 'activityName': 'Cuauhtémoc - Threshold',\n",
+ " 'activityStartDateTimeInGMT': 1671549377000,\n",
+ " 'actStartDateTimeInGMTFormatted': '2022-12-20T15:16:17.0',\n",
+ " 'activityStartDateTimeLocal': 1671527777000,\n",
+ " 'activityStartDateTimeLocalFormatted': '2022-12-20T09:16:17.0',\n",
+ " 'value': 1413.6650390625,\n",
+ " 'prStartTimeGmt': 1671549990000,\n",
+ " 'prStartTimeGmtFormatted': '2022-12-20T15:26:30.0',\n",
+ " 'prStartTimeLocal': None,\n",
+ " 'prStartTimeLocalFormatted': None,\n",
+ " 'prTypeLabelKey': None,\n",
+ " 'poolLengthUnit': None}]"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "garmin.get_personal_record()[:2]"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ },
+ "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..214210c
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1,3 @@
+pytest
+pytest-vcr
+pytest-cov
diff --git a/setup.py b/setup.py
index 9595af5..7f543b1 100644
--- a/setup.py
+++ b/setup.py
@@ -30,10 +30,10 @@ def read(*parts):
name="garminconnect",
keywords=["garmin connect", "api", "client"],
license="MIT license",
- install_requires=["requests","cloudscraper"],
+ install_requires=["garth >= 0.4.23"],
long_description_content_type="text/markdown",
long_description=readme,
url="https://github.com/cyberjunky/python-garminconnect",
packages=["garminconnect"],
- version="0.1.55"
+ version="0.2.1"
)
diff --git a/tests/cassettes/test_body_battery.yaml b/tests/cassettes/test_body_battery.yaml
new file mode 100644
index 0000000..d4cf34d
--- /dev/null
+++ b/tests/cassettes/test_body_battery.yaml
@@ -0,0 +1,150 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a7b014d17b6e2-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 13:25:02 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=5fw6qcq0NdNdbleOS%2BWsMpFjTu6%2BU2E7IbbvmX8RF38%2Fitx8T5Eai8xRMWKiC%2F728gP0zlJeNtiIprepe9WLjNWkKmBb4g%2F2KiF7%2FRzTL3epslvndR22jovxvhF7Y5HZXsL%2Fzw4pRA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/bodyBattery/reports/daily?startDate=2023-07-01&endDate=2023-07-01
+ response:
+ body:
+ string: '[{"date": "2023-07-01", "charged": 43, "drained": 43, "startTimestampGMT":
+ "2023-07-01T06:00:00.0", "endTimestampGMT": "2023-07-02T06:00:00.0", "startTimestampLocal":
+ "2023-07-01T00:00:00.0", "endTimestampLocal": "2023-07-02T00:00:00.0", "bodyBatteryValuesArray":
+ [[1688191200000, 5], [1688214060000, 48], [1688220000000, 41], [1688248260000,
+ 23], [1688248800000, 23], [1688269140000, 5]], "bodyBatteryValueDescriptorDTOList":
+ [{"bodyBatteryValueDescriptorIndex": 0, "bodyBatteryValueDescriptorKey": "timestamp"},
+ {"bodyBatteryValueDescriptorIndex": 1, "bodyBatteryValueDescriptorKey": "bodyBatteryLevel"}]}]'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a7b0288481549-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 13:25:02 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=vuNozV%2Btdqrb2pnWC8vZa8XsRe8ATc8162AEj6jyD7wtVGJRTuI3LHxioV%2BMV%2FGZ%2BwoicMvVtiKVYZl2xLmGkhhuglHn0eUgQ7TtGcBIOHDPqxdHxwMC%2BbkPjL6ZgxgExIOyFPSPqQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_body_composition.yaml b/tests/cassettes/test_body_composition.yaml
new file mode 100644
index 0000000..c8e042e
--- /dev/null
+++ b/tests/cassettes/test_body_composition.yaml
@@ -0,0 +1,243 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a76f769ba154b-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 13:22:16 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=lo9dkCkcmm6pDJ7C8KfF9H5Xsm6EbKYAx5OGFo2CxXMSvazLtN2abgat2ArkGI7%2FT4Dce9ol7dMfHKTsMIz%2F7EfBUyrEf%2BZp6uYs%2BErKS0GqJxQHwgfLk%2Fc9gVSiA6mpJxTDRgN7pg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a76f84f181549-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 13:22:17 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=SX4h8ANa8PBe0mV0%2FZc0DXrBiAG602wMOfwDx3byFBPbwvKmlIRkoZMn%2BU9DiJr25G9rAoczD4hxiZ6kJcJ0NOuTVr773ki%2FHVtFtDr7zVfqJsiPvlZZOva4bJGbIyMOD6ZlYNPVZA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/weight-service/weight/dateRange?startDate=2023-07-01&endDate=2023-07-01
+ response:
+ body:
+ string: '{"startDate": "2023-07-01", "endDate": "2023-07-01", "dateWeightList":
+ [], "totalAverage": {"from": 1688169600000, "until": 1688255999999, "weight":
+ null, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass": null, "muscleMass":
+ null, "physiqueRating": null, "visceralFat": null, "metabolicAge": null}}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a76f95d5b154b-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 13:22:17 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=WlbmmdUw8POQfCo2mGGrioz8FYa2CwVacFV6T0SHkjcsivJxi%2FFXkVlGkxa0g7lgrgCiEAxuC%2BZZRmvbJmeEV9ifNtvGnh3av7y7Kf3LfMXN56dzSELEhAl7br0lBvAiC40I2fSgNA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_daily_steps.yaml b/tests/cassettes/test_daily_steps.yaml
new file mode 100644
index 0000000..bb82653
--- /dev/null
+++ b/tests/cassettes/test_daily_steps.yaml
@@ -0,0 +1,149 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8742c799e3477e-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 04:02:22 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=czRLz0eUqHstTuo2H%2FYUb52Rz7eVD5R9UOXsKHcFLC0FAHNqwLMRCxUKnR%2Fynq5azwvix59xAqI1mcvAmo4nw4DfQCJjrNE6gzdz6Vxo6%2F2PuIQiirUxV21XXXnYdg%2BdZVi5zsW2JA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/usersummary-service/stats/steps/daily/2023-07-01/2023-07-01
+ response:
+ body:
+ string: '[{"calendarDate": "2023-07-01", "totalSteps": 12413, "totalDistance":
+ 10368, "stepGoal": 7950}]'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8742c8d857154a-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 04:02:22 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=LOAJ0KmYwtWF%2FFcsOa642XdT7pWAnsZuRlnwrgarfZslIB7JmldRym430NLhzrJhu4gjXPM4lh97u0aVuZLVe7ZEc4Bh7eA3iK%2FiCAzsAejjZNEDSi2Tgd6jjesOcBL%2F6qCxo0%2FD1Q%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_download_activity.yaml b/tests/cassettes/test_download_activity.yaml
new file mode 100644
index 0000000..5b2e92e
--- /dev/null
+++ b/tests/cassettes/test_download_activity.yaml
@@ -0,0 +1,12962 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 82700.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 50.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 80520990a8541449-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Mon, 11 Sep 2023 18:40:07 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=mjfkUmmuM03wMYvK2AJhO5TiS8fgIQ0Rr4Tn0FDZbLmyrgDwnZzohCAQB9GHFLZHkKF4VJvtk9REsn%2FhvrbizjazrNPz4tQhAgReEaObZ5rSw3YG5skXuDnTKcD9VWBLfIO0dphghg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/download-service/export/tcx/activity/11998957007
+ response:
+ body:
+ string: "\n\n \n
+ \ \n 2023-09-11T13:44:29.000Z\n
+ \ \n 4338.02\n
+ \ 0.0\n 151\n
+ \ \n 72\n \n
+ \ \n 96\n \n
+ \ Active\n Manual\n
+ \ \n \n \n
+ \ \n \n \n
+ \ fenix 6X Sapphire\n 3329978681\n
+ \ 3291\n \n 26\n
+ \ 0\n 0\n
+ \ 0\n \n \n
+ \ \n \n \n
+ \ Connect Api\n \n \n 0\n
+ \ 0\n 0\n
+ \ 0\n \n \n en\n
+ \ 006-D2449-00\n \n\n"
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 80520992b990ea98-DFW
+ Connection:
+ - keep-alive
+ Content-Type:
+ - application/vnd.garmin.tcx+xml
+ Date:
+ - Mon, 11 Sep 2023 18:40:08 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=rvnC%2FcYg065EoSEomRiV7Oq%2FuwPFFhuPAmrhDEX%2B6i%2Fo5pabl%2B0mYJ7DUHhSH8Dp1qhHdKSWlf9YwtGDPpy1YT55JAAEesRGDtPXsdNl8KKLX0dxHSvJXof%2BgbxxI1zq45dBk2kWXg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ content-disposition:
+ - attachment; filename=activity_11998957007.tcx
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTs=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_floors.yaml b/tests/cassettes/test_floors.yaml
new file mode 100644
index 0000000..109e5cc
--- /dev/null
+++ b/tests/cassettes/test_floors.yaml
@@ -0,0 +1,212 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8740a48af1155e-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 04:00:54 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=iYV7gIdYcQH86D%2B1SrhLDbvBU1beua9HcesS6kbu9jiIorHzXEtQVjoq49jR5udXvF3rCsKxWcURNblr%2FWyqhsirWC%2FiOfbWaxzB7ZAoozVD0QfB1DK5E1iU8bVsUO%2FQd%2F4sWjEDvQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/floorsChartData/daily/2023-07-01
+ response:
+ body:
+ string: '{"startTimestampGMT": "2023-07-01T06:00:00.0", "endTimestampGMT": "2023-07-02T06:00:00.0",
+ "startTimestampLocal": "2023-07-01T00:00:00.0", "endTimestampLocal": "2023-07-02T00:00:00.0",
+ "floorsValueDescriptorDTOList": [{"key": "startTimeGMT", "index": 0}, {"key":
+ "endTimeGMT", "index": 1}, {"key": "floorsAscended", "index": 2}, {"key":
+ "floorsDescended", "index": 3}], "floorValuesArray": [["2023-07-01T06:00:00.0",
+ "2023-07-01T06:15:00.0", 0, 0], ["2023-07-01T06:15:00.0", "2023-07-01T06:30:00.0",
+ 0, 0], ["2023-07-01T06:30:00.0", "2023-07-01T06:45:00.0", 0, 0], ["2023-07-01T06:45:00.0",
+ "2023-07-01T07:00:00.0", 0, 0], ["2023-07-01T07:00:00.0", "2023-07-01T07:15:00.0",
+ 0, 0], ["2023-07-01T07:15:00.0", "2023-07-01T07:30:00.0", 0, 0], ["2023-07-01T07:30:00.0",
+ "2023-07-01T07:45:00.0", 0, 0], ["2023-07-01T07:45:00.0", "2023-07-01T08:00:00.0",
+ 0, 0], ["2023-07-01T08:00:00.0", "2023-07-01T08:15:00.0", 0, 0], ["2023-07-01T08:15:00.0",
+ "2023-07-01T08:30:00.0", 0, 0], ["2023-07-01T08:30:00.0", "2023-07-01T08:45:00.0",
+ 0, 0], ["2023-07-01T08:45:00.0", "2023-07-01T09:00:00.0", 0, 0], ["2023-07-01T09:00:00.0",
+ "2023-07-01T09:15:00.0", 0, 0], ["2023-07-01T09:15:00.0", "2023-07-01T09:30:00.0",
+ 0, 0], ["2023-07-01T09:30:00.0", "2023-07-01T09:45:00.0", 0, 0], ["2023-07-01T09:45:00.0",
+ "2023-07-01T10:00:00.0", 0, 0], ["2023-07-01T10:00:00.0", "2023-07-01T10:15:00.0",
+ 0, 0], ["2023-07-01T10:15:00.0", "2023-07-01T10:30:00.0", 0, 0], ["2023-07-01T10:30:00.0",
+ "2023-07-01T10:45:00.0", 0, 0], ["2023-07-01T10:45:00.0", "2023-07-01T11:00:00.0",
+ 0, 0], ["2023-07-01T11:00:00.0", "2023-07-01T11:15:00.0", 0, 0], ["2023-07-01T11:15:00.0",
+ "2023-07-01T11:30:00.0", 0, 1], ["2023-07-01T11:30:00.0", "2023-07-01T11:45:00.0",
+ 0, 0], ["2023-07-01T11:45:00.0", "2023-07-01T12:00:00.0", 1, 0], ["2023-07-01T12:00:00.0",
+ "2023-07-01T12:15:00.0", 0, 0], ["2023-07-01T12:15:00.0", "2023-07-01T12:30:00.0",
+ 0, 0], ["2023-07-01T12:30:00.0", "2023-07-01T12:45:00.0", 0, 0], ["2023-07-01T12:45:00.0",
+ "2023-07-01T13:00:00.0", 1, 1], ["2023-07-01T13:00:00.0", "2023-07-01T13:15:00.0",
+ 0, 0], ["2023-07-01T13:15:00.0", "2023-07-01T13:30:00.0", 0, 0], ["2023-07-01T13:30:00.0",
+ "2023-07-01T13:45:00.0", 0, 1], ["2023-07-01T13:45:00.0", "2023-07-01T14:00:00.0",
+ 0, 0], ["2023-07-01T14:00:00.0", "2023-07-01T14:15:00.0", 1, 2], ["2023-07-01T14:15:00.0",
+ "2023-07-01T14:30:00.0", 0, 0], ["2023-07-01T14:30:00.0", "2023-07-01T14:45:00.0",
+ 0, 0], ["2023-07-01T14:45:00.0", "2023-07-01T15:00:00.0", 0, 0], ["2023-07-01T15:00:00.0",
+ "2023-07-01T15:15:00.0", 0, 0], ["2023-07-01T15:15:00.0", "2023-07-01T15:30:00.0",
+ 0, 0], ["2023-07-01T15:30:00.0", "2023-07-01T15:45:00.0", 1, 0], ["2023-07-01T15:45:00.0",
+ "2023-07-01T16:00:00.0", 0, 0], ["2023-07-01T16:00:00.0", "2023-07-01T16:15:00.0",
+ 0, 0], ["2023-07-01T16:15:00.0", "2023-07-01T16:30:00.0", 0, 0], ["2023-07-01T16:30:00.0",
+ "2023-07-01T16:45:00.0", 0, 0], ["2023-07-01T16:45:00.0", "2023-07-01T17:00:00.0",
+ 0, 0], ["2023-07-01T17:00:00.0", "2023-07-01T17:15:00.0", 3, 1], ["2023-07-01T17:15:00.0",
+ "2023-07-01T17:30:00.0", 0, 0], ["2023-07-01T17:30:00.0", "2023-07-01T17:45:00.0",
+ 0, 0], ["2023-07-01T17:45:00.0", "2023-07-01T18:00:00.0", 0, 0], ["2023-07-01T18:00:00.0",
+ "2023-07-01T18:15:00.0", 0, 0], ["2023-07-01T18:15:00.0", "2023-07-01T18:30:00.0",
+ 0, 0], ["2023-07-01T18:30:00.0", "2023-07-01T18:45:00.0", 0, 0], ["2023-07-01T18:45:00.0",
+ "2023-07-01T19:00:00.0", 1, 0], ["2023-07-01T19:00:00.0", "2023-07-01T19:15:00.0",
+ 0, 0], ["2023-07-01T19:15:00.0", "2023-07-01T19:30:00.0", 0, 1], ["2023-07-01T19:30:00.0",
+ "2023-07-01T19:45:00.0", 0, 4], ["2023-07-01T19:45:00.0", "2023-07-01T20:00:00.0",
+ 0, 0], ["2023-07-01T20:00:00.0", "2023-07-01T20:15:00.0", 0, 0], ["2023-07-01T20:15:00.0",
+ "2023-07-01T20:30:00.0", 0, 0], ["2023-07-01T20:30:00.0", "2023-07-01T20:45:00.0",
+ 0, 0], ["2023-07-01T20:45:00.0", "2023-07-01T21:00:00.0", 0, 0], ["2023-07-01T21:00:00.0",
+ "2023-07-01T21:15:00.0", 1, 2], ["2023-07-01T21:15:00.0", "2023-07-01T21:30:00.0",
+ 0, 0], ["2023-07-01T21:30:00.0", "2023-07-01T21:45:00.0", 0, 0], ["2023-07-01T21:45:00.0",
+ "2023-07-01T22:00:00.0", 0, 0], ["2023-07-01T22:00:00.0", "2023-07-01T22:15:00.0",
+ 0, 0], ["2023-07-01T22:15:00.0", "2023-07-01T22:30:00.0", 0, 0], ["2023-07-01T22:30:00.0",
+ "2023-07-01T22:45:00.0", 0, 0], ["2023-07-01T22:45:00.0", "2023-07-01T23:00:00.0",
+ 0, 0], ["2023-07-01T23:00:00.0", "2023-07-01T23:15:00.0", 0, 0], ["2023-07-01T23:15:00.0",
+ "2023-07-01T23:30:00.0", 0, 0], ["2023-07-01T23:30:00.0", "2023-07-01T23:45:00.0",
+ 0, 0], ["2023-07-01T23:45:00.0", "2023-07-02T00:00:00.0", 2, 0], ["2023-07-02T00:00:00.0",
+ "2023-07-02T00:15:00.0", 0, 0], ["2023-07-02T00:15:00.0", "2023-07-02T00:30:00.0",
+ 2, 0], ["2023-07-02T00:30:00.0", "2023-07-02T00:45:00.0", 0, 0], ["2023-07-02T00:45:00.0",
+ "2023-07-02T01:00:00.0", 2, 2], ["2023-07-02T01:00:00.0", "2023-07-02T01:15:00.0",
+ 1, 1], ["2023-07-02T01:15:00.0", "2023-07-02T01:30:00.0", 0, 0], ["2023-07-02T01:30:00.0",
+ "2023-07-02T01:45:00.0", 0, 2], ["2023-07-02T01:45:00.0", "2023-07-02T02:00:00.0",
+ 4, 2], ["2023-07-02T02:00:00.0", "2023-07-02T02:15:00.0", 0, 0], ["2023-07-02T02:15:00.0",
+ "2023-07-02T02:30:00.0", 0, 0], ["2023-07-02T02:30:00.0", "2023-07-02T02:45:00.0",
+ 0, 0], ["2023-07-02T02:45:00.0", "2023-07-02T03:00:00.0", 0, 0], ["2023-07-02T03:00:00.0",
+ "2023-07-02T03:15:00.0", 0, 0], ["2023-07-02T03:15:00.0", "2023-07-02T03:30:00.0",
+ 0, 0], ["2023-07-02T03:30:00.0", "2023-07-02T03:45:00.0", 0, 1], ["2023-07-02T03:45:00.0",
+ "2023-07-02T04:00:00.0", 4, 1], ["2023-07-02T04:00:00.0", "2023-07-02T04:15:00.0",
+ 0, 0], ["2023-07-02T04:15:00.0", "2023-07-02T04:30:00.0", 0, 0], ["2023-07-02T04:30:00.0",
+ "2023-07-02T04:45:00.0", 0, 0], ["2023-07-02T04:45:00.0", "2023-07-02T05:00:00.0",
+ 0, 0], ["2023-07-02T05:00:00.0", "2023-07-02T05:15:00.0", 0, 2], ["2023-07-02T05:15:00.0",
+ "2023-07-02T05:30:00.0", 0, 0], ["2023-07-02T05:30:00.0", "2023-07-02T05:45:00.0",
+ 5, 5], ["2023-07-02T05:45:00.0", "2023-07-02T06:00:00.0", 0, 0]]}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8740a6ef41463e-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 04:00:54 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=lxY7Q%2BGKPaXN2DpJu8%2BCjHI6CMPtwvhQDlFrjA09aAG4aIAy2bUBICGsi4T688SrIsoayL3lZGWnqNIjzdm0ybZ9Tlry3M30rNTNppkI1wNRZIkQj3NfukAtdH0SDXkaTpB%2F5hpM7g%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_heart_rates.yaml b/tests/cassettes/test_heart_rates.yaml
new file mode 100644
index 0000000..56b356e
--- /dev/null
+++ b/tests/cassettes/test_heart_rates.yaml
@@ -0,0 +1,423 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a4dfd38ceb6ee-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 12:54:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=La2NeCtpgizXPwqTA6hQIqHxHZp3Q7NKoQpteVkisyVSSERbgN4lDUZ5tc2dxWena37ZPgY12J0dvwsfCGcoI9A1Y2s%2F%2FLMzXlGcsUBNyuYhaYlcBiD%2BBRQKODoqJCYIrgUi5GoFmA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a4dfdf980b6e5-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 12:54:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=I5KMiCopdLs1oRVeXXMDAPzqBe%2BvuJTYtO%2BiQ3BjpTVhWpMicJVwZ%2BQ6MKe2rMgrLFD%2FcnikpBSUW7ePlKyy2IKZJSMzxgCzIIOemxy8o70roRlr9CH2xD7rMqhMEpRsErmGVmDyaQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/dailyHeartRate/mtamizi?date=2023-07-01
+ response:
+ body:
+ string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-01", "startTimestampGMT":
+ "2023-07-01T06:00:00.0", "endTimestampGMT": "2023-07-02T06:00:00.0", "startTimestampLocal":
+ "2023-07-01T00:00:00.0", "endTimestampLocal": "2023-07-02T00:00:00.0", "maxHeartRate":
+ 106, "minHeartRate": 49, "restingHeartRate": 51, "lastSevenDaysAvgRestingHeartRate":
+ 49, "heartRateValueDescriptors": [{"key": "timestamp", "index": 0}, {"key":
+ "heartrate", "index": 1}], "heartRateValues": [[1688191200000, 56], [1688191320000,
+ 57], [1688191440000, 56], [1688191560000, 56], [1688191680000, 57], [1688191800000,
+ 59], [1688191920000, 58], [1688192040000, 59], [1688192160000, 60], [1688192280000,
+ 57], [1688192400000, 57], [1688192520000, 60], [1688192640000, 59], [1688192760000,
+ 58], [1688192880000, 58], [1688193000000, 59], [1688193120000, 59], [1688193240000,
+ 61], [1688193360000, 58], [1688193480000, 58], [1688193600000, 58], [1688193720000,
+ 58], [1688193840000, 59], [1688193960000, 58], [1688194080000, 57], [1688194200000,
+ 58], [1688194320000, 58], [1688194440000, 57], [1688194560000, 60], [1688194680000,
+ 62], [1688194800000, 57], [1688194920000, 57], [1688195040000, 56], [1688195160000,
+ 55], [1688195280000, 55], [1688195400000, 55], [1688195520000, 54], [1688195640000,
+ 54], [1688195760000, 54], [1688195880000, 55], [1688196000000, 55], [1688196120000,
+ 54], [1688196240000, 55], [1688196360000, 54], [1688196480000, 55], [1688196600000,
+ 54], [1688196720000, 54], [1688196840000, 54], [1688196960000, 54], [1688197080000,
+ 54], [1688197200000, 55], [1688197320000, 55], [1688197440000, 55], [1688197560000,
+ 55], [1688197680000, 53], [1688197800000, 55], [1688197920000, 54], [1688198040000,
+ 54], [1688198160000, 56], [1688198280000, 54], [1688198400000, 54], [1688198520000,
+ 54], [1688198640000, 55], [1688198760000, 54], [1688198880000, 54], [1688199000000,
+ 54], [1688199120000, 55], [1688199240000, 55], [1688199360000, 55], [1688199480000,
+ 55], [1688199600000, 55], [1688199720000, 54], [1688199840000, 54], [1688199960000,
+ 54], [1688200080000, 52], [1688200200000, 53], [1688200320000, 53], [1688200440000,
+ 53], [1688200560000, 53], [1688200680000, 53], [1688200800000, 53], [1688200920000,
+ 53], [1688201040000, 53], [1688201160000, 53], [1688201280000, 53], [1688201400000,
+ 53], [1688201520000, 53], [1688201640000, 52], [1688201760000, 53], [1688201880000,
+ 53], [1688202000000, 52], [1688202120000, 53], [1688202240000, 54], [1688202360000,
+ 52], [1688202480000, 53], [1688202600000, 51], [1688202720000, 52], [1688202840000,
+ 52], [1688202960000, 53], [1688203080000, 52], [1688203200000, 52], [1688203320000,
+ 52], [1688203440000, 52], [1688203560000, 51], [1688203680000, 51], [1688203800000,
+ 50], [1688203920000, 51], [1688204040000, 52], [1688204160000, 52], [1688204280000,
+ 52], [1688204400000, 53], [1688204520000, 53], [1688204640000, 51], [1688204760000,
+ 53], [1688204880000, 51], [1688205000000, 51], [1688205120000, 51], [1688205240000,
+ 51], [1688205360000, 51], [1688205480000, 51], [1688205600000, 51], [1688205720000,
+ 51], [1688205840000, 53], [1688205960000, 51], [1688206080000, 52], [1688206200000,
+ 53], [1688206320000, 52], [1688206440000, 52], [1688206560000, 52], [1688206680000,
+ 52], [1688206800000, 52], [1688206920000, 53], [1688207040000, 52], [1688207160000,
+ 53], [1688207280000, 52], [1688207400000, 52], [1688207520000, 54], [1688207640000,
+ 53], [1688207760000, 52], [1688207880000, 53], [1688208000000, 52], [1688208120000,
+ 53], [1688208240000, 53], [1688208360000, 55], [1688208480000, 53], [1688208600000,
+ 52], [1688208720000, 50], [1688208840000, 52], [1688208960000, 52], [1688209080000,
+ 54], [1688209200000, 49], [1688209320000, null], [1688209440000, 67], [1688209560000,
+ 53], [1688209680000, 51], [1688209800000, 52], [1688209920000, 51], [1688210040000,
+ 51], [1688210160000, 51], [1688210280000, 52], [1688210400000, 52], [1688210520000,
+ 52], [1688210640000, 54], [1688210760000, 56], [1688210880000, 59], [1688211000000,
+ 75], [1688211120000, 79], [1688211240000, 84], [1688211360000, 90], [1688211480000,
+ 84], [1688211600000, 77], [1688211720000, 88], [1688211840000, 78], [1688211960000,
+ 83], [1688212080000, 62], [1688212200000, 56], [1688212320000, 53], [1688212440000,
+ 53], [1688212560000, 53], [1688212680000, 55], [1688212800000, 56], [1688212920000,
+ 59], [1688213040000, 55], [1688213160000, 60], [1688213280000, 57], [1688213400000,
+ 58], [1688213520000, 56], [1688213640000, 56], [1688213760000, 57], [1688213880000,
+ 55], [1688214000000, 55], [1688214120000, 57], [1688214240000, 58], [1688214360000,
+ 69], [1688214480000, 72], [1688214600000, 78], [1688214720000, 79], [1688214840000,
+ 77], [1688214960000, 72], [1688215080000, 75], [1688215200000, 77], [1688215320000,
+ 72], [1688215440000, 75], [1688215560000, 74], [1688215680000, 77], [1688215800000,
+ 75], [1688215920000, 73], [1688216040000, 77], [1688216160000, 73], [1688216280000,
+ 72], [1688216400000, 78], [1688216520000, 78], [1688216640000, 72], [1688216760000,
+ 73], [1688216880000, 75], [1688217000000, 77], [1688217120000, 71], [1688217240000,
+ 74], [1688217360000, 74], [1688217480000, 72], [1688217600000, 73], [1688217720000,
+ 71], [1688217840000, 74], [1688217960000, 72], [1688218080000, 75], [1688218200000,
+ 78], [1688218320000, 73], [1688218440000, 89], [1688218560000, 96], [1688218680000,
+ 102], [1688218800000, 91], [1688218920000, 91], [1688219040000, 87], [1688219160000,
+ 81], [1688219280000, 71], [1688219400000, 73], [1688219520000, 84], [1688219640000,
+ 85], [1688219760000, 96], [1688219880000, 72], [1688220000000, 89], [1688220120000,
+ 75], [1688220240000, 74], [1688220360000, 70], [1688220480000, 75], [1688220600000,
+ 75], [1688220720000, 90], [1688220840000, 94], [1688220960000, 78], [1688221080000,
+ 85], [1688221200000, 93], [1688221320000, 90], [1688221440000, 96], [1688221560000,
+ 103], [1688221680000, 97], [1688221800000, 92], [1688221920000, 92], [1688222040000,
+ 86], [1688222160000, 84], [1688222280000, 83], [1688222400000, 86], [1688222520000,
+ 72], [1688222640000, 65], [1688222760000, 63], [1688222880000, 61], [1688223000000,
+ 70], [1688223120000, 75], [1688223240000, 77], [1688223360000, 75], [1688223480000,
+ 70], [1688223600000, 73], [1688223720000, 77], [1688223840000, 80], [1688223960000,
+ 78], [1688224080000, 70], [1688224200000, 77], [1688224320000, 76], [1688224440000,
+ 79], [1688224560000, 77], [1688224680000, 74], [1688224800000, 75], [1688224920000,
+ 72], [1688225040000, 71], [1688225160000, 70], [1688225280000, 76], [1688225400000,
+ 70], [1688225520000, 75], [1688225640000, 80], [1688225760000, 78], [1688225880000,
+ 80], [1688226000000, 80], [1688226120000, 76], [1688226240000, 81], [1688226360000,
+ 81], [1688226480000, 84], [1688226600000, 93], [1688226720000, 90], [1688226840000,
+ 93], [1688226960000, 77], [1688227080000, 68], [1688227200000, 67], [1688227320000,
+ 90], [1688227440000, 85], [1688227560000, 83], [1688227680000, 83], [1688227800000,
+ 77], [1688227920000, 74], [1688228040000, 69], [1688228160000, 76], [1688228280000,
+ 62], [1688228400000, 74], [1688228520000, 61], [1688228640000, 61], [1688228760000,
+ 65], [1688228880000, 68], [1688229000000, 64], [1688229120000, 63], [1688229240000,
+ 74], [1688229360000, 76], [1688229480000, 73], [1688229600000, 77], [1688229720000,
+ 77], [1688229840000, 77], [1688229960000, 73], [1688230080000, 78], [1688230200000,
+ 77], [1688230320000, 79], [1688230440000, 86], [1688230560000, 84], [1688230680000,
+ 77], [1688230800000, 80], [1688230920000, 73], [1688231040000, 59], [1688231160000,
+ 54], [1688231280000, 55], [1688231400000, 68], [1688231520000, 76], [1688231640000,
+ 62], [1688231760000, 67], [1688231880000, 64], [1688232000000, 61], [1688232120000,
+ 62], [1688232240000, 66], [1688232360000, 66], [1688232480000, 64], [1688232600000,
+ 66], [1688232720000, 63], [1688232840000, 73], [1688232960000, 68], [1688233080000,
+ 65], [1688233200000, 67], [1688233320000, 67], [1688233440000, 68], [1688233560000,
+ 67], [1688233680000, 71], [1688233800000, 68], [1688233920000, 70], [1688234040000,
+ 69], [1688234160000, 69], [1688234280000, 65], [1688234400000, 70], [1688234520000,
+ 66], [1688234640000, 69], [1688234760000, 71], [1688234880000, 66], [1688235000000,
+ 69], [1688235120000, 67], [1688235240000, 67], [1688235360000, 67], [1688235480000,
+ 72], [1688235600000, 71], [1688235720000, 76], [1688235840000, 74], [1688235960000,
+ 69], [1688236080000, 71], [1688236200000, 70], [1688236320000, 69], [1688236440000,
+ 73], [1688236560000, 73], [1688236680000, 73], [1688236800000, 71], [1688236920000,
+ 72], [1688237040000, 74], [1688237160000, 74], [1688237280000, 73], [1688237400000,
+ 71], [1688237520000, 72], [1688237640000, 75], [1688237760000, 73], [1688237880000,
+ 79], [1688238000000, null], [1688238960000, 84], [1688239080000, null], [1688239440000,
+ 86], [1688239560000, 91], [1688239680000, 74], [1688239800000, 62], [1688239920000,
+ 77], [1688240040000, 84], [1688240160000, 83], [1688240280000, 73], [1688240400000,
+ 89], [1688240520000, 88], [1688240640000, 81], [1688240760000, 87], [1688240880000,
+ 85], [1688241000000, 94], [1688241120000, 93], [1688241240000, 95], [1688241360000,
+ 90], [1688241480000, 70], [1688241600000, 60], [1688241720000, 57], [1688241840000,
+ 60], [1688241960000, 61], [1688242080000, 67], [1688242200000, 64], [1688242320000,
+ 62], [1688242440000, 62], [1688242560000, 63], [1688242680000, 66], [1688242800000,
+ 74], [1688242920000, 75], [1688243040000, 86], [1688243160000, 78], [1688243280000,
+ 74], [1688243400000, 65], [1688243520000, 59], [1688243640000, 61], [1688243760000,
+ 67], [1688243880000, 64], [1688244000000, 66], [1688244120000, 63], [1688244240000,
+ 63], [1688244360000, 65], [1688244480000, 70], [1688244600000, 66], [1688244720000,
+ 65], [1688244840000, 85], [1688244960000, 67], [1688245080000, 60], [1688245200000,
+ 68], [1688245320000, 75], [1688245440000, 77], [1688245560000, 76], [1688245680000,
+ 76], [1688245800000, 75], [1688245920000, 70], [1688246040000, 70], [1688246160000,
+ 71], [1688246280000, 70], [1688246400000, 72], [1688246520000, 67], [1688246640000,
+ 69], [1688246760000, 70], [1688246880000, 71], [1688247000000, 69], [1688247120000,
+ 67], [1688247240000, 69], [1688247360000, 67], [1688247480000, 71], [1688247600000,
+ 66], [1688247720000, 85], [1688247840000, 91], [1688247960000, 84], [1688248080000,
+ 89], [1688248200000, 77], [1688248320000, 85], [1688248440000, 94], [1688248560000,
+ 106], [1688248680000, 106], [1688248800000, 87], [1688248920000, 71], [1688249040000,
+ 69], [1688249160000, 78], [1688249280000, 84], [1688249400000, 87], [1688249520000,
+ 86], [1688249640000, 84], [1688249760000, 84], [1688249880000, 78], [1688250000000,
+ 85], [1688250120000, 89], [1688250240000, 92], [1688250360000, 91], [1688250480000,
+ 87], [1688250600000, 85], [1688250720000, 85], [1688250840000, 85], [1688250960000,
+ 83], [1688251080000, 81], [1688251200000, 88], [1688251320000, 91], [1688251440000,
+ 87], [1688251560000, 91], [1688251680000, 86], [1688251800000, 85], [1688251920000,
+ 77], [1688252040000, 78], [1688252160000, 86], [1688252280000, 79], [1688252400000,
+ 79], [1688252520000, 89], [1688252640000, 82], [1688252760000, 79], [1688252880000,
+ 77], [1688253000000, 82], [1688253120000, 76], [1688253240000, 79], [1688253360000,
+ 83], [1688253480000, 80], [1688253600000, 82], [1688253720000, 73], [1688253840000,
+ 72], [1688253960000, 73], [1688254080000, 76], [1688254200000, 76], [1688254320000,
+ 94], [1688254440000, 94], [1688254560000, 84], [1688254680000, 85], [1688254800000,
+ 90], [1688254920000, 94], [1688255040000, 87], [1688255160000, 80], [1688255280000,
+ 85], [1688255400000, 86], [1688255520000, 97], [1688255640000, 96], [1688255760000,
+ 85], [1688255880000, 76], [1688256000000, 71], [1688256120000, 75], [1688256240000,
+ 74], [1688256360000, 74], [1688256480000, 70], [1688256600000, 69], [1688256720000,
+ 69], [1688256840000, 69], [1688256960000, 70], [1688257080000, 73], [1688257200000,
+ 73], [1688257320000, 74], [1688257440000, 80], [1688257560000, 94], [1688257680000,
+ 102], [1688257800000, 85], [1688257920000, 74], [1688258040000, 71], [1688258160000,
+ 71], [1688258280000, 70], [1688258400000, 72], [1688258520000, 69], [1688258640000,
+ 70], [1688258760000, 69], [1688258880000, 69], [1688259000000, 71], [1688259120000,
+ 88], [1688259240000, 93], [1688259360000, 82], [1688259480000, 80], [1688259600000,
+ 76], [1688259720000, 73], [1688259840000, 93], [1688259960000, 84], [1688260080000,
+ 70], [1688260200000, 67], [1688260320000, 72], [1688260440000, 76], [1688260560000,
+ 71], [1688260680000, 70], [1688260800000, 73], [1688260920000, 71], [1688261040000,
+ 71], [1688261160000, 70], [1688261280000, 74], [1688261400000, 78], [1688261520000,
+ 74], [1688261640000, 70], [1688261760000, 72], [1688261880000, 78], [1688262000000,
+ 97], [1688262120000, 96], [1688262240000, 101], [1688262360000, 85], [1688262480000,
+ 88], [1688262600000, 93], [1688262720000, 72], [1688262840000, 84], [1688262960000,
+ 92], [1688263080000, 96], [1688263200000, 88], [1688263320000, 81], [1688263440000,
+ 79], [1688263560000, 76], [1688263680000, 78], [1688263800000, 79], [1688263920000,
+ 80], [1688264040000, 78], [1688264160000, 77], [1688264280000, 84], [1688264400000,
+ 77], [1688264520000, 80], [1688264640000, 77], [1688264760000, 78], [1688264880000,
+ 77], [1688265000000, 89], [1688265120000, 88], [1688265240000, 85], [1688265360000,
+ 80], [1688265480000, 73], [1688265600000, 76], [1688265720000, 74], [1688265840000,
+ 76], [1688265960000, 77], [1688266080000, 77], [1688266200000, 79], [1688266320000,
+ 75], [1688266440000, 74], [1688266560000, 77], [1688266680000, 78], [1688266800000,
+ 78], [1688266920000, 80], [1688267040000, 76], [1688267160000, 77], [1688267280000,
+ 75], [1688267400000, 74], [1688267520000, 75], [1688267640000, 70], [1688267760000,
+ 76], [1688267880000, 76], [1688268000000, 75], [1688268120000, 75], [1688268240000,
+ 72], [1688268360000, 75], [1688268480000, 74], [1688268600000, 81], [1688268720000,
+ 82], [1688268840000, 81], [1688268960000, 77], [1688269080000, 73], [1688269200000,
+ 89], [1688269320000, 95], [1688269440000, 94], [1688269560000, 94], [1688269680000,
+ 82], [1688269800000, 81], [1688269920000, 82], [1688270040000, 85], [1688270160000,
+ 81], [1688270280000, 77], [1688270400000, 71], [1688270520000, 72], [1688270640000,
+ 70], [1688270760000, 70], [1688270880000, 72], [1688271000000, 73], [1688271120000,
+ 70], [1688271240000, 74], [1688271360000, 68], [1688271480000, 71], [1688271600000,
+ 71], [1688271720000, 72], [1688271840000, 76], [1688271960000, 78], [1688272080000,
+ 62], [1688272200000, 60], [1688272320000, 62], [1688272440000, 62], [1688272560000,
+ 65], [1688272680000, 64], [1688272800000, 66], [1688272920000, 67], [1688273040000,
+ 65], [1688273160000, 66], [1688273280000, 63], [1688273400000, 64], [1688273520000,
+ 64], [1688273640000, 66], [1688273760000, 63], [1688273880000, 63], [1688274000000,
+ 62], [1688274120000, 64], [1688274240000, 86], [1688274360000, 83], [1688274480000,
+ 81], [1688274600000, 78], [1688274720000, 85], [1688274840000, 83], [1688274960000,
+ 80], [1688275080000, 79], [1688275200000, 85], [1688275320000, 86], [1688275440000,
+ 82], [1688275560000, 84], [1688275680000, 70], [1688275800000, 86], [1688275920000,
+ 80], [1688276040000, 70], [1688276160000, 81], [1688276280000, 77], [1688276400000,
+ 86], [1688276520000, 90], [1688276640000, 64], [1688276760000, 62], [1688276880000,
+ 65], [1688277000000, 74], [1688277120000, 65], [1688277240000, 72], [1688277360000,
+ 56], [1688277480000, 56]]}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a4dfee830b6e8-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 12:54:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=RtawwZ3pSW1TMssDM4vc6f7eVeb9oADwxK%2BINk45Vx4e1AyguWYrznt%2FoDCl6UEkpB1NwQ%2FFbPxambaMTGURI7ZV6N4U6yGHHCKvskFW3RdDdQ1aSXBdM1vpM%2BApri80AajDX17GxQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_hrv_data.yaml b/tests/cassettes/test_hrv_data.yaml
new file mode 100644
index 0000000..9750b91
--- /dev/null
+++ b/tests/cassettes/test_hrv_data.yaml
@@ -0,0 +1,318 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8c22b8fd5db6ed-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 18:14:17 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=0E77t2nW5dGJbcmzeOjPJoIjH2646y1L2BeBzRVSPirgt6bd2fNJbl8cpsSu%2Bvb0XSQ0E4kbICTNiK%2FJnEhNsgwkeHWFbjC7APT867Vf%2FdAInYViBoc7S1CMJyZtmBB2Fybh1dz32g%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8c22baef2146e9-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 18:14:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=%2BZjeDtpfTSyeEJtm6VjqgfKiqPie8kaSR1fdapEWVOUg1%2BjoTebr%2FmIk7mri7ypjXTL2A8ZX%2Bi3OLErVyTv6HDuUNpJw9i7LLALaMQoidzMGEwUDfKsQQG5MCrY06CT0SYZxun%2BdSw%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/hrv-service/hrv/2023-07-01
+ response:
+ body:
+ string: '{"userProfilePk": 2591602, "hrvSummary": {"calendarDate": "2023-07-01",
+ "weeklyAvg": 43, "lastNightAvg": 43, "lastNight5MinHigh": 60, "baseline":
+ {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835},
+ "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
+ "2023-07-01T12:27:14.85"}, "hrvReadings": [{"hrvValue": 44, "readingTimeGMT":
+ "2023-07-01T06:44:41.0", "readingTimeLocal": "2023-07-01T00:44:41.0"}, {"hrvValue":
+ 39, "readingTimeGMT": "2023-07-01T06:49:41.0", "readingTimeLocal": "2023-07-01T00:49:41.0"},
+ {"hrvValue": 49, "readingTimeGMT": "2023-07-01T06:54:41.0", "readingTimeLocal":
+ "2023-07-01T00:54:41.0"}, {"hrvValue": 55, "readingTimeGMT": "2023-07-01T06:59:41.0",
+ "readingTimeLocal": "2023-07-01T00:59:41.0"}, {"hrvValue": 46, "readingTimeGMT":
+ "2023-07-01T07:04:41.0", "readingTimeLocal": "2023-07-01T01:04:41.0"}, {"hrvValue":
+ 42, "readingTimeGMT": "2023-07-01T07:09:41.0", "readingTimeLocal": "2023-07-01T01:09:41.0"},
+ {"hrvValue": 56, "readingTimeGMT": "2023-07-01T07:14:41.0", "readingTimeLocal":
+ "2023-07-01T01:14:41.0"}, {"hrvValue": 51, "readingTimeGMT": "2023-07-01T07:19:41.0",
+ "readingTimeLocal": "2023-07-01T01:19:41.0"}, {"hrvValue": 42, "readingTimeGMT":
+ "2023-07-01T07:24:41.0", "readingTimeLocal": "2023-07-01T01:24:41.0"}, {"hrvValue":
+ 49, "readingTimeGMT": "2023-07-01T07:29:41.0", "readingTimeLocal": "2023-07-01T01:29:41.0"},
+ {"hrvValue": 43, "readingTimeGMT": "2023-07-01T07:34:41.0", "readingTimeLocal":
+ "2023-07-01T01:34:41.0"}, {"hrvValue": 40, "readingTimeGMT": "2023-07-01T07:39:41.0",
+ "readingTimeLocal": "2023-07-01T01:39:41.0"}, {"hrvValue": 45, "readingTimeGMT":
+ "2023-07-01T07:44:41.0", "readingTimeLocal": "2023-07-01T01:44:41.0"}, {"hrvValue":
+ 42, "readingTimeGMT": "2023-07-01T07:49:41.0", "readingTimeLocal": "2023-07-01T01:49:41.0"},
+ {"hrvValue": 45, "readingTimeGMT": "2023-07-01T07:54:41.0", "readingTimeLocal":
+ "2023-07-01T01:54:41.0"}, {"hrvValue": 42, "readingTimeGMT": "2023-07-01T07:59:41.0",
+ "readingTimeLocal": "2023-07-01T01:59:41.0"}, {"hrvValue": 38, "readingTimeGMT":
+ "2023-07-01T08:04:41.0", "readingTimeLocal": "2023-07-01T02:04:41.0"}, {"hrvValue":
+ 39, "readingTimeGMT": "2023-07-01T08:09:41.0", "readingTimeLocal": "2023-07-01T02:09:41.0"},
+ {"hrvValue": 45, "readingTimeGMT": "2023-07-01T08:14:41.0", "readingTimeLocal":
+ "2023-07-01T02:14:41.0"}, {"hrvValue": 40, "readingTimeGMT": "2023-07-01T08:19:41.0",
+ "readingTimeLocal": "2023-07-01T02:19:41.0"}, {"hrvValue": 30, "readingTimeGMT":
+ "2023-07-01T08:24:41.0", "readingTimeLocal": "2023-07-01T02:24:41.0"}, {"hrvValue":
+ 36, "readingTimeGMT": "2023-07-01T08:29:41.0", "readingTimeLocal": "2023-07-01T02:29:41.0"},
+ {"hrvValue": 27, "readingTimeGMT": "2023-07-01T08:34:41.0", "readingTimeLocal":
+ "2023-07-01T02:34:41.0"}, {"hrvValue": 33, "readingTimeGMT": "2023-07-01T08:39:41.0",
+ "readingTimeLocal": "2023-07-01T02:39:41.0"}, {"hrvValue": 29, "readingTimeGMT":
+ "2023-07-01T08:44:41.0", "readingTimeLocal": "2023-07-01T02:44:41.0"}, {"hrvValue":
+ 30, "readingTimeGMT": "2023-07-01T08:49:41.0", "readingTimeLocal": "2023-07-01T02:49:41.0"},
+ {"hrvValue": 29, "readingTimeGMT": "2023-07-01T08:54:41.0", "readingTimeLocal":
+ "2023-07-01T02:54:41.0"}, {"hrvValue": 37, "readingTimeGMT": "2023-07-01T08:59:41.0",
+ "readingTimeLocal": "2023-07-01T02:59:41.0"}, {"hrvValue": 47, "readingTimeGMT":
+ "2023-07-01T09:04:41.0", "readingTimeLocal": "2023-07-01T03:04:41.0"}, {"hrvValue":
+ 39, "readingTimeGMT": "2023-07-01T09:09:41.0", "readingTimeLocal": "2023-07-01T03:09:41.0"},
+ {"hrvValue": 38, "readingTimeGMT": "2023-07-01T09:14:41.0", "readingTimeLocal":
+ "2023-07-01T03:14:41.0"}, {"hrvValue": 42, "readingTimeGMT": "2023-07-01T09:19:41.0",
+ "readingTimeLocal": "2023-07-01T03:19:41.0"}, {"hrvValue": 35, "readingTimeGMT":
+ "2023-07-01T09:24:41.0", "readingTimeLocal": "2023-07-01T03:24:41.0"}, {"hrvValue":
+ 55, "readingTimeGMT": "2023-07-01T09:29:41.0", "readingTimeLocal": "2023-07-01T03:29:41.0"},
+ {"hrvValue": 50, "readingTimeGMT": "2023-07-01T09:34:41.0", "readingTimeLocal":
+ "2023-07-01T03:34:41.0"}, {"hrvValue": 41, "readingTimeGMT": "2023-07-01T09:39:41.0",
+ "readingTimeLocal": "2023-07-01T03:39:41.0"}, {"hrvValue": 57, "readingTimeGMT":
+ "2023-07-01T09:44:41.0", "readingTimeLocal": "2023-07-01T03:44:41.0"}, {"hrvValue":
+ 44, "readingTimeGMT": "2023-07-01T09:49:41.0", "readingTimeLocal": "2023-07-01T03:49:41.0"},
+ {"hrvValue": 36, "readingTimeGMT": "2023-07-01T09:54:41.0", "readingTimeLocal":
+ "2023-07-01T03:54:41.0"}, {"hrvValue": 41, "readingTimeGMT": "2023-07-01T09:59:41.0",
+ "readingTimeLocal": "2023-07-01T03:59:41.0"}, {"hrvValue": 47, "readingTimeGMT":
+ "2023-07-01T10:04:41.0", "readingTimeLocal": "2023-07-01T04:04:41.0"}, {"hrvValue":
+ 47, "readingTimeGMT": "2023-07-01T10:09:41.0", "readingTimeLocal": "2023-07-01T04:09:41.0"},
+ {"hrvValue": 40, "readingTimeGMT": "2023-07-01T10:14:41.0", "readingTimeLocal":
+ "2023-07-01T04:14:41.0"}, {"hrvValue": 28, "readingTimeGMT": "2023-07-01T10:19:41.0",
+ "readingTimeLocal": "2023-07-01T04:19:41.0"}, {"hrvValue": 33, "readingTimeGMT":
+ "2023-07-01T10:24:41.0", "readingTimeLocal": "2023-07-01T04:24:41.0"}, {"hrvValue":
+ 37, "readingTimeGMT": "2023-07-01T10:29:41.0", "readingTimeLocal": "2023-07-01T04:29:41.0"},
+ {"hrvValue": 50, "readingTimeGMT": "2023-07-01T10:34:41.0", "readingTimeLocal":
+ "2023-07-01T04:34:41.0"}, {"hrvValue": 37, "readingTimeGMT": "2023-07-01T10:39:41.0",
+ "readingTimeLocal": "2023-07-01T04:39:41.0"}, {"hrvValue": 41, "readingTimeGMT":
+ "2023-07-01T10:44:41.0", "readingTimeLocal": "2023-07-01T04:44:41.0"}, {"hrvValue":
+ 36, "readingTimeGMT": "2023-07-01T10:49:41.0", "readingTimeLocal": "2023-07-01T04:49:41.0"},
+ {"hrvValue": 60, "readingTimeGMT": "2023-07-01T10:54:41.0", "readingTimeLocal":
+ "2023-07-01T04:54:41.0"}, {"hrvValue": 51, "readingTimeGMT": "2023-07-01T10:59:41.0",
+ "readingTimeLocal": "2023-07-01T04:59:41.0"}, {"hrvValue": 46, "readingTimeGMT":
+ "2023-07-01T11:04:41.0", "readingTimeLocal": "2023-07-01T05:04:41.0"}, {"hrvValue":
+ 37, "readingTimeGMT": "2023-07-01T11:09:41.0", "readingTimeLocal": "2023-07-01T05:09:41.0"},
+ {"hrvValue": 36, "readingTimeGMT": "2023-07-01T11:14:41.0", "readingTimeLocal":
+ "2023-07-01T05:14:41.0"}, {"hrvValue": 33, "readingTimeGMT": "2023-07-01T11:19:41.0",
+ "readingTimeLocal": "2023-07-01T05:19:41.0"}, {"hrvValue": 50, "readingTimeGMT":
+ "2023-07-01T11:24:41.0", "readingTimeLocal": "2023-07-01T05:24:41.0"}], "startTimestampGMT":
+ "2023-07-01T06:40:00.0", "endTimestampGMT": "2023-07-01T11:24:41.0", "startTimestampLocal":
+ "2023-07-01T00:40:00.0", "endTimestampLocal": "2023-07-01T05:24:41.0", "sleepStartTimestampGMT":
+ "2023-07-01T06:40:00.0", "sleepEndTimestampGMT": "2023-07-01T11:26:00.0",
+ "sleepStartTimestampLocal": "2023-07-01T00:40:00.0", "sleepEndTimestampLocal":
+ "2023-07-01T05:26:00.0"}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8c22bc4fbeb6df-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 18:14:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=K26isoQlhIUy1i4ANfOL1efqqIjmSH6tUk0Hf7JP59UL6VBoYZUZHyEHOzZ8pQcjWsDoUlbaFPGxcqQv5DsngZN6Ji8WsQY%2BhHNjn5t2KGN0gY%2FnV2O0gHI95EvJoadReFAtn4Zbuw%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_hydration_data.yaml b/tests/cassettes/test_hydration_data.yaml
new file mode 100644
index 0000000..fe72eee
--- /dev/null
+++ b/tests/cassettes/test_hydration_data.yaml
@@ -0,0 +1,246 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f967101d861154b-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:15:22 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=463w%2FuByRoJKGUmocUIzMvqYeScq0aei%2FT%2Bd3Ggsl8BMIlsKs%2BrGflSDcKjqo8BYotrFMwj6emCZ2IQ1MjkbQJMEy0l%2FRVf%2By7eQougtqIicbH9d%2Fds9HGH35hYJTPLg7cTEndSWFA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f9671031c331547-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:15:22 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=zqBSaLvwHc8CP3rS73t37hqtQPhwoJiQ0NDoQe7uMlDezK8fIqj%2BcDNd1ZkQqcC4SlQoImjwIkDRFK4oMCblXf4iKPgV%2FOQAV%2B8VNUSKzSyYQpQKsYKaAj6bjAt2Z1QYlwA%2Fhx6Rmw%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/usersummary-service/usersummary/hydration/daily/2023-07-01
+ response:
+ body:
+ string: '{"userId": 2591602, "calendarDate": "2023-07-01", "valueInML": null,
+ "goalInML": 2800.0, "dailyAverageinML": null, "lastEntryTimestampLocal": null,
+ "sweatLossInML": null, "activityIntakeInML": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f967106dca5155f-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Sun, 20 Aug 2023 00:15:22 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=W0QFlPjJmMM%2FvZq47%2BpTK9Yu5mIQ0I88klxliwZmKHkGuibvBDOrovy8V8nfsoDWZroCHXBDJDD1lxuHapbfEtJetCyAPC1LzvaG3ETD3qjhCrvZjF7x%2F88teqWDnR%2F24les6bZuJA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_respiration_data.yaml b/tests/cassettes/test_respiration_data.yaml
new file mode 100644
index 0000000..5a171c5
--- /dev/null
+++ b/tests/cassettes/test_respiration_data.yaml
@@ -0,0 +1,461 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f967264a894469e-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:16:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Pplw7XeFrFBQGsxhQbxmv6CyYuk9Au5dsqX0wTdZOTBdwFHJIHPyHFODx%2BpAZvXGIlDawmircK1HaY6PEPvrrtS8dhI71CtDP%2BL6wnE7Zg3wfBaaMSALs4H%2BryDn4GMgQEA6q%2FxJmg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f9672657a45b6eb-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:16:18 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=5vujgX3CuoVNNop66LDmTlEIKzAwMQEOYVPexU9pSvaK9TDGrmKheF16geBIkb%2FMB0%2FrnXiSx%2F3%2BAeC1NTZi3v5AMfO727UmaRyrNxryz6nCDfIYsI4RdlD2cAO%2Fwnis%2FvgBT3%2FtEg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/daily/respiration/2023-07-01
+ response:
+ body:
+ string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-01", "startTimestampGMT":
+ "2023-07-01T06:00:00.0", "endTimestampGMT": "2023-07-02T06:00:00.0", "startTimestampLocal":
+ "2023-07-01T00:00:00.0", "endTimestampLocal": "2023-07-02T00:00:00.0", "sleepStartTimestampGMT":
+ "2023-07-01T06:40:00.0", "sleepEndTimestampGMT": "2023-07-01T11:26:00.0",
+ "sleepStartTimestampLocal": "2023-07-01T00:40:00.0", "sleepEndTimestampLocal":
+ "2023-07-01T05:26:00.0", "tomorrowSleepStartTimestampGMT": "2023-07-02T06:04:00.0",
+ "tomorrowSleepEndTimestampGMT": "2023-07-02T11:49:00.0", "tomorrowSleepStartTimestampLocal":
+ "2023-07-02T00:04:00.0", "tomorrowSleepEndTimestampLocal": "2023-07-02T05:49:00.0",
+ "lowestRespirationValue": 9.0, "highestRespirationValue": 21.0, "avgWakingRespirationValue":
+ 13.0, "avgSleepRespirationValue": 15.0, "avgTomorrowSleepRespirationValue":
+ 15.0, "respirationValueDescriptorsDTOList": [{"key": "timestamp", "index":
+ 0}, {"key": "respiration", "index": 1}], "respirationValuesArray": [[1688191320000,
+ 13.0], [1688191440000, 14.0], [1688191560000, 14.0], [1688191680000, 16.0],
+ [1688191800000, 17.0], [1688191920000, 15.0], [1688192040000, 15.0], [1688192160000,
+ 14.0], [1688192280000, 14.0], [1688192400000, 14.0], [1688192520000, 14.0],
+ [1688192640000, 13.0], [1688192760000, 13.0], [1688192880000, 13.0], [1688193000000,
+ 13.0], [1688193120000, 14.0], [1688193240000, 15.0], [1688193360000, 14.0],
+ [1688193480000, 17.0], [1688193600000, 17.0], [1688193720000, 15.0], [1688193840000,
+ 14.0], [1688193960000, 14.0], [1688194080000, 14.0], [1688194200000, 15.0],
+ [1688194320000, 14.0], [1688194440000, 13.0], [1688194560000, 16.0], [1688194680000,
+ 15.0], [1688194800000, 14.0], [1688194920000, 15.0], [1688195040000, 15.0],
+ [1688195160000, 15.0], [1688195280000, 15.0], [1688195400000, 15.0], [1688195520000,
+ 15.0], [1688195640000, 15.0], [1688195760000, 15.0], [1688195880000, 15.0],
+ [1688196000000, 15.0], [1688196120000, 15.0], [1688196240000, 15.0], [1688196360000,
+ 15.0], [1688196480000, 15.0], [1688196600000, 15.0], [1688196720000, 15.0],
+ [1688196840000, 15.0], [1688196960000, 15.0], [1688197080000, 15.0], [1688197200000,
+ 15.0], [1688197320000, 15.0], [1688197440000, 15.0], [1688197560000, 15.0],
+ [1688197680000, 14.0], [1688197800000, 14.0], [1688197920000, 13.0], [1688198040000,
+ 14.0], [1688198160000, 14.0], [1688198280000, 14.0], [1688198400000, 13.0],
+ [1688198520000, 15.0], [1688198640000, 14.0], [1688198760000, 15.0], [1688198880000,
+ 15.0], [1688199000000, 15.0], [1688199120000, 15.0], [1688199240000, 15.0],
+ [1688199360000, 15.0], [1688199480000, 15.0], [1688199600000, 16.0], [1688199720000,
+ 15.0], [1688199840000, 15.0], [1688199960000, 14.0], [1688200080000, 15.0],
+ [1688200200000, 14.0], [1688200320000, 15.0], [1688200440000, 15.0], [1688200560000,
+ 15.0], [1688200680000, 15.0], [1688200800000, 15.0], [1688200920000, 15.0],
+ [1688201040000, 15.0], [1688201160000, 15.0], [1688201280000, 15.0], [1688201400000,
+ 14.0], [1688201520000, 15.0], [1688201640000, 15.0], [1688201760000, 15.0],
+ [1688201880000, 15.0], [1688202000000, 14.0], [1688202120000, 14.0], [1688202240000,
+ 15.0], [1688202360000, 15.0], [1688202480000, 13.0], [1688202600000, 13.0],
+ [1688202720000, 14.0], [1688202840000, 14.0], [1688202960000, 14.0], [1688203080000,
+ 14.0], [1688203200000, 14.0], [1688203320000, 13.0], [1688203440000, 13.0],
+ [1688203560000, 13.0], [1688203680000, 13.0], [1688203800000, 15.0], [1688203920000,
+ 15.0], [1688204040000, 15.0], [1688204160000, 15.0], [1688204280000, 15.0],
+ [1688204400000, 14.0], [1688204520000, 14.0], [1688204640000, 14.0], [1688204760000,
+ 13.0], [1688204880000, 15.0], [1688205000000, 15.0], [1688205120000, 15.0],
+ [1688205240000, 15.0], [1688205360000, 15.0], [1688205480000, 15.0], [1688205600000,
+ 14.0], [1688205720000, 15.0], [1688205840000, 15.0], [1688205960000, 14.0],
+ [1688206080000, 14.0], [1688206200000, 15.0], [1688206320000, 15.0], [1688206440000,
+ 14.0], [1688206560000, 15.0], [1688206680000, 15.0], [1688206800000, 15.0],
+ [1688206920000, 15.0], [1688207040000, 14.0], [1688207160000, 14.0], [1688207280000,
+ 15.0], [1688207400000, 15.0], [1688207520000, 15.0], [1688207640000, 14.0],
+ [1688207760000, 14.0], [1688207880000, 15.0], [1688208000000, 15.0], [1688208120000,
+ 15.0], [1688208240000, 15.0], [1688208360000, 16.0], [1688208480000, 15.0],
+ [1688208600000, 14.0], [1688208720000, 14.0], [1688208840000, 15.0], [1688208960000,
+ 14.0], [1688209080000, 15.0], [1688209200000, 15.0], [1688209320000, 14.0],
+ [1688209440000, 12.0], [1688209560000, 12.0], [1688209680000, 14.0], [1688209800000,
+ 14.0], [1688209920000, 15.0], [1688210040000, 15.0], [1688210160000, 15.0],
+ [1688210280000, 15.0], [1688210400000, 14.0], [1688210520000, 14.0], [1688210640000,
+ 14.0], [1688210760000, 15.0], [1688210880000, 14.0], [1688211000000, -1.0],
+ [1688211120000, -1.0], [1688211240000, -1.0], [1688211360000, -1.0], [1688211480000,
+ 13.0], [1688211600000, 13.0], [1688211720000, 14.0], [1688211840000, 13.0],
+ [1688211960000, -1.0], [1688212080000, 14.0], [1688212200000, 14.0], [1688212320000,
+ 13.0], [1688212440000, 14.0], [1688212560000, 15.0], [1688212680000, 14.0],
+ [1688212800000, 16.0], [1688212920000, 14.0], [1688213040000, 14.0], [1688213160000,
+ 14.0], [1688213280000, 15.0], [1688213400000, 14.0], [1688213520000, 14.0],
+ [1688213640000, 16.0], [1688213760000, 16.0], [1688213880000, 15.0], [1688214000000,
+ 16.0], [1688214120000, 16.0], [1688214240000, 15.0], [1688214360000, 15.0],
+ [1688214480000, 15.0], [1688214600000, 13.0], [1688214720000, 12.0], [1688214840000,
+ 12.0], [1688214960000, 13.0], [1688215080000, 13.0], [1688215200000, 14.0],
+ [1688215320000, 14.0], [1688215440000, 13.0], [1688215560000, 13.0], [1688215680000,
+ 13.0], [1688215800000, 13.0], [1688215920000, 14.0], [1688216040000, 14.0],
+ [1688216160000, 14.0], [1688216280000, 13.0], [1688216400000, 13.0], [1688216520000,
+ 14.0], [1688216640000, 14.0], [1688216760000, 13.0], [1688216880000, 13.0],
+ [1688217000000, 12.0], [1688217120000, 12.0], [1688217240000, 13.0], [1688217360000,
+ 13.0], [1688217480000, 14.0], [1688217600000, 13.0], [1688217720000, 13.0],
+ [1688217840000, 13.0], [1688217960000, 10.0], [1688218080000, 11.0], [1688218200000,
+ 11.0], [1688218320000, 10.0], [1688218440000, -1.0], [1688218560000, 14.0],
+ [1688218680000, -1.0], [1688218800000, 14.0], [1688218920000, 14.0], [1688219040000,
+ 13.0], [1688219160000, -1.0], [1688219280000, 13.0], [1688219400000, 13.0],
+ [1688219520000, -1.0], [1688219640000, -1.0], [1688219760000, -1.0], [1688219880000,
+ 14.0], [1688220000000, 14.0], [1688220120000, 14.0], [1688220240000, 13.0],
+ [1688220360000, 13.0], [1688220480000, 13.0], [1688220600000, 12.0], [1688220720000,
+ -1.0], [1688220840000, -1.0], [1688220960000, -1.0], [1688221080000, -1.0],
+ [1688221200000, 13.0], [1688221320000, -1.0], [1688221440000, -1.0], [1688221560000,
+ -1.0], [1688221680000, 14.0], [1688221800000, -1.0], [1688221920000, -1.0],
+ [1688222040000, -1.0], [1688222160000, -1.0], [1688222280000, -1.0], [1688222400000,
+ -1.0], [1688222520000, 13.0], [1688222640000, 14.0], [1688222760000, 14.0],
+ [1688222880000, 14.0], [1688223000000, 14.0], [1688223120000, 15.0], [1688223240000,
+ -1.0], [1688223360000, -1.0], [1688223480000, -1.0], [1688223600000, 14.0],
+ [1688223720000, 14.0], [1688223840000, -1.0], [1688223960000, -1.0], [1688224080000,
+ 14.0], [1688224200000, -1.0], [1688224320000, 14.0], [1688224440000, 13.0],
+ [1688224560000, 13.0], [1688224680000, 14.0], [1688224800000, 14.0], [1688224920000,
+ 14.0], [1688225040000, 13.0], [1688225160000, 14.0], [1688225280000, 15.0],
+ [1688225400000, 14.0], [1688225520000, 13.0], [1688225640000, 14.0], [1688225760000,
+ 14.0], [1688225880000, -1.0], [1688226000000, -1.0], [1688226120000, 15.0],
+ [1688226240000, 14.0], [1688226360000, 13.0], [1688226480000, 14.0], [1688226600000,
+ -1.0], [1688226720000, -1.0], [1688226840000, -1.0], [1688226960000, 15.0],
+ [1688227080000, 15.0], [1688227200000, 13.0], [1688227320000, -1.0], [1688227440000,
+ -1.0], [1688227560000, -1.0], [1688227680000, -1.0], [1688227800000, -1.0],
+ [1688227920000, -1.0], [1688228040000, -1.0], [1688228160000, -1.0], [1688228280000,
+ 13.0], [1688228400000, 12.0], [1688228520000, 12.0], [1688228640000, 13.0],
+ [1688228760000, 13.0], [1688228880000, 14.0], [1688229000000, 13.0], [1688229120000,
+ 12.0], [1688229240000, -1.0], [1688229360000, 13.0], [1688229480000, 13.0],
+ [1688229600000, 13.0], [1688229720000, 13.0], [1688229840000, -1.0], [1688229960000,
+ 14.0], [1688230080000, -1.0], [1688230200000, 12.0], [1688230320000, -1.0],
+ [1688230440000, -1.0], [1688230560000, -1.0], [1688230680000, -1.0], [1688230800000,
+ -1.0], [1688230920000, 14.0], [1688231040000, 14.0], [1688231160000, 12.0],
+ [1688231280000, 13.0], [1688231400000, 14.0], [1688231520000, -1.0], [1688231640000,
+ 14.0], [1688231760000, 13.0], [1688231880000, 13.0], [1688232000000, 14.0],
+ [1688232120000, 14.0], [1688232240000, 14.0], [1688232360000, 14.0], [1688232480000,
+ 14.0], [1688232600000, 14.0], [1688232720000, 14.0], [1688232840000, 14.0],
+ [1688232960000, 13.0], [1688233080000, 14.0], [1688233200000, 13.0], [1688233320000,
+ 13.0], [1688233440000, 14.0], [1688233560000, 14.0], [1688233680000, 14.0],
+ [1688233800000, 14.0], [1688233920000, 14.0], [1688234040000, 13.0], [1688234160000,
+ 13.0], [1688234280000, 14.0], [1688234400000, 14.0], [1688234520000, 14.0],
+ [1688234640000, 13.0], [1688234760000, 14.0], [1688234880000, 14.0], [1688235000000,
+ 15.0], [1688235120000, 14.0], [1688235240000, 13.0], [1688235360000, 12.0],
+ [1688235480000, 12.0], [1688235600000, 15.0], [1688235720000, 15.0], [1688235840000,
+ 13.0], [1688235960000, 13.0], [1688236080000, 13.0], [1688236200000, 13.0],
+ [1688236320000, 13.0], [1688236440000, 13.0], [1688236560000, 14.0], [1688236680000,
+ 14.0], [1688236800000, 14.0], [1688236920000, 15.0], [1688237040000, 13.0],
+ [1688237160000, 13.0], [1688237280000, 14.0], [1688237400000, 13.0], [1688237520000,
+ 13.0], [1688237640000, 14.0], [1688237760000, 14.0], [1688237880000, 13.0],
+ [1688238000000, -1.0], [1688238120000, -1.0], [1688238240000, -1.0], [1688238360000,
+ -1.0], [1688238480000, -1.0], [1688238600000, -1.0], [1688238720000, -1.0],
+ [1688238840000, -1.0], [1688238960000, 16.0], [1688239080000, 16.0], [1688239200000,
+ -2.0], [1688239320000, -2.0], [1688239440000, -1.0], [1688239560000, -1.0],
+ [1688239680000, -1.0], [1688239800000, 14.0], [1688239920000, 14.0], [1688240040000,
+ -1.0], [1688240160000, -1.0], [1688240280000, -1.0], [1688240400000, -1.0],
+ [1688240520000, 13.0], [1688240640000, 13.0], [1688240760000, 13.0], [1688240880000,
+ -1.0], [1688241000000, -1.0], [1688241120000, -1.0], [1688241240000, -1.0],
+ [1688241360000, -1.0], [1688241480000, 14.0], [1688241600000, 15.0], [1688241720000,
+ 14.0], [1688241840000, 14.0], [1688241960000, 13.0], [1688242080000, 12.0],
+ [1688242200000, 13.0], [1688242320000, 13.0], [1688242440000, 13.0], [1688242560000,
+ 14.0], [1688242680000, 14.0], [1688242800000, 13.0], [1688242920000, 13.0],
+ [1688243040000, -1.0], [1688243160000, -1.0], [1688243280000, 13.0], [1688243400000,
+ 13.0], [1688243520000, 14.0], [1688243640000, 13.0], [1688243760000, 14.0],
+ [1688243880000, 12.0], [1688244000000, 12.0], [1688244120000, 13.0], [1688244240000,
+ 14.0], [1688244360000, 13.0], [1688244480000, -1.0], [1688244600000, 13.0],
+ [1688244720000, 15.0], [1688244840000, -1.0], [1688244960000, 14.0], [1688245080000,
+ 14.0], [1688245200000, 13.0], [1688245320000, 13.0], [1688245440000, -1.0],
+ [1688245560000, -1.0], [1688245680000, -1.0], [1688245800000, 13.0], [1688245920000,
+ 14.0], [1688246040000, 14.0], [1688246160000, 14.0], [1688246280000, 13.0],
+ [1688246400000, 13.0], [1688246520000, 13.0], [1688246640000, 13.0], [1688246760000,
+ 13.0], [1688246880000, 13.0], [1688247000000, 13.0], [1688247120000, 13.0],
+ [1688247240000, 14.0], [1688247360000, 13.0], [1688247480000, 12.0], [1688247600000,
+ 11.0], [1688247720000, 12.0], [1688247840000, -1.0], [1688247960000, -1.0],
+ [1688248080000, -1.0], [1688248200000, 14.0], [1688248320000, 13.0], [1688248440000,
+ 13.0], [1688248560000, -1.0], [1688248680000, -1.0], [1688248800000, 14.0],
+ [1688248920000, 13.0], [1688249040000, 12.0], [1688249160000, 12.0], [1688249280000,
+ -1.0], [1688249400000, -1.0], [1688249520000, 14.0], [1688249640000, 14.0],
+ [1688249760000, 13.0], [1688249880000, 13.0], [1688250000000, -1.0], [1688250120000,
+ -1.0], [1688250240000, -1.0], [1688250360000, -1.0], [1688250480000, 13.0],
+ [1688250600000, 13.0], [1688250720000, -1.0], [1688250840000, -1.0], [1688250960000,
+ -1.0], [1688251080000, 12.0], [1688251200000, 13.0], [1688251320000, -1.0],
+ [1688251440000, -1.0], [1688251560000, 14.0], [1688251680000, 13.0], [1688251800000,
+ 14.0], [1688251920000, 13.0], [1688252040000, 14.0], [1688252160000, -1.0],
+ [1688252280000, 14.0], [1688252400000, 13.0], [1688252520000, -1.0], [1688252640000,
+ 13.0], [1688252760000, 13.0], [1688252880000, 13.0], [1688253000000, -1.0],
+ [1688253120000, 13.0], [1688253240000, -1.0], [1688253360000, -1.0], [1688253480000,
+ -1.0], [1688253600000, -1.0], [1688253720000, 13.0], [1688253840000, 13.0],
+ [1688253960000, 13.0], [1688254080000, 13.0], [1688254200000, 14.0], [1688254320000,
+ -1.0], [1688254440000, -1.0], [1688254560000, 14.0], [1688254680000, -1.0],
+ [1688254800000, -1.0], [1688254920000, -1.0], [1688255040000, -1.0], [1688255160000,
+ 13.0], [1688255280000, -1.0], [1688255400000, 14.0], [1688255520000, -1.0],
+ [1688255640000, -1.0], [1688255760000, -1.0], [1688255880000, -1.0], [1688256000000,
+ 12.0], [1688256120000, 14.0], [1688256240000, 14.0], [1688256360000, 13.0],
+ [1688256480000, 12.0], [1688256600000, 15.0], [1688256720000, 20.0], [1688256840000,
+ 21.0], [1688256960000, 21.0], [1688257080000, 21.0], [1688257200000, 20.0],
+ [1688257320000, 18.0], [1688257440000, 16.0], [1688257560000, 14.0], [1688257680000,
+ 13.0], [1688257800000, 13.0], [1688257920000, 13.0], [1688258040000, 13.0],
+ [1688258160000, 13.0], [1688258280000, 12.0], [1688258400000, 12.0], [1688258520000,
+ 13.0], [1688258640000, 12.0], [1688258760000, 11.0], [1688258880000, 11.0],
+ [1688259000000, 13.0], [1688259120000, -1.0], [1688259240000, 14.0], [1688259360000,
+ 13.0], [1688259480000, 13.0], [1688259600000, 12.0], [1688259720000, 13.0],
+ [1688259840000, -1.0], [1688259960000, 13.0], [1688260080000, 14.0], [1688260200000,
+ 13.0], [1688260320000, 13.0], [1688260440000, 13.0], [1688260560000, 12.0],
+ [1688260680000, 13.0], [1688260800000, 13.0], [1688260920000, 13.0], [1688261040000,
+ 12.0], [1688261160000, 13.0], [1688261280000, 11.0], [1688261400000, 10.0],
+ [1688261520000, 11.0], [1688261640000, 13.0], [1688261760000, 14.0], [1688261880000,
+ 13.0], [1688262000000, 14.0], [1688262120000, -1.0], [1688262240000, -1.0],
+ [1688262360000, 13.0], [1688262480000, 14.0], [1688262600000, -1.0], [1688262720000,
+ 13.0], [1688262840000, 13.0], [1688262960000, -1.0], [1688263080000, -1.0],
+ [1688263200000, 12.0], [1688263320000, 14.0], [1688263440000, 14.0], [1688263560000,
+ 13.0], [1688263680000, 12.0], [1688263800000, 12.0], [1688263920000, 13.0],
+ [1688264040000, 13.0], [1688264160000, 13.0], [1688264280000, 13.0], [1688264400000,
+ 13.0], [1688264520000, 13.0], [1688264640000, 13.0], [1688264760000, 12.0],
+ [1688264880000, 12.0], [1688265000000, -1.0], [1688265120000, 14.0], [1688265240000,
+ 14.0], [1688265360000, 15.0], [1688265480000, 14.0], [1688265600000, 13.0],
+ [1688265720000, 13.0], [1688265840000, 13.0], [1688265960000, 13.0], [1688266080000,
+ 13.0], [1688266200000, 13.0], [1688266320000, 13.0], [1688266440000, 13.0],
+ [1688266560000, 13.0], [1688266680000, 13.0], [1688266800000, 13.0], [1688266920000,
+ 13.0], [1688267040000, 12.0], [1688267160000, 13.0], [1688267280000, 12.0],
+ [1688267400000, 11.0], [1688267520000, 10.0], [1688267640000, 10.0], [1688267760000,
+ 10.0], [1688267880000, 13.0], [1688268000000, 13.0], [1688268120000, 12.0],
+ [1688268240000, 12.0], [1688268360000, 14.0], [1688268480000, 14.0], [1688268600000,
+ 13.0], [1688268720000, 13.0], [1688268840000, 13.0], [1688268960000, 12.0],
+ [1688269080000, 12.0], [1688269200000, -1.0], [1688269320000, -1.0], [1688269440000,
+ -1.0], [1688269560000, -1.0], [1688269680000, -1.0], [1688269800000, 12.0],
+ [1688269920000, 13.0], [1688270040000, -1.0], [1688270160000, -1.0], [1688270280000,
+ 13.0], [1688270400000, 12.0], [1688270520000, 13.0], [1688270640000, 13.0],
+ [1688270760000, 13.0], [1688270880000, 14.0], [1688271000000, 13.0], [1688271120000,
+ 14.0], [1688271240000, 13.0], [1688271360000, 13.0], [1688271480000, 13.0],
+ [1688271600000, 12.0], [1688271720000, 13.0], [1688271840000, 14.0], [1688271960000,
+ 15.0], [1688272080000, 15.0], [1688272200000, 13.0], [1688272320000, 13.0],
+ [1688272440000, 13.0], [1688272560000, 13.0], [1688272680000, 14.0], [1688272800000,
+ 14.0], [1688272920000, 14.0], [1688273040000, 13.0], [1688273160000, 14.0],
+ [1688273280000, 13.0], [1688273400000, 12.0], [1688273520000, 13.0], [1688273640000,
+ 13.0], [1688273760000, 14.0], [1688273880000, 13.0], [1688274000000, 13.0],
+ [1688274120000, 14.0], [1688274240000, -1.0], [1688274360000, -1.0], [1688274480000,
+ 14.0], [1688274600000, -1.0], [1688274720000, -1.0], [1688274840000, -1.0],
+ [1688274960000, -1.0], [1688275080000, -1.0], [1688275200000, -1.0], [1688275320000,
+ -1.0], [1688275440000, -1.0], [1688275560000, 14.0], [1688275680000, 14.0],
+ [1688275800000, 14.0], [1688275920000, 15.0], [1688276040000, 14.0], [1688276160000,
+ 14.0], [1688276280000, -1.0], [1688276400000, 12.0], [1688276520000, -1.0],
+ [1688276640000, 11.0], [1688276760000, 10.0], [1688276880000, 11.0], [1688277000000,
+ -1.0], [1688277120000, 14.0], [1688277240000, -1.0], [1688277360000, 14.0],
+ [1688277480000, 16.0], [1688277600000, 18.0]]}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f9672679aa046e0-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:16:19 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=tIdu9j1vxLwcmOid2VTk%2FceMF0Fz09w5lrJ1ksDogFxdYKEka2JL6V3hM6y7gq0B2MnIbz9Y3X95pO4FYoFx49MKQZflS7VvXmKMtGQr1hUAZJ6rsxDehQfEtNONIQWKA8jSCYbhIg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_spo2_data.yaml b/tests/cassettes/test_spo2_data.yaml
new file mode 100644
index 0000000..b5d4fa1
--- /dev/null
+++ b/tests/cassettes/test_spo2_data.yaml
@@ -0,0 +1,261 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f96736daf684654-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:17:01 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=8hCnedDFmiM8JESa95GBirj4lArL2UXpU0KwC2gVYU3RYcM%2B0nwXKnMDNu9pVkGr%2FeJxTSYq6P7DvEMgBMim08CRZWnwrRmrr5X3gk90UQ4KtsWoLpGum83XHfS4%2B9Umi%2B6ecrhYFA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f96736f6f5c46cb-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:17:01 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=yMear%2F3f3GXlzgu943Sz3ohKAGgRVM3hJhJSUve6tbFeDHwUZEc4rdCK1BpAgtN%2BOj%2BlVeGJ8kqjxxewjevaDx75HCQwa9kJlzP8NX%2FU1R5o6Zgg65ZA0iTN2Mr7SSKnKrTpLTUHxA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/daily/spo2/2023-07-01
+ response:
+ body:
+ string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-01", "startTimestampGMT":
+ "2023-07-01T06:00:00.0", "endTimestampGMT": "2023-07-02T06:00:00.0", "startTimestampLocal":
+ "2023-07-01T00:00:00.0", "endTimestampLocal": "2023-07-02T00:00:00.0", "sleepStartTimestampGMT":
+ "2023-07-01T06:40:00.0", "sleepEndTimestampGMT": "2023-07-01T11:26:00.0",
+ "sleepStartTimestampLocal": "2023-07-01T00:40:00.0", "sleepEndTimestampLocal":
+ "2023-07-01T05:26:00.0", "tomorrowSleepStartTimestampGMT": "2023-07-02T06:04:00.0",
+ "tomorrowSleepEndTimestampGMT": "2023-07-02T11:49:00.0", "tomorrowSleepStartTimestampLocal":
+ "2023-07-02T00:04:00.0", "tomorrowSleepEndTimestampLocal": "2023-07-02T05:49:00.0",
+ "averageSpO2": 92.0, "lowestSpO2": 85, "lastSevenDaysAvgSpO2": 92.14285714285714,
+ "latestSpO2": 86, "latestSpO2TimestampGMT": "2023-07-02T06:00:00.0", "latestSpO2TimestampLocal":
+ "2023-07-02T00:00:00.0", "avgSleepSpO2": 91.0, "avgTomorrowSleepSpO2": 91.0,
+ "spO2ValueDescriptorsDTOList": [{"spo2ValueDescriptorIndex": 0, "spo2ValueDescriptorKey":
+ "timestamp"}, {"spo2ValueDescriptorIndex": 1, "spo2ValueDescriptorKey": "spo2Reading"},
+ {"spo2ValueDescriptorIndex": 2, "spo2ValueDescriptorKey": "singleReadingPlottable"}],
+ "spO2SingleValues": null, "continuousReadingDTOList": null, "spO2HourlyAverages":
+ [[1688191200000, 93], [1688194800000, 92], [1688198400000, 92], [1688202000000,
+ 91], [1688205600000, 92], [1688209200000, 92], [1688212800000, 95], [1688270400000,
+ 92], [1688274000000, 91]]}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f9673718c4e4612-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Sun, 20 Aug 2023 00:17:01 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=4V3RixJ95rW1%2FJhQSQVcbd9qjYC6BEbSquD1EWxS7UYGNW9u2BCAOZjGQzknvJvD29LUUQ6wLLO6yz3WS2tgCV8QpDbuJtqY%2Fww%2BLJHPZ5QJOTYprKzzieFQMixW%2FyTKOdp9hwJL4A%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_stats.yaml b/tests/cassettes/test_stats.yaml
new file mode 100644
index 0000000..ffc753c
--- /dev/null
+++ b/tests/cassettes/test_stats.yaml
@@ -0,0 +1,380 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.31.0
+ method: GET
+ uri: https://thegarth.s3.amazonaws.com/oauth_consumer.json
+ response:
+ body:
+ string: '{"consumer_key": "SANITIZED", "consumer_secret": "SANITIZED"}'
+ headers:
+ Accept-Ranges:
+ - bytes
+ Content-Length:
+ - '124'
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 03:34:01 GMT
+ ETag:
+ - '"20240b1013cb35419bb5b2cff1407a4e"'
+ Last-Modified:
+ - Thu, 03 Aug 2023 00:16:11 GMT
+ Server:
+ - AmazonS3
+ x-amz-id-2:
+ - R7zVwQSGGFYnP/OaY5q6xyh3Gcgk3e9AhBYN1UyITG30CzhxyN27iyRBAY3DYZT+X57gwzt/duk=
+ x-amz-request-id:
+ - 36Y608PXR8QXGSCH
+ x-amz-server-side-encryption:
+ - AES256
+ status:
+ code: 200
+ message: OK
+- request:
+ body: mfa_token=MFA-2032-l6G3RaeR91x4hBZoFvG6onbHvYrSMYAerVc0duF7pywYWLiub1-cas
+ headers:
+ Accept:
+ - !!binary |
+ Ki8q
+ Accept-Encoding:
+ - !!binary |
+ Z3ppcCwgZGVmbGF0ZQ==
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - !!binary |
+ a2VlcC1hbGl2ZQ==
+ Content-Length:
+ - '73'
+ Content-Type:
+ - !!binary |
+ YXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVk
+ User-Agent:
+ - !!binary |
+ Y29tLmdhcm1pbi5hbmRyb2lkLmFwcHMuY29ubmVjdG1vYmlsZQ==
+ method: POST
+ uri: https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0
+ response:
+ body:
+ string: '{"scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ
+ GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE
+ GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ
+ GCOFFER_READ CONNECT_READ ATP_WRITE", "jti": "SANITIZED", "access_token":
+ "SANITIZED", "token_type": "Bearer", "refresh_token": "SANITIZED", "expires_in":
+ 102053, "refresh_token_expires_in": 2591999}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f87193f28d7467d-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 03:34:01 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=nco%2B%2FkuMyKpMVxzT%2FJAxyVOW%2Fe8ZAHQ1AHfWNJrofA4xi4D1BSDjkBc9%2FPwlsJIMqgDvh7V6U%2FXvVQg7KfEn53ybKccuRCsgWjrBlXlYJonF5XEVndVSsRGi7zFYiG9kZWLFDj2Yng%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Set-Cookie:
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8719444b88b6ee-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 03:34:01 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FHLUJ6gclJGE2hmU8io0iG5H7mnXiBH%2F0Kxif0JKQQ99ved77vTp3Uu6GnZi1VK5IJBsD7mvDmjuLGLGOtiiVp7ApzQsRlFSLOBPYA5dHnzWKutMrPFA72ot2TqnW6D%2F8alV6614Cg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f871946bb28464e-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 03:34:02 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=XuM%2FJWbKUjA%2FxEp%2BjovlwckL59G0zsCqCzBoXSbRZuDGgTSkCADoAs%2FrM6Ah7k8VkHXkbYt%2B5YWdZBfqgOFk2FjST9SJUXnkpF8bya7yZnwW10iaKxfpNmy0GXAeVt1wJeF4yfYYqA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/usersummary-service/usersummary/daily/mtamizi?calendarDate=2023-07-01
+ response:
+ body:
+ string: '{"userProfileId": 2591602, "totalKilocalories": 2498.0, "activeKilocalories":
+ 370.0, "bmrKilocalories": 2128.0, "wellnessKilocalories": 2498.0, "burnedKilocalories":
+ null, "consumedKilocalories": null, "remainingKilocalories": 2498.0, "totalSteps":
+ 12413, "netCalorieGoal": null, "totalDistanceMeters": 10368, "wellnessDistanceMeters":
+ 10368, "wellnessActiveKilocalories": 370.0, "netRemainingKilocalories": 370.0,
+ "userDailySummaryId": 2591602, "calendarDate": "2023-07-01", "rule": {"typeId":
+ 2, "typeKey": "private"}, "uuid": "08064eebd5f64e299745fce9e3ae5c19", "dailyStepGoal":
+ 7950, "totalPushDistance": 0, "totalPushes": 0, "wellnessStartTimeGmt": "2023-07-01T06:00:00.0",
+ "wellnessStartTimeLocal": "2023-07-01T00:00:00.0", "wellnessEndTimeGmt": "2023-07-02T06:00:00.0",
+ "wellnessEndTimeLocal": "2023-07-02T00:00:00.0", "durationInMilliseconds":
+ 86400000, "wellnessDescription": null, "highlyActiveSeconds": 768, "activeSeconds":
+ 10219, "sedentarySeconds": 58253, "sleepingSeconds": 17160, "includesWellnessData":
+ true, "includesActivityData": false, "includesCalorieConsumedData": false,
+ "privacyProtected": false, "moderateIntensityMinutes": 0, "vigorousIntensityMinutes":
+ 0, "floorsAscendedInMeters": 90.802, "floorsDescendedInMeters": 88.567, "floorsAscended":
+ 29.79068, "floorsDescended": 29.05741, "intensityMinutesGoal": 150, "userFloorsAscendedGoal":
+ 35, "minHeartRate": 48, "maxHeartRate": 107, "restingHeartRate": 51, "lastSevenDaysAvgRestingHeartRate":
+ 49, "source": "GARMIN", "averageStressLevel": 35, "maxStressLevel": 86, "stressDuration":
+ 36120, "restStressDuration": 27780, "activityStressDuration": 17400, "uncategorizedStressDuration":
+ 5040, "totalStressDuration": 86340, "lowStressDuration": 21780, "mediumStressDuration":
+ 12660, "highStressDuration": 1680, "stressPercentage": 41.83, "restStressPercentage":
+ 32.18, "activityStressPercentage": 20.15, "uncategorizedStressPercentage":
+ 5.84, "lowStressPercentage": 25.23, "mediumStressPercentage": 14.66, "highStressPercentage":
+ 1.95, "stressQualifier": "STRESSFUL", "measurableAwakeDuration": 64260, "measurableAsleepDuration":
+ 17040, "lastSyncTimestampGMT": null, "minAvgHeartRate": 49, "maxAvgHeartRate":
+ 106, "bodyBatteryChargedValue": 43, "bodyBatteryDrainedValue": 43, "bodyBatteryHighestValue":
+ 48, "bodyBatteryLowestValue": 5, "bodyBatteryMostRecentValue": 5, "bodyBatteryVersion":
+ 2.0, "abnormalHeartRateAlertsCount": null, "averageSpo2": 92.0, "lowestSpo2":
+ 85, "latestSpo2": 86, "latestSpo2ReadingTimeGmt": "2023-07-02T06:00:00.0",
+ "latestSpo2ReadingTimeLocal": "2023-07-02T00:00:00.0", "averageMonitoringEnvironmentAltitude":
+ 2254.0, "restingCaloriesFromActivity": null, "avgWakingRespirationValue":
+ 13.0, "highestRespirationValue": 21.0, "lowestRespirationValue": 9.0, "latestRespirationValue":
+ 18.0, "latestRespirationTimeGMT": "2023-07-02T06:00:00.0"}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f87194bfc474630-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 03:34:02 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=tuYDE5sNjkqgtYm%2Fxb9mKn7QlL0LOZE0mFIH09bXZa9UMyHN3G62ptc5H4P8asYIyOpeeA0veLeCpMXfY%2Bc96FrojM6fTw16LnIf%2BrW%2BWCrnbVHkD1%2BMePyd%2FhsJWeXjCMScUqkntg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_stats_and_body.yaml b/tests/cassettes/test_stats_and_body.yaml
new file mode 100644
index 0000000..58fa0b4
--- /dev/null
+++ b/tests/cassettes/test_stats_and_body.yaml
@@ -0,0 +1,341 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/socialProfile
+ response:
+ body:
+ string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
+ "displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
+ "profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
+ "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
+ "profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
+ "profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
+ "location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
+ null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
+ null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
+ 0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
+ 0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
+ "private", "activityMapVisibility": "public", "courseVisibility": "public",
+ "activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
+ "badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
+ false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
+ "showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
+ "showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
+ false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
+ false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
+ null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
+ "SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
+ "SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
+ "SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
+ "SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
+ "SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
+ "ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
+ "nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
+ true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
+ "userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
+ "levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
+ "userPro": false}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a56095b71b6e7-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 12:59:48 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=xnumb7924cZnXVSAdsgVPkUyq4aBAZa%2BzoO3Fg%2FyKNqahpZsaKnF2Nj2q4rjgRfCbcjVyIhh1QOGJ4bF54hFZLJ%2FXDvVNkSCixzHaiB0NYlCUosW%2FnpqZ9qzSlK04O11fOMhncqjLA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a560a29521556-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 12:59:48 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=9rAywogINvfA%2FLcuCArFPR%2Fb03HHA%2BgJetJMJ13SBPSBezpQ7Ix%2Bv1hHyY4vIzAOszB0BxbPnhqPZwTBOamu6uYEc6ktsgDx6%2FZw21w6a6Iw64vSxI106onMPYR19Hdu2XjTQdwdBQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/usersummary-service/usersummary/daily/mtamizi?calendarDate=2023-07-01
+ response:
+ body:
+ string: '{"userProfileId": 2591602, "totalKilocalories": 2498.0, "activeKilocalories":
+ 370.0, "bmrKilocalories": 2128.0, "wellnessKilocalories": 2498.0, "burnedKilocalories":
+ null, "consumedKilocalories": null, "remainingKilocalories": 2498.0, "totalSteps":
+ 12413, "netCalorieGoal": null, "totalDistanceMeters": 10368, "wellnessDistanceMeters":
+ 10368, "wellnessActiveKilocalories": 370.0, "netRemainingKilocalories": 370.0,
+ "userDailySummaryId": 2591602, "calendarDate": "2023-07-01", "rule": {"typeId":
+ 2, "typeKey": "private"}, "uuid": "08064eebd5f64e299745fce9e3ae5c19", "dailyStepGoal":
+ 7950, "totalPushDistance": 0, "totalPushes": 0, "wellnessStartTimeGmt": "2023-07-01T06:00:00.0",
+ "wellnessStartTimeLocal": "2023-07-01T00:00:00.0", "wellnessEndTimeGmt": "2023-07-02T06:00:00.0",
+ "wellnessEndTimeLocal": "2023-07-02T00:00:00.0", "durationInMilliseconds":
+ 86400000, "wellnessDescription": null, "highlyActiveSeconds": 768, "activeSeconds":
+ 10219, "sedentarySeconds": 58253, "sleepingSeconds": 17160, "includesWellnessData":
+ true, "includesActivityData": false, "includesCalorieConsumedData": false,
+ "privacyProtected": false, "moderateIntensityMinutes": 0, "vigorousIntensityMinutes":
+ 0, "floorsAscendedInMeters": 90.802, "floorsDescendedInMeters": 88.567, "floorsAscended":
+ 29.79068, "floorsDescended": 29.05741, "intensityMinutesGoal": 150, "userFloorsAscendedGoal":
+ 35, "minHeartRate": 48, "maxHeartRate": 107, "restingHeartRate": 51, "lastSevenDaysAvgRestingHeartRate":
+ 49, "source": "GARMIN", "averageStressLevel": 35, "maxStressLevel": 86, "stressDuration":
+ 36120, "restStressDuration": 27780, "activityStressDuration": 17400, "uncategorizedStressDuration":
+ 5040, "totalStressDuration": 86340, "lowStressDuration": 21780, "mediumStressDuration":
+ 12660, "highStressDuration": 1680, "stressPercentage": 41.83, "restStressPercentage":
+ 32.18, "activityStressPercentage": 20.15, "uncategorizedStressPercentage":
+ 5.84, "lowStressPercentage": 25.23, "mediumStressPercentage": 14.66, "highStressPercentage":
+ 1.95, "stressQualifier": "STRESSFUL", "measurableAwakeDuration": 64260, "measurableAsleepDuration":
+ 17040, "lastSyncTimestampGMT": null, "minAvgHeartRate": 49, "maxAvgHeartRate":
+ 106, "bodyBatteryChargedValue": 43, "bodyBatteryDrainedValue": 43, "bodyBatteryHighestValue":
+ 48, "bodyBatteryLowestValue": 5, "bodyBatteryMostRecentValue": 5, "bodyBatteryVersion":
+ 2.0, "abnormalHeartRateAlertsCount": null, "averageSpo2": 92.0, "lowestSpo2":
+ 85, "latestSpo2": 86, "latestSpo2ReadingTimeGmt": "2023-07-02T06:00:00.0",
+ "latestSpo2ReadingTimeLocal": "2023-07-02T00:00:00.0", "averageMonitoringEnvironmentAltitude":
+ 2254.0, "restingCaloriesFromActivity": null, "avgWakingRespirationValue":
+ 13.0, "highestRespirationValue": 21.0, "lowestRespirationValue": 9.0, "latestRespirationValue":
+ 18.0, "latestRespirationTimeGMT": "2023-07-02T06:00:00.0"}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a560b1e471557-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 12:59:48 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Pop5dpRgSkel9wBP3m0EVt%2Br9RHJcHUMoAkg9SteHwaj%2BVVCsDzaCRLCtaroyZ3%2F4Ckqr7sWT91zwPzbg64rN9M%2FYPag%2Bb630vreTUeGLer7TjX38HjbOfHVFstRah9k1QoEIJ7vbg%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/weight-service/weight/dateRange?startDate=2023-07-01&endDate=2023-07-01
+ response:
+ body:
+ string: '{"startDate": "2023-07-01", "endDate": "2023-07-01", "dateWeightList":
+ [], "totalAverage": {"from": 1688169600000, "until": 1688255999999, "weight":
+ null, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass": null, "muscleMass":
+ null, "physiqueRating": null, "visceralFat": null, "metabolicAge": null}}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f8a560c6b6fb6e5-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 12:59:48 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=ic7LyCpt86r5D2o6Dcd5K4S3vghREmCTp40Kmrbtjj7bQCyf2PBh%2BOuSc9xQILHTQ2edEFtVHqfX7U7V4NVbCti0BxAUCIc9JI6MhFpzRWn9y%2FUvnuPREox17qs7HQ%2BAIoIiz1nVDA%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_steps_data.yaml b/tests/cassettes/test_steps_data.yaml
new file mode 100644
index 0000000..87f12a3
--- /dev/null
+++ b/tests/cassettes/test_steps_data.yaml
@@ -0,0 +1,336 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f873ddaf815b6ed-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 03:59:00 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=3dTHCTMSRwi%2F0Ahvm1maSWvDJOjAMveznEpyN%2F5DWLcjzHP0hXHS8tVKpiaAIW42ziwHj7E52yQC4Jt7KwGiUFCdbb2gqizHjlMVST8OzUSSwWhmJf3ljzr7FkpgoOMoboppJ7mBYQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/wellness-service/wellness/dailySummaryChart/mtamizi?date=2023-07-01
+ response:
+ body:
+ string: '[{"startGMT": "2023-07-01T06:00:00.0", "endGMT": "2023-07-01T06:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T06:15:00.0", "endGMT": "2023-07-01T06:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T06:30:00.0", "endGMT": "2023-07-01T06:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T06:45:00.0", "endGMT": "2023-07-01T07:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T07:00:00.0", "endGMT": "2023-07-01T07:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T07:15:00.0", "endGMT": "2023-07-01T07:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T07:30:00.0", "endGMT": "2023-07-01T07:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T07:45:00.0", "endGMT": "2023-07-01T08:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T08:00:00.0", "endGMT": "2023-07-01T08:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T08:15:00.0", "endGMT": "2023-07-01T08:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T08:30:00.0", "endGMT": "2023-07-01T08:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T08:45:00.0", "endGMT": "2023-07-01T09:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T09:00:00.0", "endGMT": "2023-07-01T09:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T09:15:00.0", "endGMT": "2023-07-01T09:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T09:30:00.0", "endGMT": "2023-07-01T09:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T09:45:00.0", "endGMT": "2023-07-01T10:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T10:00:00.0", "endGMT": "2023-07-01T10:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T10:15:00.0", "endGMT": "2023-07-01T10:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T10:30:00.0", "endGMT": "2023-07-01T10:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T10:45:00.0", "endGMT": "2023-07-01T11:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T11:00:00.0", "endGMT": "2023-07-01T11:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T11:15:00.0", "endGMT": "2023-07-01T11:30:00.0",
+ "steps": 56, "pushes": 0, "primaryActivityLevel": "sleeping", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T11:30:00.0", "endGMT": "2023-07-01T11:45:00.0",
+ "steps": 406, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T11:45:00.0", "endGMT": "2023-07-01T12:00:00.0",
+ "steps": 27, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T12:00:00.0", "endGMT": "2023-07-01T12:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T12:15:00.0", "endGMT": "2023-07-01T12:30:00.0",
+ "steps": 39, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T12:30:00.0", "endGMT": "2023-07-01T12:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "highlyActive", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T12:45:00.0", "endGMT": "2023-07-01T13:00:00.0",
+ "steps": 35, "pushes": 0, "primaryActivityLevel": "highlyActive", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T13:00:00.0", "endGMT": "2023-07-01T13:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T13:15:00.0", "endGMT": "2023-07-01T13:30:00.0",
+ "steps": 13, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T13:30:00.0", "endGMT": "2023-07-01T13:45:00.0",
+ "steps": 457, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T13:45:00.0", "endGMT": "2023-07-01T14:00:00.0",
+ "steps": 370, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T14:00:00.0", "endGMT": "2023-07-01T14:15:00.0",
+ "steps": 135, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T14:15:00.0", "endGMT": "2023-07-01T14:30:00.0",
+ "steps": 1006, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T14:30:00.0", "endGMT": "2023-07-01T14:45:00.0",
+ "steps": 901, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T14:45:00.0", "endGMT": "2023-07-01T15:00:00.0",
+ "steps": 79, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T15:00:00.0", "endGMT": "2023-07-01T15:15:00.0",
+ "steps": 83, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T15:15:00.0", "endGMT": "2023-07-01T15:30:00.0",
+ "steps": 21, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T15:30:00.0", "endGMT": "2023-07-01T15:45:00.0",
+ "steps": 189, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T15:45:00.0", "endGMT": "2023-07-01T16:00:00.0",
+ "steps": 941, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T16:00:00.0", "endGMT": "2023-07-01T16:15:00.0",
+ "steps": 842, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T16:15:00.0", "endGMT": "2023-07-01T16:30:00.0",
+ "steps": 38, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T16:30:00.0", "endGMT": "2023-07-01T16:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T16:45:00.0", "endGMT": "2023-07-01T17:00:00.0",
+ "steps": 513, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T17:00:00.0", "endGMT": "2023-07-01T17:15:00.0",
+ "steps": 106, "pushes": 0, "primaryActivityLevel": "highlyActive", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T17:15:00.0", "endGMT": "2023-07-01T17:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T17:30:00.0", "endGMT": "2023-07-01T17:45:00.0",
+ "steps": 19, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T17:45:00.0", "endGMT": "2023-07-01T18:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T18:00:00.0", "endGMT": "2023-07-01T18:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T18:15:00.0", "endGMT": "2023-07-01T18:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T18:30:00.0", "endGMT": "2023-07-01T18:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T18:45:00.0", "endGMT": "2023-07-01T19:00:00.0",
+ "steps": 53, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T19:00:00.0", "endGMT": "2023-07-01T19:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T19:15:00.0", "endGMT": "2023-07-01T19:30:00.0",
+ "steps": 158, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T19:30:00.0", "endGMT": "2023-07-01T19:45:00.0",
+ "steps": 495, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T19:45:00.0", "endGMT": "2023-07-01T20:00:00.0",
+ "steps": 348, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T20:00:00.0", "endGMT": "2023-07-01T20:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T20:15:00.0", "endGMT": "2023-07-01T20:30:00.0",
+ "steps": 214, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T20:30:00.0", "endGMT": "2023-07-01T20:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T20:45:00.0", "endGMT": "2023-07-01T21:00:00.0",
+ "steps": 173, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T21:00:00.0", "endGMT": "2023-07-01T21:15:00.0",
+ "steps": 199, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T21:15:00.0", "endGMT": "2023-07-01T21:30:00.0",
+ "steps": 38, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T21:30:00.0", "endGMT": "2023-07-01T21:45:00.0",
+ "steps": 348, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T21:45:00.0", "endGMT": "2023-07-01T22:00:00.0",
+ "steps": 544, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T22:00:00.0", "endGMT": "2023-07-01T22:15:00.0",
+ "steps": 217, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T22:15:00.0", "endGMT": "2023-07-01T22:30:00.0",
+ "steps": 133, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T22:30:00.0", "endGMT": "2023-07-01T22:45:00.0",
+ "steps": 63, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T22:45:00.0", "endGMT": "2023-07-01T23:00:00.0",
+ "steps": 39, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T23:00:00.0", "endGMT": "2023-07-01T23:15:00.0",
+ "steps": 144, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-01T23:15:00.0", "endGMT": "2023-07-01T23:30:00.0",
+ "steps": 42, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T23:30:00.0", "endGMT": "2023-07-01T23:45:00.0",
+ "steps": 320, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-01T23:45:00.0", "endGMT": "2023-07-02T00:00:00.0",
+ "steps": 540, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T00:00:00.0", "endGMT": "2023-07-02T00:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T00:15:00.0", "endGMT": "2023-07-02T00:30:00.0",
+ "steps": 84, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T00:30:00.0", "endGMT": "2023-07-02T00:45:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T00:45:00.0", "endGMT": "2023-07-02T01:00:00.0",
+ "steps": 140, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T01:00:00.0", "endGMT": "2023-07-02T01:15:00.0",
+ "steps": 63, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T01:15:00.0", "endGMT": "2023-07-02T01:30:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T01:30:00.0", "endGMT": "2023-07-02T01:45:00.0",
+ "steps": 164, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T01:45:00.0", "endGMT": "2023-07-02T02:00:00.0",
+ "steps": 318, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T02:00:00.0", "endGMT": "2023-07-02T02:15:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T02:15:00.0", "endGMT": "2023-07-02T02:30:00.0",
+ "steps": 23, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T02:30:00.0", "endGMT": "2023-07-02T02:45:00.0",
+ "steps": 13, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T02:45:00.0", "endGMT": "2023-07-02T03:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T03:00:00.0", "endGMT": "2023-07-02T03:15:00.0",
+ "steps": 13, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T03:15:00.0", "endGMT": "2023-07-02T03:30:00.0",
+ "steps": 9, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T03:30:00.0", "endGMT": "2023-07-02T03:45:00.0",
+ "steps": 101, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T03:45:00.0", "endGMT": "2023-07-02T04:00:00.0",
+ "steps": 279, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T04:00:00.0", "endGMT": "2023-07-02T04:15:00.0",
+ "steps": 10, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T04:15:00.0", "endGMT": "2023-07-02T04:30:00.0",
+ "steps": 12, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T04:30:00.0", "endGMT": "2023-07-02T04:45:00.0",
+ "steps": 11, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T04:45:00.0", "endGMT": "2023-07-02T05:00:00.0",
+ "steps": 0, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}, {"startGMT": "2023-07-02T05:00:00.0", "endGMT": "2023-07-02T05:15:00.0",
+ "steps": 151, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T05:15:00.0", "endGMT": "2023-07-02T05:30:00.0",
+ "steps": 294, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T05:30:00.0", "endGMT": "2023-07-02T05:45:00.0",
+ "steps": 365, "pushes": 0, "primaryActivityLevel": "active", "activityLevelConstant":
+ false}, {"startGMT": "2023-07-02T05:45:00.0", "endGMT": "2023-07-02T06:00:00.0",
+ "steps": 19, "pushes": 0, "primaryActivityLevel": "sedentary", "activityLevelConstant":
+ true}]'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f873ddd1a594791-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 03:59:00 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=tJacsT8vopyIAffN684LcOMbK15rMrBOqEpskYmxq4YlGwiZbrizv9WNX6lEBv89nLZ0SdMqmuDY8QGV6NKFFb2PWZkFEjQLcywqorvWGMblFTGOwq0njWceIXlL7xZkc70Bx%2BHpHQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/cassettes/test_user_summary.yaml b/tests/cassettes/test_user_summary.yaml
new file mode 100644
index 0000000..dae73dd
--- /dev/null
+++ b/tests/cassettes/test_user_summary.yaml
@@ -0,0 +1,182 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
+ response:
+ body:
+ string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 79800.0, "height":
+ 182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "1984-10-17", "measurementSystem":
+ "metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
+ 30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
+ true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
+ "bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
+ null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
+ "isPossibleFirstDay": true}, "vo2MaxRunning": 46.0, "vo2MaxCycling": null,
+ "lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
+ null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
+ 3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
+ "hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
+ null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
+ 1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
+ "trainingStatusPausedDate": null, "weatherLocation": {"useFixedLocation":
+ false, "latitude": null, "longitude": null, "locationName": null, "isoCountryCode":
+ null, "postalCode": null}, "golfDistanceUnit": "statute_us", "golfElevationUnit":
+ null, "golfSpeedUnit": null, "externalBottomTime": null}, "userSleep": {"sleepTime":
+ 80400, "defaultSleepTime": false, "wakeTime": 24000, "defaultWakeTime": false},
+ "connectDate": null, "sourceType": null}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f873cc1594eb6ed-QRO
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json;charset=UTF-8
+ Date:
+ - Fri, 18 Aug 2023 03:58:15 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=aEek6tpphYzvySDDJURR7L7T%2FYzHg1kFiECinHHd49fQL3L9uzMItVct2bhBMrlxghE246LjQ8ktcvbwRsQthnLRkZIyGzVYOlltlOQYYJnF8s7LTNixQeIQYIvXF1E122T5qlNMlQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer SANITIZED
+ Connection:
+ - keep-alive
+ Cookie:
+ - _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
+ User-Agent:
+ - Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
+ (KHTML, like Gecko) Mobile/15E148
+ method: GET
+ uri: https://connectapi.garmin.com/usersummary-service/usersummary/daily/mtamizi?calendarDate=2023-07-01
+ response:
+ body:
+ string: '{"userProfileId": 2591602, "totalKilocalories": 2498.0, "activeKilocalories":
+ 370.0, "bmrKilocalories": 2128.0, "wellnessKilocalories": 2498.0, "burnedKilocalories":
+ null, "consumedKilocalories": null, "remainingKilocalories": 2498.0, "totalSteps":
+ 12413, "netCalorieGoal": null, "totalDistanceMeters": 10368, "wellnessDistanceMeters":
+ 10368, "wellnessActiveKilocalories": 370.0, "netRemainingKilocalories": 370.0,
+ "userDailySummaryId": 2591602, "calendarDate": "2023-07-01", "rule": {"typeId":
+ 2, "typeKey": "private"}, "uuid": "08064eebd5f64e299745fce9e3ae5c19", "dailyStepGoal":
+ 7950, "totalPushDistance": 0, "totalPushes": 0, "wellnessStartTimeGmt": "2023-07-01T06:00:00.0",
+ "wellnessStartTimeLocal": "2023-07-01T00:00:00.0", "wellnessEndTimeGmt": "2023-07-02T06:00:00.0",
+ "wellnessEndTimeLocal": "2023-07-02T00:00:00.0", "durationInMilliseconds":
+ 86400000, "wellnessDescription": null, "highlyActiveSeconds": 768, "activeSeconds":
+ 10219, "sedentarySeconds": 58253, "sleepingSeconds": 17160, "includesWellnessData":
+ true, "includesActivityData": false, "includesCalorieConsumedData": false,
+ "privacyProtected": false, "moderateIntensityMinutes": 0, "vigorousIntensityMinutes":
+ 0, "floorsAscendedInMeters": 90.802, "floorsDescendedInMeters": 88.567, "floorsAscended":
+ 29.79068, "floorsDescended": 29.05741, "intensityMinutesGoal": 150, "userFloorsAscendedGoal":
+ 35, "minHeartRate": 48, "maxHeartRate": 107, "restingHeartRate": 51, "lastSevenDaysAvgRestingHeartRate":
+ 49, "source": "GARMIN", "averageStressLevel": 35, "maxStressLevel": 86, "stressDuration":
+ 36120, "restStressDuration": 27780, "activityStressDuration": 17400, "uncategorizedStressDuration":
+ 5040, "totalStressDuration": 86340, "lowStressDuration": 21780, "mediumStressDuration":
+ 12660, "highStressDuration": 1680, "stressPercentage": 41.83, "restStressPercentage":
+ 32.18, "activityStressPercentage": 20.15, "uncategorizedStressPercentage":
+ 5.84, "lowStressPercentage": 25.23, "mediumStressPercentage": 14.66, "highStressPercentage":
+ 1.95, "stressQualifier": "STRESSFUL", "measurableAwakeDuration": 64260, "measurableAsleepDuration":
+ 17040, "lastSyncTimestampGMT": null, "minAvgHeartRate": 49, "maxAvgHeartRate":
+ 106, "bodyBatteryChargedValue": 43, "bodyBatteryDrainedValue": 43, "bodyBatteryHighestValue":
+ 48, "bodyBatteryLowestValue": 5, "bodyBatteryMostRecentValue": 5, "bodyBatteryVersion":
+ 2.0, "abnormalHeartRateAlertsCount": null, "averageSpo2": 92.0, "lowestSpo2":
+ 85, "latestSpo2": 86, "latestSpo2ReadingTimeGmt": "2023-07-02T06:00:00.0",
+ "latestSpo2ReadingTimeLocal": "2023-07-02T00:00:00.0", "averageMonitoringEnvironmentAltitude":
+ 2254.0, "restingCaloriesFromActivity": null, "avgWakingRespirationValue":
+ 13.0, "highestRespirationValue": 21.0, "lowestRespirationValue": 9.0, "latestRespirationValue":
+ 18.0, "latestRespirationTimeGMT": "2023-07-02T06:00:00.0"}'
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-RAY:
+ - 7f873cc6a8b54659-DFW
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 18 Aug 2023 03:58:16 GMT
+ NEL:
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
+ Report-To:
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=l9QNfRD2GFrbmbYPa0jUrwjJxNySHcV%2Fy4Hj2ihXGsjhhe4M6PaV2Oa7HbD2p4ide12TeIY0HlsR52xOurplH8bOicHR8kwOIqH2FsW4Wu7VOMC5DgBtFLnDIEmlDwSByNTflvwIuQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
+ Server:
+ - cloudflare
+ Transfer-Encoding:
+ - chunked
+ alt-svc:
+ - h3=":443"; ma=86400
+ cache-control:
+ - no-cache, no-store, private
+ pragma:
+ - no-cache
+ set-cookie:
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ - ADRUM_BTs=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
+ Secure
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..2bcea49
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,75 @@
+import json
+import os
+import re
+
+import pytest
+
+
+@pytest.fixture
+def vcr(vcr):
+ if "GARTH_HOME" not in os.environ:
+ vcr.record_mode = "none"
+ return vcr
+
+
+def sanitize_cookie(cookie_value) -> str:
+ return re.sub(r"=[^;]*", "=SANITIZED", cookie_value)
+
+
+def sanitize_request(request):
+ if request.body:
+ body = request.body.decode("utf8")
+ for key in ["username", "password", "refresh_token"]:
+ body = re.sub(key + r"=[^&]*", f"{key}=SANITIZED", body)
+ request.body = body.encode("utf8")
+
+ if "Cookie" in request.headers:
+ cookies = request.headers["Cookie"].split("; ")
+ sanitized_cookies = [sanitize_cookie(cookie) for cookie in cookies]
+ request.headers["Cookie"] = "; ".join(sanitized_cookies)
+ return request
+
+
+def sanitize_response(response):
+ for key in ["set-cookie", "Set-Cookie"]:
+ if key in response["headers"]:
+ cookies = response["headers"][key]
+ sanitized_cookies = [sanitize_cookie(cookie) for cookie in cookies]
+ response["headers"][key] = sanitized_cookies
+
+ body = response["body"]["string"].decode("utf8")
+ patterns = [
+ "oauth_token=[^&]*",
+ "oauth_token_secret=[^&]*",
+ "mfa_token=[^&]*",
+ ]
+ for pattern in patterns:
+ body = re.sub(pattern, pattern.split("=")[0] + "=SANITIZED", body)
+ try:
+ body_json = json.loads(body)
+ except json.JSONDecodeError:
+ pass
+ else:
+ for field in [
+ "access_token",
+ "refresh_token",
+ "jti",
+ "consumer_key",
+ "consumer_secret",
+ ]:
+ if field in body_json:
+ body_json[field] = "SANITIZED"
+
+ body = json.dumps(body_json)
+ response["body"]["string"] = body.encode("utf8")
+
+ return response
+
+
+@pytest.fixture(scope="session")
+def vcr_config():
+ return {
+ "filter_headers": [("Authorization", "Bearer SANITIZED")],
+ "before_record_request": sanitize_request,
+ "before_record_response": sanitize_response,
+ }
diff --git a/tests/test_garmin.py b/tests/test_garmin.py
new file mode 100644
index 0000000..f5a2bd1
--- /dev/null
+++ b/tests/test_garmin.py
@@ -0,0 +1,122 @@
+import pytest
+
+import garminconnect
+
+
+DATE = "2023-07-01"
+
+
+@pytest.fixture(scope="session")
+def garmin():
+ return garminconnect.Garmin("email", "password")
+
+
+@pytest.mark.vcr
+def test_stats(garmin):
+ garmin.login()
+ stats = garmin.get_stats(DATE)
+ assert "totalKilocalories" in stats
+ assert "activeKilocalories" in stats
+
+
+@pytest.mark.vcr
+def test_user_summary(garmin):
+ garmin.login()
+ user_summary = garmin.get_user_summary(DATE)
+ assert "totalKilocalories" in user_summary
+ assert "activeKilocalories" in user_summary
+
+
+@pytest.mark.vcr
+def test_steps_data(garmin):
+ garmin.login()
+ steps_data = garmin.get_steps_data(DATE)[0]
+ assert "steps" in steps_data
+
+
+@pytest.mark.vcr
+def test_floors(garmin):
+ garmin.login()
+ floors_data = garmin.get_floors(DATE)
+ assert "floorValuesArray" in floors_data
+
+
+@pytest.mark.vcr
+def test_daily_steps(garmin):
+ garmin.login()
+ daily_steps = garmin.get_daily_steps(DATE, DATE)[0]
+ assert "calendarDate" in daily_steps
+ assert "totalSteps" in daily_steps
+ assert "stepGoal" in daily_steps
+
+
+@pytest.mark.vcr
+def test_heart_rates(garmin):
+ garmin.login()
+ heart_rates = garmin.get_heart_rates(DATE)
+ assert "calendarDate" in heart_rates
+ assert "restingHeartRate" in heart_rates
+
+
+@pytest.mark.vcr
+def test_stats_and_body(garmin):
+ garmin.login()
+ stats_and_body = garmin.get_stats_and_body(DATE)
+ assert "calendarDate" in stats_and_body
+ assert "metabolicAge" in stats_and_body
+
+
+@pytest.mark.vcr
+def test_body_composition(garmin):
+ garmin.login()
+ body_composition = garmin.get_body_composition(DATE)
+ assert "totalAverage" in body_composition
+ assert "metabolicAge" in body_composition["totalAverage"]
+
+
+@pytest.mark.vcr
+def test_body_battery(garmin):
+ garmin.login()
+ body_battery = garmin.get_body_battery(DATE)[0]
+ assert "date" in body_battery
+ assert "charged" in body_battery
+
+
+@pytest.mark.vcr
+def test_hydration_data(garmin):
+ garmin.login()
+ hydration_data = garmin.get_hydration_data(DATE)
+ assert hydration_data
+ assert "calendarDate" in hydration_data
+
+
+@pytest.mark.vcr
+def test_respiration_data(garmin):
+ garmin.login()
+ respiration_data = garmin.get_respiration_data(DATE)
+ assert "calendarDate" in respiration_data
+ assert "avgSleepRespirationValue" in respiration_data
+
+
+@pytest.mark.vcr
+def test_spo2_data(garmin):
+ garmin.login()
+ spo2_data = garmin.get_spo2_data(DATE)
+ assert "calendarDate" in spo2_data
+ assert "averageSpO2" in spo2_data
+
+
+@pytest.mark.vcr
+def test_hrv_data(garmin):
+ garmin.login()
+ hrv_data = garmin.get_hrv_data(DATE)
+ assert "hrvSummary" in hrv_data
+ assert "weeklyAvg" in hrv_data["hrvSummary"]
+
+
+@pytest.mark.vcr
+def test_download_activity(garmin):
+ garmin.login()
+ activity_id = "11998957007"
+ activity = garmin.download_activity(activity_id)
+ assert activity