garth 0.5.20.dev4__tar.gz → 0.5.22.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.github/workflows/ci.yml +8 -6
  2. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.github/workflows/publish.yml +2 -2
  3. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/PKG-INFO +2 -1
  4. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/pyproject.toml +1 -0
  5. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/body_battery/events.py +4 -4
  6. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/body_battery/readings.py +2 -2
  7. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/hrv.py +4 -1
  8. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/sleep.py +4 -1
  9. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/http.py +1 -1
  10. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/sso.py +21 -8
  11. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/utils.py +1 -1
  12. garth-0.5.22.dev0/src/garth/version.py +1 -0
  13. garth-0.5.22.dev0/tests/data/cassettes/test_daily_body_battery_stress_get_incomplete_data.yaml +350 -0
  14. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/test_body_battery_data.py +9 -0
  15. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_http.py +1 -1
  16. garth-0.5.22.dev0/uv.lock +2263 -0
  17. garth-0.5.20.dev4/src/garth/version.py +0 -1
  18. garth-0.5.20.dev4/uv.lock +0 -2129
  19. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.coderabbit.yaml +0 -0
  20. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.devcontainer/Dockerfile +0 -0
  21. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.devcontainer/devcontainer.json +0 -0
  22. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.devcontainer/noop.txt +0 -0
  23. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.gitattributes +0 -0
  24. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.github/dependabot.yml +0 -0
  25. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.gitignore +0 -0
  26. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.markdownlint.json +0 -0
  27. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.pre-commit-config.yaml +0 -0
  28. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/.vscode/settings.json +0 -0
  29. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/LICENSE +0 -0
  30. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/Makefile +0 -0
  31. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/README.md +0 -0
  32. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/colabs/chatgpt_analysis_of_stats.ipynb +0 -0
  33. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/colabs/sleep.ipynb +0 -0
  34. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/colabs/stress.ipynb +0 -0
  35. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/__init__.py +0 -0
  36. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/auth_tokens.py +0 -0
  37. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/cli.py +0 -0
  38. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/__init__.py +0 -0
  39. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/_base.py +0 -0
  40. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/body_battery/__init__.py +0 -0
  41. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/body_battery/daily_stress.py +0 -0
  42. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/data/weight.py +0 -0
  43. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/exc.py +0 -0
  44. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/py.typed +0 -0
  45. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/__init__.py +0 -0
  46. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/_base.py +0 -0
  47. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/hrv.py +0 -0
  48. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/hydration.py +0 -0
  49. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/intensity_minutes.py +0 -0
  50. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/sleep.py +0 -0
  51. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/steps.py +0 -0
  52. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/stats/stress.py +0 -0
  53. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/users/__init__.py +0 -0
  54. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/users/profile.py +0 -0
  55. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/src/garth/users/settings.py +0 -0
  56. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/12129115726_ACTIVITY.fit +0 -0
  57. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_client_request.yaml +0 -0
  58. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_connectapi.yaml +0 -0
  59. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_delete.yaml +0 -0
  60. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_download.yaml +0 -0
  61. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_exchange.yaml +0 -0
  62. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_command.yaml +0 -0
  63. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_email_password_fail.yaml +0 -0
  64. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_mfa_fail.yaml +0 -0
  65. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_return_on_mfa.yaml +0 -0
  66. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_success.yaml +0 -0
  67. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_success_mfa.yaml +0 -0
  68. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_login_success_mfa_async.yaml +0 -0
  69. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_profile_alias.yaml +0 -0
  70. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_put.yaml +0 -0
  71. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_refresh_oauth2_token.yaml +0 -0
  72. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_resume_login.yaml +0 -0
  73. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_upload.yaml +0 -0
  74. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_user_profile.yaml +0 -0
  75. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_user_settings.yaml +0 -0
  76. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_user_settings_sleep_windows.yaml +0 -0
  77. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/cassettes/test_username.yaml +0 -0
  78. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/conftest.py +0 -0
  79. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_body_battery_data_get.yaml +0 -0
  80. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_body_battery_data_list.yaml +0 -0
  81. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_body_battery_properties_edge_cases.yaml +0 -0
  82. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_daily_body_battery_stress_get.yaml +0 -0
  83. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_daily_body_battery_stress_get_no_data.yaml +0 -0
  84. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_daily_body_battery_stress_list.yaml +0 -0
  85. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_get_daily_weight_data.yaml +0 -0
  86. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_get_manual_weight_data.yaml +0 -0
  87. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_get_nonexistent_weight_data.yaml +0 -0
  88. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_hrv_data_get.yaml +0 -0
  89. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_hrv_data_list.yaml +0 -0
  90. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_sleep_data_get.yaml +0 -0
  91. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_sleep_data_list.yaml +0 -0
  92. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_weight_data_list.yaml +0 -0
  93. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_weight_data_list_empty.yaml +0 -0
  94. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/cassettes/test_weight_data_list_single_day.yaml +0 -0
  95. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/test_hrv_data.py +0 -0
  96. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/test_sleep_data.py +0 -0
  97. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/data/test_weight_data.py +0 -0
  98. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_hrv.yaml +0 -0
  99. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_hrv_no_results.yaml +0 -0
  100. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_hrv_paginate.yaml +0 -0
  101. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_hrv_paginate_no_results.yaml +0 -0
  102. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_hydration.yaml +0 -0
  103. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_intensity_minutes.yaml +0 -0
  104. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_sleep.yaml +0 -0
  105. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_steps.yaml +0 -0
  106. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_stress.yaml +0 -0
  107. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_daily_stress_pagination.yaml +0 -0
  108. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_weekly_intensity_minutes.yaml +0 -0
  109. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_weekly_steps.yaml +0 -0
  110. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_weekly_stress.yaml +0 -0
  111. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_weekly_stress_beyond_data.yaml +0 -0
  112. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/cassettes/test_weekly_stress_pagination.yaml +0 -0
  113. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_hrv.py +0 -0
  114. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_hydration.py +0 -0
  115. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_intensity_minutes.py +0 -0
  116. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_sleep_stats.py +0 -0
  117. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_steps.py +0 -0
  118. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/stats/test_stress.py +0 -0
  119. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_auth_tokens.py +0 -0
  120. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_cli.py +0 -0
  121. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_sso.py +0 -0
  122. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_users.py +0 -0
  123. {garth-0.5.20.dev4 → garth-0.5.22.dev0}/tests/test_utils.py +0 -0
@@ -24,13 +24,14 @@ jobs:
24
24
  strategy:
25
25
  fail-fast: false
26
26
  matrix:
27
- python-version: ["3.10", "3.11", "3.12", "3.13"]
27
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
28
28
  steps:
29
- - uses: actions/checkout@v4
29
+ - uses: actions/checkout@v6
30
30
 
31
- - uses: actions/setup-python@v5
31
+ - uses: actions/setup-python@v6
32
32
  with:
33
33
  python-version: ${{ matrix.python-version }}
34
+ allow-prereleases: true
34
35
 
35
36
  - uses: astral-sh/setup-uv@v7
36
37
 
@@ -51,7 +52,7 @@ jobs:
51
52
  fail-fast: false
52
53
  matrix:
53
54
  os: [ubuntu, macos, windows]
54
- python-version: ["3.10", "3.11", "3.12", "3.13"]
55
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
55
56
 
56
57
  env:
57
58
  PYTHON: ${{ matrix.python-version }}
@@ -60,11 +61,12 @@ jobs:
60
61
  runs-on: ${{ matrix.os }}-latest
61
62
 
62
63
  steps:
63
- - uses: actions/checkout@v4
64
+ - uses: actions/checkout@v6
64
65
 
65
- - uses: actions/setup-python@v5
66
+ - uses: actions/setup-python@v6
66
67
  with:
67
68
  python-version: ${{ matrix.python-version }}
69
+ allow-prereleases: true
68
70
 
69
71
  - uses: astral-sh/setup-uv@v7
70
72
 
@@ -14,9 +14,9 @@ jobs:
14
14
  id-token: write
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
- - uses: actions/setup-python@v5
19
+ - uses: actions/setup-python@v6
20
20
  with:
21
21
  python-version: "3.13"
22
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: garth
3
- Version: 0.5.20.dev4
3
+ Version: 0.5.22.dev0
4
4
  Summary: Garmin SSO auth + Connect client
5
5
  Project-URL: Homepage, https://github.com/matin/garth
6
6
  Project-URL: Repository, https://github.com/matin/garth
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
23
24
  Requires-Python: >=3.10
24
25
  Requires-Dist: pydantic<3.0.0,>=1.10.12
25
26
  Requires-Dist: requests-oauthlib<3.0.0,>=1.3.1
@@ -20,6 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.11",
21
21
  "Programming Language :: Python :: 3.12",
22
22
  "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
23
24
  "Operating System :: MacOS :: MacOS X",
24
25
  "Operating System :: Microsoft :: Windows",
25
26
  "Operating System :: POSIX :: Linux",
@@ -121,7 +121,7 @@ class BodyBatteryData(Data):
121
121
 
122
122
  # Validate numeric fields
123
123
  timezone_offset = event_data.get("timezoneOffset", 0)
