Skip to content

Add DailyHeartRate data class#156

Merged
matin merged 3 commits intomainfrom
add-heart-rate-data
Jan 8, 2026
Merged

Add DailyHeartRate data class#156
matin merged 3 commits intomainfrom
add-heart-rate-data

Conversation

@matin
Copy link
Copy Markdown
Owner

@matin matin commented Jan 8, 2026

Summary

  • Adds DailyHeartRate data class for fetching daily heart rate metrics from Garmin Connect
  • Includes HeartRateReading dataclass for individual heart rate samples with timestamps
  • Supports get() for single day and list() for date ranges
  • Includes readings property to convert raw heart rate values to typed objects

Closes #134

Test plan

  • Unit tests for get(), list(), and readings property
  • VCR cassettes recorded for all test cases
  • All 112 tests pass
  • Lint and type checks pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Heart rate metrics now available: daily readings as timestamped series, min/max/resting rates, and seven-day average resting rate.
    • Added ability to retrieve a single day's heart rate and list heart rate data across date ranges.
  • Tests

    • Added test coverage for heart rate retrieval, per-reading processing, and sorted date-range listing.

✏️ Tip: You can customize this high-level summary in your review settings.

Closes #134

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

Warning

Rate limit exceeded

@matin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dc62679 and c32a069.

📒 Files selected for processing (1)
  • src/garth/data/heart_rate.py

Walkthrough

Adds HeartRateReading and DailyHeartRate data models, exposes DailyHeartRate in package exports, implements API fetch/listing and readings conversion, and adds tests covering get, readings, and list behavior.

Changes

Cohort / File(s) Summary
Public API Exports
src/garth/__init__.py, src/garth/data/__init__.py
Expose DailyHeartRate via imports and include it in __all__, expanding the package public surface.
Heart Rate Data Models
src/garth/data/heart_rate.py
New module adding HeartRateReading and DailyHeartRate (pydantic dataclasses). Implements readings property, get(day, client=None) to call /wellness-service/wellness/dailyHeartRate/?date={day}, and list(...) returning results sorted by calendar_date. Uses camel_to_snake_dict and existing HTTP client utilities.
Tests
tests/data/test_heart_rate.py
Adds tests: test_daily_heart_rate_get, test_daily_heart_rate_readings, and test_daily_heart_rate_list validating retrieval, readings conversion, and sorted listing.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant API as Garmin API
    participant Converter as camel_to_snake_dict
    participant Model as DailyHeartRate

    Client->>API: GET /wellness-service/wellness/dailyHeartRate/?date={day}
    API-->>Client: JSON response (camelCase keys)
    Client->>Converter: Convert response keys to snake_case
    Converter-->>Client: snake_case dict
    Client->>Model: Instantiate DailyHeartRate(**dict)
    Model-->>Client: DailyHeartRate instance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add DailyHeartRate data class' accurately and clearly describes the main change - introducing a new DailyHeartRate data class to the codebase.
Linked Issues check ✅ Passed The pull request implements all primary coding requirements from issue #134: DailyHeartRate dataclass with required fields, get() classmethod for single day fetching, API endpoint integration with camelCase conversion, and HeartRateReading support.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the DailyHeartRate data class and its integration. The import additions in init.py files and test additions are necessary supporting changes without out-of-scope modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-heart-rate-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @tests/data/test_heart_rate.py:
- Around line 29-37: The VCR cassette only contains a single date but the test
calls DailyHeartRate.list(end="2026-01-07", days=3) which requests three dates;
either change the test to request only the recorded date (e.g., set days=1 on
the DailyHeartRate.list call) or re-record the cassette to include the three
dates by running the test with VCR in record mode (e.g., use
--vcr-record=new_episodes) so the recorded interactions match the requests.
🧹 Nitpick comments (2)
src/garth/data/heart_rate.py (2)

22-50: Consider more precise typing for heart_rate_values.

The type list[list[int | None]] suggests a list of arbitrary-length integer lists, but the actual structure is [[timestamp_ms, heart_rate], ...] (pairs). A more precise type like list[tuple[int | None, int | None]] would better document the expected structure and could help catch misuse.

That said, this matches the raw API response shape so keeping it flexible is reasonable.


67-80: Consider replacing assert with an explicit exception for production safety.

assert statements are removed when Python runs with optimization flags (-O or -OO). If the API returns an unexpected type in production with optimizations enabled, the code would fail with a confusing TypeError from camel_to_snake_dict instead of a clear message.

♻️ Suggested fix
         if not hr_data:
             return None
-        assert isinstance(hr_data, dict), (
-            f"Expected dict from {path}, got {type(hr_data).__name__}"
-        )
+        if not isinstance(hr_data, dict):
+            raise TypeError(
+                f"Expected dict from {path}, got {type(hr_data).__name__}"
+            )
         hr_data = camel_to_snake_dict(hr_data)
         return cls(**hr_data)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d20fa1e and 4925e04.

