Skip to content

Commit fcc493b

Browse files
committed
Many coderabbit suggestions applied
1 parent 7b8bd08 commit fcc493b

File tree

1 file changed

+44
-20
lines changed

1 file changed

+44
-20
lines changed

garminconnect/__init__.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def login(self, /, tokenstore: str | None = None) -> tuple[str | None, str | Non
331331
)
332332

333333
# Validate email format when actually used for login
334-
if self.username and "@" not in self.username:
334+
if not self.is_cn and self.username and "@" not in self.username:
335335
raise GarminConnectAuthenticationError(
336336
"Email must contain '@' symbol"
337337
)
@@ -342,12 +342,16 @@ def login(self, /, tokenstore: str | None = None) -> tuple[str | None, str | Non
342342
self.password,
343343
return_on_mfa=self.return_on_mfa,
344344
)
345+
# In MFA early-return mode, profile/settings are not loaded yet
346+
return token1, token2
345347
else:
346348
token1, token2 = self.garth.login(
347349
self.username,
348350
self.password,
349351
prompt_mfa=self.prompt_mfa,
350352
)
353+
# In MFA early-return mode, profile/settings are not loaded yet
354+
return token1, token2
351355

352356
# Validate profile data exists
353357
if not hasattr(self.garth, "profile") or not self.garth.profile:
@@ -532,8 +536,10 @@ def get_body_composition(
532536
'YYYY-MM-DD' through enddate 'YYYY-MM-DD'.
533537
"""
534538

535-
if enddate is None:
536-
enddate = startdate
539+
startdate = _validate_date_format(startdate, "startdate")
540+
enddate = startdate if enddate is None else _validate_date_format(enddate, "enddate")
541+
if datetime.strptime(startdate, DATE_FORMAT_STR).date() > datetime.strptime(enddate, DATE_FORMAT_STR).date():
542+
raise ValueError("Startdate cannot be after enddate")
537543
url = f"{self.garmin_connect_weight_url}/weight/dateRange"
538544
params = {"startDate": str(startdate), "endDate": str(enddate)}
539545
logger.debug("Requesting body composition")
@@ -556,6 +562,7 @@ def add_body_composition(
556562
visceral_fat_rating: float | None = None,
557563
bmi: float | None = None,
558564
) -> dict[str, Any]:
565+
weight = _validate_positive_number(weight, "weight")
559566
dt = datetime.fromisoformat(timestamp) if timestamp else datetime.now()
560567
fitEncoder = FitEncoderWeight()
561568
fitEncoder.write_file_info()
@@ -626,6 +633,8 @@ def add_weigh_in_with_timestamps(
626633

627634
url = f"{self.garmin_connect_weight_url}/user-weight"
628635

636+
if unitKey not in VALID_WEIGHT_UNITS:
637+
raise ValueError(f"UnitKey must be one of {VALID_WEIGHT_UNITS}")
629638
# Validate and format the timestamps
630639
dt = datetime.fromisoformat(dateTimestamp) if dateTimestamp else datetime.now()
631640
dtGMT = (
@@ -858,25 +867,24 @@ def get_lactate_threshold(
858867
# (or more, if cyclingHeartRate ever gets values) nearly identical dicts.
859868
# We're combining them here
860869
for entry in speed_and_heart_rate:
861-
if entry["speed"] is not None:
870+
speed = entry.get("speed")
871+
if speed is not None:
862872
speed_and_heart_rate_dict["userProfilePK"] = entry["userProfilePK"]
863873
speed_and_heart_rate_dict["version"] = entry["version"]
864874
speed_and_heart_rate_dict["calendarDate"] = entry["calendarDate"]
865875
speed_and_heart_rate_dict["sequence"] = entry["sequence"]
866-
speed_and_heart_rate_dict["speed"] = entry["speed"]
876+
speed_and_heart_rate_dict["speed"] = speed
867877

868878
# This is not a typo. The Garmin dictionary has a typo as of 2025-07-08, referring to it as "hearRate"
869-
elif entry["hearRate"] is not None:
870-
speed_and_heart_rate_dict["heartRate"] = entry[
871-
"hearRate"
872-
] # Fix Garmin's typo
879+
hr = entry.get("hearRate")
880+
if hr is not None:
881+
speed_and_heart_rate_dict["heartRate"] = hr
882+
# Fix Garmin's typo
873883

874884
# Doesn't exist for me but adding it just in case. We'll check for each entry
875-
if entry["heartRateCycling"] is not None:
876-
speed_and_heart_rate_dict["heartRateCycling"] = entry[
877-
"heartRateCycling"
878-
]
879-
885+
hrc = entry.get("heartRateCycling")
886+
if hrc is not None:
887+
speed_and_heart_rate_dict["heartRateCycling"] = hrc
880888
return {
881889
"speed_and_heart_rate": speed_and_heart_rate_dict,
882890
"power": power_dict,
@@ -1205,6 +1213,7 @@ def get_endurance_score(
12051213
Using a range returns the aggregated weekly values for that week.
12061214
"""
12071215

1216+
startdate = _validate_date_format(startdate, "startdate")
12081217
if enddate is None:
12091218
url = self.garmin_connect_endurance_score_url
12101219
params = {"calendarDate": str(startdate)}
@@ -1213,6 +1222,7 @@ def get_endurance_score(
12131222
return self.connectapi(url, params=params)
12141223
else:
12151224
url = f"{self.garmin_connect_endurance_score_url}/stats"
1225+
enddate = _validate_date_format(enddate, "enddate")
12161226
params = {
12171227
"startDate": str(startdate),
12181228
"endDate": str(enddate),
@@ -1299,13 +1309,16 @@ def get_hill_score(
12991309

13001310
if enddate is None:
13011311
url = self.garmin_connect_hill_score_url
1312+
startdate = _validate_date_format(startdate, "startdate")
13021313
params = {"calendarDate": str(startdate)}
13031314
logger.debug("Requesting hill score data for a single day")
13041315

13051316
return self.connectapi(url, params=params)
13061317

13071318
else:
13081319
url = f"{self.garmin_connect_hill_score_url}/stats"
1320+
startdate = _validate_date_format(startdate, "startdate")
1321+
enddate = _validate_date_format(enddate, "enddate")
13091322
params = {
13101323
"startDate": str(startdate),
13111324
"endDate": str(enddate),
@@ -1351,6 +1364,8 @@ def get_device_solar_data(
13511364
else:
13521365
single_day = False
13531366

1367+
startdate = _validate_date_format(startdate, "startdate")
1368+
enddate = _validate_date_format(enddate, "enddate")
13541369
params = {"singleDayView": single_day}
13551370

13561371
url = f"{self.garmin_connect_solar_url}/{device_id}/{startdate}/{enddate}"
@@ -1648,6 +1663,8 @@ def get_progress_summary_between_dates(
16481663
"""
16491664

16501665
url = self.garmin_connect_fitnessstats
1666+
startdate = _validate_date_format(startdate, "startdate")
1667+
enddate = _validate_date_format(enddate, "enddate")
16511668
params = {
16521669
"startDate": str(startdate),
16531670
"endDate": str(enddate),
@@ -1680,6 +1697,8 @@ def get_goals(
16801697

16811698
goals = []
16821699
url = self.garmin_connect_goals_url
1700+
start = _validate_positive_integer(start, "start")
1701+
limit = _validate_positive_integer(limit, "limit")
16831702
params = {
16841703
"status": status,
16851704
"start": str(start),
@@ -1832,10 +1851,9 @@ def get_activity_details(
18321851
"""Return activity details."""
18331852

18341853
activity_id = str(activity_id)
1835-
params = {
1836-
"maxChartSize": str(maxchart),
1837-
"maxPolylineSize": str(maxpoly),
1838-
}
1854+
maxchart = _validate_positive_integer(maxchart, "maxchart")
1855+
maxpoly = _validate_positive_integer(maxpoly, "maxpoly")
1856+
params = {"maxChartSize": str(maxchart), "maxPolylineSize": str(maxpoly)}
18391857
url = f"{self.garmin_connect_activity}/{activity_id}/details"
18401858
logger.debug("Requesting details for activity id %s", activity_id)
18411859

@@ -1844,7 +1862,7 @@ def get_activity_details(
18441862
def get_activity_exercise_sets(self, activity_id: str) -> dict[str, Any]:
18451863
"""Return activity exercise sets."""
18461864

1847-
activity_id = str(activity_id)
1865+
activity_id = _validate_positive_integer(activity_id, "activity_id")
18481866
url = f"{self.garmin_connect_activity}/{activity_id}/exerciseSets"
18491867
logger.debug("Requesting exercise sets for activity id %s", activity_id)
18501868

@@ -1853,7 +1871,7 @@ def get_activity_exercise_sets(self, activity_id: str) -> dict[str, Any]:
18531871
def get_activity_gear(self, activity_id: str) -> dict[str, Any]:
18541872
"""Return gears used for activity id."""
18551873

1856-
activity_id = str(activity_id)
1874+
activity_id = _validate_positive_integer(activity_id, "activity_id")
18571875
params = {
18581876
"activityId": str(activity_id),
18591877
}
@@ -1907,19 +1925,23 @@ def get_workouts(self, start: int = 0, limit: int = 100) -> dict[str, Any]:
19071925
"""Return workouts from start till end."""
19081926

19091927
url = f"{self.garmin_workouts}/workouts"
1928+
start = _validate_non_negative_integer(start, "start")
1929+
limit = _validate_positive_integer(limit, "limit")
19101930
logger.debug(f"Requesting workouts from {start} with limit {limit}")
19111931
params = {"start": start, "limit": limit}
19121932
return self.connectapi(url, params=params)
19131933

19141934
def get_workout_by_id(self, workout_id: str) -> dict[str, Any]:
19151935
"""Return workout by id."""
19161936

1937+
workout_id = _validate_positive_integer(workout_id, "workout_id")
19171938
url = f"{self.garmin_workouts}/workout/{workout_id}"
19181939
return self.connectapi(url)
19191940

19201941
def download_workout(self, workout_id: str) -> bytes:
19211942
"""Download workout by id."""
19221943

1944+
workout_id = _validate_positive_integer(workout_id, "workout_id")
19231945
url = f"{self.garmin_workouts}/workout/FIT/{workout_id}"
19241946
logger.debug("Downloading workout from %s", url)
19251947

@@ -1958,6 +1980,8 @@ def get_menstrual_calendar_data(
19581980
) -> dict[str, Any]:
19591981
"""Return summaries of cycles that have days between startdate and enddate."""
19601982

1983+
startdate = _validate_date_format(startdate, "startdate")
1984+
enddate = _validate_date_format(enddate, "enddate")
19611985
url = f"{self.garmin_connect_menstrual_calendar_url}/{startdate}/{enddate}"
19621986
logger.debug(
19631987
f"Requesting menstrual data for dates {startdate} through {enddate}"

0 commit comments

Comments
 (0)