refactor(manager): enhance API error handling and fallback logic
Some checks are pending
Auto-Tag on Version Bump / Check and create version tag (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run

Improve error handling for API fetch failures by implementing a fallback to cached intervals. This ensures the system can continue functioning during transient API issues.

Impact: Users experience fewer interruptions when the API is temporarily unavailable, as cached data will be used seamlessly.
This commit is contained in:
Julian Pawlowski 2026-04-13 12:28:36 +00:00
parent bf95dc5efc
commit 27ab58bbf5

View file

@ -9,7 +9,10 @@ from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from custom_components.tibber_prices.api.exceptions import TibberPricesApiClientError from custom_components.tibber_prices.api.exceptions import (
TibberPricesApiClientCommunicationError,
TibberPricesApiClientError,
)
from homeassistant.util import dt as dt_utils from homeassistant.util import dt as dt_utils
from .cache import TibberPricesIntervalPoolFetchGroupCache from .cache import TibberPricesIntervalPoolFetchGroupCache
@ -201,9 +204,11 @@ class TibberPricesIntervalPool:
) )
# Fetch missing ranges from API # Fetch missing ranges from API
api_fetch_failed = False
if missing_ranges: if missing_ranges:
fetch_time_iso = dt_utils.now().isoformat() fetch_time_iso = dt_utils.now().isoformat()
try:
# Fetch with callback for immediate caching # Fetch with callback for immediate caching
await self._fetcher.fetch_missing_ranges( await self._fetcher.fetch_missing_ranges(
api_client=api_client, api_client=api_client,
@ -211,13 +216,29 @@ class TibberPricesIntervalPool:
missing_ranges=missing_ranges, missing_ranges=missing_ranges,
on_intervals_fetched=lambda intervals, _: self._add_intervals(intervals, fetch_time_iso), on_intervals_fetched=lambda intervals, _: self._add_intervals(intervals, fetch_time_iso),
) )
except TibberPricesApiClientCommunicationError as err:
if cached_intervals:
# Transient API error (e.g. 503) but we have cached data - use it as
# fallback so the coordinator can finish initializing. The next regular
# update cycle will retry the API automatically.
_LOGGER.warning(
"API temporarily unavailable for home %s (%s) - using %d cached intervals as fallback",
self._home_id,
err,
len(cached_intervals),
)
api_fetch_failed = True
else:
# No cached data at all - re-raise so the caller can decide
raise
# After caching all API responses, read from cache again to get final result # After caching all API responses, read from cache again to get final result
# This ensures we return exactly what user requested, filtering out extra intervals # This ensures we return exactly what user requested, filtering out extra intervals
final_result = self._get_cached_intervals(start_time_iso, end_time_iso) final_result = self._get_cached_intervals(start_time_iso, end_time_iso)
# Track if API was called (True if any missing ranges were fetched) # Track if API was called (True if any missing ranges were attempted)
api_called = len(missing_ranges) > 0 # If fetch failed but we fell back to cache, treat as "no API call succeeded"
api_called = len(missing_ranges) > 0 and not api_fetch_failed
_LOGGER_DETAILS.debug( _LOGGER_DETAILS.debug(
"Pool returning %d intervals for home %s (from cache: %d, fetched from API: %d ranges, api_called=%s)", "Pool returning %d intervals for home %s (from cache: %d, fetched from API: %d ranges, api_called=%s)",