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:
Paulus Schoutsen 2020-02-06 09:29:29 -08:00 committed by GitHub
parent d407b8e215
commit 0d474e1183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 69 additions and 41 deletions

View File

@ -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"]

View File

@ -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"]
)

View File

@ -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()

View File

@ -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:

View File

@ -13,6 +13,7 @@ class Debouncer:
self,
hass: HomeAssistant,
logger: Logger,
*,
cooldown: float,
immediate: bool,
function: Optional[Callable[..., Awaitable[Any]]] = None,

View File

@ -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:

View File

@ -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,
)

View File

@ -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()

View File

@ -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