@@ -613,7 +613,9 @@ def login(self, /, tokenstore: str | None = None) -> tuple[str | None, str | Non
613613 ) from e
614614
615615 auth_indicators = ["401" , "unauthorized" , "authentication" , "login failed" ]
616- is_auth_error = any (indicator in error_lower for indicator in auth_indicators )
616+ is_auth_error = any (
617+ indicator in error_lower for indicator in auth_indicators
618+ )
617619
618620 if is_auth_error :
619621 raise GarminConnectAuthenticationError (
@@ -643,6 +645,22 @@ def resume_login(
643645
644646 return result1 , result2
645647
648+ def _require_display_name (self ) -> str :
649+ """Return display_name or raise if not set.
650+
651+ New/empty Garmin profiles may not have a displayName, which
652+ would cause 'None' to be interpolated into API URLs and
653+ result in 403 Forbidden errors.
654+ """
655+ if not self .display_name :
656+ raise GarminConnectConnectionError (
657+ "Display name is not set. This usually means your "
658+ "Garmin profile is incomplete (new account with no "
659+ "display name configured). Please set a display name "
660+ "at https://connect.garmin.com and try again."
661+ )
662+ return self .display_name
663+
646664 def get_full_name (self ) -> str | None :
647665 """Return full name."""
648666 return self .full_name
@@ -662,7 +680,7 @@ def get_user_summary(self, cdate: str) -> dict[str, Any]:
662680 # Validate input
663681 cdate = _validate_date_format (cdate , "cdate" )
664682
665- url = f"{ self .garmin_connect_daily_summary_url } /{ self .display_name } "
683+ url = f"{ self .garmin_connect_daily_summary_url } /{ self ._require_display_name () } "
666684 params = {"calendarDate" : cdate }
667685 logger .debug ("Requesting user summary" )
668686
@@ -681,7 +699,7 @@ def get_steps_data(self, cdate: str) -> list[dict[str, Any]]:
681699 # Validate input
682700 cdate = _validate_date_format (cdate , "cdate" )
683701
684- url = f"{ self .garmin_connect_user_summary_chart } /{ self .display_name } "
702+ url = f"{ self .garmin_connect_user_summary_chart } /{ self ._require_display_name () } "
685703 params = {"date" : cdate }
686704 logger .debug ("Requesting steps data" )
687705
@@ -1313,7 +1331,7 @@ def add_hydration_data(
13131331 cdate = raw_ts .date ().isoformat ()
13141332 timestamp = _fmt_ts (raw_ts )
13151333 except ValueError as e :
1316- raise ValueError ("Invalid timestamp format (expected ISO 8601)" ) from e
1334+ raise ValueError ("invalid timestamp format (expected ISO 8601)" ) from e
13171335 else :
13181336 # Both provided - validate consistency and normalize
13191337 cdate = _validate_date_format (cdate , "cdate" )
@@ -1526,7 +1544,7 @@ def get_lifestyle_logging_data(self, cdate: str) -> dict[str, Any]:
15261544 def get_rhr_day (self , cdate : str ) -> dict [str , Any ]:
15271545 """Return resting heartrate data for current user."""
15281546 cdate = _validate_date_format (cdate , "cdate" )
1529- url = f"{ self .garmin_connect_rhr_url } /{ self .display_name } "
1547+ url = f"{ self .garmin_connect_rhr_url } /{ self ._require_display_name () } "
15301548 params = {
15311549 "fromDate" : cdate ,
15321550 "untilDate" : cdate ,
@@ -1646,7 +1664,7 @@ def get_running_tolerance(
16461664 enddate = _validate_date_format (enddate , "enddate" )
16471665 if aggregation not in ("daily" , "weekly" ):
16481666 raise ValueError (
1649- f"Invalid aggregation '{ aggregation } '. Must be 'daily' or 'weekly'. "
1667+ f"invalid aggregation '{ aggregation } ', must be 'daily' or 'weekly'"
16501668 )
16511669 url = self .garmin_connect_running_tolerance_url
16521670 params = {
@@ -1688,7 +1706,8 @@ def get_race_predictions(
16881706
16891707 if _type is None and startdate is None and enddate is None :
16901708 url = (
1691- self .garmin_connect_race_predictor_url + f"/latest/{ self .display_name } "
1709+ self .garmin_connect_race_predictor_url
1710+ + f"/latest/{ self ._require_display_name ()} "
16921711 )
16931712 return self .connectapi (url )
16941713
@@ -1700,10 +1719,11 @@ def get_race_predictions(
17001719 - datetime .strptime (startdate , DATE_FORMAT_STR ).date ()
17011720 ).days > 366 :
17021721 raise ValueError (
1703- "Startdate cannot be more than one year before enddate"
1722+ "startdate cannot be more than one year before enddate"
17041723 )
17051724 url = (
1706- self .garmin_connect_race_predictor_url + f"/{ _type } /{ self .display_name } "
1725+ self .garmin_connect_race_predictor_url
1726+ + f"/{ _type } /{ self ._require_display_name ()} "
17071727 )
17081728 params = {"fromCalendarDate" : startdate , "toCalendarDate" : enddate }
17091729 return self .connectapi (url , params = params )
0 commit comments