From 6a801fc0583954228184326085aa42fc03ab837e Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 10 Jan 2023 01:48:39 -0700 Subject: [PATCH] Remove no-longer-needed invalid API key monitor for OpenUV (#85573) * Remove no-longer-needed invalid API key monitor for OpenUV * Handle re-auth cancellation * Use automatic API status check --- homeassistant/components/openuv/__init__.py | 12 +-- .../components/openuv/config_flow.py | 1 - .../components/openuv/coordinator.py | 81 ++++--------------- homeassistant/components/openuv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 21 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 3e65f33d8c55..cb8d1bffceb7 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -31,7 +31,7 @@ from .const import ( DOMAIN, LOGGER, ) -from .coordinator import InvalidApiKeyMonitor, OpenUvCoordinator +from .coordinator import OpenUvCoordinator PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] @@ -45,6 +45,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data.get(CONF_LONGITUDE, hass.config.longitude), altitude=entry.data.get(CONF_ELEVATION, hass.config.elevation), session=websession, + check_status_before_request=True, ) async def async_update_protection_data() -> dict[str, Any]: @@ -53,16 +54,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: high = entry.options.get(CONF_TO_WINDOW, DEFAULT_TO_WINDOW) return await client.uv_protection_window(low=low, high=high) - invalid_api_key_monitor = InvalidApiKeyMonitor(hass, entry) - coordinators: dict[str, OpenUvCoordinator] = { coordinator_name: OpenUvCoordinator( hass, + entry=entry, name=coordinator_name, latitude=client.latitude, longitude=client.longitude, update_method=update_method, - invalid_api_key_monitor=invalid_api_key_monitor, ) for coordinator_name, update_method in ( (DATA_UV, client.uv_index), @@ -70,16 +69,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) } - # We disable the client's request retry abilities here to avoid a lengthy (and - # blocking) startup; then, if the initial update is successful, we re-enable client - # request retries: - client.disable_request_retries() init_tasks = [ coordinator.async_config_entry_first_refresh() for coordinator in coordinators.values() ] await asyncio.gather(*init_tasks) - client.enable_request_retries() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinators diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 3951a6ffb089..d78fa84c8c5c 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -103,7 +103,6 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Verify the credentials and create/re-auth the entry.""" websession = aiohttp_client.async_get_clientsession(self.hass) client = Client(data.api_key, 0, 0, session=websession) - client.disable_request_retries() try: await client.uv_index() diff --git a/homeassistant/components/openuv/coordinator.py b/homeassistant/components/openuv/coordinator.py index f89a9c696a86..7472f213f824 100644 --- a/homeassistant/components/openuv/coordinator.py +++ b/homeassistant/components/openuv/coordinator.py @@ -1,15 +1,14 @@ """Define an update coordinator for OpenUV.""" from __future__ import annotations -import asyncio from collections.abc import Awaitable, Callable from typing import Any, cast from pyopenuv.errors import InvalidApiKeyError, OpenUvError from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry -from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -18,64 +17,6 @@ from .const import LOGGER DEFAULT_DEBOUNCER_COOLDOWN_SECONDS = 15 * 60 -class InvalidApiKeyMonitor: - """Define a monitor for failed API calls (due to bad keys) across coordinators.""" - - DEFAULT_FAILED_API_CALL_THRESHOLD = 5 - - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: - """Initialize.""" - self._count = 1 - self._lock = asyncio.Lock() - self._reauth_flow_manager = ReauthFlowManager(hass, entry) - self.entry = entry - - async def async_increment(self) -> None: - """Increment the counter.""" - async with self._lock: - self._count += 1 - if self._count > self.DEFAULT_FAILED_API_CALL_THRESHOLD: - LOGGER.info("Starting reauth after multiple failed API calls") - self._reauth_flow_manager.start_reauth() - - async def async_reset(self) -> None: - """Reset the counter.""" - async with self._lock: - self._count = 0 - self._reauth_flow_manager.cancel_reauth() - - -class ReauthFlowManager: - """Define an OpenUV reauth flow manager.""" - - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: - """Initialize.""" - self.entry = entry - self.hass = hass - - @callback - def _get_active_reauth_flow(self) -> FlowResult | None: - """Get an active reauth flow (if it exists).""" - return next( - iter(self.entry.async_get_active_flows(self.hass, {SOURCE_REAUTH})), - None, - ) - - @callback - def cancel_reauth(self) -> None: - """Cancel a reauth flow (if appropriate).""" - if reauth_flow := self._get_active_reauth_flow(): - LOGGER.debug("API seems to have recovered; canceling reauth flow") - self.hass.config_entries.flow.async_abort(reauth_flow["flow_id"]) - - @callback - def start_reauth(self) -> None: - """Start a reauth flow (if appropriate).""" - if not self._get_active_reauth_flow(): - LOGGER.debug("Multiple API failures in a row; starting reauth flow") - self.entry.async_start_reauth(self.hass) - - class OpenUvCoordinator(DataUpdateCoordinator): """Define an OpenUV data coordinator.""" @@ -86,11 +27,11 @@ class OpenUvCoordinator(DataUpdateCoordinator): self, hass: HomeAssistant, *, + entry: ConfigEntry, name: str, latitude: str, longitude: str, update_method: Callable[[], Awaitable[dict[str, Any]]], - invalid_api_key_monitor: InvalidApiKeyMonitor, ) -> None: """Initialize.""" super().__init__( @@ -106,7 +47,7 @@ class OpenUvCoordinator(DataUpdateCoordinator): ), ) - self._invalid_api_key_monitor = invalid_api_key_monitor + self._entry = entry self.latitude = latitude self.longitude = longitude @@ -115,10 +56,18 @@ class OpenUvCoordinator(DataUpdateCoordinator): try: data = await self.update_method() except InvalidApiKeyError as err: - await self._invalid_api_key_monitor.async_increment() - raise UpdateFailed(str(err)) from err + raise ConfigEntryAuthFailed("Invalid API key") from err except OpenUvError as err: raise UpdateFailed(str(err)) from err - await self._invalid_api_key_monitor.async_reset() + # OpenUV uses HTTP 403 to indicate both an invalid API key and an API key that + # has hit its daily/monthly limit; both cases will result in a reauth flow. If + # coordinator update succeeds after a reauth flow has been started, terminate + # it: + if reauth_flow := next( + iter(self._entry.async_get_active_flows(self.hass, {SOURCE_REAUTH})), + None, + ): + self.hass.config_entries.flow.async_abort(reauth_flow["flow_id"]) + return cast(dict[str, Any], data["result"]) diff --git a/homeassistant/components/openuv/manifest.json b/homeassistant/components/openuv/manifest.json index 5e89f495b037..ef367b94dac7 100644 --- a/homeassistant/components/openuv/manifest.json +++ b/homeassistant/components/openuv/manifest.json @@ -3,7 +3,7 @@ "name": "OpenUV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/openuv", - "requirements": ["pyopenuv==2022.04.0"], + "requirements": ["pyopenuv==2023.01.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "loggers": ["pyopenuv"], diff --git a/requirements_all.txt b/requirements_all.txt index 4a6a938e6631..3e37d3ea91b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1822,7 +1822,7 @@ pyoctoprintapi==0.1.9 pyombi==0.1.10 # homeassistant.components.openuv -pyopenuv==2022.04.0 +pyopenuv==2023.01.0 # homeassistant.components.opnsense pyopnsense==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 12fe865b160b..25839ac868f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1305,7 +1305,7 @@ pynzbgetapi==0.2.0 pyoctoprintapi==0.1.9 # homeassistant.components.openuv -pyopenuv==2022.04.0 +pyopenuv==2023.01.0 # homeassistant.components.opnsense pyopnsense==0.2.0