124
- if not isinstance(timezone_offset, (int, float)):
124
+ if not isinstance(timezone_offset, int | float):
125
125
  logging.warning(
126
126
  f"Invalid timezone_offset type: "
127
127
  f"{type(timezone_offset)}, using 0"
@@ -129,7 +129,7 @@ class BodyBatteryData(Data):
129
129
  timezone_offset = 0
130
130
 
131
131
  duration_ms = event_data.get("durationInMilliseconds", 0)
132
- if not isinstance(duration_ms, (int, float)):
132
+ if not isinstance(duration_ms, int | float):
133
133
  logging.warning(
134
134
  f"Invalid durationInMilliseconds type: "
135
135
  f"{type(duration_ms)}, using 0"
@@ -137,7 +137,7 @@ class BodyBatteryData(Data):
137
137
  duration_ms = 0
138
138
 
139
139
  battery_impact = event_data.get("bodyBatteryImpact", 0)
140
- if not isinstance(battery_impact, (int, float)):
140
+ if not isinstance(battery_impact, int | float):
141
141
  logging.warning(
142
142
  f"Invalid bodyBatteryImpact type: "
143
143
  f"{type(battery_impact)}, using 0"
@@ -178,7 +178,7 @@ class BodyBatteryData(Data):
178
178
  # Validate average_stress
179
179
  avg_stress = item.get("averageStress")
180
180
  if avg_stress is not None and not isinstance(
181
- avg_stress, (int, float)
181
+ avg_stress, int | float
182
182
  ):
183
183
  logging.warning(
184
184
  f"Invalid averageStress type: "
@@ -29,8 +29,8 @@ def parse_body_battery_readings(
29
29
  for values in body_battery_values_array or []:
30
30
  # Each reading requires 4 values: timestamp, status, level, version
31
31
  if len(values) >= 4:
32
- timestamp, status, level, version = values
33
- if not level or not status:
32
+ timestamp, status, level, version, *_ = values
33
+ if level is None or status is None:
34
34
  continue
35
35
  readings.append(
36
36
  BodyBatteryReading(
@@ -1,3 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import builtins
1
4
  from datetime import date, datetime
2
5
 
3
6
  from pydantic.dataclasses import dataclass
@@ -39,7 +42,7 @@ class HRVReading:
39
42
  class HRVData(Data):
40
43
  user_profile_pk: int
41
44
  hrv_summary: HRVSummary
42
- hrv_readings: list[HRVReading]
45
+ hrv_readings: builtins.list[HRVReading]
43
46
  start_timestamp_gmt: datetime
44
47
  end_timestamp_gmt: datetime
45
48
  start_timestamp_local: datetime
@@ -1,3 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import builtins
1
4
  from datetime import date, datetime
2
5
 
3
6
  from pydantic.dataclasses import dataclass
@@ -93,7 +96,7 @@ class SleepMovement:
93
96
  @dataclass
94
97
  class SleepData(Data):
95
98
  daily_sleep_dto: DailySleepDTO
96
- sleep_movement: list[SleepMovement] | None = None
99
+ sleep_movement: builtins.list[SleepMovement] | None = None
97
100
 
98
101
  @classmethod
99
102
  def get(
@@ -24,7 +24,7 @@ class Client:
24
24
  oauth2_token: OAuth2Token | dict[str, Any] | None = None
25
25
  timeout: int = 10
26
26
  retries: int = 3
27
- status_forcelist: tuple[int, ...] = (408, 429, 500, 502, 503, 504)
27
+ status_forcelist: tuple[int, ...] = (408, 500, 502, 503, 504)
28
28
  backoff_factor: float = 0.5
29
29
  pool_connections: int = 10
30
30
  pool_maxsize: int = 10
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
4
+ import inspect
2
5
  import re
3
6
  import time
4
7
  from collections.abc import Callable
@@ -46,7 +49,7 @@ def login(
46
49
  email: str,
47
50
  password: str,
48
51
  /,
49
- client: "http.Client | None" = None,
52
+ client: http.Client | None = None,
50
53
  prompt_mfa: Callable | None = lambda: input("MFA code: "),
51
54
  return_on_mfa: bool = False,
52
55
  ) -> (
@@ -128,11 +131,11 @@ def login(
128
131
  title = get_title(client.last_resp.text)
129
132
 
130
133
  if title != "Success":
131
- raise GarthException(f"Unexpected title: {title}")
134
+ raise GarthException(f"Login failed: {title}") # pragma: no cover
132
135
  return _complete_login(client)
133
136
 
134
137
 
135
- def get_oauth1_token(ticket: str, client: "http.Client") -> OAuth1Token:
138
+ def get_oauth1_token(ticket: str, client: http.Client) -> OAuth1Token:
136
139
  sess = GarminOAuth1Session(parent=client.sess)
137
140
  base_url = f"https://connectapi.{client.domain}/oauth-service/oauth/"
138
141
  login_url = f"https://sso.{client.domain}/sso/embed"
@@ -151,7 +154,7 @@ def get_oauth1_token(ticket: str, client: "http.Client") -> OAuth1Token:
151
154
  return OAuth1Token(domain=client.domain, **token) # type: ignore
152
155
 
153
156
 
154
- def exchange(oauth1: OAuth1Token, client: "http.Client") -> OAuth2Token:
157
+ def exchange(oauth1: OAuth1Token, client: http.Client) -> OAuth2Token:
155
158
  sess = GarminOAuth1Session(
156
159
  resource_owner_key=oauth1.oauth_token,
157
160
  resource_owner_secret=oauth1.oauth_token_secret,
@@ -176,10 +179,10 @@ def exchange(oauth1: OAuth1Token, client: "http.Client") -> OAuth2Token:
176
179
 
177
180
 
178
181
  def handle_mfa(
179
- client: "http.Client", signin_params: dict, prompt_mfa: Callable
182
+ client: http.Client, signin_params: dict, prompt_mfa: Callable
180
183
  ) -> None:
181
184
  csrf_token = get_csrf_token(client.last_resp.text)
182
- if asyncio.iscoroutinefunction(prompt_mfa):
185
+ if inspect.iscoroutinefunction(prompt_mfa):
183
186
  mfa_code = asyncio.run(prompt_mfa())
184
187
  else:
185
188
  mfa_code = prompt_mfa()
@@ -196,6 +199,10 @@ def handle_mfa(
196
199
  },
197
200
  )
198
201
 
202
+ title = get_title(client.last_resp.text)
203
+ if title != "Success":
204
+ raise GarthException("MFA verification failed")
205
+
199
206
 
200
207
  def set_expirations(token: dict) -> dict:
201
208
  token["expires_at"] = int(time.time() + token["expires_in"])
@@ -234,10 +241,16 @@ def resume_login(
234
241
  client = client_state["client"]
235
242
  signin_params = client_state["signin_params"]
236
243
  handle_mfa(client, signin_params, lambda: mfa_code)
237
- return _complete_login(client)
244
+ oauth1, oauth2 = _complete_login(client)
245
+
246
+ # Explicitly set tokens on the client
247
+ client.oauth1_token = oauth1
248
+ client.oauth2_token = oauth2
249
+
250
+ return oauth1, oauth2
238
251
 
239
252
 
240
- def _complete_login(client: "http.Client") -> tuple[OAuth1Token, OAuth2Token]:
253
+ def _complete_login(client: http.Client) -> tuple[OAuth1Token, OAuth2Token]:
241
254
  """Complete the login process after successful authentication.
242
255
 
243
256
  Args:
@@ -58,7 +58,7 @@ def asdict(obj):
58
58
  if isinstance(obj, list):
59
59
  return [asdict(v) for v in obj]
60
60
 
61
- if isinstance(obj, (datetime, date)):
61
+ if isinstance(obj, datetime | date):
62
62
  return obj.isoformat()
63
63
 
64
64
  return obj
@@ -0,0 +1 @@
1
+ __version__ = "0.5.22.dev0"
@@ -0,0 +1,350 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers:
5
+ Accept:
6
+ - '*/*'
7
+ Accept-Encoding:
8
+ - gzip, deflate
9
+ Authorization:
10
+ - Bearer SANITIZED
11
+ Connection:
12
+ - keep-alive
13
+ User-Agent:
14
+ - GCM-iOS-5.7.2.1
15
+ method: GET
16
+ uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2025-12-18
17
+ response:
18
+ body:
19
+ string: '{"userProfilePK": 104516149, "calendarDate": "2025-12-18", "startTimestampGMT":
20
+ "2025-12-18T06:00:00.0", "endTimestampGMT": "2025-12-19T06:00:00.0", "startTimestampLocal":
21
+ "2025-12-18T00:00:00.0", "endTimestampLocal": "2025-12-19T00:00:00.0", "maxStressLevel":
22
+ 97, "avgStressLevel": 22, "stressChartValueOffset": 1, "stressChartYAxisOrigin":
23
+ -1, "stressValueDescriptorsDTOList": [{"key": "timestamp", "index": 0}, {"key":
24
+ "stressLevel", "index": 1}], "stressValuesArray": [[1766037600000, -1], [1766037780000,
25
+ -1], [1766037960000, -1], [1766038140000, -1], [1766038320000, -1], [1766038500000,
26
+ -1], [1766038680000, -1], [1766038860000, -1], [1766039040000, -1], [1766039220000,
27
+ -1], [1766039400000, -1], [1766039580000, -1], [1766039760000, -1], [1766039940000,
28
+ -1], [1766040120000, 25], [1766040300000, 25], [1766040480000, 25], [1766040660000,
29
+ 28], [1766040840000, 34], [1766041020000, -1], [1766041200000, 25], [1766041380000,
30
+ 16], [1766041560000, 16], [1766041740000, 15], [1766041920000, 11], [1766042100000,
31
+ 16], [1766042280000, 18], [1766042460000, 14], [1766042640000, 13], [1766042820000,
32
+ 15], [1766043000000, 15], [1766043180000, 15], [1766043360000, 12], [1766043540000,
33
+ 13], [1766043720000, 12], [1766043900000, 26], [1766044080000, 38], [1766044260000,
34
+ 44], [1766044440000, 28], [1766044620000, 25], [1766044800000, 20], [1766044980000,
35
+ 18], [1766045160000, 18], [1766045340000, 17], [1766045520000, 15], [1766045700000,
36
+ 16], [1766045880000, 15], [1766046060000, 10], [1766046240000, 12], [1766046420000,
37
+ 8], [1766046600000, 4], [1766046780000, 4], [1766046960000, 5], [1766047140000,
38
+ 4], [1766047320000, 4], [1766047500000, 2], [1766047680000, 3], [1766047860000,
39
+ 2], [1766048040000, 4], [1766048220000, 5], [1766048400000, 15], [1766048580000,
40
+ 15], [1766048760000, 20], [1766048940000, 17], [1766049120000, 14], [1766049300000,
41
+ 17], [1766049480000, 12], [1766049660000, 5], [1766049840000, 5], [1766050020000,
42
+ 2], [1766050200000, 2], [1766050380000, 2], [1766050560000, 3], [1766050740000,
43
+ 3], [1766050920000, 3], [1766051100000, 3], [1766051280000, 5], [1766051460000,
44
+ 3], [1766051640000, 2], [1766051820000, 5], [1766052000000, 5], [1766052180000,
45
+ 3], [1766052360000, 5], [1766052540000, 3], [1766052720000, 3], [1766052900000,
46
+ 4], [1766053080000, 4], [1766053260000, 7], [1766053440000, 5], [1766053620000,
47
+ 6], [1766053800000, 5], [1766053980000, 5], [1766054160000, 6], [1766054340000,
48
+ 14], [1766054520000, 8], [1766054700000, 5], [1766054880000, 2], [1766055060000,
49
+ 6], [1766055240000, 6], [1766055420000, 4], [1766055600000, 10], [1766055780000,
50
+ 3], [1766055960000, 5], [1766056140000, 2], [1766056320000, 2], [1766056500000,
51
+ 6], [1766056680000, 4], [1766056860000, 4], [1766057040000, 4], [1766057220000,
52
+ 4], [1766057400000, 6], [1766057580000, 6], [1766057760000, 7], [1766057940000,
53
+ 16], [1766058120000, 25], [1766058300000, 25], [1766058480000, 12], [1766058660000,
54
+ 11], [1766058840000, 16], [1766059020000, 19], [1766059200000, 15], [1766059380000,
55
+ 14], [1766059560000, 13], [1766059740000, 15], [1766059920000, 12], [1766060100000,
56
+ 12], [1766060280000, 15], [1766060460000, 15], [1766060640000, 15], [1766060820000,
57
+ 15], [1766061000000, 16], [1766061180000, 17], [1766061360000, 11], [1766061540000,
58
+ 14], [1766061720000, 13], [1766061900000, 7], [1766062080000, 8], [1766062260000,
59
+ 11], [1766062440000, 16], [1766062620000, 14], [1766062800000, 15], [1766062980000,
60
+ 4], [1766063160000, 8], [1766063340000, 12], [1766063520000, 13], [1766063700000,
61
+ 6], [1766063880000, 11], [1766064060000, 13], [1766064240000, 10], [1766064420000,
62
+ 12], [1766064600000, 16], [1766064780000, 13], [1766064960000, 6], [1766065140000,
63
+ 9], [1766065320000, 5], [1766065500000, 6], [1766065680000, 5], [1766065860000,
64
+ 6], [1766066040000, 13], [1766066220000, 10], [1766066400000, 15], [1766066580000,
65
+ 20], [1766066760000, 23], [1766066940000, 22], [1766067120000, 18], [1766067300000,
66
+ 17], [1766067480000, 17], [1766067660000, 16], [1766067840000, 14], [1766068020000,
67
+ 33], [1766068200000, 26], [1766068380000, 24], [1766068560000, 31], [1766068740000,
68
+ 30], [1766083860000, -1], [1766084040000, -1], [1766084220000, -1], [1766084400000,
69
+ -1], [1766084580000, -1], [1766084760000, -1], [1766084940000, -1], [1766085120000,
70
+ -1], [1766085300000, -1], [1766085480000, -1], [1766085660000, -1], [1766085840000,
71
+ -1], [1766086020000, -1], [1766086200000, -1], [1766086380000, -1], [1766086560000,
72
+ -1], [1766086740000, -1], [1766086920000, -1], [1766087100000, -1], [1766087280000,
73
+ -1], [1766087460000, -1], [1766087640000, -1], [1766087820000, -1], [1766088000000,
74
+ -1], [1766088180000, -1], [1766088360000, -1], [1766088540000, -1], [1766088720000,
75
+ -1], [1766088900000, -1], [1766089080000, -1], [1766089260000, -1], [1766089440000,
76
+ -1], [1766089620000, -1], [1766089800000, -1], [1766089980000, -1], [1766090160000,
77
+ -1], [1766090340000, -1], [1766090520000, -1], [1766090700000, -1], [1766090880000,
78
+ -1], [1766091060000, -1], [1766091240000, -1], [1766091420000, -1], [1766091600000,
79
+ -1], [1766091780000, -1], [1766091960000, -1], [1766092140000, -1], [1766092320000,
80
+ -1], [1766092500000, -1], [1766092680000, -1], [1766092860000, -1], [1766093040000,
81
+ -1], [1766093220000, -1], [1766093400000, -1], [1766093580000, -1], [1766093760000,
82
+ -1], [1766093940000, -1], [1766094120000, -1], [1766094300000, -1], [1766094480000,
83
+ -1], [1766094660000, -1], [1766094840000, -1], [1766095020000, -1], [1766095200000,
84
+ -1], [1766095380000, -1], [1766095560000, -1], [1766095740000, -1], [1766095920000,
85
+ -1], [1766096100000, -1], [1766096280000, -1], [1766096460000, -1], [1766096640000,
86
+ -1], [1766096820000, -1], [1766097000000, -1], [1766097180000, -1], [1766097360000,
87
+ -1], [1766097540000, -1], [1766097720000, -1], [1766097900000, -1], [1766098080000,
88
+ -1], [1766098260000, -1], [1766098440000, -1], [1766098620000, -1], [1766098800000,
89
+ -1], [1766098980000, -1], [1766099160000, -1], [1766099340000, -1], [1766099520000,
90
+ -1], [1766099700000, -1], [1766099880000, -1], [1766100060000, -1], [1766100240000,
91
+ -1], [1766100420000, -1], [1766100600000, -1], [1766100780000, -1], [1766100960000,
92
+ -1], [1766101140000, -1], [1766101320000, -1], [1766101500000, -1], [1766101680000,
93
+ -1], [1766101860000, -1], [1766102040000, -1], [1766102220000, -1], [1766102400000,
94
+ -1], [1766102580000, -1], [1766102760000, -1], [1766102940000, -1], [1766103120000,
95
+ -1], [1766103300000, -1], [1766103480000, -1], [1766103660000, -1], [1766103840000,
96
+ -1], [1766104020000, -1], [1766104200000, -1], [1766104380000, -1], [1766104560000,
97
+ -1], [1766104740000, -1], [1766104920000, -1], [1766105100000, -1], [1766105280000,
98
+ -1], [1766105460000, -1], [1766105640000, -1], [1766105820000, -1], [1766106000000,
99
+ -1], [1766106180000, -1], [1766106360000, -1], [1766106540000, -1], [1766106720000,
100
+ -1], [1766106900000, -1], [1766107080000, -1], [1766107260000, 92], [1766107440000,
101
+ 84], [1766107620000, 71], [1766107800000, 57], [1766107980000, -2], [1766108160000,
102
+ -1], [1766108340000, -2], [1766108520000, -1], [1766108700000, -2], [1766108880000,
103
+ -2], [1766109060000, -2], [1766109240000, -2], [1766109420000, -1], [1766109600000,
104
+ 78], [1766109780000, 59], [1766109960000, 51], [1766110140000, 54], [1766110320000,
105
+ 43], [1766110500000, -1], [1766110680000, -1], [1766110860000, -1], [1766111040000,
106
+ -2], [1766111220000, -1], [1766111400000, 66], [1766111580000, 55], [1766111760000,
107
+ 58], [1766111940000, 41], [1766112120000, 37], [1766112300000, 40], [1766112480000,
108
+ 33], [1766112660000, 30], [1766112840000, 28], [1766113020000, 31], [1766113200000,
109
+ -2], [1766113380000, -2], [1766113560000, -2], [1766113740000, -1], [1766113920000,
110
+ 73], [1766114100000, 69], [1766114280000, 64], [1766114460000, 36], [1766114640000,
111
+ 30], [1766114820000, 25], [1766115000000, 25], [1766115180000, 58], [1766115360000,
112
+ 50], [1766115540000, 67], [1766115720000, 73], [1766115900000, 79], [1766116080000,
113
+ 63], [1766116260000, 34], [1766116440000, 39], [1766116620000, 43], [1766116800000,
114
+ -2], [1766116980000, -2], [1766117160000, -2], [1766117340000, -1], [1766117520000,
115
+ 89], [1766117700000, 86], [1766117880000, 91], [1766118060000, 80], [1766118240000,
116
+ 84], [1766118420000, 64], [1766118600000, 40], [1766118780000, 34], [1766118960000,
117
+ 37], [1766119140000, 34], [1766119320000, 34], [1766119500000, 32], [1766119680000,
118
+ 31], [1766119860000, 28], [1766120040000, 25], [1766120220000, 29], [1766120400000,
119
+ 28], [1766120580000, 27], [1766120760000, 24], [1766120940000, 25], [1766121120000,
120
+ 31], [1766121300000, 38], [1766121480000, 24], [1766121660000, 25], [1766121840000,
121
+ 24], [1766122020000, 21], [1766122200000, 23], [1766122380000, 29], [1766122560000,
122
+ 37], [1766122740000, 32], [1766122920000, 31], [1766123100000, 34], [1766123280000,
123
+ 34], [1766123460000, 31], [1766123640000, 25], [1766123820000, 32]], "bodyBatteryValueDescriptorsDTOList":
124
+ [{"bodyBatteryValueDescriptorIndex": 0, "bodyBatteryValueDescriptorKey": "timestamp"},
125
+ {"bodyBatteryValueDescriptorIndex": 1, "bodyBatteryValueDescriptorKey": "bodyBatteryStatus"},
126
+ {"bodyBatteryValueDescriptorIndex": 2, "bodyBatteryValueDescriptorKey": "bodyBatteryLevel"},
127
+ {"bodyBatteryValueDescriptorIndex": 3, "bodyBatteryValueDescriptorKey": "bodyBatteryVersion"}],
128
+ "bodyBatteryValuesArray": [[1766037600000, null, null, 2.0], [1766037780000,
129
+ null, null, 2.0], [1766037960000, null, null, 2.0], [1766038140000, null,
130
+ null, 2.0], [1766038320000, null, null, 2.0], [1766038500000, null, null,
131
+ 2.0], [1766038680000, null, null, 2.0], [1766038860000, null, null, 2.0],
132
+ [1766039040000, null, null, 2.0], [1766039220000, null, null, 2.0], [1766039400000,
133
+ null, null, 2.0], [1766039580000, null, null, 2.0], [1766039760000, null,
134
+ null, 2.0], [1766039940000, "RESET", 28, 2.0], [1766040120000, "MEASURED",
135
+ 26, 2.0], [1766040300000, "MEASURED", 26, 2.0], [1766040480000, "MEASURED",
136
+ 26, 2.0], [1766040660000, "MEASURED", 26, 2.0], [1766040840000, "MEASURED",
137
+ 26, 2.0], [1766041020000, "MEASURED", 26, 2.0], [1766041200000, "MEASURED",
138
+ 26, 2.0], [1766041380000, "MEASURED", 26, 2.0], [1766041560000, "MEASURED",
139
+ 26, 2.0], [1766041740000, "MEASURED", 26, 2.0], [1766041920000, "MEASURED",
140
+ 26, 2.0], [1766042100000, "MEASURED", 28, 2.0], [1766042280000, "MEASURED",
141
+ 28, 2.0], [1766042460000, "MEASURED", 28, 2.0], [1766042640000, "MEASURED",
142
+ 28, 2.0], [1766042820000, "MEASURED", 29, 2.0], [1766043000000, "MEASURED",
143
+ 29, 2.0], [1766043180000, "MEASURED", 30, 2.0], [1766043360000, "MEASURED",
144
+ 30, 2.0], [1766043540000, "MEASURED", 31, 2.0], [1766043720000, "MEASURED",
145
+ 32, 2.0], [1766043900000, "MEASURED", 32, 2.0], [1766044080000, "MEASURED",
146
+ 32, 2.0], [1766044260000, "MEASURED", 32, 2.0], [1766044440000, "MEASURED",
147
+ 32, 2.0], [1766044620000, "MEASURED", 33, 2.0], [1766044800000, "MEASURED",
148
+ 33, 2.0], [1766044980000, "MEASURED", 33, 2.0], [1766045160000, "MEASURED",
149
+ 34, 2.0], [1766045340000, "MEASURED", 34, 2.0], [1766045520000, "MEASURED",
150
+ 34, 2.0], [1766045700000, "MEASURED", 36, 2.0], [1766045880000, "MEASURED",
151
+ 36, 2.0], [1766046060000, "MEASURED", 36, 2.0], [1766046240000, "MEASURED",
152
+ 37, 2.0], [1766046420000, "MEASURED", 38, 2.0], [1766046600000, "MEASURED",
153
+ 39, 2.0], [1766046780000, "MEASURED", 40, 2.0], [1766046960000, "MEASURED",
154
+ 40, 2.0], [1766047140000, "MEASURED", 41, 2.0], [1766047320000, "MEASURED",
155
+ 42, 2.0], [1766047500000, "MEASURED", 43, 2.0], [1766047680000, "MEASURED",
156
+ 44, 2.0], [1766047860000, "MEASURED", 45, 2.0], [1766048040000, "MEASURED",
157
+ 46, 2.0], [1766048220000, "MEASURED", 47, 2.0], [1766048400000, "MEASURED",
158
+ 48, 2.0], [1766048580000, "MEASURED", 49, 2.0], [1766048760000, "MEASURED",
159
+ 49, 2.0], [1766048940000, "MEASURED", 50, 2.0], [1766049120000, "MEASURED",
160
+ 50, 2.0], [1766049300000, "MEASURED", 52, 2.0], [1766049480000, "MEASURED",
161
+ 52, 2.0], [1766049660000, "MEASURED", 52, 2.0], [1766049840000, "MEASURED",
162
+ 53, 2.0], [1766050020000, "MEASURED", 55, 2.0], [1766050200000, "MEASURED",
163
+ 55, 2.0], [1766050380000, "MEASURED", 56, 2.0], [1766050560000, "MEASURED",
164
+ 58, 2.0], [1766050740000, "MEASURED", 58, 2.0], [1766050920000, "MEASURED",
165
+ 59, 2.0], [1766051100000, "MEASURED", 60, 2.0], [1766051280000, "MEASURED",
166
+ 60, 2.0], [1766051460000, "MEASURED", 61, 2.0], [1766051640000, "MEASURED",
167
+ 62, 2.0], [1766051820000, "MEASURED", 62, 2.0], [1766052000000, "MEASURED",
168
+ 63, 2.0], [1766052180000, "MEASURED", 64, 2.0], [1766052360000, "MEASURED",
169
+ 65, 2.0], [1766052540000, "MEASURED", 65, 2.0], [1766052720000, "MEASURED",
170
+ 66, 2.0], [1766052900000, "MEASURED", 67, 2.0], [1766053080000, "MEASURED",
171
+ 68, 2.0], [1766053260000, "MEASURED", 68, 2.0], [1766053440000, "MEASURED",
172
+ 68, 2.0], [1766053620000, "MEASURED", 69, 2.0], [1766053800000, "MEASURED",
173
+ 69, 2.0], [1766053980000, "MEASURED", 71, 2.0], [1766054160000, "MEASURED",
174
+ 71, 2.0], [1766054340000, "MEASURED", 72, 2.0], [1766054520000, "MEASURED",
175
+ 72, 2.0], [1766054700000, "MEASURED", 73, 2.0], [1766054880000, "MEASURED",
176
+ 73, 2.0], [1766055060000, "MEASURED", 74, 2.0], [1766055240000, "MEASURED",
177
+ 75, 2.0], [1766055420000, "MEASURED", 75, 2.0], [1766055600000, "MEASURED",
178
+ 76, 2.0], [1766055780000, "MEASURED", 77, 2.0], [1766055960000, "MEASURED",
179
+ 77, 2.0], [1766056140000, "MEASURED", 78, 2.0], [1766056320000, "MEASURED",
180
+ 79, 2.0], [1766056500000, "MEASURED", 79, 2.0], [1766056680000, "MEASURED",
181
+ 80, 2.0], [1766056860000, "MEASURED", 80, 2.0], [1766057040000, "MEASURED",
182
+ 81, 2.0], [1766057220000, "MEASURED", 81, 2.0], [1766057400000, "MEASURED",
183
+ 82, 2.0], [1766057580000, "MEASURED", 83, 2.0], [1766057760000, "MEASURED",
184
+ 83, 2.0], [1766057940000, "MEASURED", 83, 2.0], [1766058120000, "MEASURED",
185
+ 84, 2.0], [1766058300000, "MEASURED", 84, 2.0], [1766058480000, "MEASURED",
186
+ 84, 2.0], [1766058660000, "MEASURED", 84, 2.0], [1766058840000, "MEASURED",
187
+ 84, 2.0], [1766059020000, "MEASURED", 85, 2.0], [1766059200000, "MEASURED",
188
+ 85, 2.0], [1766059380000, "MEASURED", 86, 2.0], [1766059560000, "MEASURED",
189
+ 86, 2.0], [1766059740000, "MEASURED", 86, 2.0], [1766059920000, "MEASURED",
190
+ 86, 2.0], [1766060100000, "MEASURED", 87, 2.0], [1766060280000, "MEASURED",
191
+ 87, 2.0], [1766060460000, "MEASURED", 87, 2.0], [1766060640000, "MEASURED",
192
+ 87, 2.0], [1766060820000, "MEASURED", 87, 2.0], [1766061000000, "MEASURED",
193
+ 88, 2.0], [1766061180000, "MEASURED", 89, 2.0], [1766061360000, "MEASURED",
194
+ 89, 2.0], [1766061540000, "MEASURED", 89, 2.0], [1766061720000, "MEASURED",
195
+ 89, 2.0], [1766061900000, "MEASURED", 89, 2.0], [1766062080000, "MEASURED",
196
+ 89, 2.0], [1766062260000, "MEASURED", 90, 2.0], [1766062440000, "MEASURED",
197
+ 90, 2.0], [1766062620000, "MEASURED", 91, 2.0], [1766062800000, "MEASURED",
198
+ 91, 2.0], [1766062980000, "MEASURED", 91, 2.0], [1766063160000, "MEASURED",
199
+ 92, 2.0], [1766063340000, "MEASURED", 92, 2.0], [1766063520000, "MEASURED",
200
+ 93, 2.0], [1766063700000, "MEASURED", 93, 2.0], [1766063880000, "MEASURED",
201
+ 94, 2.0], [1766064060000, "MEASURED", 94, 2.0], [1766064240000, "MEASURED",
202
+ 94, 2.0], [1766064420000, "MEASURED", 94, 2.0], [1766064600000, "MEASURED",
203
+ 95, 2.0], [1766064780000, "MEASURED", 95, 2.0], [1766064960000, "MEASURED",
204
+ 95, 2.0], [1766065140000, "MEASURED", 96, 2.0], [1766065320000, "MEASURED",
205
+ 96, 2.0], [1766065500000, "MEASURED", 96, 2.0], [1766065680000, "MEASURED",
206
+ 96, 2.0], [1766065860000, "MEASURED", 96, 2.0], [1766066040000, "MEASURED",
207
+ 96, 2.0], [1766066220000, "MEASURED", 97, 2.0], [1766066400000, "MEASURED",
208
+ 97, 2.0], [1766066580000, "MEASURED", 97, 2.0], [1766066760000, "MEASURED",
209
+ 97, 2.0], [1766066940000, "MEASURED", 97, 2.0], [1766067120000, "MEASURED",
210
+ 96, 2.0], [1766067300000, "MEASURED", 96, 2.0], [1766067480000, "MEASURED",
211
+ 96, 2.0], [1766067660000, "MEASURED", 96, 2.0], [1766067840000, "MEASURED",
212
+ 96, 2.0], [1766068020000, "MEASURED", 95, 2.0], [1766068200000, "MEASURED",
213
+ 95, 2.0], [1766068380000, "MEASURED", 95, 2.0], [1766068560000, "MEASURED",
214
+ 94, 2.0], [1766068740000, "MEASURED", 94, 2.0], [1766083860000, null, null,
215
+ 2.0], [1766084040000, null, null, 2.0], [1766084220000, null, null, 2.0],
216
+ [1766084400000, null, null, 2.0], [1766084580000, null, null, 2.0], [1766084760000,
217
+ null, null, 2.0], [1766084940000, null, null, 2.0], [1766085120000, null,
218
+ null, 2.0], [1766085300000, null, null, 2.0], [1766085480000, null, null,
219
+ 2.0], [1766085660000, null, null, 2.0], [1766085840000, null, null, 2.0],
220
+ [1766086020000, null, null, 2.0], [1766086200000, null, null, 2.0], [1766086380000,
221
+ null, null, 2.0], [1766086560000, null, null, 2.0], [1766086740000, null,
222
+ null, 2.0], [1766086920000, null, null, 2.0], [1766087100000, null, null,
223
+ 2.0], [1766087280000, null, null, 2.0], [1766087460000, null, null, 2.0],
224
+ [1766087640000, null, null, 2.0], [1766087820000, null, null, 2.0], [1766088000000,
225
+ null, null, 2.0], [1766088180000, null, null, 2.0], [1766088360000, null,
226
+ null, 2.0], [1766088540000, null, null, 2.0], [1766088720000, null, null,
227
+ 2.0], [1766088900000, null, null, 2.0], [1766089080000, null, null, 2.0],
228
+ [1766089260000, null, null, 2.0], [1766089440000, null, null, 2.0], [1766089620000,
229
+ null, null, 2.0], [1766089800000, null, null, 2.0], [1766089980000, null,
230
+ null, 2.0], [1766090160000, null, null, 2.0], [1766090340000, null, null,
231
+ 2.0], [1766090520000, null, null, 2.0], [1766090700000, null, null, 2.0],
232
+ [1766090880000, null, null, 2.0], [1766091060000, null, null, 2.0], [1766091240000,
233
+ null, null, 2.0], [1766091420000, null, null, 2.0], [1766091600000, null,
234
+ null, 2.0], [1766091780000, null, null, 2.0], [1766091960000, null, null,
235
+ 2.0], [1766092140000, null, null, 2.0], [1766092320000, null, null, 2.0],
236
+ [1766092500000, null, null, 2.0], [1766092680000, null, null, 2.0], [1766092860000,
237
+ null, null, 2.0], [1766093040000, null, null, 2.0], [1766093220000, null,
238
+ null, 2.0], [1766093400000, null, null, 2.0], [1766093580000, null, null,
239
+ 2.0], [1766093760000, null, null, 2.0], [1766093940000, null, null, 2.0],
240
+ [1766094120000, null, null, 2.0], [1766094300000, null, null, 2.0], [1766094480000,
241
+ null, null, 2.0], [1766094660000, null, null, 2.0], [1766094840000, null,
242
+ null, 2.0], [1766095020000, null, null, 2.0], [1766095200000, null, null,
243
+ 2.0], [1766095380000, null, null, 2.0], [1766095560000, null, null, 2.0],
244
+ [1766095740000, null, null, 2.0], [1766095920000, null, null, 2.0], [1766096100000,
245
+ null, null, 2.0], [1766096280000, null, null, 2.0], [1766096460000, null,
246
+ null, 2.0], [1766096640000, null, null, 2.0], [1766096820000, null, null,
247
+ 2.0], [1766097000000, null, null, 2.0], [1766097180000, null, null, 2.0],
248
+ [1766097360000, null, null, 2.0], [1766097540000, null, null, 2.0], [1766097720000,
249
+ null, null, 2.0], [1766097900000, null, null, 2.0], [1766098080000, null,
250
+ null, 2.0], [1766098260000, null, null, 2.0], [1766098440000, null, null,
251
+ 2.0], [1766098620000, null, null, 2.0], [1766098800000, null, null, 2.0],
252
+ [1766098980000, null, null, 2.0], [1766099160000, null, null, 2.0], [1766099340000,
253
+ null, null, 2.0], [1766099520000, null, null, 2.0], [1766099700000, null,
254
+ null, 2.0], [1766099880000, null, null, 2.0], [1766100060000, null, null,
255
+ 2.0], [1766100240000, null, null, 2.0], [1766100420000, null, null, 2.0],
256
+ [1766100600000, null, null, 2.0], [1766100780000, null, null, 2.0], [1766100960000,
257
+ null, null, 2.0], [1766101140000, null, null, 2.0], [1766101320000, null,
258
+ null, 2.0], [1766101500000, null, null, 2.0], [1766101680000, null, null,
259
+ 2.0], [1766101860000, null, null, 2.0], [1766102040000, null, null, 2.0],
260
+ [1766102220000, null, null, 2.0], [1766102400000, null, null, 2.0], [1766102580000,
261
+ null, null, 2.0], [1766102760000, null, null, 2.0], [1766102940000, null,
262
+ null, 2.0], [1766103120000, null, null, 2.0], [1766103300000, null, null,
263
+ 2.0], [1766103480000, null, null, 2.0], [1766103660000, null, null, 2.0],
264
+ [1766103840000, null, null, 2.0], [1766104020000, null, null, 2.0], [1766104200000,
265
+ null, null, 2.0], [1766104380000, null, null, 2.0], [1766104560000, null,
266
+ null, 2.0], [1766104740000, null, null, 2.0], [1766104920000, null, null,
267
+ 2.0], [1766105100000, null, null, 2.0], [1766105280000, null, null, 2.0],
268
+ [1766105460000, null, null, 2.0], [1766105640000, null, null, 2.0], [1766105820000,
269
+ null, null, 2.0], [1766106000000, null, null, 2.0], [1766106180000, "MEASURED",
270
+ 35, 2.0], [1766106360000, "MEASURED", 34, 2.0], [1766106540000, "MEASURED",
271
+ 34, 2.0], [1766106720000, "MEASURED", 34, 2.0], [1766106900000, "MEASURED",
272
+ 34, 2.0], [1766107080000, "MEASURED", 33, 2.0], [1766107260000, "MEASURED",
273
+ 33, 2.0], [1766107440000, "MEASURED", 32, 2.0], [1766107620000, "MEASURED",
274
+ 32, 2.0], [1766107800000, "MEASURED", 31, 2.0], [1766107980000, "MEASURED",
275
+ 31, 2.0], [1766108160000, "MEASURED", 31, 2.0], [1766108340000, "MEASURED",
276
+ 31, 2.0], [1766108520000, "MEASURED", 30, 2.0], [1766108700000, "MEASURED",
277
+ 30, 2.0], [1766108880000, "MEASURED", 30, 2.0], [1766109060000, "MEASURED",
278
+ 30, 2.0], [1766109240000, "MEASURED", 30, 2.0], [1766109420000, "MEASURED",
279
+ 30, 2.0], [1766109600000, "MEASURED", 29, 2.0], [1766109780000, "MEASURED",
280
+ 29, 2.0], [1766109960000, "MEASURED", 29, 2.0], [1766110140000, "MEASURED",
281
+ 29, 2.0], [1766110320000, "MEASURED", 29, 2.0], [1766110500000, "MEASURED",
282
+ 28, 2.0], [1766110680000, "MEASURED", 28, 2.0], [1766110860000, "MEASURED",
283
+ 28, 2.0], [1766111040000, "MEASURED", 28, 2.0], [1766111220000, "MEASURED",
284
+ 28, 2.0], [1766111400000, "MEASURED", 28, 2.0], [1766111580000, "MEASURED",
285
+ 27, 2.0], [1766111760000, "MEASURED", 27, 2.0], [1766111940000, "MEASURED",
286
+ 27, 2.0], [1766112120000, "MEASURED", 27, 2.0], [1766112300000, "MEASURED",
287
+ 27, 2.0], [1766112480000, "MEASURED", 26, 2.0], [1766112660000, "MEASURED",
288
+ 26, 2.0], [1766112840000, "MEASURED", 26, 2.0], [1766113020000, "MEASURED",
289
+ 26, 2.0], [1766113200000, "MEASURED", 26, 2.0], [1766113380000, "MEASURED",
290
+ 25, 2.0], [1766113560000, "MEASURED", 25, 2.0], [1766113740000, "MEASURED",
291
+ 25, 2.0], [1766113920000, "MEASURED", 25, 2.0], [1766114100000, "MEASURED",
292
+ 24, 2.0], [1766114280000, "MEASURED", 24, 2.0], [1766114460000, "MEASURED",
293
+ 24, 2.0], [1766114640000, "MEASURED", 24, 2.0], [1766114820000, "MEASURED",
294
+ 24, 2.0], [1766115000000, "MEASURED", 24, 2.0], [1766115180000, "MEASURED",
295
+ 24, 2.0], [1766115360000, "MEASURED", 24, 2.0], [1766115540000, "MEASURED",
296
+ 24, 2.0], [1766115720000, "MEASURED", 23, 2.0], [1766115900000, "MEASURED",
297
+ 23, 2.0], [1766116080000, "MEASURED", 23, 2.0], [1766116260000, "MEASURED",
298
+ 23, 2.0], [1766116440000, "MEASURED", 23, 2.0], [1766116620000, "MEASURED",
299
+ 22, 2.0], [1766116800000, "MEASURED", 22, 2.0], [1766116980000, "MEASURED",
300
+ 22, 2.0], [1766117160000, "MEASURED", 22, 2.0], [1766117340000, "MEASURED",
301
+ 22, 2.0], [1766117520000, "MEASURED", 21, 2.0], [1766117700000, "MEASURED",
302
+ 21, 2.0], [1766117880000, "MEASURED", 21, 2.0], [1766118060000, "MEASURED",
303
+ 21, 2.0], [1766118240000, "MEASURED", 20, 2.0], [1766118420000, "MEASURED",
304
+ 20, 2.0], [1766118600000, "MEASURED", 20, 2.0], [1766118780000, "MEASURED",
305
+ 20, 2.0], [1766118960000, "MEASURED", 20, 2.0], [1766119140000, "MEASURED",
306
+ 20, 2.0], [1766119320000, "MEASURED", 19, 2.0], [1766119500000, "MEASURED",
307
+ 19, 2.0], [1766119680000, "MEASURED", 19, 2.0], [1766119860000, "MEASURED",
308
+ 19, 2.0], [1766120040000, "MEASURED", 19, 2.0], [1766120220000, "MEASURED",
309
+ 19, 2.0], [1766120400000, "MEASURED", 19, 2.0], [1766120580000, "MEASURED",
310
+ 19, 2.0], [1766120760000, "MEASURED", 19, 2.0], [1766120940000, "MEASURED",
311
+ 19, 2.0], [1766121120000, "MEASURED", 18, 2.0], [1766121300000, "MEASURED",
312
+ 18, 2.0], [1766121480000, "MEASURED", 18, 2.0], [1766121660000, "MEASURED",
313
+ 18, 2.0], [1766121840000, "MEASURED", 18, 2.0], [1766122020000, "MEASURED",
314
+ 18, 2.0], [1766122200000, "MEASURED", 18, 2.0], [1766122380000, "MEASURED",
315
+ 18, 2.0], [1766122560000, "MEASURED", 18, 2.0], [1766122740000, "MEASURED",
316
+ 18, 2.0], [1766122920000, "MEASURED", 18, 2.0], [1766123100000, "MEASURED",
317
+ 18, 2.0], [1766123280000, "MEASURED", 18, 2.0], [1766123460000, "MEASURED",
318
+ 18, 2.0], [1766123640000, "MEASURED", 18, 2.0], [1766123820000, "MEASURED",
319
+ 18, 2.0]]}'
320
+ headers:
321
+ CF-RAY:
322
+ - 9b0a4923693fd930-QRO
323
+ Cache-Control:
324
+ - no-cache, no-store, private
325
+ Connection:
326
+ - keep-alive
327
+ Content-Type:
328
+ - application/json
329
+ Date:
330
+ - Fri, 19 Dec 2025 22:20:10 GMT
331
+ NEL:
332
+ - '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
333
+ Report-To:
334
+ - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=V%2FWjj7T5kCnWOJMyV%2FGr8OhrvUE3nUWvXMLrAXEjdLUZt29WdJVmC%2FGG0cnI4qRvjEcSFKe3B%2FVk%2Fp%2B4yUFbtOv2rIf66Df%2FNJERdCDWA0yjbLrKnLcuPqHOJr2SLvX9whFe95uPs3%2BcCPiKz2l9Ek9xiQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
335
+ Server:
336
+ - cloudflare
337
+ Set-Cookie:
338
+ - _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
339
+ Transfer-Encoding:
340
+ - chunked
341
+ alt-svc:
342
+ - h3=":443"; ma=86400
343
+ cf-cache-status:
344
+ - DYNAMIC
345
+ pragma:
346
+ - no-cache
347
+ status:
348
+ code: 200
349
+ message: OK
350
+ version: 1
@@ -110,10 +110,19 @@ def test_daily_body_battery_stress_get(authed_client: Client):
110
110
  def test_daily_body_battery_stress_get_no_data(authed_client: Client):
111
111
  # Test with a date that likely has no data
112
112
  daily_data = DailyBodyBatteryStress.get("2020-01-01", client=authed_client)
113
+
113
114
  # Should return None if no data available
114
115
  assert daily_data is None or isinstance(daily_data, DailyBodyBatteryStress)
115
116
 
116
117
 
118
+ @pytest.mark.vcr
119
+ def test_daily_body_battery_stress_get_incomplete_data(authed_client: Client):
120
+ daily_data = DailyBodyBatteryStress.get("2025-12-18", client=authed_client)
121
+ assert daily_data
122
+ assert all(r.level is not None for r in daily_data.body_battery_readings)
123
+ assert all(r.status is not None for r in daily_data.body_battery_readings)
124
+
125
+
117
126
  @pytest.mark.vcr
118
127
  def test_daily_body_battery_stress_list(authed_client: Client):
119
128
  days = 3