Update error handling in update coordinator (#32452)
This commit is contained in:
parent
f62322cfb4
commit
b27c46750c
8 changed files with 58 additions and 67 deletions
|
@ -3,7 +3,6 @@ import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import coronavirus
|
import coronavirus
|
||||||
|
|
||||||
|
@ -73,16 +72,13 @@ async def get_coordinator(hass):
|
||||||
return hass.data[DOMAIN]
|
return hass.data[DOMAIN]
|
||||||
|
|
||||||
async def async_get_cases():
|
async def async_get_cases():
|
||||||
try:
|
with async_timeout.timeout(10):
|
||||||
with async_timeout.timeout(10):
|
return {
|
||||||
return {
|
case.country: case
|
||||||
case.country: case
|
for case in await coronavirus.get_cases(
|
||||||
for case in await coronavirus.get_cases(
|
aiohttp_client.async_get_clientsession(hass)
|
||||||
aiohttp_client.async_get_clientsession(hass)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
|
||||||
raise update_coordinator.UpdateFailed
|
|
||||||
|
|
||||||
hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator(
|
hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
"""Support for the Philips Hue lights."""
|
"""Support for the Philips Hue lights."""
|
||||||
import asyncio
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from aiohttp import client_exceptions
|
|
||||||
import aiohue
|
import aiohue
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
|
@ -172,13 +170,9 @@ async def async_safe_fetch(bridge, fetch_method):
|
||||||
return await bridge.async_request_call(fetch_method)
|
return await bridge.async_request_call(fetch_method)
|
||||||
except aiohue.Unauthorized:
|
except aiohue.Unauthorized:
|
||||||
await bridge.handle_unauthorized_error()
|
await bridge.handle_unauthorized_error()
|
||||||
raise UpdateFailed
|
raise UpdateFailed("Unauthorized")
|
||||||
except (
|
except (aiohue.AiohueException,) as err:
|
||||||
asyncio.TimeoutError,
|
raise UpdateFailed(f"Hue error: {err}")
|
||||||
aiohue.AiohueException,
|
|
||||||
client_exceptions.ClientError,
|
|
||||||
):
|
|
||||||
raise UpdateFailed
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
"""Support for the Philips Hue sensors as a platform."""
|
"""Support for the Philips Hue sensors as a platform."""
|
||||||
import asyncio
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp import client_exceptions
|
|
||||||
from aiohue import AiohueException, Unauthorized
|
from aiohue import AiohueException, Unauthorized
|
||||||
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
@ -60,9 +58,9 @@ class SensorManager:
|
||||||
)
|
)
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
await self.bridge.handle_unauthorized_error()
|
await self.bridge.handle_unauthorized_error()
|
||||||
raise UpdateFailed
|
raise UpdateFailed("Unauthorized")
|
||||||
except (asyncio.TimeoutError, AiohueException, client_exceptions.ClientError):
|
except AiohueException as err:
|
||||||
raise UpdateFailed
|
raise UpdateFailed(f"Hue error: {err}")
|
||||||
|
|
||||||
async def async_register_component(self, binary, async_add_entities):
|
async def async_register_component(self, binary, async_add_entities):
|
||||||
"""Register async_add_entities methods for components."""
|
"""Register async_add_entities methods for components."""
|
||||||
|
|
|
@ -36,7 +36,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||||
try:
|
try:
|
||||||
return await tankerkoenig.fetch_data()
|
return await tankerkoenig.fetch_data()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise UpdateFailed
|
raise UpdateFailed("Failed to fetch data")
|
||||||
|
|
||||||
coordinator = DataUpdateCoordinator(
|
coordinator = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
"""Support to check for available updates."""
|
"""Support to check for available updates."""
|
||||||
import asyncio
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
from distro import linux_distribution # pylint: disable=import-error
|
from distro import linux_distribution # pylint: disable=import-error
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -156,29 +154,27 @@ async def get_newest_version(hass, huuid, include_components):
|
||||||
info_object = {}
|
info_object = {}
|
||||||
|
|
||||||
session = async_get_clientsession(hass)
|
session = async_get_clientsession(hass)
|
||||||
try:
|
|
||||||
with async_timeout.timeout(5):
|
with async_timeout.timeout(15):
|
||||||
req = await session.post(UPDATER_URL, json=info_object)
|
req = await session.post(UPDATER_URL, json=info_object)
|
||||||
_LOGGER.info(
|
|
||||||
(
|
_LOGGER.info(
|
||||||
"Submitted analytics to Home Assistant servers. "
|
(
|
||||||
"Information submitted includes %s"
|
"Submitted analytics to Home Assistant servers. "
|
||||||
),
|
"Information submitted includes %s"
|
||||||
info_object,
|
),
|
||||||
)
|
info_object,
|
||||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
)
|
||||||
_LOGGER.error("Could not contact Home Assistant Update to check for updates")
|
|
||||||
raise update_coordinator.UpdateFailed
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = await req.json()
|
res = await req.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.error("Received invalid JSON from Home Assistant Update")
|
raise update_coordinator.UpdateFailed(
|
||||||
raise update_coordinator.UpdateFailed
|
"Received invalid JSON from Home Assistant Update"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = RESPONSE_SCHEMA(res)
|
res = RESPONSE_SCHEMA(res)
|
||||||
return res["version"], res["release-notes"]
|
return res["version"], res["release-notes"]
|
||||||
except vol.Invalid:
|
except vol.Invalid as err:
|
||||||
_LOGGER.error("Got unexpected response: %s", res)
|
raise update_coordinator.UpdateFailed(f"Got unexpected response: {err}")
|
||||||
raise update_coordinator.UpdateFailed
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import logging
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
from typing import Any, Awaitable, Callable, List, Optional
|
from typing import Any, Awaitable, Callable, List, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
@ -114,6 +116,16 @@ class DataUpdateCoordinator:
|
||||||
start = monotonic()
|
start = monotonic()
|
||||||
self.data = await self.update_method()
|
self.data = await self.update_method()
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
if self.last_update_success:
|
||||||
|
self.logger.error("Timeout fetching %s data", self.name)
|
||||||
|
self.last_update_success = False
|
||||||
|
|
||||||
|
except aiohttp.ClientError as err:
|
||||||
|
if self.last_update_success:
|
||||||
|
self.logger.error("Error requesting %s data: %s", self.name, err)
|
||||||
|
self.last_update_success = False
|
||||||
|
|
||||||
except UpdateFailed as err:
|
except UpdateFailed as err:
|
||||||
if self.last_update_success:
|
if self.last_update_success:
|
||||||
self.logger.error("Error fetching %s data: %s", self.name, err)
|
self.logger.error("Error fetching %s data: %s", self.name, err)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"""The tests for the Updater component."""
|
"""The tests for the Updater component."""
|
||||||
import asyncio
|
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from asynctest import patch
|
from asynctest import patch
|
||||||
|
@ -130,17 +129,6 @@ async def test_get_newest_version_analytics_when_huuid(hass, aioclient_mock):
|
||||||
assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"])
|
assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"])
|
||||||
|
|
||||||
|
|
||||||
async def test_error_fetching_new_version_timeout(hass):
|
|
||||||
"""Test we handle timeout error while fetching new version."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.helpers.system_info.async_get_system_info",
|
|
||||||
Mock(return_value=mock_coro({"fake": "bla"})),
|
|
||||||
), patch("async_timeout.timeout", side_effect=asyncio.TimeoutError), pytest.raises(
|
|
||||||
UpdateFailed
|
|
||||||
):
|
|
||||||
await updater.get_newest_version(hass, MOCK_HUUID, False)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_error_fetching_new_version_bad_json(hass, aioclient_mock):
|
async def test_error_fetching_new_version_bad_json(hass, aioclient_mock):
|
||||||
"""Test we handle json error while fetching new version."""
|
"""Test we handle json error while fetching new version."""
|
||||||
aioclient_mock.post(updater.UPDATER_URL, text="not json")
|
aioclient_mock.post(updater.UPDATER_URL, text="not json")
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
"""Tests for the update coordinator."""
|
"""Tests for the update coordinator."""
|
||||||
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from asynctest import CoroutineMock, Mock
|
from asynctest import CoroutineMock, Mock
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -70,25 +72,30 @@ async def test_request_refresh(crd):
|
||||||
assert crd.last_update_success is True
|
assert crd.last_update_success is True
|
||||||
|
|
||||||
|
|
||||||
async def test_refresh_fail(crd, caplog):
|
@pytest.mark.parametrize(
|
||||||
"""Test a failing update function."""
|
"err_msg",
|
||||||
crd.update_method = CoroutineMock(side_effect=update_coordinator.UpdateFailed)
|
[
|
||||||
|
(asyncio.TimeoutError, "Timeout fetching test data"),
|
||||||
|
(aiohttp.ClientError, "Error requesting test data"),
|
||||||
|
(update_coordinator.UpdateFailed, "Error fetching test data"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_refresh_known_errors(err_msg, crd, caplog):
|
||||||
|
"""Test raising known errors."""
|
||||||
|
crd.update_method = CoroutineMock(side_effect=err_msg[0])
|
||||||
|
|
||||||
await crd.async_refresh()
|
await crd.async_refresh()
|
||||||
|
|
||||||
assert crd.data is None
|
assert crd.data is None
|
||||||
assert crd.last_update_success is False
|
assert crd.last_update_success is False
|
||||||
assert "Error fetching test data" in caplog.text
|
assert err_msg[1] in caplog.text
|
||||||
|
|
||||||
crd.update_method = CoroutineMock(return_value=1)
|
|
||||||
|
|
||||||
|
async def test_refresh_fail_unknown(crd, caplog):
|
||||||
|
"""Test raising unknown error."""
|
||||||
await crd.async_refresh()
|
await crd.async_refresh()
|
||||||
|
|
||||||
assert crd.data == 1
|
|
||||||
assert crd.last_update_success is True
|
|
||||||
|
|
||||||
crd.update_method = CoroutineMock(side_effect=ValueError)
|
crd.update_method = CoroutineMock(side_effect=ValueError)
|
||||||
caplog.clear()
|
|
||||||
|
|
||||||
await crd.async_refresh()
|
await crd.async_refresh()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue