Use an eager task in the update coordinator scheduled refresh (#111570)

* Use an eager task in the update coordinator scheduled refresh

We have a lot of places that will not suspend because the refresh function
decides it does not need to update. Currently these have to be scheduled
on the event loop even though they are a noop.

Since _handle_refresh_interval is subclassed in some integrations, I created
a dunder wrapper function to avoid integraions subclassing it

* fix time fires outside of patch
This commit is contained in:
J. Nick Koston 2024-02-26 18:31:08 -10:00 committed by GitHub
parent b692f30c33
commit d5e1934942
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 15 additions and 7 deletions

View File

@ -112,9 +112,9 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]):
if entry := self.config_entry: if entry := self.config_entry:
job_name += f" {entry.title} {entry.domain} {entry.entry_id}" job_name += f" {entry.title} {entry.domain} {entry.entry_id}"
self._job = HassJob( self._job = HassJob(
self._handle_refresh_interval, self.__wrap_handle_refresh_interval,
job_name, job_name,
job_type=HassJobType.Coroutinefunction, job_type=HassJobType.Callback,
) )
self._unsub_refresh: CALLBACK_TYPE | None = None self._unsub_refresh: CALLBACK_TYPE | None = None
self._unsub_shutdown: CALLBACK_TYPE | None = None self._unsub_shutdown: CALLBACK_TYPE | None = None
@ -250,6 +250,11 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]):
next_refresh, hass.async_run_hass_job, self._job next_refresh, hass.async_run_hass_job, self._job
).cancel ).cancel
@callback
def __wrap_handle_refresh_interval(self) -> None:
"""Handle a refresh interval occurrence."""
self.hass.async_create_task(self._handle_refresh_interval(), eager_start=True)
async def _handle_refresh_interval(self, _now: datetime | None = None) -> None: async def _handle_refresh_interval(self, _now: datetime | None = None) -> None:
"""Handle a refresh interval occurrence.""" """Handle a refresh interval occurrence."""
self._unsub_refresh = None self._unsub_refresh = None

View File

@ -44,11 +44,12 @@ async def test_update_failed(
assert coordinator.last_update_success is True assert coordinator.last_update_success is True
freezer.tick(timedelta(minutes=5, seconds=1)) freezer.tick(timedelta(minutes=5, seconds=1))
async_fire_time_changed(hass)
with patch( with patch(
"bimmer_connected.account.MyBMWAccount.get_vehicles", "bimmer_connected.account.MyBMWAccount.get_vehicles",
side_effect=MyBMWAPIError("Test error"), side_effect=MyBMWAPIError("Test error"),
): ):
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.last_update_success is False assert coordinator.last_update_success is False
@ -70,22 +71,22 @@ async def test_update_reauth(
assert coordinator.last_update_success is True assert coordinator.last_update_success is True
freezer.tick(timedelta(minutes=5, seconds=1)) freezer.tick(timedelta(minutes=5, seconds=1))
async_fire_time_changed(hass)
with patch( with patch(
"bimmer_connected.account.MyBMWAccount.get_vehicles", "bimmer_connected.account.MyBMWAccount.get_vehicles",
side_effect=MyBMWAuthError("Test error"), side_effect=MyBMWAuthError("Test error"),
): ):
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.last_update_success is False assert coordinator.last_update_success is False
assert isinstance(coordinator.last_exception, UpdateFailed) is True assert isinstance(coordinator.last_exception, UpdateFailed) is True
freezer.tick(timedelta(minutes=5, seconds=1)) freezer.tick(timedelta(minutes=5, seconds=1))
async_fire_time_changed(hass)
with patch( with patch(
"bimmer_connected.account.MyBMWAccount.get_vehicles", "bimmer_connected.account.MyBMWAccount.get_vehicles",
side_effect=MyBMWAuthError("Test error"), side_effect=MyBMWAuthError("Test error"),
): ):
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.last_update_success is False assert coordinator.last_update_success is False

View File

@ -30,7 +30,7 @@ async def test_floorplan_image(
assert body is not None assert body is not None
# Call a third time - this time forcing it to update # Call a third time - this time forcing it to update
now = dt_util.utcnow() + timedelta(seconds=91) now = dt_util.utcnow() + timedelta(seconds=91)
async_fire_time_changed(hass, now)
# Copy the device prop so we don't override it # Copy the device prop so we don't override it
prop = copy.deepcopy(PROP) prop = copy.deepcopy(PROP)
prop.status.in_cleaning = 1 prop.status.in_cleaning = 1
@ -40,6 +40,7 @@ async def test_floorplan_image(
), patch( ), patch(
"homeassistant.components.roborock.image.dt_util.utcnow", return_value=now "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now
): ):
async_fire_time_changed(hass, now)
await hass.async_block_till_done() await hass.async_block_till_done()
resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs") resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs")
assert resp.status == HTTPStatus.OK assert resp.status == HTTPStatus.OK
@ -57,7 +58,6 @@ async def test_floorplan_image_failed_parse(
map_data = copy.deepcopy(MAP_DATA) map_data = copy.deepcopy(MAP_DATA)
map_data.image = None map_data.image = None
now = dt_util.utcnow() + timedelta(seconds=91) now = dt_util.utcnow() + timedelta(seconds=91)
async_fire_time_changed(hass, now)
# Copy the device prop so we don't override it # Copy the device prop so we don't override it
prop = copy.deepcopy(PROP) prop = copy.deepcopy(PROP)
prop.status.in_cleaning = 1 prop.status.in_cleaning = 1
@ -71,5 +71,6 @@ async def test_floorplan_image_failed_parse(
), patch( ), patch(
"homeassistant.components.roborock.image.dt_util.utcnow", return_value=now "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now
): ):
async_fire_time_changed(hass, now)
resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs") resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs")
assert not resp.ok assert not resp.ok

View File

@ -88,6 +88,7 @@ async def test_update_fail(
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=MAX_UPDATE_INTERVAL_SEC - 1) hass, dt_util.utcnow() + timedelta(seconds=MAX_UPDATE_INTERVAL_SEC - 1)
) )
await hass.async_block_till_done()
for device in DUMMY_SWITCHER_DEVICES: for device in DUMMY_SWITCHER_DEVICES:
entity_id = f"switch.{slugify(device.name)}" entity_id = f"switch.{slugify(device.name)}"