diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index d2ff91e3..b7da1adf 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -48,6 +48,7 @@ from pyhilo.util import from_utc_timestamp, time_diff from pysignalr.exceptions import ServerError as SignalRServerError +from .api import HiloAuth from .config_flow import STEP_OPTION_SCHEMA, HiloFlowHandler from .const import ( CONF_APPRECIATION_PHASE, @@ -212,8 +213,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: HiloConfigEntry) -> bool session=async_create_clientsession( hass, cookie_jar=CookieJar(quote_cookie=False) ), - oauth_session=config_entry_oauth2_flow.OAuth2Session( - hass, entry, implementation + auth=HiloAuth( + config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) ), log_traces=current_options.get(CONF_LOG_TRACES, DEFAULT_LOG_TRACES), state_yaml=hass.config.path("hilo_state.yaml"), diff --git a/custom_components/hilo/api.py b/custom_components/hilo/api.py new file mode 100644 index 00000000..657e794a --- /dev/null +++ b/custom_components/hilo/api.py @@ -0,0 +1,17 @@ +"""API for Hilo bound to Home Assistant OAuth.""" + +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session +from pyhilo import AbstractAuth + + +class HiloAuth(AbstractAuth): + """Provide Hilo authentication tied to an OAuth2 based config entry.""" + + def __init__(self, oauth_session: OAuth2Session) -> None: + """Initialize.""" + self._oauth_session = oauth_session + + async def async_get_access_token(self) -> str: + """Return a valid access token, refreshing it first if it expired.""" + await self._oauth_session.async_ensure_token_valid() + return str(self._oauth_session.token["access_token"]) diff --git a/custom_components/hilo/manifest.json b/custom_components/hilo/manifest.json index a33a7735..213c9ce7 100755 --- a/custom_components/hilo/manifest.json +++ b/custom_components/hilo/manifest.json @@ -11,6 +11,6 @@ "documentation": "https://github.com/dvd-dev/hilo", "iot_class": "cloud_push", "issue_tracker": "https://github.com/dvd-dev/hilo/issues", - "requirements": ["python-hilo==2026.10.1"], + "requirements": ["python-hilo==2026.10.2"], "version": "2026.10.3" } diff --git a/custom_components/hilo/quality_scale.yaml b/custom_components/hilo/quality_scale.yaml index 24941fb5..93493849 100644 --- a/custom_components/hilo/quality_scale.yaml +++ b/custom_components/hilo/quality_scale.yaml @@ -14,9 +14,7 @@ rules: comment: entity.py exists; coordinator.py does not. config-flow-test-coverage: todo config-flow: done - dependency-transparency: - status: todo - comment: python-hilo imports homeassistant; fixed by python-hilo PR 460. + dependency-transparency: done docs-actions: status: exempt comment: The integration does not register actions. diff --git a/pyproject.toml b/pyproject.toml index 5b3f08b9..3a5f39d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ test = [ "pytest", "pytest-cov", "pytest-homeassistant-custom-component>=0.13.0", - "python-hilo==2026.10.1", + "python-hilo==2026.10.2", "bandit>=1.7.1", "ruff>=0.17.2", "pre-commit>=4.7.0", diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..1007f146 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,19 @@ +"""Tests for the Hilo auth bridge.""" + +from unittest.mock import AsyncMock, MagicMock + +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session + +from custom_components.hilo.api import HiloAuth + + +async def test_access_token_refreshed_before_return() -> None: + """The session is asked to refresh before its token is read.""" + oauth_session = MagicMock(spec=OAuth2Session) + oauth_session.async_ensure_token_valid = AsyncMock() + oauth_session.token = {"access_token": "fresh-token"} + + token = await HiloAuth(oauth_session).async_get_access_token() + + oauth_session.async_ensure_token_valid.assert_awaited_once() + assert token == "fresh-token" diff --git a/uv.lock b/uv.lock index 1eb1b215..18734ed4 100644 --- a/uv.lock +++ b/uv.lock @@ -2612,7 +2612,7 @@ test = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-homeassistant-custom-component", specifier = ">=0.13.0" }, - { name = "python-hilo", specifier = "==2026.10.1" }, + { name = "python-hilo", specifier = "==2026.10.2" }, { name = "ruff", specifier = ">=0.17.2" }, ] @@ -5469,7 +5469,7 @@ wheels = [ [[package]] name = "python-hilo" -version = "2026.10.1" +version = "2026.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, @@ -5484,9 +5484,9 @@ dependencies = [ { name = "pyyaml", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13.2'" }, { name = "pyyaml", version = "6.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13.2'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/e4/0d43bb9bc2804c3e475dfba843580c0567ee6ef95eb00b8bfc9f15c62aa0/python_hilo-2026.10.1.tar.gz", hash = "sha256:a4f625874449c99220b435a497fd3694259c9555d4a0f128d24812111e928adf", size = 170699, upload-time = "2026-10-12T01:16:33.943Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/88/e6f4c7126053ef19773dd6281a05d687067a1df0b315c86b36a999f65836/python_hilo-2026.10.2.tar.gz", hash = "sha256:1b94d492c6330f6028f8b899bba1c16c7ddb2560be3aefb8e5215e064ec24a6f", size = 139070, upload-time = "2026-10-27T23:23:13.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/2f/693f5ed2d07ff0e04ea0155157bfeb238c39c5d6dc82786b9fc76a727916/python_hilo-2026.10.1-py3-none-any.whl", hash = "sha256:218043d7a37c541686f9491acdf1c2e40f6868fb5b8310cb20d04f974145ba6e", size = 39657, upload-time = "2026-10-12T01:16:32.794Z" }, + { url = "https://files.pythonhosted.org/packages/03/63/52b5b96a493c5580da58572ea49a386c34e4f0f5b963eeb6e9eee11c8309/python_hilo-2026.10.2-py3-none-any.whl", hash = "sha256:356707e875bf5a190ba07a3d4d697e011a781dbd3cec8ba4d3c865b64127887f", size = 40610, upload-time = "2026-10-27T23:23:12.14Z" }, ] [[package]]