-
-
Notifications
You must be signed in to change notification settings - Fork 424
Comparing changes
Open a pull request
base repository: cyberjunky/python-garminconnect
base: 0.3.3
head repository: cyberjunky/python-garminconnect
compare: 0.3.4
- 7 commits
- 6 files changed
- 5 contributors
Commits on Apr 21, 2026
-
feat(typed): add optional Pydantic namespace wrapper (#354)
Adds ``g.typed`` — a lazy namespace accessor that wraps a curated set of high-value read endpoints with Pydantic response models, while leaving the 132 existing methods entirely unchanged. Design follows Option D from the issue discussion: g = Garmin(email, password) raw = g.get_stats(date) # dict[str, Any] — unchanged stats = g.typed.get_stats(date) # DailyStats (Pydantic model) Scope of the first cut (7 methods, 6 response models): - get_stats / get_user_summary -> DailyStats - get_sleep_data -> SleepData (with nested DailySleepDTO) - get_hrv_data -> HrvData | None - get_body_battery -> list[BodyBatteryEntry] - get_training_readiness -> list[TrainingReadiness] - get_activities_by_date -> list[Activity] Design notes: - Lazy import. ``g.typed`` is a ``@functools.cached_property`` that imports ``garminconnect.typed`` on first access, so pydantic stays optional. If pydantic is missing, the import raises a clear install hint. - Kept in a new ``[typed]`` extra (pydantic>=2.0.0) rather than promoted to a core dep, so the default ``pip install garminconnect`` footprint doesn't change. - ``extra='allow'`` + all fields ``Optional`` — models tolerate Garmin schema drift (new firmware / subscription tiers add fields) and partial responses (privacy-protected accounts, missing device data). Unknown fields land in ``model_extra`` for callers who want them. - Validation failures raise ``GarminConnectResponseValidationError`` with the unvalidated response preserved on ``.raw`` so users can fall back without losing data. - ``get_training_readiness`` wrapper reflects the real list-shaped response (the existing ``dict[str, Any]`` annotation on the raw method is wrong but out of scope here). Marked **experimental** in module and class docstrings — model shapes may change between minor releases while the pattern stabilises. Tests (16 mocked, all green): - accessor returns cached ``TypedGarmin`` - each wrapper calls through to the raw method and validates the response - extra fields tolerated (preserved in ``model_extra``) - missing fields default to ``None`` - validation errors raise ``GarminConnectResponseValidationError`` with ``.raw`` populated - list endpoints with non-list responses return ``[]`` - ``get_hrv_data`` ``None`` passes through unchanged Lint clean under ruff + mypy + black + isort.Configuration menu - View commit details
-
Copy full SHA for ba2ad04 - Browse repository at this point
Copy the full SHA ba2ad04View commit details
Commits on Apr 26, 2026
-
fix: route DI token endpoint to garmin.cn for CN accounts
`is_cn=True` correctly routes the SSO and Connect API endpoints via the domain-aware `_sso` / `_connect` / `_connectapi` attributes, but the DI OAuth2 token endpoint stayed hardcoded to `diauth.garmin.com`. CN users don't exist in the .com user database, so: - Token exchange (post-SSO) succeeded for fresh logins because the SSO ticket validates server-side regardless of which DI auth host receives it (the .com endpoint accepts the ticket and issues a token whose JWT payload still carries `iss=https://diauth.garmin.cn` — confusing!) - BUT token refresh fails with 400: `{"error":"invalid_grant","error_description":"User doesn't exist in DB for GarminGUID: <uuid>"}` because the refresh path looks up the user by GUID in the host's DB. This matters in practice: the access token's TTL is ~4 hours, so any CN account that stays logged in longer than a single session will hit the refresh path and break. Fix: store `_di_token_url` as an instance attribute built from `domain` (same pattern as `_sso` / `_connect`), and use it in `_exchange_ticket()` and `_refresh_di_token()`. The module-level `DI_TOKEN_URL` constant is kept for backwards compatibility with external imports. Reproduce: `Garmin(email, password, is_cn=True).login()` then wait 4+ hours, then make any API call → 401 → 400 invalid_grant on refresh. After fix: token refresh hits `https://diauth.garmin.cn/...` → 200. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for 78565a4 - Browse repository at this point
Copy the full SHA 78565a4View commit details
Commits on May 2, 2026
-
Merge pull request #360 from MidnightV1/fix/cn-di-token-url
fix: route DI token endpoint to garmin.cn for CN accounts
Configuration menu - View commit details
-
Copy full SHA for 94ff883 - Browse repository at this point
Copy the full SHA 94ff883View commit details -
Configuration menu - View commit details
-
Copy full SHA for 64c76d6 - Browse repository at this point
Copy the full SHA 64c76d6View commit details -
Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for ff1db53 - Browse repository at this point
Copy the full SHA ff1db53View commit details -
Merge pull request #359 from sorlen008/feat/typed-namespace-models
feat(typed): add optional Pydantic namespace wrapper (#354)
Configuration menu - View commit details
-
Copy full SHA for be815ea - Browse repository at this point
Copy the full SHA be815eaView commit details -
fix: resolve CI failures — ruff format and mypy errors in tests
- Auto-format typed.py (ruff format) - Add type: ignore[method-assign] to MagicMock assignments in test_typed.py - Pass response= as keyword arg to HTTPError constructor in test_retry_decorator.py Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for d206167 - Browse repository at this point
Copy the full SHA d206167View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff 0.3.3...0.3.4