diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 3c8a5d2924e..b7a36379107 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -30,7 +30,7 @@ class DataUpdateCoordinator: logger: logging.Logger, *, name: str, - update_interval: timedelta, + update_interval: Optional[timedelta] = None, update_method: Optional[Callable[[], Awaitable]] = None, request_refresh_debouncer: Optional[Debouncer] = None, ): @@ -91,6 +91,9 @@ class DataUpdateCoordinator: @callback def _schedule_refresh(self) -> None: """Schedule a refresh.""" + if self.update_interval is None: + return + if self._unsub_refresh: self._unsub_refresh() self._unsub_refresh = None diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 8d4f6934d78..99399fee30f 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -15,9 +15,8 @@ from tests.common import async_fire_time_changed LOGGER = logging.getLogger(__name__) -@pytest.fixture -def crd(hass): - """Coordinator mock.""" +def get_crd(hass, update_interval): + """Make coordinator mocks.""" calls = 0 async def refresh(): @@ -30,11 +29,26 @@ def crd(hass): LOGGER, name="test", update_method=refresh, - update_interval=timedelta(seconds=10), + update_interval=update_interval, ) return crd +DEFAULT_UPDATE_INTERVAL = timedelta(seconds=10) + + +@pytest.fixture +def crd(hass): + """Coordinator mock with default update interval.""" + return get_crd(hass, DEFAULT_UPDATE_INTERVAL) + + +@pytest.fixture +def crd_without_update_interval(hass): + """Coordinator mock that never automatically updates.""" + return get_crd(hass, None) + + async def test_async_refresh(crd): """Test async_refresh for update coordinator.""" assert crd.data is None @@ -79,6 +93,20 @@ async def test_request_refresh(crd): assert crd.last_update_success is True +async def test_request_refresh_no_auto_update(crd_without_update_interval): + """Test request refresh for update coordinator without automatic update.""" + crd = crd_without_update_interval + assert crd.data is None + await crd.async_request_refresh() + assert crd.data == 1 + assert crd.last_update_success is True + + # Second time we hit the debonuce + await crd.async_request_refresh() + assert crd.data == 1 + assert crd.last_update_success is True + + @pytest.mark.parametrize( "err_msg", [ @@ -151,6 +179,37 @@ async def test_update_interval(hass, crd): assert crd.data == 2 +async def test_update_interval_not_present(hass, crd_without_update_interval): + """Test update never happens with no update interval.""" + crd = crd_without_update_interval + # Test we don't update without subscriber with no update interval + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + # Add subscriber + update_callback = Mock() + crd.async_add_listener(update_callback) + + # Test twice we don't update with subscriber with no update interval + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert crd.data is None + + # Test removing listener + crd.async_remove_listener(update_callback) + + async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL) + await hass.async_block_till_done() + + # Test we stop don't update after we lose last subscriber + assert crd.data is None + + async def test_refresh_recover(crd, caplog): """Test recovery of freshing data.""" crd.last_update_success = False