Compare commits

...

20 Commits
0.2.33 ... main

Author SHA1 Message Date
Ron
c9df43a1f3 Merge pull request #365 from cyberjunky/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2026-01-01 17:34:32 +01:00
Ron
d6053f2977 Merge pull request #369 from cyberjunky/dependabot/pip/pyupgrade-3.21.2
Bump pyupgrade from 3.21.0 to 3.21.2
2026-01-01 17:33:22 +01:00
Ron
40f4bb9b44 Merge branch 'main' into dependabot/pip/pyupgrade-3.21.2 2026-01-01 17:33:06 +01:00
Ron
6fe3fb3881 Merge pull request #376 from cyberjunky/dependabot/pip/pre-commit-4.5.1
Bump pre-commit from 4.3.0 to 4.5.1
2026-01-01 17:31:47 +01:00
Ron
2b17d44056 Merge pull request #377 from cyberjunky/dependabot/pip/homeassistant-2025.12.4
Bump homeassistant from 2025.10.2 to 2025.12.4
2026-01-01 17:31:36 +01:00
Ron
b2fa46400e Merge pull request #378 from cyberjunky/dependabot/pip/ruff-0.14.10
Bump ruff from 0.14.3 to 0.14.10
2026-01-01 17:31:23 +01:00
dependabot[bot]
51764dbdee Bump ruff from 0.14.3 to 0.14.10
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.3 to 0.14.10.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.3...0.14.10)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:23 +00:00
dependabot[bot]
a5b6a77480 Bump homeassistant from 2025.10.2 to 2025.12.4
Bumps [homeassistant](https://github.com/home-assistant/core) from 2025.10.2 to 2025.12.4.
- [Release notes](https://github.com/home-assistant/core/releases)
- [Commits](https://github.com/home-assistant/core/compare/2025.10.2...2025.12.4)

---
updated-dependencies:
- dependency-name: homeassistant
  dependency-version: 2025.12.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:14 +00:00
dependabot[bot]
e61f38f02d Bump pre-commit from 4.3.0 to 4.5.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.3.0 to 4.5.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.3.0...v4.5.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-version: 4.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:06:04 +00:00
dependabot[bot]
1946869604 Bump pyupgrade from 3.21.0 to 3.21.2
Bumps [pyupgrade](https://github.com/asottile/pyupgrade) from 3.21.0 to 3.21.2.
- [Commits](https://github.com/asottile/pyupgrade/compare/v3.21.0...v3.21.2)

---
updated-dependencies:
- dependency-name: pyupgrade
  dependency-version: 3.21.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 06:13:22 +00:00
dependabot[bot]
d366b64013 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 06:08:05 +00:00
Ron Klinkien
e1371ee532 Fix home-assistant vulnerability 2025-11-06 16:11:20 +01:00
Ron Klinkien
2f2f38839a Handle 403/404 errors gracefully for users without gear
- Change 403 Forbidden errors from ERROR to DEBUG level
- Change 404 Not Found errors from ERROR to DEBUG level
- Add helpful message indicating user may not have gear configured
- Prevents error spam in logs for users without Garmin gear
- Integration continues to work normally without gear sensors
2025-11-06 16:06:02 +01:00
Ron
d6e669494d Enhance README with project badges
Added badges for GitHub release, activity, license, and maintenance to the README.
2025-11-06 15:25:01 +01:00
Ron Klinkien
53b21e92fb Fix userprofile keyerror, remove duplicate gear fetch code 2025-11-06 14:55:29 +01:00
Ron Klinkien
441e4a0018 Reauth fix 2025-11-05 09:58:25 +00:00
Ron Klinkien
8dd4995fb2 Merged several fixes 2025-11-05 09:58:19 +00:00
Ron Klinkien
160a1b96e9 Config migration improvements 2025-11-04 20:59:04 +00:00
Ron Klinkien
355f9d7732 More edge case migration handling 2025-11-04 21:14:00 +01:00
Ron Klinkien
1776cb4037 More robust config flow when integration is reinstalled 2025-11-04 18:03:35 +00:00
7 changed files with 135 additions and 61 deletions

View File

@@ -10,5 +10,5 @@ jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v5"
- uses: "actions/checkout@v6"
- uses: home-assistant/actions/hassfest@master

View File

@@ -1,3 +1,8 @@
[![GitHub Release][releases-shield]][releases]
[![GitHub Activity][commits-shield]][commits]
[![License][license-shield]](LICENSE)
![Project Maintenance][maintenance-shield]
[![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=for-the-badge&logo=paypal)](https://www.paypal.me/cyberjunkynl/)
[![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-GitHub-red.svg?style=for-the-badge&logo=github)](https://github.com/sponsors/cyberjunky)
@@ -281,3 +286,10 @@ If you find this library useful for your projects, please consider supporting it
- Shows appreciation for hundreds of hours of development
Every contribution, no matter the size, makes a difference and is greatly appreciated! 🙏
[releases-shield]: https://img.shields.io/github/release/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[releases]: https://github.com/cyberjunky/home-assistant-garmin_connect/releases
[commits-shield]: https://img.shields.io/github/commit-activity/y/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[commits]: https://github.com/cyberjunky/home-assistant-garmin_connect/commits/main
[license-shield]: https://img.shields.io/github/license/cyberjunky/home-assistant-garmin_connect.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-cyberjunky-blue.svg?style=for-the-badge

View File

@@ -39,10 +39,15 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"Migrating Garmin Connect config entry from version %s", entry.version)
if entry.version == 1:
# Check if we need to migrate (old entries have username/password, new ones have token)
if CONF_TOKEN not in entry.data and CONF_USERNAME in entry.data and CONF_PASSWORD in entry.data:
# Scenario 1: Has USERNAME + PASSWORD but no TOKEN (old auth method)
# Migrate to: ID + TOKEN
if (
CONF_TOKEN not in entry.data
and CONF_USERNAME in entry.data
and CONF_PASSWORD in entry.data
):
_LOGGER.info(
"Migrating Garmin Connect config entry to token-based authentication")
"Migrating Garmin Connect config entry from username/password to token-based authentication")
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
@@ -76,10 +81,54 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except Exception as err: # pylint: disable=broad-except
_LOGGER.error(
"Failed to migrate Garmin Connect config entry. "
"Please re-add the integration. Error: %s", err
"Please re-add the integration. Error: %s",
err,
)
return False
# Scenario 2: Has USERNAME + TOKEN but no ID (partially migrated)
# Migrate to: ID + TOKEN (remove USERNAME)
elif (
CONF_ID not in entry.data
and CONF_USERNAME in entry.data
and CONF_TOKEN in entry.data
):
_LOGGER.info(
"Migrating Garmin Connect config entry: converting USERNAME to ID")
username = entry.data[CONF_USERNAME]
# Create new data with ID instead of USERNAME
new_data = {
CONF_ID: username,
CONF_TOKEN: entry.data[CONF_TOKEN],
}
# Update the config entry
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Successfully migrated Garmin Connect config entry from USERNAME to ID")
return True
# Scenario 3: Missing both TOKEN and credentials (incomplete/corrupted)
# Add placeholder ID to allow reauth flow
elif CONF_TOKEN not in entry.data:
if CONF_ID not in entry.data:
_LOGGER.info(
"Config entry missing CONF_ID, adding placeholder for reauth flow")
new_data = {
**entry.data,
CONF_ID: entry.entry_id, # Use entry_id as fallback
}
hass.config_entries.async_update_entry(entry, data=new_data)
_LOGGER.info(
"Garmin Connect config entry is incomplete (missing token). "
"Reauthentication will be required to complete setup."
)
return True
return True
@@ -152,14 +201,18 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
try:
# Check if the token exists in the entry data
if CONF_TOKEN not in self.entry.data:
_LOGGER.error(
"Token not found in config entry. This may be an old config entry that needs migration. "
"Please remove and re-add the Garmin Connect integration."
_LOGGER.info(
"Token not found in config entry. Reauthentication required."
)
raise ConfigEntryAuthFailed(
"Token not found, please re-add the integration")
"Token not found in config entry. This may be an old or incomplete configuration. "
"A reauthentication flow will be initiated. Please check your notifications."
)
await self.hass.async_add_executor_job(self.api.login, self.entry.data[CONF_TOKEN])
except ConfigEntryAuthFailed:
# Re-raise ConfigEntryAuthFailed without logging as "unknown error"
raise
except GarminConnectAuthenticationError as err:
_LOGGER.error(
"Authentication error occurred during login: %s", err.response.text)
@@ -332,10 +385,14 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
# Gear data
try:
# Check if userProfileId exists in summary before fetching gear data
if Gear.USERPROFILE_ID in summary:
gear = await self.hass.async_add_executor_job(
self.api.get_gear, summary[Gear.USERPROFILE_ID]
)
_LOGGER.debug("Gear data fetched: %s", gear)
else:
_LOGGER.debug("No userProfileId found in summary, skipping gear data fetch")
# Fitness age data
fitnessage_data = await self.hass.async_add_executor_job(
@@ -385,15 +442,8 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
return {}
try:
# Gear data like shoes, bike, etc.
gear = await self.hass.async_add_executor_job(
self.api.get_gear, summary[Gear.USERPROFILE_ID]
)
# Use gear data from the first fetch if available
if gear:
_LOGGER.debug("Gear data fetched: %s", gear)
else:
_LOGGER.debug("No gear data found")
# Gear stats data like distance, time, etc.
tasks: list[Awaitable] = [
self.hass.async_add_executor_job(
@@ -408,6 +458,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
_LOGGER.debug("No gear statistics data found")
# Gear defaults data like shoe, bike, etc.
if Gear.USERPROFILE_ID in summary:
gear_defaults = await self.hass.async_add_executor_job(
self.api.get_gear_defaults, summary[Gear.USERPROFILE_ID]
)
@@ -415,6 +466,10 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
_LOGGER.debug("Gear defaults data fetched: %s", gear_defaults)
else:
_LOGGER.debug("No gear defaults data found")
else:
_LOGGER.debug("No userProfileId found in summary, skipping gear defaults fetch")
else:
_LOGGER.debug("No gear data available, skipping gear stats and defaults fetch")
except GarminConnectAuthenticationError as err:
_LOGGER.error(
"Authentication error occurred while fetching Gear data: %s", err.response.text)
@@ -431,9 +486,12 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
if err.response.status_code == 401:
_LOGGER.error(
"Authentication error while fetching Gear data: %s", err.response.text)
elif err.response.status_code == 403:
_LOGGER.debug(
"Access forbidden while fetching Gear data (user may not have gear configured): %s", err.response.text)
elif err.response.status_code == 404:
_LOGGER.error(
"URL not found error while fetching Gear data: %s", err.response.text)
_LOGGER.debug(
"Gear data not found (user may not have gear configured): %s", err.response.text)
elif err.response.status_code == 429:
_LOGGER.error(
"Too many requests error while fetching Gear data: %s", err.response.text)

View File

@@ -126,13 +126,15 @@ class GarminConnectConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""
config_data = {
CONF_ID: self._username,
CONF_USERNAME: self._username,
CONF_TOKEN: self._api.garth.dumps(),
}
existing_entry = await self.async_set_unique_id(self._username)
if existing_entry:
return self.async_update_reload_and_abort(existing_entry, data=config_data)
self.hass.config_entries.async_update_entry(
existing_entry, data=config_data)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(
title=cast(str, self._username), data=config_data
@@ -180,9 +182,11 @@ class GarminConnectConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""
Start the reauthorization process using existing configuration entry data.
Extracts the username from the entry data and advances to the reauthorization confirmation step.
Extracts the username from the entry data (using CONF_ID if CONF_USERNAME is not available for migrated entries) and advances to the reauthorization confirmation step.
"""
self._username = entry_data[CONF_USERNAME]
# For backward compatibility: try CONF_USERNAME first, fall back to CONF_ID
self._username = entry_data.get(
CONF_USERNAME) or entry_data.get(CONF_ID)
return await self.async_step_reauth_confirm()

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
"requirements": ["garminconnect>=0.2.31"],
"version": "0.2.33"
"version": "0.2.38"
}

View File

@@ -1 +1 @@
homeassistant==2025.1.4
homeassistant==2025.12.4

View File

@@ -1,8 +1,8 @@
--requirement requirements_base.txt
codespell==2.4.1
isort==7.0.0
pre-commit==4.3.0
pre-commit==4.5.1
pre-commit-hooks==6.0.0
pyupgrade==3.21.0
ruff==0.14.3
pyupgrade==3.21.2
ruff==0.14.10
vulture==2.14