Skip to content

Commit c83cb64

Browse files
committed
fix: guard against None display_name in API URLs and fix ValueError capitalization
1 parent 728c180 commit c83cb64

1 file changed

Lines changed: 29 additions & 9 deletions

File tree

garminconnect/__init__.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)