"""API for Viessmann ViCare bound to Home Assistant OAuth.""" from asyncio import run_coroutine_threadsafe import logging from typing import Any, override from PyViCare.PyViCareAbstractOAuthManager import AbstractViCareOAuthManager import requests from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow _LOGGER = logging.getLogger(__name__) _DEFAULT_TIMEOUT = 31 class _TimeoutSession(requests.Session): """requests.Session that applies a default timeout when callers omit one.""" @override def request( # type: ignore[override] self, method: str, url: str, **kwargs: Any ) -> requests.Response: """Forward to Session.request with a default timeout.""" kwargs.setdefault("timeout", _DEFAULT_TIMEOUT) return super().request(method, url, **kwargs) class ConfigEntryAuth(AbstractViCareOAuthManager): """Provide Viessmann ViCare authentication tied to an OAuth2 based config entry.""" def __init__( self, hass: HomeAssistant, oauth_session: config_entry_oauth2_flow.OAuth2Session, ) -> None: """Initialize Viessmann ViCare Auth.""" self._hass = hass self._ha_session = oauth_session # Header mutation in renewToken() races with concurrent reads from # PyViCare's executor calls; self-healing via PyViCare's EXPIRED TOKEN # retry, so no lock. session = _TimeoutSession() session.headers["Authorization"] = self._bearer_header() super().__init__(session) def renewToken(self) -> None: """Refresh OAuth2 token via HA and update the bearer header.""" _LOGGER.debug("Renewing OAuth2 token") run_coroutine_threadsafe( self._ha_session.async_ensure_token_valid(), self._hass.loop ).result() self.oauth_session.headers["Authorization"] = self._bearer_header() _LOGGER.debug("OAuth2 token renewed successfully") def _bearer_header(self) -> str: token: dict[str, Any] = self._ha_session.token return f"Bearer {token['access_token']}"