⛔ Files ignored due to path filters (3)
  • tests/data/cassettes/test_daily_heart_rate_get.yaml is excluded by !tests/**/cassettes/**
  • tests/data/cassettes/test_daily_heart_rate_list.yaml is excluded by !tests/**/cassettes/**
  • tests/data/cassettes/test_daily_heart_rate_readings.yaml is excluded by !tests/**/cassettes/**
📒 Files selected for processing (4)
  • src/garth/__init__.py
  • src/garth/data/__init__.py
  • src/garth/data/heart_rate.py
  • tests/data/test_heart_rate.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use make format to auto-format Python source files using ruff
Use make lint to lint Python source files (ruff format check, ruff check, mypy)

Files:

  • src/garth/__init__.py
  • src/garth/data/__init__.py
  • src/garth/data/heart_rate.py
  • tests/data/test_heart_rate.py
**/__init__.py

📄 CodeRabbit inference engine (CLAUDE.md)

Provide the main client instance as garth.client for direct API access

Files:

  • src/garth/__init__.py
  • src/garth/data/__init__.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest with VCR cassettes for HTTP recording/playback in tests
Maintain comprehensive test coverage across all modules with separate test directories mirroring source structure

Files:

  • tests/data/test_heart_rate.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: - test functions shouldn't have a return type hint

  • it's ok to use assert instead of pytest.assume()

Files:

  • tests/data/test_heart_rate.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: matin/garth PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-08T00:25:14.763Z
Learning: Applies to data/**/*.py : Raw data retrieval from Garmin Connect API should be organized in the `data/` module with submodules for specific data types (body_battery/, hrv.py, sleep.py, weight.py)
📚 Learning: 2026-01-08T00:25:14.763Z
Learnt from: CR
Repo: matin/garth PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-08T00:25:14.763Z
Learning: Applies to data/**/*.py : Raw data retrieval from Garmin Connect API should be organized in the `data/` module with submodules for specific data types (body_battery/, hrv.py, sleep.py, weight.py)

Applied to files:

  • src/garth/__init__.py
  • src/garth/data/__init__.py
  • src/garth/data/heart_rate.py
🧬 Code graph analysis (3)
src/garth/__init__.py (1)
src/garth/data/heart_rate.py (1)
  • DailyHeartRate (23-85)
src/garth/data/__init__.py (1)
src/garth/data/heart_rate.py (1)
  • DailyHeartRate (23-85)
src/garth/data/heart_rate.py (3)
src/garth/utils.py (1)
  • camel_to_snake_dict (17-33)
src/garth/data/_base.py (1)
  • Data (15-47)
src/garth/http.py (2)
  • Client (19-245)
  • connectapi (186-192)
🪛 GitHub Actions: CI
tests/data/test_heart_rate.py

[error] 1-1: VCR cassette mismatch: CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/home/runner/work/garth/garth/tests/data/cassettes/test_daily_heart_rate_list.yaml') in your current record mode ('none'). No match for the request (GET https://connectapi.garmin.com/wellness-service/wellness/dailyHeartRate/?date=2026-01-06). Found 1 similar request with 1 different matcher(s): date mismatch (expected 2026-01-07, got 2026-01-06).

🔇 Additional comments (7)
src/garth/data/heart_rate.py (3)

14-19: LGTM!

Clean dataclass definition for individual heart rate readings with appropriate fields.


52-65: LGTM!

The readings property correctly filters out None values and converts millisecond timestamps to UTC datetimes. The list comprehension is clean and follows Pythonic patterns.


82-85: LGTM!

The list override correctly delegates to the parent class and adds sorting by calendar_date, which is a sensible default for time-series data.

src/garth/__init__.py (1)

5-5: LGTM!

DailyHeartRate is correctly exported and added to __all__ in alphabetical order, consistent with the existing pattern for other data classes.

Also applies to: 37-37

tests/data/test_heart_rate.py (2)

7-14: LGTM!

Test validates the key fields returned by DailyHeartRate.get() with sensible assertions. The VCR marker ensures reproducible HTTP interactions.


17-26: LGTM!

Good test coverage for the readings property, validating both the presence of readings and the structure of individual HeartRateReading objects.

src/garth/data/__init__.py (1)

7-7: LGTM!

DailyHeartRate is correctly exported from the data subpackage, following the established pattern for other data types. This aligns with the project structure where raw data retrieval from Garmin Connect API is organized in the data/ module. Based on learnings.

Also applies to: 27-27

Comment thread tests/data/test_heart_rate.py
Use max_workers=1 to ensure sequential requests are properly recorded
by VCR for the list test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (d20fa1e) to head (c32a069).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #156   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           58        60    +2     
  Lines         2603      2672   +69     
=========================================
+ Hits          2603      2672   +69     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@matin matin mentioned this pull request Jan 8, 2026
19 tasks
@matin matin merged commit 8324deb into main Jan 8, 2026
26 checks passed
@matin matin deleted the add-heart-rate-data branch January 8, 2026 22:47
@matin matin mentioned this pull request Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add class for heart rate

1 participant