From 27b1270239d21a1476ab389c60aaf4ab763b4266 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Mon, 3 Jul 2023 15:38:20 +0300 Subject: [PATCH 1/5] Limit backoff and maximum time to retry for AioPrices.fetch() Infinite backoff and retry is a rather strange configuration.. --- custom_components/nordpool/aio_price.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/nordpool/aio_price.py b/custom_components/nordpool/aio_price.py index 5a947b0..6539fa3 100644 --- a/custom_components/nordpool/aio_price.py +++ b/custom_components/nordpool/aio_price.py @@ -179,7 +179,10 @@ async def _fetch_json(self, data_type, end_date=None): # Add more exceptions as we find them. KeyError is raised when the api return # junk due to currency not being available in the data. - @backoff.on_exception(backoff.expo, (aiohttp.ClientError, KeyError), logger=_LOGGER) + @backoff.on_exception( + backoff.expo, + (aiohttp.ClientError, KeyError), + logger=_LOGGER, max_value=20, max_time=60) async def fetch(self, data_type, end_date=None, areas=None): """ Fetch data from API. From a0c575c4512c042f580045de42e3e95b666cd420 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Thu, 6 Jul 2023 17:18:30 +0300 Subject: [PATCH 2/5] Stop accepting an unused parameter in update_today and update_tomorrow This seems like a vestigial leftover from an earlier implementation (probably where these functions were called directly by listeners). --- custom_components/nordpool/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/custom_components/nordpool/__init__.py b/custom_components/nordpool/__init__.py index 7043ddf..25a6e9a 100644 --- a/custom_components/nordpool/__init__.py +++ b/custom_components/nordpool/__init__.py @@ -74,12 +74,12 @@ async def _update(self, type_="today", dt=None): if data: self._data[currency][type_] = data["areas"] - async def update_today(self, _: datetime): + async def update_today(self): """Update today's prices""" _LOGGER.debug("Updating today's prices.") await self._update("today") - async def update_tomorrow(self, _: datetime): + async def update_tomorrow(self): """Update tomorrows prices.""" _LOGGER.debug("Updating tomorrows prices.") await self._update(type_="tomorrow", dt=dt_utils.now() + timedelta(hours=24)) @@ -96,8 +96,8 @@ async def _someday(self, area: str, currency: str, day: str): # set in the sensor. if currency not in self.currency: self.currency.append(currency) - await self.update_today(None) - await self.update_tomorrow(None) + await self.update_today() + await self.update_tomorrow() # Send a new data request after new data is updated for this first run # This way if the user has multiple sensors they will all update @@ -128,7 +128,7 @@ async def new_day_cb(_): for curr in api.currency: if not api._data.get(curr, {}).get("tomorrow"): - api._data[curr]["today"] = await api.update_today(None) + api._data[curr]["today"] = await api.update_today() else: api._data[curr]["today"] = api._data[curr]["tomorrow"] api._data[curr]["tomorrow"] = {} @@ -140,12 +140,12 @@ async def new_hr(_): _LOGGER.debug("Called new_hr callback") async_dispatcher_send(hass, EVENT_NEW_HOUR) - async def new_data_cb(tdo): + async def new_data_cb(_): """Callback to fetch new data for tomorrows prices at 1300ish CET and notify any sensors, about the new data """ # _LOGGER.debug("Called new_data_cb") - await api.update_tomorrow(tdo) + await api.update_tomorrow() async_dispatcher_send(hass, EVENT_NEW_PRICE) # Handles futures updates From 56379f3c156ee430338ec330c9ebc45c260f2669 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Mon, 3 Jul 2023 15:39:47 +0300 Subject: [PATCH 3/5] Raise exception if data is not available when update is called This happens if the first auction is unsuccessful. Implemented by verifying that the data is valid (in join_result_for_correct_time()) and raising an exception if it isn't. This partially fixes issue #326. --- custom_components/nordpool/aio_price.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/custom_components/nordpool/aio_price.py b/custom_components/nordpool/aio_price.py index 6539fa3..7314ac6 100644 --- a/custom_components/nordpool/aio_price.py +++ b/custom_components/nordpool/aio_price.py @@ -90,6 +90,12 @@ "PL ": "PL", } +INVALID_VALUES = frozenset((None, float("inf"))) + + +class InvalidValueException(ValueError): + pass + def join_result_for_correct_time(results, dt): """Parse a list of responses from the api @@ -135,6 +141,8 @@ def join_result_for_correct_time(results, dt): local = val["start"].astimezone(zone) local_end = val["end"].astimezone(zone) if start_of_day <= local and local <= end_of_day: + if val['value'] in INVALID_VALUES: + raise InvalidValueException() if local == local_end: _LOGGER.info( "Hour has the same start and end, most likly due to dst change %s exluded this hour", From 72d3e16be701cfa434c1f838b511bee48c4e6923 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Fri, 28 Jul 2023 11:50:40 +0300 Subject: [PATCH 4/5] Handle exception from update_tommorrow() on initial data load Data for tomorrow may or may not be available when initializing the component. --- custom_components/nordpool/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/custom_components/nordpool/__init__.py b/custom_components/nordpool/__init__.py index 25a6e9a..3a5936d 100644 --- a/custom_components/nordpool/__init__.py +++ b/custom_components/nordpool/__init__.py @@ -13,7 +13,7 @@ from homeassistant.util import dt as dt_utils from pytz import timezone -from .aio_price import AioPrices +from .aio_price import AioPrices, InvalidValueException from .events import async_track_time_change_in_tz DOMAIN = "nordpool" @@ -97,7 +97,10 @@ async def _someday(self, area: str, currency: str, day: str): if currency not in self.currency: self.currency.append(currency) await self.update_today() - await self.update_tomorrow() + try: + await self.update_tomorrow() + except InvalidValueException: + _LOGGER.debug("No data available for tomorrow, retrying later") # Send a new data request after new data is updated for this first run # This way if the user has multiple sensors they will all update From 944b43442f10c675cf9b4c70b21ef67db5998d83 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Sat, 29 Jul 2023 14:55:11 +0300 Subject: [PATCH 5/5] Retry new_data_cb() if data isn't available upstream Using the already-used-elsewhere backoff library. Retry every 10 minutes for a maximum of two hours. --- custom_components/nordpool/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/custom_components/nordpool/__init__.py b/custom_components/nordpool/__init__.py index 3a5936d..cec4f61 100644 --- a/custom_components/nordpool/__init__.py +++ b/custom_components/nordpool/__init__.py @@ -1,9 +1,9 @@ import logging from collections import defaultdict -from datetime import datetime, timedelta -from functools import partial +from datetime import timedelta from random import randint +import backoff import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant @@ -143,6 +143,10 @@ async def new_hr(_): _LOGGER.debug("Called new_hr callback") async_dispatcher_send(hass, EVENT_NEW_HOUR) + @backoff.on_exception( + backoff.constant, + (InvalidValueException), + logger=_LOGGER, interval=600, max_time=7200, jitter=None) async def new_data_cb(_): """Callback to fetch new data for tomorrows prices at 1300ish CET and notify any sensors, about the new data