add last_update_success_time and a failed update interval

This commit is contained in:
Flamm, Matthew H 2020-05-16 01:53:14 +01:00
parent 9adc48c0b0
commit 09428c9686
2 changed files with 81 additions and 28 deletions

View file

@ -33,6 +33,7 @@ class DataUpdateCoordinator:
update_interval: timedelta,
update_method: Optional[Callable[[], Awaitable]] = None,
request_refresh_debouncer: Optional[Debouncer] = None,
failed_update_interval: Optional[timedelta] = None,
):
"""Initialize global data updater."""
self.hass = hass
@ -40,13 +41,17 @@ class DataUpdateCoordinator:
self.name = name
self.update_method = update_method
self.update_interval = update_interval
if failed_update_interval:
self.failed_update_interval = failed_update_interval
else:
self.failed_update_interval = update_interval
self.data: Optional[Any] = None
self._listeners: List[CALLBACK_TYPE] = []
self._unsub_refresh: Optional[CALLBACK_TYPE] = None
self._request_refresh_task: Optional[asyncio.TimerHandle] = None
self.last_update_success = True
self.last_update_success_time = None # type: Optional[datetime]
if request_refresh_debouncer is None:
request_refresh_debouncer = Debouncer(
@ -99,10 +104,14 @@ class DataUpdateCoordinator:
# minimizing the time between the point and the real activation.
# That way we obtain a constant update frequency,
# as long as the update process takes less than a second
if self.last_update_success:
update_interval = self.update_interval
else:
update_interval = self.failed_update_interval
self._unsub_refresh = async_track_point_in_utc_time(
self.hass,
self._handle_refresh_interval,
utcnow().replace(microsecond=0) + self.update_interval,
utcnow().replace(microsecond=0) + update_interval,
)
async def _handle_refresh_interval(self, _now: datetime) -> None:
@ -163,6 +172,7 @@ class DataUpdateCoordinator:
if not self.last_update_success:
self.last_update_success = True
self.logger.info("Fetching %s data recovered", self.name)
self.last_update_success_time = utcnow()
finally:
self.logger.debug(

View file

@ -9,7 +9,7 @@ import pytest
from homeassistant.helpers import update_coordinator
from homeassistant.util.dt import utcnow
from tests.async_mock import AsyncMock, Mock
from tests.async_mock import AsyncMock, Mock, patch
from tests.common import async_fire_time_changed
LOGGER = logging.getLogger(__name__)
@ -37,12 +37,16 @@ def crd(hass):
async def test_async_refresh(crd):
"""Test async_refresh for update coordinator."""
assert crd.data is None
await crd.async_refresh()
assert crd.data == 1
assert crd.last_update_success is True
# Make sure we didn't schedule a refresh because we have 0 listeners
assert crd._unsub_refresh is None
utc_time = utcnow()
with patch("homeassistant.helpers.update_coordinator.utcnow") as mock_utc:
mock_utc.return_value = utc_time
assert crd.data is None
await crd.async_refresh()
assert crd.data == 1
assert crd.last_update_success is True
assert crd.last_update_success_time == utc_time
# Make sure we didn't schedule a refresh because we have 0 listeners
assert crd._unsub_refresh is None
updates = []
@ -124,31 +128,70 @@ async def test_refresh_no_update_method(crd):
async def test_update_interval(hass, crd):
"""Test update interval works."""
# Test we don't update without subscriber
async_fire_time_changed(hass, utcnow() + crd.update_interval)
await hass.async_block_till_done()
assert crd.data is None
utc_time = utcnow()
with patch("homeassistant.helpers.update_coordinator.utcnow") as mock_utc:
mock_utc.return_value = utc_time + crd.update_interval
async_fire_time_changed(hass, mock_utc.return_value)
await hass.async_block_till_done()
assert crd.data is None
assert crd.last_update_success_time is None
# Add subscriber
update_callback = Mock()
crd.async_add_listener(update_callback)
# Add subscriber
update_callback = Mock()
crd.async_add_listener(update_callback)
# Test twice we update with subscriber
async_fire_time_changed(hass, utcnow() + crd.update_interval)
await hass.async_block_till_done()
assert crd.data == 1
# Test twice we update with subscriber
mock_utc.return_value += crd.update_interval
async_fire_time_changed(hass, mock_utc.return_value)
await hass.async_block_till_done()
assert crd.data == 1
assert crd.last_update_success_time == mock_utc.return_value
async_fire_time_changed(hass, utcnow() + crd.update_interval)
await hass.async_block_till_done()
assert crd.data == 2
mock_utc.return_value += crd.update_interval
async_fire_time_changed(hass, mock_utc.return_value)
await hass.async_block_till_done()
assert crd.data == 2
assert crd.last_update_success_time == mock_utc.return_value
last_success_time = mock_utc.return_value
# Test removing listener
crd.async_remove_listener(update_callback)
# Test removing listener
crd.async_remove_listener(update_callback)
async_fire_time_changed(hass, utcnow() + crd.update_interval)
await hass.async_block_till_done()
mock_utc.return_value += crd.update_interval
async_fire_time_changed(hass, mock_utc.return_value)
await hass.async_block_till_done()
# Test we stop updating after we lose last subscriber
assert crd.data == 2
# Test we stop updating after we lose last subscriber
assert crd.data == 2
assert crd.last_update_success_time == last_success_time
async def test_failed_update_interval(crd, hass):
"""Test failed update interval."""
utc_time = utcnow()
with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow:
mock_utcnow.return_value = utc_time
old_update_method = crd.update_method
crd.update_method = AsyncMock(side_effect=asyncio.TimeoutError)
crd.failed_update_interval = timedelta(seconds=5)
def update_callback():
pass
crd.async_add_listener(update_callback)
await crd.async_refresh()
await hass.async_block_till_done()
assert crd.data is None
assert crd.last_update_success is False
crd.update_method = old_update_method
mock_utcnow.return_value += timedelta(seconds=5)
async_fire_time_changed(hass, mock_utcnow.return_value)
await hass.async_block_till_done()
assert crd.data == 1
assert crd.last_update_success is True
async def test_refresh_recover(crd, caplog):