Add retry to api calls in Nord Pool (#132768)

This commit is contained in:
G Johansson 2024-12-10 18:26:49 +01:00 committed by GitHub
parent dba405dd88
commit f99239538c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 15 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Callable from collections.abc import Callable
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -69,23 +70,30 @@ class NordPoolDataUpdateCoordinator(DataUpdateCoordinator[DeliveryPeriodData]):
self.unsub = async_track_point_in_utc_time( self.unsub = async_track_point_in_utc_time(
self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow()) self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow())
) )
data = await self.api_call()
if data:
self.async_set_updated_data(data)
async def api_call(self, retry: int = 3) -> DeliveryPeriodData | None:
"""Make api call to retrieve data with retry if failure."""
data = None
try: try:
data = await self.client.async_get_delivery_period( data = await self.client.async_get_delivery_period(
dt_util.now(), dt_util.now(),
Currency(self.config_entry.data[CONF_CURRENCY]), Currency(self.config_entry.data[CONF_CURRENCY]),
self.config_entry.data[CONF_AREAS], self.config_entry.data[CONF_AREAS],
) )
except NordPoolEmptyResponseError as error: except (
LOGGER.debug("Empty response error: %s", error) NordPoolEmptyResponseError,
self.async_set_update_error(error) NordPoolResponseError,
return NordPoolError,
except NordPoolResponseError as error: ) as error:
LOGGER.debug("Response error: %s", error)
self.async_set_update_error(error)
return
except NordPoolError as error:
LOGGER.debug("Connection error: %s", error) LOGGER.debug("Connection error: %s", error)
if retry > 0:
next_run = (4 - retry) * 15
LOGGER.debug("Wait %d seconds for next try", next_run)
await asyncio.sleep(next_run)
return await self.api_call(retry - 1)
self.async_set_update_error(error) self.async_set_update_error(error)
return
self.async_set_updated_data(data) return data

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import AsyncGenerator
from datetime import datetime from datetime import datetime
import json import json
from typing import Any from typing import Any
@ -23,6 +24,13 @@ from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def no_sleep() -> AsyncGenerator[None]:
"""No sleeping."""
with patch("homeassistant.components.nordpool.coordinator.asyncio.sleep"):
yield
@pytest.fixture @pytest.fixture
async def load_int( async def load_int(
hass: HomeAssistant, get_data: DeliveryPeriodData hass: HomeAssistant, get_data: DeliveryPeriodData

View File

@ -58,7 +58,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
mock_data.assert_called_once() assert mock_data.call_count == 4
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
mock_data.reset_mock() mock_data.reset_mock()
@ -68,7 +68,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
mock_data.assert_called_once() assert mock_data.call_count == 4
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Authentication error" in caplog.text assert "Authentication error" in caplog.text
@ -79,7 +79,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
mock_data.assert_called_once() assert mock_data.call_count == 4
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Empty response" in caplog.text assert "Empty response" in caplog.text
@ -90,7 +90,7 @@ async def test_coordinator(
freezer.tick(timedelta(hours=1)) freezer.tick(timedelta(hours=1))
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
mock_data.assert_called_once() assert mock_data.call_count == 4
state = hass.states.get("sensor.nord_pool_se3_current_price") state = hass.states.get("sensor.nord_pool_se3_current_price")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Response error" in caplog.text assert "Response error" in caplog.text