mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Update the update coordinator API to make it easier to use (#31471)
* Update the update coordinator API to make it easier to use * failed_last_update -> last_update_success
This commit is contained in:
parent
d407b8e215
commit
0d474e1183
@ -79,17 +79,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
light_coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
"light",
|
||||
partial(async_safe_fetch, bridge, bridge.api.lights.update),
|
||||
SCAN_INTERVAL,
|
||||
Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True),
|
||||
name="light",
|
||||
update_method=partial(async_safe_fetch, bridge, bridge.api.lights.update),
|
||||
update_interval=SCAN_INTERVAL,
|
||||
request_refresh_debouncer=Debouncer(
|
||||
bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
# First do a refresh to see if we can reach the hub.
|
||||
# Otherwise we will declare not ready.
|
||||
await light_coordinator.async_refresh()
|
||||
|
||||
if light_coordinator.failed_last_update:
|
||||
if not light_coordinator.last_update_success:
|
||||
raise PlatformNotReady
|
||||
|
||||
update_lights = partial(
|
||||
@ -122,10 +124,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
group_coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
"group",
|
||||
partial(async_safe_fetch, bridge, bridge.api.groups.update),
|
||||
SCAN_INTERVAL,
|
||||
Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True),
|
||||
name="group",
|
||||
update_method=partial(async_safe_fetch, bridge, bridge.api.groups.update),
|
||||
update_interval=SCAN_INTERVAL,
|
||||
request_refresh_debouncer=Debouncer(
|
||||
bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
update_groups = partial(
|
||||
@ -277,7 +281,7 @@ class HueLight(Light):
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if light is available."""
|
||||
return not self.coordinator.failed_last_update and (
|
||||
return self.coordinator.last_update_success and (
|
||||
self.is_group
|
||||
or self.bridge.allow_unreachable
|
||||
or self.light.state["reachable"]
|
||||
|
@ -42,10 +42,12 @@ class SensorManager:
|
||||
self.coordinator = DataUpdateCoordinator(
|
||||
bridge.hass,
|
||||
_LOGGER,
|
||||
"sensor",
|
||||
self.async_update_data,
|
||||
self.SCAN_INTERVAL,
|
||||
debounce.Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True),
|
||||
name="sensor",
|
||||
update_method=self.async_update_data,
|
||||
update_interval=self.SCAN_INTERVAL,
|
||||
request_refresh_debouncer=debounce.Debouncer(
|
||||
bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
async def async_update_data(self):
|
||||
@ -183,7 +185,7 @@ class GenericHueSensor(entity.Entity):
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if sensor is available."""
|
||||
return not self.bridge.sensor_manager.coordinator.failed_last_update and (
|
||||
return self.bridge.sensor_manager.coordinator.last_update_success and (
|
||||
self.bridge.allow_unreachable or self.sensor.config["reachable"]
|
||||
)
|
||||
|
||||
|
@ -123,7 +123,11 @@ async def async_setup(hass, config):
|
||||
return Updater(update_available, newest, release_notes)
|
||||
|
||||
coordinator = hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator(
|
||||
hass, _LOGGER, "Home Assistant update", check_new_version, timedelta(days=1)
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Home Assistant update",
|
||||
update_method=check_new_version,
|
||||
update_interval=timedelta(days=1),
|
||||
)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
@ -38,7 +38,7 @@ class UpdaterBinary(BinarySensorDevice):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return not self.coordinator.failed_last_update
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
|
@ -13,6 +13,7 @@ class Debouncer:
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
logger: Logger,
|
||||
*,
|
||||
cooldown: float,
|
||||
immediate: bool,
|
||||
function: Optional[Callable[..., Awaitable[Any]]] = None,
|
||||
|
@ -26,6 +26,7 @@ class DataUpdateCoordinator:
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
logger: logging.Logger,
|
||||
*,
|
||||
name: str,
|
||||
update_method: Callable[[], Awaitable],
|
||||
update_interval: timedelta,
|
||||
@ -43,16 +44,20 @@ class DataUpdateCoordinator:
|
||||
self._listeners: List[CALLBACK_TYPE] = []
|
||||
self._unsub_refresh: Optional[CALLBACK_TYPE] = None
|
||||
self._request_refresh_task: Optional[asyncio.TimerHandle] = None
|
||||
self.failed_last_update = False
|
||||
self.last_update_success = True
|
||||
|
||||
if request_refresh_debouncer is None:
|
||||
request_refresh_debouncer = Debouncer(
|
||||
hass,
|
||||
logger,
|
||||
REQUEST_REFRESH_DEFAULT_COOLDOWN,
|
||||
REQUEST_REFRESH_DEFAULT_IMMEDIATE,
|
||||
cooldown=REQUEST_REFRESH_DEFAULT_COOLDOWN,
|
||||
immediate=REQUEST_REFRESH_DEFAULT_IMMEDIATE,
|
||||
function=self.async_refresh,
|
||||
)
|
||||
else:
|
||||
request_refresh_debouncer.function = self.async_refresh
|
||||
|
||||
self._debounced_refresh = request_refresh_debouncer
|
||||
request_refresh_debouncer.function = self.async_refresh
|
||||
|
||||
@callback
|
||||
def async_add_listener(self, update_callback: CALLBACK_TYPE) -> None:
|
||||
@ -110,19 +115,19 @@ class DataUpdateCoordinator:
|
||||
self.data = await self.update_method()
|
||||
|
||||
except UpdateFailed as err:
|
||||
if not self.failed_last_update:
|
||||
if self.last_update_success:
|
||||
self.logger.error("Error fetching %s data: %s", self.name, err)
|
||||
self.failed_last_update = True
|
||||
self.last_update_success = False
|
||||
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
self.failed_last_update = True
|
||||
self.last_update_success = False
|
||||
self.logger.exception(
|
||||
"Unexpected error fetching %s data: %s", self.name, err
|
||||
)
|
||||
|
||||
else:
|
||||
if self.failed_last_update:
|
||||
self.failed_last_update = False
|
||||
if not self.last_update_success:
|
||||
self.last_update_success = True
|
||||
self.logger.info("Fetching %s data recovered")
|
||||
|
||||
finally:
|
||||
|
@ -703,7 +703,7 @@ def test_available():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(allow_unreachable=False),
|
||||
is_group=False,
|
||||
)
|
||||
@ -717,7 +717,7 @@ def test_available():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(allow_unreachable=True),
|
||||
is_group=False,
|
||||
)
|
||||
@ -731,7 +731,7 @@ def test_available():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(allow_unreachable=False),
|
||||
is_group=True,
|
||||
)
|
||||
@ -748,7 +748,7 @@ def test_hs_color():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
@ -762,7 +762,7 @@ def test_hs_color():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
@ -776,7 +776,7 @@ def test_hs_color():
|
||||
colorgamuttype=LIGHT_GAMUT_TYPE,
|
||||
colorgamut=LIGHT_GAMUT,
|
||||
),
|
||||
coordinator=Mock(failed_last_update=False),
|
||||
coordinator=Mock(last_update_success=True),
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
|
@ -8,7 +8,11 @@ async def test_immediate_works(hass):
|
||||
"""Test immediate works."""
|
||||
calls = []
|
||||
debouncer = debounce.Debouncer(
|
||||
hass, None, 0.01, True, CoroutineMock(side_effect=lambda: calls.append(None))
|
||||
hass,
|
||||
None,
|
||||
cooldown=0.01,
|
||||
immediate=True,
|
||||
function=CoroutineMock(side_effect=lambda: calls.append(None)),
|
||||
)
|
||||
|
||||
await debouncer.async_call()
|
||||
@ -37,7 +41,11 @@ async def test_not_immediate_works(hass):
|
||||
"""Test immediate works."""
|
||||
calls = []
|
||||
debouncer = debounce.Debouncer(
|
||||
hass, None, 0.01, False, CoroutineMock(side_effect=lambda: calls.append(None))
|
||||
hass,
|
||||
None,
|
||||
cooldown=0.01,
|
||||
immediate=False,
|
||||
function=CoroutineMock(side_effect=lambda: calls.append(None)),
|
||||
)
|
||||
|
||||
await debouncer.async_call()
|
||||
|
@ -23,7 +23,11 @@ def crd(hass):
|
||||
return len(calls)
|
||||
|
||||
crd = update_coordinator.DataUpdateCoordinator(
|
||||
hass, LOGGER, "test", refresh, timedelta(seconds=10),
|
||||
hass,
|
||||
LOGGER,
|
||||
name="test",
|
||||
update_method=refresh,
|
||||
update_interval=timedelta(seconds=10),
|
||||
)
|
||||
return crd
|
||||
|
||||
@ -33,7 +37,7 @@ async def test_async_refresh(crd):
|
||||
assert crd.data is None
|
||||
await crd.async_refresh()
|
||||
assert crd.data == 1
|
||||
assert crd.failed_last_update is False
|
||||
assert crd.last_update_success is True
|
||||
|
||||
updates = []
|
||||
|
||||
@ -58,12 +62,12 @@ async def test_request_refresh(crd):
|
||||
assert crd.data is None
|
||||
await crd.async_request_refresh()
|
||||
assert crd.data == 1
|
||||
assert crd.failed_last_update is False
|
||||
assert crd.last_update_success is True
|
||||
|
||||
# Second time we hit the debonuce
|
||||
await crd.async_request_refresh()
|
||||
assert crd.data == 1
|
||||
assert crd.failed_last_update is False
|
||||
assert crd.last_update_success is True
|
||||
|
||||
|
||||
async def test_refresh_fail(crd, caplog):
|
||||
@ -73,7 +77,7 @@ async def test_refresh_fail(crd, caplog):
|
||||
await crd.async_refresh()
|
||||
|
||||
assert crd.data is None
|
||||
assert crd.failed_last_update is True
|
||||
assert crd.last_update_success is False
|
||||
assert "Error fetching test data" in caplog.text
|
||||
|
||||
crd.update_method = CoroutineMock(return_value=1)
|
||||
@ -81,7 +85,7 @@ async def test_refresh_fail(crd, caplog):
|
||||
await crd.async_refresh()
|
||||
|
||||
assert crd.data == 1
|
||||
assert crd.failed_last_update is False
|
||||
assert crd.last_update_success is True
|
||||
|
||||
crd.update_method = CoroutineMock(side_effect=ValueError)
|
||||
caplog.clear()
|
||||
@ -89,7 +93,7 @@ async def test_refresh_fail(crd, caplog):
|
||||
await crd.async_refresh()
|
||||
|
||||
assert crd.data == 1 # value from previous fetch
|
||||
assert crd.failed_last_update is True
|
||||
assert crd.last_update_success is False
|
||||
assert "Unexpected error fetching test data" in caplog.text
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user