garth-ng 1.0.0a1__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 (193) hide show
  1. garth_ng-1.0.0a1/.coderabbit.yaml +27 -0
  2. garth_ng-1.0.0a1/.devcontainer/Dockerfile +7 -0
  3. garth_ng-1.0.0a1/.devcontainer/devcontainer.json +10 -0
  4. garth_ng-1.0.0a1/.devcontainer/noop.txt +3 -0
  5. garth_ng-1.0.0a1/.gitattributes +1 -0
  6. garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/auth-issue.yml +65 -0
  7. garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/bug.yml +44 -0
  8. garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/config.yml +1 -0
  9. garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/feature.yml +22 -0
  10. garth_ng-1.0.0a1/.github/dependabot.yml +17 -0
  11. garth_ng-1.0.0a1/.github/workflows/ci.yml +89 -0
  12. garth_ng-1.0.0a1/.github/workflows/publish.yml +31 -0
  13. garth_ng-1.0.0a1/.gitignore +53 -0
  14. garth_ng-1.0.0a1/.markdownlint.json +10 -0
  15. garth_ng-1.0.0a1/.pre-commit-config.yaml +33 -0
  16. garth_ng-1.0.0a1/.readthedocs.yaml +15 -0
  17. garth_ng-1.0.0a1/.vscode/settings.json +13 -0
  18. garth_ng-1.0.0a1/CLAUDE.md +141 -0
  19. garth_ng-1.0.0a1/LICENSE +22 -0
  20. garth_ng-1.0.0a1/Makefile +81 -0
  21. garth_ng-1.0.0a1/PKG-INFO +187 -0
  22. garth_ng-1.0.0a1/README.md +154 -0
  23. garth_ng-1.0.0a1/colabs/chatgpt_analysis_of_stats.ipynb +1084 -0
  24. garth_ng-1.0.0a1/colabs/sleep.ipynb +478 -0
  25. garth_ng-1.0.0a1/colabs/stress.ipynb +502 -0
  26. garth_ng-1.0.0a1/docs/api/connect-api.md +84 -0
  27. garth_ng-1.0.0a1/docs/api/data.md +677 -0
  28. garth_ng-1.0.0a1/docs/api/scores.md +290 -0
  29. garth_ng-1.0.0a1/docs/api/stats.md +252 -0
  30. garth_ng-1.0.0a1/docs/api/training.md +117 -0
  31. garth_ng-1.0.0a1/docs/api/user.md +299 -0
  32. garth_ng-1.0.0a1/docs/configuration.md +75 -0
  33. garth_ng-1.0.0a1/docs/contributing.md +101 -0
  34. garth_ng-1.0.0a1/docs/examples.md +54 -0
  35. garth_ng-1.0.0a1/docs/getting-started.md +128 -0
  36. garth_ng-1.0.0a1/docs/index.md +29 -0
  37. garth_ng-1.0.0a1/docs/mcp.md +112 -0
  38. garth_ng-1.0.0a1/docs/telemetry.md +64 -0
  39. garth_ng-1.0.0a1/mkdocs.yml +59 -0
  40. garth_ng-1.0.0a1/pyproject.toml +104 -0
  41. garth_ng-1.0.0a1/src/garth/__init__.py +81 -0
  42. garth_ng-1.0.0a1/src/garth/auth_tokens.py +59 -0
  43. garth_ng-1.0.0a1/src/garth/cli.py +34 -0
  44. garth_ng-1.0.0a1/src/garth/data/__init__.py +37 -0
  45. garth_ng-1.0.0a1/src/garth/data/_base.py +51 -0
  46. garth_ng-1.0.0a1/src/garth/data/activity.py +237 -0
  47. garth_ng-1.0.0a1/src/garth/data/body_battery/__init__.py +11 -0
  48. garth_ng-1.0.0a1/src/garth/data/body_battery/daily_stress.py +90 -0
  49. garth_ng-1.0.0a1/src/garth/data/body_battery/events.py +225 -0
  50. garth_ng-1.0.0a1/src/garth/data/body_battery/readings.py +59 -0
  51. garth_ng-1.0.0a1/src/garth/data/daily_sleep_data.py +160 -0
  52. garth_ng-1.0.0a1/src/garth/data/daily_summary.py +64 -0
  53. garth_ng-1.0.0a1/src/garth/data/fitness_stats.py +84 -0
  54. garth_ng-1.0.0a1/src/garth/data/garmin_scores.py +85 -0
  55. garth_ng-1.0.0a1/src/garth/data/heart_rate.py +89 -0
  56. garth_ng-1.0.0a1/src/garth/data/hrv.py +77 -0
  57. garth_ng-1.0.0a1/src/garth/data/morning_training_readiness.py +72 -0
  58. garth_ng-1.0.0a1/src/garth/data/sleep.py +132 -0
  59. garth_ng-1.0.0a1/src/garth/data/training_readiness.py +66 -0
  60. garth_ng-1.0.0a1/src/garth/data/weight.py +94 -0
  61. garth_ng-1.0.0a1/src/garth/exc.py +21 -0
  62. garth_ng-1.0.0a1/src/garth/http.py +306 -0
  63. garth_ng-1.0.0a1/src/garth/py.typed +0 -0
  64. garth_ng-1.0.0a1/src/garth/sso.py +270 -0
  65. garth_ng-1.0.0a1/src/garth/stats/__init__.py +26 -0
  66. garth_ng-1.0.0a1/src/garth/stats/_base.py +71 -0
  67. garth_ng-1.0.0a1/src/garth/stats/hrv.py +69 -0
  68. garth_ng-1.0.0a1/src/garth/stats/hydration.py +66 -0
  69. garth_ng-1.0.0a1/src/garth/stats/intensity_minutes.py +28 -0
  70. garth_ng-1.0.0a1/src/garth/stats/sleep.py +15 -0
  71. garth_ng-1.0.0a1/src/garth/stats/steps.py +30 -0
  72. garth_ng-1.0.0a1/src/garth/stats/stress.py +28 -0
  73. garth_ng-1.0.0a1/src/garth/stats/training_status/__init__.py +9 -0
  74. garth_ng-1.0.0a1/src/garth/stats/training_status/daily.py +68 -0
  75. garth_ng-1.0.0a1/src/garth/stats/training_status/monthly.py +71 -0
  76. garth_ng-1.0.0a1/src/garth/stats/training_status/weekly.py +71 -0
  77. garth_ng-1.0.0a1/src/garth/telemetry.py +235 -0
  78. garth_ng-1.0.0a1/src/garth/users/__init__.py +5 -0
  79. garth_ng-1.0.0a1/src/garth/users/profile.py +79 -0
  80. garth_ng-1.0.0a1/src/garth/users/settings.py +107 -0
  81. garth_ng-1.0.0a1/src/garth/utils.py +103 -0
  82. garth_ng-1.0.0a1/src/garth/version.py +1 -0
  83. garth_ng-1.0.0a1/tests/12129115726_ACTIVITY.fit +0 -0
  84. garth_ng-1.0.0a1/tests/cassettes/test_client_request.yaml +461 -0
  85. garth_ng-1.0.0a1/tests/cassettes/test_connectapi.yaml +65 -0
  86. garth_ng-1.0.0a1/tests/cassettes/test_delete.yaml +223 -0
  87. garth_ng-1.0.0a1/tests/cassettes/test_download.yaml +618 -0
  88. garth_ng-1.0.0a1/tests/cassettes/test_exchange.yaml +105 -0
  89. garth_ng-1.0.0a1/tests/cassettes/test_login_command.yaml +941 -0
  90. garth_ng-1.0.0a1/tests/cassettes/test_login_email_password_fail.yaml +555 -0
  91. garth_ng-1.0.0a1/tests/cassettes/test_login_mfa_fail.yaml +555 -0
  92. garth_ng-1.0.0a1/tests/cassettes/test_login_return_on_mfa.yaml +941 -0
  93. garth_ng-1.0.0a1/tests/cassettes/test_login_success.yaml +877 -0
  94. garth_ng-1.0.0a1/tests/cassettes/test_login_success_mfa.yaml +941 -0
  95. garth_ng-1.0.0a1/tests/cassettes/test_login_success_mfa_async.yaml +941 -0
  96. garth_ng-1.0.0a1/tests/cassettes/test_profile_alias.yaml +82 -0
  97. garth_ng-1.0.0a1/tests/cassettes/test_put.yaml +133 -0
  98. garth_ng-1.0.0a1/tests/cassettes/test_refresh_oauth2_token.yaml +193 -0
  99. garth_ng-1.0.0a1/tests/cassettes/test_resume_login.yaml +941 -0
  100. garth_ng-1.0.0a1/tests/cassettes/test_telemetry_enabled_request.yaml +85 -0
  101. garth_ng-1.0.0a1/tests/cassettes/test_upload.yaml +181 -0
  102. garth_ng-1.0.0a1/tests/cassettes/test_user_profile.yaml +105 -0
  103. garth_ng-1.0.0a1/tests/cassettes/test_user_settings.yaml +92 -0
  104. garth_ng-1.0.0a1/tests/cassettes/test_user_settings_sleep_windows.yaml +116 -0
  105. garth_ng-1.0.0a1/tests/cassettes/test_username.yaml +91 -0
  106. garth_ng-1.0.0a1/tests/conftest.py +157 -0
  107. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_get.yaml +243 -0
  108. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_list.yaml +195 -0
  109. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_list_pagination.yaml +284 -0
  110. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[both].yaml +182 -0
  111. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[description_only].yaml +182 -0
  112. garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[name_only].yaml +182 -0
  113. garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_data_get.yaml +35 -0
  114. garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_data_list.yaml +90 -0
  115. garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_properties_edge_cases.yaml +33 -0
  116. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get.yaml +41 -0
  117. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get_incomplete_data.yaml +350 -0
  118. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get_no_data.yaml +29 -0
  119. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_list.yaml +93 -0
  120. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_get.yaml +233 -0
  121. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_list.yaml +663 -0
  122. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_readings.yaml +233 -0
  123. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_sleep_data_get.yaml +147 -0
  124. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_sleep_data_list.yaml +180 -0
  125. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_summary_get.yaml +190 -0
  126. garth_ng-1.0.0a1/tests/data/cassettes/test_daily_summary_list.yaml +249 -0
  127. garth_ng-1.0.0a1/tests/data/cassettes/test_fitness_activity_list.yaml +70 -0
  128. garth_ng-1.0.0a1/tests/data/cassettes/test_garmin_scores_data_get.yaml +296 -0
  129. garth_ng-1.0.0a1/tests/data/cassettes/test_garmin_scores_data_list.yaml +218 -0
  130. garth_ng-1.0.0a1/tests/data/cassettes/test_get_daily_weight_data.yaml +58 -0
  131. garth_ng-1.0.0a1/tests/data/cassettes/test_get_manual_weight_data.yaml +62 -0
  132. garth_ng-1.0.0a1/tests/data/cassettes/test_get_nonexistent_weight_data.yaml +53 -0
  133. garth_ng-1.0.0a1/tests/data/cassettes/test_hrv_data_get.yaml +222 -0
  134. garth_ng-1.0.0a1/tests/data/cassettes/test_hrv_data_list.yaml +162 -0
  135. garth_ng-1.0.0a1/tests/data/cassettes/test_morning_training_readiness_data_get.yaml +181 -0
  136. garth_ng-1.0.0a1/tests/data/cassettes/test_morning_training_readiness_data_list.yaml +180 -0
  137. garth_ng-1.0.0a1/tests/data/cassettes/test_sleep_data_get.yaml +170 -0
  138. garth_ng-1.0.0a1/tests/data/cassettes/test_sleep_data_list.yaml +1665 -0
  139. garth_ng-1.0.0a1/tests/data/cassettes/test_training_readiness_data_get.yaml +167 -0
  140. garth_ng-1.0.0a1/tests/data/cassettes/test_training_readiness_data_list.yaml +180 -0
  141. garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list.yaml +97 -0
  142. garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list_empty.yaml +61 -0
  143. garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list_single_day.yaml +77 -0
  144. garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_timestamps_preserved.yaml +58 -0
  145. garth_ng-1.0.0a1/tests/data/test_activity.py +84 -0
  146. garth_ng-1.0.0a1/tests/data/test_body_battery_data.py +344 -0
  147. garth_ng-1.0.0a1/tests/data/test_daily_sleep_data.py +52 -0
  148. garth_ng-1.0.0a1/tests/data/test_daily_summary.py +25 -0
  149. garth_ng-1.0.0a1/tests/data/test_fitness_stats.py +29 -0
  150. garth_ng-1.0.0a1/tests/data/test_garmin_scores.py +34 -0
  151. garth_ng-1.0.0a1/tests/data/test_heart_rate.py +37 -0
  152. garth_ng-1.0.0a1/tests/data/test_hrv_data.py +25 -0
  153. garth_ng-1.0.0a1/tests/data/test_morning_training_readiness.py +34 -0
  154. garth_ng-1.0.0a1/tests/data/test_sleep_data.py +24 -0
  155. garth_ng-1.0.0a1/tests/data/test_training_readiness.py +49 -0
  156. garth_ng-1.0.0a1/tests/data/test_weight_data.py +105 -0
  157. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv.yaml +128 -0
  158. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_no_results.yaml +54 -0
  159. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_paginate.yaml +256 -0
  160. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_paginate_no_results.yaml +54 -0
  161. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration.yaml +50 -0
  162. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration_log.yaml +138 -0
  163. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration_log_default_timestamp.yaml +138 -0
  164. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_intensity_minutes.yaml +82 -0
  165. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_sleep.yaml +65 -0
  166. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_steps.yaml +84 -0
  167. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_stress.yaml +107 -0
  168. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_stress_pagination.yaml +328 -0
  169. garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_training_status.yaml +95 -0
  170. garth_ng-1.0.0a1/tests/stats/cassettes/test_monthly_training_status.yaml +114 -0
  171. garth_ng-1.0.0a1/tests/stats/cassettes/test_monthly_training_status_no_data.yaml +57 -0
  172. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_intensity_minutes.yaml +75 -0
  173. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_steps.yaml +180 -0
  174. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress.yaml +87 -0
  175. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress_beyond_data.yaml +323 -0
  176. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress_pagination.yaml +155 -0
  177. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_training_status.yaml +250 -0
  178. garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_training_status_pagination.yaml +3578 -0
  179. garth_ng-1.0.0a1/tests/stats/test_hrv.py +39 -0
  180. garth_ng-1.0.0a1/tests/stats/test_hydration.py +38 -0
  181. garth_ng-1.0.0a1/tests/stats/test_intensity_minutes.py +29 -0
  182. garth_ng-1.0.0a1/tests/stats/test_sleep_stats.py +15 -0
  183. garth_ng-1.0.0a1/tests/stats/test_steps.py +24 -0
  184. garth_ng-1.0.0a1/tests/stats/test_stress.py +49 -0
  185. garth_ng-1.0.0a1/tests/stats/test_training_status.py +288 -0
  186. garth_ng-1.0.0a1/tests/test_auth_tokens.py +31 -0
  187. garth_ng-1.0.0a1/tests/test_cli.py +44 -0
  188. garth_ng-1.0.0a1/tests/test_http.py +437 -0
  189. garth_ng-1.0.0a1/tests/test_sso.py +213 -0
  190. garth_ng-1.0.0a1/tests/test_telemetry.py +416 -0
  191. garth_ng-1.0.0a1/tests/test_users.py +27 -0
  192. garth_ng-1.0.0a1/tests/test_utils.py +132 -0
  193. garth_ng-1.0.0a1/uv.lock +2608 -0
