add last_update_success_time and a failed update interval
This commit is contained in:
parent
9adc48c0b0
commit
09428c9686
2 changed files with 81 additions and 28 deletions
|
@ -33,6 +33,7 @@ class DataUpdateCoordinator:
|
||||||
update_interval: timedelta,
|
update_interval: timedelta,
|
||||||
update_method: Optional[Callable[[], Awaitable]] = None,
|
update_method: Optional[Callable[[], Awaitable]] = None,
|
||||||
request_refresh_debouncer: Optional[Debouncer] = None,
|
request_refresh_debouncer: Optional[Debouncer] = None,
|
||||||
|
failed_update_interval: Optional[timedelta] = None,
|
||||||
):
|
):
|
||||||
"""Initialize global data updater."""
|
"""Initialize global data updater."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
@ -40,13 +41,17 @@ class DataUpdateCoordinator:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.update_method = update_method
|
self.update_method = update_method
|
||||||
self.update_interval = update_interval
|
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.data: Optional[Any] = None
|
||||||
|
|
||||||
self._listeners: List[CALLBACK_TYPE] = []
|
self._listeners: List[CALLBACK_TYPE] = []
|
||||||
self._unsub_refresh: Optional[CALLBACK_TYPE] = None
|
self._unsub_refresh: Optional[CALLBACK_TYPE] = None
|
||||||
self._request_refresh_task: Optional[asyncio.TimerHandle] = None
|
self._request_refresh_task: Optional[asyncio.TimerHandle] = None
|
||||||
self.last_update_success = True
|
self.last_update_success = True
|
||||||
|
self.last_update_success_time = None # type: Optional[datetime]
|
||||||
|
|
||||||
if request_refresh_debouncer is None:
|
if request_refresh_debouncer is None:
|
||||||
request_refresh_debouncer = Debouncer(
|
request_refresh_debouncer = Debouncer(
|
||||||
|
@ -99,10 +104,14 @@ class DataUpdateCoordinator:
|
||||||
# minimizing the time between the point and the real activation.
|
# minimizing the time between the point and the real activation.
|
||||||
# That way we obtain a constant update frequency,
|
# That way we obtain a constant update frequency,
|
||||||
# as long as the update process takes less than a second
|
# 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._unsub_refresh = async_track_point_in_utc_time(
|
||||||
self.hass,
|
self.hass,
|
||||||
self._handle_refresh_interval,
|
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:
|
async def _handle_refresh_interval(self, _now: datetime) -> None:
|
||||||
|
@ -163,6 +172,7 @@ class DataUpdateCoordinator:
|
||||||
if not self.last_update_success:
|
if not self.last_update_success:
|
||||||
self.last_update_success = True
|
self.last_update_success = True
|
||||||
self.logger.info("Fetching %s data recovered", self.name)
|
self.logger.info("Fetching %s data recovered", self.name)
|
||||||
|
self.last_update_success_time = utcnow()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
|
|
|
@ -9,7 +9,7 @@ import pytest
|
||||||
from homeassistant.helpers import update_coordinator
|
from homeassistant.helpers import update_coordinator
|
||||||
from homeassistant.util.dt import utcnow
|
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
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -37,12 +37,16 @@ def crd(hass):
|
||||||
|
|
||||||
async def test_async_refresh(crd):
|
async def test_async_refresh(crd):
|
||||||
"""Test async_refresh for update coordinator."""
|
"""Test async_refresh for update coordinator."""
|
||||||
assert crd.data is None
|
utc_time = utcnow()
|
||||||
await crd.async_refresh()
|
with patch("homeassistant.helpers.update_coordinator.utcnow") as mock_utc:
|
||||||
assert crd.data == 1
|
mock_utc.return_value = utc_time
|
||||||
assert crd.last_update_success is True
|
assert crd.data is None
|
||||||
# Make sure we didn't schedule a refresh because we have 0 listeners
|
await crd.async_refresh()
|
||||||
assert crd._unsub_refresh is None
|
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 = []
|
updates = []
|
||||||
|
|
||||||
|
@ -124,31 +128,70 @@ async def test_refresh_no_update_method(crd):
|
||||||
async def test_update_interval(hass, crd):
|
async def test_update_interval(hass, crd):
|
||||||
"""Test update interval works."""
|
"""Test update interval works."""
|
||||||
# Test we don't update without subscriber
|
# Test we don't update without subscriber
|
||||||
async_fire_time_changed(hass, utcnow() + crd.update_interval)
|
utc_time = utcnow()
|
||||||
await hass.async_block_till_done()
|
with patch("homeassistant.helpers.update_coordinator.utcnow") as mock_utc:
|
||||||
assert crd.data is None
|
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
|
# Add subscriber
|
||||||
update_callback = Mock()
|
update_callback = Mock()
|
||||||
crd.async_add_listener(update_callback)
|
crd.async_add_listener(update_callback)
|
||||||
|
|
||||||
# Test twice we update with subscriber
|
# Test twice we update with subscriber
|
||||||
async_fire_time_changed(hass, utcnow() + crd.update_interval)
|
mock_utc.return_value += crd.update_interval
|
||||||
await hass.async_block_till_done()
|
async_fire_time_changed(hass, mock_utc.return_value)
|
||||||
assert crd.data == 1
|
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)
|
mock_utc.return_value += crd.update_interval
|
||||||
await hass.async_block_till_done()
|
async_fire_time_changed(hass, mock_utc.return_value)
|
||||||
assert crd.data == 2
|
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
|
# Test removing listener
|
||||||
crd.async_remove_listener(update_callback)
|
crd.async_remove_listener(update_callback)
|
||||||
|
|
||||||
async_fire_time_changed(hass, utcnow() + crd.update_interval)
|
mock_utc.return_value += crd.update_interval
|
||||||
await hass.async_block_till_done()
|
async_fire_time_changed(hass, mock_utc.return_value)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Test we stop updating after we lose last subscriber
|
# Test we stop updating after we lose last subscriber
|
||||||
assert crd.data == 2
|
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):
|
async def test_refresh_recover(crd, caplog):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue