@@ -291,13 +291,15 @@ def connectapi(self, path: str, **kwargs: Any) -> Any:
291291 else :
292292 raise GarminConnectConnectionError (f"HTTP error: { e } " ) from e
293293 except Exception as e :
294+ logger .exception ("Connection error during connectapi path=%s" , path )
294295 raise GarminConnectConnectionError (f"Connection error: { e } " ) from e
295296
296297 def download (self , path : str , ** kwargs : Any ) -> Any :
297298 """Wrapper for garth download with error handling."""
298299 try :
299300 return self .garth .download (path , ** kwargs )
300301 except Exception as e :
302+ logger .exception ("Download error path=%s" , path )
301303 status = getattr (getattr (e , "response" , None ), "status_code" , None )
302304 logger .error (
303305 "Download failed for path '%s': %s (status=%s)" , path , e , status
@@ -483,8 +485,8 @@ def get_daily_steps(self, start: str, end: str) -> list[dict[str, Any]]:
483485 end = _validate_date_format (end , "end" )
484486
485487 # Validate date range
486- start_date = datetime .strptime (start , "%Y-%m-%d" ).date ()
487- end_date = datetime .strptime (end , "%Y-%m-%d" ).date ()
488+ start_date = datetime .strptime (start , DATE_FORMAT_STR ).date ()
489+ end_date = datetime .strptime (end , DATE_FORMAT_STR ).date ()
488490
489491 if start_date > end_date :
490492 raise ValueError ("start date cannot be after end date" )
@@ -783,7 +785,13 @@ def set_blood_pressure(
783785 "sourceType" : "MANUAL" ,
784786 "notes" : notes ,
785787 }
786-
788+ for name , val , lo , hi in (
789+ ("systolic" , systolic , 70 , 260 ),
790+ ("diastolic" , diastolic , 40 , 150 ),
791+ ("pulse" , pulse , 20 , 250 ),
792+ ):
793+ if not isinstance (val , int ) or not (lo <= val <= hi ):
794+ raise ValueError (f"{ name } must be an int in [{ lo } , { hi } ]" )
787795 logger .debug ("Adding blood pressure" )
788796
789797 return self .garth .post ("connectapi" , url , json = payload ).json ()
@@ -852,10 +860,11 @@ def get_lactate_threshold(
852860 power_url = f"{ self .garmin_connect_biometric_url } /powerToWeight/latest/{ date .today ()} ?sport=Running"
853861
854862 power = self .connectapi (power_url )
855- try :
863+ if isinstance ( power , list ) and power :
856864 power_dict = power [0 ]
857- except IndexError :
858- # If no power available
865+ elif isinstance (power , dict ):
866+ power_dict = power
867+ else :
859868 power_dict = {}
860869
861870 speed_and_heart_rate = self .connectapi (speed_and_heart_rate_url )
@@ -965,10 +974,13 @@ def add_hydration_data(
965974 # If cdate is not null, validate and use timestamp associated with midnight
966975 cdate = _validate_date_format (cdate , "cdate" )
967976 try :
968- raw_ts = datetime .strptime (cdate , "%Y-%m-%d" )
969- timestamp = _fmt_ts (raw_ts )
970- except ValueError as e :
971- raise ValueError (f"invalid cdate: { e } " ) from e
977+ raw_ts = datetime .fromisoformat (cdate )
978+ except ValueError :
979+ # if cdate is just a date, parse with format and set time to 00:00:00
980+ raw_ts = datetime .strptime (cdate , DATE_FORMAT_STR )
981+ timestamp = raw_ts .replace (
982+ hour = 0 , minute = 0 , second = 0 , microsecond = 0
983+ ).isoformat (timespec = "microseconds" )
972984
973985 elif cdate is None and timestamp is not None :
974986 # If timestamp is not null, validate and set cdate equal to date part of timestamp
@@ -1123,6 +1135,8 @@ def is_badge_in_progress(badge: dict) -> bool:
11231135 def get_adhoc_challenges (self , start : int , limit : int ) -> dict [str , Any ]:
11241136 """Return adhoc challenges for current user."""
11251137
1138+ start = _validate_non_negative_integer (start , "start" )
1139+ limit = _validate_positive_integer (limit , "limit" )
11261140 url = self .garmin_connect_adhoc_challenges_url
11271141 params = {"start" : str (start ), "limit" : str (limit )}
11281142 logger .debug ("Requesting adhoc challenges for user" )
@@ -1132,6 +1146,8 @@ def get_adhoc_challenges(self, start: int, limit: int) -> dict[str, Any]:
11321146 def get_badge_challenges (self , start : int , limit : int ) -> dict [str , Any ]:
11331147 """Return badge challenges for current user."""
11341148
1149+ start = _validate_non_negative_integer (start , "start" )
1150+ limit = _validate_positive_integer (limit , "limit" )
11351151 url = self .garmin_connect_badge_challenges_url
11361152 params = {"start" : str (start ), "limit" : str (limit )}
11371153 logger .debug ("Requesting badge challenges for user" )
@@ -1141,6 +1157,8 @@ def get_badge_challenges(self, start: int, limit: int) -> dict[str, Any]:
11411157 def get_available_badge_challenges (self , start : int , limit : int ) -> dict [str , Any ]:
11421158 """Return available badge challenges."""
11431159
1160+ start = _validate_non_negative_integer (start , "start" )
1161+ limit = _validate_positive_integer (limit , "limit" )
11441162 url = self .garmin_connect_available_badge_challenges_url
11451163 params = {"start" : str (start ), "limit" : str (limit )}
11461164 logger .debug ("Requesting available badge challenges" )
@@ -1152,6 +1170,8 @@ def get_non_completed_badge_challenges(
11521170 ) -> dict [str , Any ]:
11531171 """Return badge non-completed challenges for current user."""
11541172
1173+ start = _validate_non_negative_integer (start , "start" )
1174+ limit = _validate_positive_integer (limit , "limit" )
11551175 url = self .garmin_connect_non_completed_badge_challenges_url
11561176 params = {"start" : str (start ), "limit" : str (limit )}
11571177 logger .debug ("Requesting badge challenges for user" )
@@ -1163,6 +1183,8 @@ def get_inprogress_virtual_challenges(
11631183 ) -> dict [str , Any ]:
11641184 """Return in-progress virtual challenges for current user."""
11651185
1186+ start = _validate_non_negative_integer (start , "start" )
1187+ limit = _validate_positive_integer (limit , "limit" )
11661188 url = self .garmin_connect_inprogress_virtual_challenges_url
11671189 params = {"start" : str (start ), "limit" : str (limit )}
11681190 logger .debug ("Requesting in-progress virtual challenges for user" )
@@ -1488,7 +1510,7 @@ def set_activity_type(
14881510
14891511 def create_manual_activity_from_json (self , payload : dict [str , Any ]) -> Any :
14901512 url = f"{ self .garmin_connect_activity } "
1491- logger .debug (f "Uploading manual activity: { str (payload )} " )
1513+ logger .debug ("Uploading manual activity: %s" , str (payload ))
14921514 return self .garth .post ("connectapi" , url , json = payload , api = True )
14931515
14941516 def create_manual_activity (
0 commit comments