Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
pip install . pytest pytest-cov

- name: Run tests with coverage
run: |
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Access your Fitbit data directly from your terminal 💻. View 💤 sleep logs,
| [Get AZM Time Series by Interval](https://dev.fitbit.com/build/reference/web-api/active-zone-minutes-timeseries/get-azm-timeseries-by-interval/) | ✅ |
| [Get Breathing Rate Summary by Interval](https://dev.fitbit.com/build/reference/web-api/breathing-rate/get-br-summary-by-interval/) | ✅ |
| [Get Daily Activity Summary](https://dev.fitbit.com/build/reference/web-api/activity/get-daily-activity-summary/) | ✅ |
| [Get Body Time Series by Date Range](https://dev.fitbit.com/build/reference/web-api/body-timeseries/get-body-timeseries-by-date-range/) | ✅ |
| [Get HRV Summary by Interval](https://dev.fitbit.com/build/reference/web-api/heartrate-variability/get-hrv-summary-by-interval/) | ✅ |

## Usage Guide
Expand All @@ -46,7 +47,8 @@ python -m pip install fitbit-cli
```bash
fitbit-cli -h
usage: fitbit-cli [-h] [-i] [-j] [-r] [-s [DATE[,DATE]|RELATIVE]] [-o [DATE[,DATE]|RELATIVE]] [-e [DATE[,DATE]|RELATIVE]] [-a [DATE[,DATE]|RELATIVE]]
[-b [DATE[,DATE]|RELATIVE]] [-t [DATE[,DATE]|RELATIVE]] [-H [DATE[,DATE]|RELATIVE]] [-u] [-d] [-v]
[-b [DATE[,DATE]|RELATIVE]] [-H [DATE[,DATE]|RELATIVE]] [-B [DATE[,DATE]|RELATIVE]]
[-t [DATE[,DATE]|RELATIVE]] [-u] [-d] [-v]

Fitbit CLI -- Access your Fitbit data at your terminal.

Expand All @@ -72,10 +74,12 @@ APIs:
Show AZM Time Series by Interval.
-b, --breathing-rate [DATE[,DATE]|RELATIVE]
Show Breathing Rate Summary by Interval.
-t, --activities [DATE[,DATE]|RELATIVE]
Show Daily Activity Summary.
-H, --hrv [DATE[,DATE]|RELATIVE]
Show HRV Summary by Interval.
-B, --body [DATE[,DATE]|RELATIVE]
Show Body Time Series for Weight, BMI, and Body Fat.
-t, --activities [DATE[,DATE]|RELATIVE]
Show Daily Activity Summary.
-u, --user-profile Show Profile.
-d, --devices Show Devices.
```
Expand Down
2 changes: 1 addition & 1 deletion fitbit_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
fitbit_cli Module
"""

__version__ = "1.7.0"
__version__ = "1.8.0"
9 changes: 9 additions & 0 deletions fitbit_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ def parse_arguments():
metavar="DATE[,DATE]|RELATIVE",
help="Show HRV Summary by Interval.",
)
group.add_argument(
"-B",
"--body",
type=parse_date_range,
nargs="?",
const=(datetime.today().date(), None),
metavar="DATE[,DATE]|RELATIVE",
help="Show Body Time Series for Weight, BMI, and Body Fat.",
)
group.add_argument(
"-t",
"--activities",
Expand Down
8 changes: 8 additions & 0 deletions fitbit_cli/fitbit_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ def get_hrv_summary(self, start_date, end_date=None):
response = self.make_request("GET", url)
return response.json()

def get_body_time_series(self, resource_path, start_date, end_date=None):
"""Get Body Time Series by Interval and Date"""

date_range = f"{start_date}/{end_date}" if end_date else f"{start_date}/1d"
url = f"https://api.fitbit.com/1/user/-/body/{resource_path}/date/{date_range}.json"
response = self.make_request("GET", url)
return response.json()

def get_daily_activity_summary(self, start_date):
"""Get Daily Activity Summary"""

Expand Down
52 changes: 52 additions & 0 deletions fitbit_cli/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,58 @@ def display_hrv(hrv_data, as_json=False):
return None


def _merge_body_data(body_data):
"""Merge weight, BMI, and body fat time series by date."""

merged = {}
resource_map = {
"weight": "body-weight",
"bmi": "body-bmi",
"fat": "body-fat",
}

for resource, response_key in resource_map.items():
for item in body_data.get(resource, {}).get(response_key, []):
date = item.get("dateTime")
if date not in merged:
merged[date] = {
"date": date,
"weight": None,
"bmi": None,
"fat": None,
}
merged[date][resource] = item.get("value")

return [merged[date] for date in sorted(merged)]


def display_body(body_data, as_json=False):
"""Body time series formatter"""

merged_body = _merge_body_data(body_data)

if as_json:
return {"body": merged_body}

table = Table(title="Body Time Series :balance_scale:", show_header=True)

table.add_column("Date :calendar:")
table.add_column("Weight :weight_lifter:")
table.add_column("BMI :straight_ruler:")
table.add_column("Body Fat % :chart_with_upwards_trend:")

for body in merged_body:
table.add_row(
str(body.get("date", "N/A")),
str(body.get("weight", "N/A")),
str(body.get("bmi", "N/A")),
str(body.get("fat", "N/A")),
)

CONSOLE.print(table)
return None


def display_devices(devices, as_json=False):
"""Devices list formatter"""

Expand Down
16 changes: 16 additions & 0 deletions fitbit_cli/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ def collect_activities(fitbit, args):
]


def collect_body(fitbit, args):
"""Fetch body time series for weight, BMI, and body fat."""
start_date, end_date = args.body
return {
"weight": fitbit.get_body_time_series("weight", start_date, end_date),
"bmi": fitbit.get_body_time_series("bmi", start_date, end_date),
"fat": fitbit.get_body_time_series("fat", start_date, end_date),
}


def json_display(fitbit, args):
"""Fetch data and render each requested endpoint as a single JSON object to stdout."""
result = {}
Expand Down Expand Up @@ -74,6 +84,8 @@ def json_display(fitbit, args):
)
if args.hrv:
result.update(fmt.display_hrv(fitbit.get_hrv_summary(*args.hrv), as_json=True))
if args.body:
result.update(fmt.display_body(collect_body(fitbit, args), as_json=True))
if args.activities:
activity_data = collect_activities(fitbit, args)
if profile is None:
Expand Down Expand Up @@ -106,6 +118,8 @@ def raw_json_display(fitbit, args):
)
if args.hrv:
result["hrv"] = fitbit.get_hrv_summary(*args.hrv)
if args.body:
result["body"] = collect_body(fitbit, args)
if args.activities:
result["activities"] = collect_activities(fitbit, args)

Expand Down Expand Up @@ -135,6 +149,8 @@ def table_display(fitbit, args):
)
if args.hrv:
fmt.display_hrv(fitbit.get_hrv_summary(*args.hrv))
if args.body:
fmt.display_body(collect_body(fitbit, args))
if args.activities:
activity_data = collect_activities(fitbit, args)
if profile is None:
Expand Down
Loading