@@ -0,0 +1,27 @@
1
+ # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json # Schema for CodeRabbit configurations
2
+ language: "en-US"
3
+ early_access: true
4
+ reviews:
5
+ request_changes_workflow: false
6
+ high_level_summary: true
7
+ poem: false
8
+ review_status: true
9
+ collapse_walkthrough: false
10
+ auto_review:
11
+ enabled: true
12
+ drafts: false
13
+ path_filters:
14
+ - "!tests/**/cassettes/**"
15
+ path_instructions:
16
+ - path: "tests/**"
17
+ instructions: |
18
+ - test functions shouldn't have a return type hint
19
+ - it's ok to use `assert` instead of `pytest.assume()`
20
+ - path: "src/garth/telemetry.py"
21
+ instructions: |
22
+ - DEFAULT_TOKEN is an intentionally embedded logfire write-only
23
+ token for community telemetry. It is not a secret — it can
24
+ only write sanitized, anonymous trace data. Do not flag it as
25
+ a hardcoded credential or secret.
26
+ chat:
27
+ auto_reply: true
@@ -0,0 +1,7 @@
1
+ FROM mcr.microsoft.com/devcontainers/anaconda:0-3
2
+
3
+ # Copy environment.yml (if found) to a temp location so we update the environment. Also
4
+ # copy "noop.txt" so the COPY instruction does not fail if no environment.yml exists.
5
+ COPY environment.yml* .devcontainer/noop.txt /tmp/conda-tmp/
6
+ RUN if [ -f "/tmp/conda-tmp/environment.yml" ]; then umask 0002 && /opt/conda/bin/conda env update -n base -f /tmp/conda-tmp/environment.yml; fi \
7
+ && rm -rf /tmp/conda-tmp
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "Anaconda (Python 3)",
3
+ "build": {
4
+ "context": "..",
5
+ "dockerfile": "Dockerfile"
6
+ },
7
+ "features": {
8
+ "ghcr.io/devcontainers/features/node:1": {}
9
+ }
10
+ }
@@ -0,0 +1,3 @@
1
+ This file copied into the container along with environment.yml* from the parent
2
+ folder. This file is included to prevents the Dockerfile COPY instruction from
3
+ failing if no environment.yml is found.
@@ -0,0 +1 @@
1
+ *.ipynb linguist-documentation=true
@@ -0,0 +1,65 @@
1
+ name: Authentication Issue
2
+ description: Report problems with Garmin authentication
3
+ labels: ["auth"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ ## Before submitting
9
+
10
+ Please try reproducing the issue with `uvx garth-ng login` to isolate
11
+ whether the problem is with Garth or your integration.
12
+
13
+ - type: checkboxes
14
+ attributes:
15
+ label: Debugging steps completed
16
+ options:
17
+ - label: I tried `uvx garth-ng login` and can reproduce the issue
18
+ required: true
19
+
20
+ - type: textarea
21
+ attributes:
22
+ label: Describe the issue
23
+ description: What happened? What did you expect to happen?
24
+ validations:
25
+ required: true
26
+
27
+ - type: textarea
28
+ attributes:
29
+ label: Steps to reproduce
30
+ description: How can we reproduce this issue?
31
+ placeholder: |
32
+ 1. Run `uvx garth-ng login`
33
+ 2. Enter credentials
34
+ 3. See error...
35
+ validations:
36
+ required: true
37
+
38
+ - type: textarea
39
+ attributes:
40
+ label: Error message
41
+ description: Paste the full error message or traceback
42
+ render: shell
43
+
44
+ - type: input
45
+ attributes:
46
+ label: Garth version
47
+ description: Run `pip show garth-ng` or check your requirements
48
+ placeholder: "0.6.0"
49
+
50
+ - type: input
51
+ attributes:
52
+ label: Python version
53
+ description: Run `python --version`
54
+ placeholder: "3.14.0"
55
+
56
+ - type: input
57
+ attributes:
58
+ label: Telemetry session ID
59
+ description: |
60
+ Garth prints a session ID to stdout on import
61
+ (e.g. "Garth session: a01e3fc1..."). Please share it so we can look
62
+ up the request logs. If you disabled telemetry
63
+ (`GARTH_TELEMETRY_ENABLED=false`), re-enable it and reproduce the
64
+ issue to generate a session ID.
65
+ placeholder: "a01e3fc1d5ac4c9a"
@@ -0,0 +1,44 @@
1
+ name: Bug Report
2
+ description: Report a bug or unexpected behavior
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ attributes:
7
+ label: Describe the bug
8
+ description: What happened? What did you expect to happen?
9
+ validations:
10
+ required: true
11
+
12
+ - type: textarea
13
+ attributes:
14
+ label: Steps to reproduce
15
+ description: How can we reproduce this issue?
16
+ placeholder: |
17
+ 1. Call `garth.connectapi(...)`
18
+ 2. Pass these parameters...
19
+ 3. See error...
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ attributes:
25
+ label: Error message
26
+ description: Paste the full error message or traceback
27
+ render: shell
28
+
29
+ - type: input
30
+ attributes:
31
+ label: Garth version
32
+ description: Run `pip show garth-ng` or check your requirements
33
+ placeholder: "0.6.0"
34
+
35
+ - type: input
36
+ attributes:
37
+ label: Python version
38
+ description: Run `python --version`
39
+ placeholder: "3.14.0"
40
+
41
+ - type: textarea
42
+ attributes:
43
+ label: Additional context
44
+ description: Any other relevant information
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,22 @@
1
+ name: Feature Request
2
+ description: Suggest a new feature or enhancement
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ attributes:
7
+ label: Describe the feature
8
+ description: What would you like to see added or changed?
9
+ validations:
10
+ required: true
11
+
12
+ - type: textarea
13
+ attributes:
14
+ label: Use case
15
+ description: Why do you need this feature? What problem does it solve?
16
+ validations:
17
+ required: true
18
+
19
+ - type: textarea
20
+ attributes:
21
+ label: Proposed solution
22
+ description: How do you think this should work? (optional)
@@ -0,0 +1,17 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "20:00"
8
+ timezone: "Europe/Prague"
9
+ open-pull-requests-limit: 5
10
+
11
+ - package-ecosystem: pip
12
+ directory: "/"
13
+ schedule:
14
+ interval: daily
15
+ time: "20:00"
16
+ timezone: "Europe/Prague"
17
+ open-pull-requests-limit: 5
@@ -0,0 +1,89 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ tags:
8
+ - "**"
9
+ pull_request: {}
10
+
11
+ env:
12
+ COLUMNS: 150
13
+
14
+ permissions:
15
+ contents: read
16
+ pull-requests: read
17
+ checks: write
18
+ statuses: write
19
+
20
+ jobs:
21
+ lint:
22
+ runs-on: ubuntu-latest
23
+ name: lint ${{ matrix.python-version }}
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
28
+ steps:
29
+ - uses: actions/checkout@v6
30
+
31
+ - uses: actions/setup-python@v6
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+ allow-prereleases: true
35
+
36
+ - uses: astral-sh/setup-uv@v7
37
+
38
+ - name: Install dependencies
39
+ run: |
40
+ uv pip install --system -e .
41
+ uv pip install --system --group linting
42
+
43
+ - uses: pre-commit/action@v3.0.1
44
+ with:
45
+ extra_args: --all-files --verbose
46
+ env:
47
+ SKIP: no-commit-to-branch
48
+
49
+ test:
50
+ name: test ${{ matrix.python-version }}
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ os: [ubuntu, macos, windows]
55
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
56
+
57
+ env:
58
+ PYTHON: ${{ matrix.python-version }}
59
+ OS: ${{ matrix.os }}
60
+
61
+ runs-on: ${{ matrix.os }}-latest
62
+
63
+ steps:
64
+ - uses: actions/checkout@v6
65
+
66
+ - uses: actions/setup-python@v6
67
+ with:
68
+ python-version: ${{ matrix.python-version }}
69
+ allow-prereleases: true
70
+
71
+ - uses: astral-sh/setup-uv@v7
72
+
73
+ - name: Install dependencies
74
+ run: |
75
+ uv pip install --system -e .
76
+ uv pip install --system --group testing
77
+
78
+ - name: test
79
+ run: make testcov
80
+ env:
81
+ CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps
82
+
83
+ - name: upload coverage to Codecov
84
+ uses: codecov/codecov-action@v5
85
+ with:
86
+ files: ./coverage/coverage.xml
87
+ flags: unittests
88
+ name: codecov-umbrella
89
+ fail_ci_if_error: false
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment:
11
+ name: pypi
12
+ url: https://pypi.org/p/garth-ng
13
+ permissions:
14
+ id-token: write
15
+ # Uses PyPI Trusted Publisher (OIDC) — no password needed
16
+
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+
20
+ - uses: actions/setup-python@v6
21
+ with:
22
+ python-version: "3.13"
23
+
24
+ - uses: astral-sh/setup-uv@v7
25
+
26
+ - name: Build package
27
+ run: |
28
+ uv build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,53 @@
1
+ # Virtual environments
2
+ env/
3
+ env3*/
4
+ venv/
5
+ .venv/
6
+ .envrc
7
+ .env
8
+ __pypackages__/
9
+
10
+ # IDEs and editors
11
+ .idea/
12
+
13
+ # Package distribution and build files
14
+ *.egg-info/
15
+ dist/
16
+ /build/
17
+ _build/
18
+
19
+ # Python bytecode and cache files
20
+ *.py[cod]
21
+ .cache/
22
+ /.ghtopdep_cache/
23
+ .hypothesis
24
+ .mypy_cache/
25
+ .pytest_cache/
26
+ /.ruff_cache/
27
+
28
+ # Benchmark and test files
29
+ /benchmarks/*.json
30
+ /htmlcov/
31
+ /codecov.sh
32
+ /coverage.lcov
33
+ .coverage
34
+ test.py
35
+ /coverage/
36
+
37
+ # Documentation files
38
+ /docs/changelog.md
39
+ /site/
40
+ /site.zip
41
+
42
+ # Other files and folders
43
+ .python-version
44
+ .DS_Store
45
+ .auto-format
46
+ /sandbox/
47
+ /worktrees/
48
+ .pdm-python
49
+ tmp/
50
+ .pdm.toml
51
+
52
+ # exclude saved oauth tokens
53
+ oauth*_token.json
@@ -0,0 +1,10 @@
1
+ {
2
+ "MD013": {
3
+ "code_blocks": false,
4
+ "tables": false
5
+ },
6
+ "MD033": {
7
+ "allowed_elements": ["img", "a", "source", "picture"]
8
+ },
9
+ "MD046": false
10
+ }
@@ -0,0 +1,33 @@
1
+ exclude: '.*\.ipynb$'
2
+
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v4.6.0
6
+ hooks:
7
+ - id: check-yaml
8
+ args: ['--unsafe']
9
+ - id: check-toml
10
+ - id: end-of-file-fixer
11
+ - id: trailing-whitespace
12
+
13
+ - repo: https://github.com/codespell-project/codespell
14
+ rev: v2.2.6
15
+ hooks:
16
+ - id: codespell
17
+ additional_dependencies:
18
+ - tomli
19
+ exclude: 'cassettes/'
20
+
21
+ - repo: https://github.com/DavidAnson/markdownlint-cli2
22
+ rev: v0.12.1
23
+ hooks:
24
+ - id: markdownlint-cli2
25
+
26
+ - repo: local
27
+ hooks:
28
+ - id: lint
29
+ name: lint
30
+ entry: make lint
31
+ types: [python]
32
+ language: system
33
+ pass_filenames: false
@@ -0,0 +1,15 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.12"
7
+ jobs:
8
+ install:
9
+ - pip install zensical
10
+ build:
11
+ html:
12
+ - zensical build
13
+ post_build:
14
+ - mkdir -p $READTHEDOCS_OUTPUT/html/
15
+ - cp --recursive site/* $READTHEDOCS_OUTPUT/html/
@@ -0,0 +1,13 @@
1
+ {
2
+ "[yaml]": {
3
+ "editor.tabSize": 2,
4
+ "editor.insertSpaces": true,
5
+ "editor.detectIndentation": false
6
+ },
7
+ "python.analysis.typeCheckingMode": "basic",
8
+ "python.testing.pytestArgs": [
9
+ "tests"
10
+ ],
11
+ "python.testing.unittestEnabled": false,
12
+ "python.testing.pytestEnabled": true
13
+ }
@@ -0,0 +1,141 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with
4
+ code in this repository.
5
+
6
+ ## Philosophy
7
+
8
+ > *"Programs must be written for people to read, and only incidentally for
9
+ > machines to execute."* — Harold Abelson
10
+
11
+ The goal is a codebase that:
12
+
13
+ - Reads naturally, almost like prose
14
+ - Surprises no one
15
+ - Makes the next person smile when they open a file
16
+
17
+ **Principles:**
18
+
19
+ - No function should require scrolling to read
20
+ - Use `Literal` types with Pydantic validation instead of magic strings where
21
+ runtime validation is needed (e.g.,
22
+ `app_mode: Literal["public", "internal", "all"]`). Use constants or enums
23
+ for values used across multiple modules without validation.
24
+ - A new contributor should understand the project in 10 minutes
25
+
26
+ ## Development Commands
27
+
28
+ ### Setup and Installation
29
+
30
+ - `make install` - Install package, dependencies, and pre-commit hooks
31
+ - `make sync` - Sync dependencies and lockfiles (force reinstall)
32
+
33
+ ### Code Quality and Testing
34
+
35
+ - `make format` - Auto-format Python source files using ruff
36
+ - `make lint` - Lint Python source files (ruff format check, ruff check, mypy)
37
+ - `make test` - Run all tests with coverage
38
+ - `make testcov` - Run tests and generate coverage reports (HTML and XML)
39
+ - `make all` - Run the complete CI pipeline (lint, codespell, testcov)
40
+
41
+ ### Utilities
42
+
43
+ - `make clean` - Clear local caches and build artifacts
44
+ - `make codespell` - Run spellchecking
45
+ - `make help` - Display all available commands
46
+
47
+ ### Testing Specific Components
48
+
49
+ - `uv run pytest tests/stats/ -v` - Run only stats module tests
50
+ - `uv run pytest tests/data/ -v` - Run only data module tests
51
+ - `uv run pytest tests/stats/test_training_status.py -v` - Run specific test
52
+
53
+ ## Project Architecture
54
+
55
+ Garth is a Python library for Garmin Connect API access with OAuth
56
+ authentication. The codebase is organized into several key modules:
57
+
58
+ ### Core Modules
59
+
60
+ - **`http.py`** - Central HTTP client (`Client` class) handling OAuth1/OAuth2
61
+ authentication, session management, and API requests
62
+ - **`sso.py`** - Single Sign-On authentication logic for Garmin services
63
+ - **`auth_tokens.py`** - OAuth1Token and OAuth2Token classes for token
64
+ management
65
+
66
+ ### Data Access Layer
67
+
68
+ - **`data/`** - Raw data retrieval from Garmin Connect API
69
+ - `body_battery/` - Body battery stress and readings data
70
+ - `hrv.py` - Heart rate variability detailed data
71
+ - `sleep.py` - Detailed sleep data with stages and movements
72
+ - `weight.py` - Weight and body composition data
73
+
74
+ ### Statistics Layer
75
+
76
+ - **`stats/`** - Processed statistics and aggregated data
77
+ - Various daily/weekly stats classes (steps, stress, hydration, etc.)
78
+ - Built on top of the data layer but provides summarized metrics
79
+
80
+ ### User Management
81
+
82
+ - **`users/`** - User profile and settings management
83
+ - `profile.py` - User profile information
84
+ - `settings.py` - User preferences and configuration
85
+
86
+ ### CLI Interface
87
+
88
+ - **`cli.py`** - Command-line interface for authentication and basic operations
89
+
90
+ ## Key Design Patterns
91
+
92
+ ### Authentication Flow
93
+
94
+ 1. OAuth1 token obtained through SSO (lasts ~1 year)
95
+ 2. OAuth2 token auto-refreshed as needed for API calls
96
+ 3. MFA support with custom handlers
97
+ 4. Session persistence via `save()` and `resume()` methods
98
+
99
+ ### API Access
100
+
101
+ - Main client instance available as `garth.client`
102
+ - `connectapi()` method for direct API calls returning JSON
103
+ - Domain configuration support (garmin.com vs garmin.cn)
104
+
105
+ ### Data Models
106
+
107
+ - Extensive use of Pydantic dataclasses for validation
108
+ - Consistent patterns across stats and data modules
109
+ - Date-based queries with period parameters
110
+
111
+ ## Testing Infrastructure
112
+
113
+ - Uses pytest with VCR cassettes for HTTP recording/playback
114
+ - VCR defaults to playback-only (`record_mode="none"`) — **tests never
115
+ hit the real Garmin API** unless explicitly recording
116
+ - To record new cassettes, set `GARTH_RECORD_CASSETTES=true` and
117
+ `GARTH_RECORD_CASSETTES_HOME=~/.garth`
118
+ - Comprehensive test coverage across all modules
119
+ - Separate test directories mirroring source structure
120
+ - Coverage reporting with HTML and XML output
121
+
122
+ ## Git Workflow
123
+
124
+ - Always squash merge PRs and delete both remote and local branches
125
+ - Use `gh pr merge <number> --squash --delete-branch` followed by
126
+ `git checkout main && git pull && git branch -D <branch> 2>/dev/null || true`
127
+ - Note: Fast-forward merges auto-delete the local branch, so the delete may
128
+ silently fail (which is fine)
129
+
130
+ ## Documentation Style
131
+
132
+ - Code examples in docs don't need import statements (we don't show
133
+ `import garth` either)
134
+ - Keep examples concise and focused on usage
135
+
136
+ ## API Design Guidelines
137
+
138
+ - Methods that make API calls should return Pydantic dataclasses of the
139
+ response for consistency
140
+ - Use `camel_to_snake_dict()` to transform API responses before passing
141
+ to dataclasses
@@ -0,0 +1,22 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 CyberFossa
4
+ Copyright (c) 2023 Matin Tamizi
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.