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.
- garth_ng-1.0.0a1/.coderabbit.yaml +27 -0
- garth_ng-1.0.0a1/.devcontainer/Dockerfile +7 -0
- garth_ng-1.0.0a1/.devcontainer/devcontainer.json +10 -0
- garth_ng-1.0.0a1/.devcontainer/noop.txt +3 -0
- garth_ng-1.0.0a1/.gitattributes +1 -0
- garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/auth-issue.yml +65 -0
- garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/bug.yml +44 -0
- garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/config.yml +1 -0
- garth_ng-1.0.0a1/.github/ISSUE_TEMPLATE/feature.yml +22 -0
- garth_ng-1.0.0a1/.github/dependabot.yml +17 -0
- garth_ng-1.0.0a1/.github/workflows/ci.yml +89 -0
- garth_ng-1.0.0a1/.github/workflows/publish.yml +31 -0
- garth_ng-1.0.0a1/.gitignore +53 -0
- garth_ng-1.0.0a1/.markdownlint.json +10 -0
- garth_ng-1.0.0a1/.pre-commit-config.yaml +33 -0
- garth_ng-1.0.0a1/.readthedocs.yaml +15 -0
- garth_ng-1.0.0a1/.vscode/settings.json +13 -0
- garth_ng-1.0.0a1/CLAUDE.md +141 -0
- garth_ng-1.0.0a1/LICENSE +22 -0
- garth_ng-1.0.0a1/Makefile +81 -0
- garth_ng-1.0.0a1/PKG-INFO +187 -0
- garth_ng-1.0.0a1/README.md +154 -0
- garth_ng-1.0.0a1/colabs/chatgpt_analysis_of_stats.ipynb +1084 -0
- garth_ng-1.0.0a1/colabs/sleep.ipynb +478 -0
- garth_ng-1.0.0a1/colabs/stress.ipynb +502 -0
- garth_ng-1.0.0a1/docs/api/connect-api.md +84 -0
- garth_ng-1.0.0a1/docs/api/data.md +677 -0
- garth_ng-1.0.0a1/docs/api/scores.md +290 -0
- garth_ng-1.0.0a1/docs/api/stats.md +252 -0
- garth_ng-1.0.0a1/docs/api/training.md +117 -0
- garth_ng-1.0.0a1/docs/api/user.md +299 -0
- garth_ng-1.0.0a1/docs/configuration.md +75 -0
- garth_ng-1.0.0a1/docs/contributing.md +101 -0
- garth_ng-1.0.0a1/docs/examples.md +54 -0
- garth_ng-1.0.0a1/docs/getting-started.md +128 -0
- garth_ng-1.0.0a1/docs/index.md +29 -0
- garth_ng-1.0.0a1/docs/mcp.md +112 -0
- garth_ng-1.0.0a1/docs/telemetry.md +64 -0
- garth_ng-1.0.0a1/mkdocs.yml +59 -0
- garth_ng-1.0.0a1/pyproject.toml +104 -0
- garth_ng-1.0.0a1/src/garth/__init__.py +81 -0
- garth_ng-1.0.0a1/src/garth/auth_tokens.py +59 -0
- garth_ng-1.0.0a1/src/garth/cli.py +34 -0
- garth_ng-1.0.0a1/src/garth/data/__init__.py +37 -0
- garth_ng-1.0.0a1/src/garth/data/_base.py +51 -0
- garth_ng-1.0.0a1/src/garth/data/activity.py +237 -0
- garth_ng-1.0.0a1/src/garth/data/body_battery/__init__.py +11 -0
- garth_ng-1.0.0a1/src/garth/data/body_battery/daily_stress.py +90 -0
- garth_ng-1.0.0a1/src/garth/data/body_battery/events.py +225 -0
- garth_ng-1.0.0a1/src/garth/data/body_battery/readings.py +59 -0
- garth_ng-1.0.0a1/src/garth/data/daily_sleep_data.py +160 -0
- garth_ng-1.0.0a1/src/garth/data/daily_summary.py +64 -0
- garth_ng-1.0.0a1/src/garth/data/fitness_stats.py +84 -0
- garth_ng-1.0.0a1/src/garth/data/garmin_scores.py +85 -0
- garth_ng-1.0.0a1/src/garth/data/heart_rate.py +89 -0
- garth_ng-1.0.0a1/src/garth/data/hrv.py +77 -0
- garth_ng-1.0.0a1/src/garth/data/morning_training_readiness.py +72 -0
- garth_ng-1.0.0a1/src/garth/data/sleep.py +132 -0
- garth_ng-1.0.0a1/src/garth/data/training_readiness.py +66 -0
- garth_ng-1.0.0a1/src/garth/data/weight.py +94 -0
- garth_ng-1.0.0a1/src/garth/exc.py +21 -0
- garth_ng-1.0.0a1/src/garth/http.py +306 -0
- garth_ng-1.0.0a1/src/garth/py.typed +0 -0
- garth_ng-1.0.0a1/src/garth/sso.py +270 -0
- garth_ng-1.0.0a1/src/garth/stats/__init__.py +26 -0
- garth_ng-1.0.0a1/src/garth/stats/_base.py +71 -0
- garth_ng-1.0.0a1/src/garth/stats/hrv.py +69 -0
- garth_ng-1.0.0a1/src/garth/stats/hydration.py +66 -0
- garth_ng-1.0.0a1/src/garth/stats/intensity_minutes.py +28 -0
- garth_ng-1.0.0a1/src/garth/stats/sleep.py +15 -0
- garth_ng-1.0.0a1/src/garth/stats/steps.py +30 -0
- garth_ng-1.0.0a1/src/garth/stats/stress.py +28 -0
- garth_ng-1.0.0a1/src/garth/stats/training_status/__init__.py +9 -0
- garth_ng-1.0.0a1/src/garth/stats/training_status/daily.py +68 -0
- garth_ng-1.0.0a1/src/garth/stats/training_status/monthly.py +71 -0
- garth_ng-1.0.0a1/src/garth/stats/training_status/weekly.py +71 -0
- garth_ng-1.0.0a1/src/garth/telemetry.py +235 -0
- garth_ng-1.0.0a1/src/garth/users/__init__.py +5 -0
- garth_ng-1.0.0a1/src/garth/users/profile.py +79 -0
- garth_ng-1.0.0a1/src/garth/users/settings.py +107 -0
- garth_ng-1.0.0a1/src/garth/utils.py +103 -0
- garth_ng-1.0.0a1/src/garth/version.py +1 -0
- garth_ng-1.0.0a1/tests/12129115726_ACTIVITY.fit +0 -0
- garth_ng-1.0.0a1/tests/cassettes/test_client_request.yaml +461 -0
- garth_ng-1.0.0a1/tests/cassettes/test_connectapi.yaml +65 -0
- garth_ng-1.0.0a1/tests/cassettes/test_delete.yaml +223 -0
- garth_ng-1.0.0a1/tests/cassettes/test_download.yaml +618 -0
- garth_ng-1.0.0a1/tests/cassettes/test_exchange.yaml +105 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_command.yaml +941 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_email_password_fail.yaml +555 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_mfa_fail.yaml +555 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_return_on_mfa.yaml +941 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_success.yaml +877 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_success_mfa.yaml +941 -0
- garth_ng-1.0.0a1/tests/cassettes/test_login_success_mfa_async.yaml +941 -0
- garth_ng-1.0.0a1/tests/cassettes/test_profile_alias.yaml +82 -0
- garth_ng-1.0.0a1/tests/cassettes/test_put.yaml +133 -0
- garth_ng-1.0.0a1/tests/cassettes/test_refresh_oauth2_token.yaml +193 -0
- garth_ng-1.0.0a1/tests/cassettes/test_resume_login.yaml +941 -0
- garth_ng-1.0.0a1/tests/cassettes/test_telemetry_enabled_request.yaml +85 -0
- garth_ng-1.0.0a1/tests/cassettes/test_upload.yaml +181 -0
- garth_ng-1.0.0a1/tests/cassettes/test_user_profile.yaml +105 -0
- garth_ng-1.0.0a1/tests/cassettes/test_user_settings.yaml +92 -0
- garth_ng-1.0.0a1/tests/cassettes/test_user_settings_sleep_windows.yaml +116 -0
- garth_ng-1.0.0a1/tests/cassettes/test_username.yaml +91 -0
- garth_ng-1.0.0a1/tests/conftest.py +157 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_get.yaml +243 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_list.yaml +195 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_list_pagination.yaml +284 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[both].yaml +182 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[description_only].yaml +182 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_activity_update[name_only].yaml +182 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_data_get.yaml +35 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_data_list.yaml +90 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_body_battery_properties_edge_cases.yaml +33 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get.yaml +41 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get_incomplete_data.yaml +350 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_get_no_data.yaml +29 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_body_battery_stress_list.yaml +93 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_get.yaml +233 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_list.yaml +663 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_heart_rate_readings.yaml +233 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_sleep_data_get.yaml +147 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_sleep_data_list.yaml +180 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_summary_get.yaml +190 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_daily_summary_list.yaml +249 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_fitness_activity_list.yaml +70 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_garmin_scores_data_get.yaml +296 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_garmin_scores_data_list.yaml +218 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_get_daily_weight_data.yaml +58 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_get_manual_weight_data.yaml +62 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_get_nonexistent_weight_data.yaml +53 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_hrv_data_get.yaml +222 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_hrv_data_list.yaml +162 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_morning_training_readiness_data_get.yaml +181 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_morning_training_readiness_data_list.yaml +180 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_sleep_data_get.yaml +170 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_sleep_data_list.yaml +1665 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_training_readiness_data_get.yaml +167 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_training_readiness_data_list.yaml +180 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list.yaml +97 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list_empty.yaml +61 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_list_single_day.yaml +77 -0
- garth_ng-1.0.0a1/tests/data/cassettes/test_weight_data_timestamps_preserved.yaml +58 -0
- garth_ng-1.0.0a1/tests/data/test_activity.py +84 -0
- garth_ng-1.0.0a1/tests/data/test_body_battery_data.py +344 -0
- garth_ng-1.0.0a1/tests/data/test_daily_sleep_data.py +52 -0
- garth_ng-1.0.0a1/tests/data/test_daily_summary.py +25 -0
- garth_ng-1.0.0a1/tests/data/test_fitness_stats.py +29 -0
- garth_ng-1.0.0a1/tests/data/test_garmin_scores.py +34 -0
- garth_ng-1.0.0a1/tests/data/test_heart_rate.py +37 -0
- garth_ng-1.0.0a1/tests/data/test_hrv_data.py +25 -0
- garth_ng-1.0.0a1/tests/data/test_morning_training_readiness.py +34 -0
- garth_ng-1.0.0a1/tests/data/test_sleep_data.py +24 -0
- garth_ng-1.0.0a1/tests/data/test_training_readiness.py +49 -0
- garth_ng-1.0.0a1/tests/data/test_weight_data.py +105 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv.yaml +128 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_no_results.yaml +54 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_paginate.yaml +256 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hrv_paginate_no_results.yaml +54 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration.yaml +50 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration_log.yaml +138 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_hydration_log_default_timestamp.yaml +138 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_intensity_minutes.yaml +82 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_sleep.yaml +65 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_steps.yaml +84 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_stress.yaml +107 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_stress_pagination.yaml +328 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_daily_training_status.yaml +95 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_monthly_training_status.yaml +114 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_monthly_training_status_no_data.yaml +57 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_intensity_minutes.yaml +75 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_steps.yaml +180 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress.yaml +87 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress_beyond_data.yaml +323 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_stress_pagination.yaml +155 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_training_status.yaml +250 -0
- garth_ng-1.0.0a1/tests/stats/cassettes/test_weekly_training_status_pagination.yaml +3578 -0
- garth_ng-1.0.0a1/tests/stats/test_hrv.py +39 -0
- garth_ng-1.0.0a1/tests/stats/test_hydration.py +38 -0
- garth_ng-1.0.0a1/tests/stats/test_intensity_minutes.py +29 -0
- garth_ng-1.0.0a1/tests/stats/test_sleep_stats.py +15 -0
- garth_ng-1.0.0a1/tests/stats/test_steps.py +24 -0
- garth_ng-1.0.0a1/tests/stats/test_stress.py +49 -0
- garth_ng-1.0.0a1/tests/stats/test_training_status.py +288 -0
- garth_ng-1.0.0a1/tests/test_auth_tokens.py +31 -0
- garth_ng-1.0.0a1/tests/test_cli.py +44 -0
- garth_ng-1.0.0a1/tests/test_http.py +437 -0
- garth_ng-1.0.0a1/tests/test_sso.py +213 -0
- garth_ng-1.0.0a1/tests/test_telemetry.py +416 -0
- garth_ng-1.0.0a1/tests/test_users.py +27 -0
- garth_ng-1.0.0a1/tests/test_utils.py +132 -0
- 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 @@
|
|
|
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,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
|
garth_ng-1.0.0a1/LICENSE
ADDED
|
@@ -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.
|