Skip to content

Commit 6cba22f

Browse files
authored
Add support for body parameters: weight, BMI and body fat (#31)
1 parent f3d604a commit 6cba22f

File tree

8 files changed

+332
-5
lines changed

8 files changed

+332
-5
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
- name: Install Dependencies
6060
run: |
6161
python -m pip install --upgrade pip
62-
pip install pytest pytest-cov
62+
pip install . pytest pytest-cov
6363
6464
- name: Run tests with coverage
6565
run: |

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Access your Fitbit data directly from your terminal 💻. View 💤 sleep logs,
3131
| [Get AZM Time Series by Interval](https://dev.fitbit.com/build/reference/web-api/active-zone-minutes-timeseries/get-azm-timeseries-by-interval/) ||
3232
| [Get Breathing Rate Summary by Interval](https://dev.fitbit.com/build/reference/web-api/breathing-rate/get-br-summary-by-interval/) ||
3333
| [Get Daily Activity Summary](https://dev.fitbit.com/build/reference/web-api/activity/get-daily-activity-summary/) ||
34+
| [Get Body Time Series by Date Range](https://dev.fitbit.com/build/reference/web-api/body-timeseries/get-body-timeseries-by-date-range/) ||
3435
| [Get HRV Summary by Interval](https://dev.fitbit.com/build/reference/web-api/heartrate-variability/get-hrv-summary-by-interval/) ||
3536

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

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

@@ -72,10 +74,12 @@ APIs:
7274
Show AZM Time Series by Interval.
7375
-b, --breathing-rate [DATE[,DATE]|RELATIVE]
7476
Show Breathing Rate Summary by Interval.
75-
-t, --activities [DATE[,DATE]|RELATIVE]
76-
Show Daily Activity Summary.
7777
-H, --hrv [DATE[,DATE]|RELATIVE]
7878
Show HRV Summary by Interval.
79+
-B, --body [DATE[,DATE]|RELATIVE]
80+
Show Body Time Series for Weight, BMI, and Body Fat.
81+
-t, --activities [DATE[,DATE]|RELATIVE]
82+
Show Daily Activity Summary.
7983
-u, --user-profile Show Profile.
8084
-d, --devices Show Devices.
8185
```

fitbit_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
fitbit_cli Module
44
"""
55

6-
__version__ = "1.7.0"
6+
__version__ = "1.8.0"

fitbit_cli/cli.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ def parse_arguments():
146146
metavar="DATE[,DATE]|RELATIVE",
147147
help="Show HRV Summary by Interval.",
148148
)
149+
group.add_argument(
150+
"-B",
151+
"--body",
152+
type=parse_date_range,
153+
nargs="?",
154+
const=(datetime.today().date(), None),
155+
metavar="DATE[,DATE]|RELATIVE",
156+
help="Show Body Time Series for Weight, BMI, and Body Fat.",
157+
)
149158
group.add_argument(
150159
"-t",
151160
"--activities",

fitbit_cli/fitbit_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ def get_hrv_summary(self, start_date, end_date=None):
158158
response = self.make_request("GET", url)
159159
return response.json()
160160

161+
def get_body_time_series(self, resource_path, start_date, end_date=None):
162+
"""Get Body Time Series by Interval and Date"""
163+
164+
date_range = f"{start_date}/{end_date}" if end_date else f"{start_date}/1d"
165+
url = f"https://api.fitbit.com/1/user/-/body/{resource_path}/date/{date_range}.json"
166+
response = self.make_request("GET", url)
167+
return response.json()
168+
161169
def get_daily_activity_summary(self, start_date):
162170
"""Get Daily Activity Summary"""
163171

fitbit_cli/formatter.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,58 @@ def display_hrv(hrv_data, as_json=False):
311311
return None
312312

313313

314+
def _merge_body_data(body_data):
315+
"""Merge weight, BMI, and body fat time series by date."""
316+
317+
merged = {}
318+
resource_map = {
319+
"weight": "body-weight",
320+
"bmi": "body-bmi",
321+
"fat": "body-fat",
322+
}
323+
324+
for resource, response_key in resource_map.items():
325+
for item in body_data.get(resource, {}).get(response_key, []):
326+
date = item.get("dateTime")
327+
if date not in merged:
328+
merged[date] = {
329+
"date": date,
330+
"weight": None,
331+
"bmi": None,
332+
"fat": None,
333+
}
334+
merged[date][resource] = item.get("value")
335+
336+
return [merged[date] for date in sorted(merged)]
337+
338+
339+
def display_body(body_data, as_json=False):
340+
"""Body time series formatter"""
341+
342+
merged_body = _merge_body_data(body_data)
343+
344+
if as_json:
345+
return {"body": merged_body}
346+
347+
table = Table(title="Body Time Series :balance_scale:", show_header=True)
348+
349+
table.add_column("Date :calendar:")
350+
table.add_column("Weight :weight_lifter:")
351+
table.add_column("BMI :straight_ruler:")
352+
table.add_column("Body Fat % :chart_with_upwards_trend:")
353+
354+
for body in merged_body:
355+
table.add_row(
356+
str(body.get("date", "N/A")),
357+
str(body.get("weight", "N/A")),
358+
str(body.get("bmi", "N/A")),
359+
str(body.get("fat", "N/A")),
360+
)
361+
362+
CONSOLE.print(table)
363+
return None
364+
365+
314366
def display_devices(devices, as_json=False):
315367
"""Devices list formatter"""
316368

fitbit_cli/output.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ def collect_activities(fitbit, args):
3636
]
3737

3838

39+
def collect_body(fitbit, args):
40+
"""Fetch body time series for weight, BMI, and body fat."""
41+
start_date, end_date = args.body
42+
return {
43+
"weight": fitbit.get_body_time_series("weight", start_date, end_date),
44+
"bmi": fitbit.get_body_time_series("bmi", start_date, end_date),
45+
"fat": fitbit.get_body_time_series("fat", start_date, end_date),
46+
}
47+
48+
3949
def json_display(fitbit, args):
4050
"""Fetch data and render each requested endpoint as a single JSON object to stdout."""
4151
result = {}
@@ -74,6 +84,8 @@ def json_display(fitbit, args):
7484
)
7585
if args.hrv:
7686
result.update(fmt.display_hrv(fitbit.get_hrv_summary(*args.hrv), as_json=True))
87+
if args.body:
88+
result.update(fmt.display_body(collect_body(fitbit, args), as_json=True))
7789
if args.activities:
7890
activity_data = collect_activities(fitbit, args)
7991
if profile is None:
@@ -106,6 +118,8 @@ def raw_json_display(fitbit, args):
106118
)
107119
if args.hrv:
108120
result["hrv"] = fitbit.get_hrv_summary(*args.hrv)
121+
if args.body:
122+
result["body"] = collect_body(fitbit, args)
109123
if args.activities:
110124
result["activities"] = collect_activities(fitbit, args)
111125

@@ -135,6 +149,8 @@ def table_display(fitbit, args):
135149
)
136150
if args.hrv:
137151
fmt.display_hrv(fitbit.get_hrv_summary(*args.hrv))
152+
if args.body:
153+
fmt.display_body(collect_body(fitbit, args))
138154
if args.activities:
139155
activity_data = collect_activities(fitbit, args)
140156
if profile is None:

0 commit comments

Comments
 (0)