From 66228f976d145ef5b1cecd2744fa0ea938c5a833 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 10 Feb 2026 09:43:55 +0100 Subject: [PATCH] Use session.request() instead of getattr dispatch in HomeAssistantAPI (#6541) Replace the dynamic `getattr(self.sys_websession, method)(...)` pattern with the explicit `self.sys_websession.request(method, ...)` call. This is type-safe and avoids runtime failures from typos in method names. Also wrap the timeout parameter in `aiohttp.ClientTimeout` for consistency with the typed `request()` signature. Co-authored-by: Claude Opus 4.6 --- supervisor/homeassistant/api.py | 6 ++++-- tests/addons/test_manager.py | 11 ++++++++--- tests/api/test_auth.py | 8 ++++---- tests/api/test_discovery.py | 16 +++++++++------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/supervisor/homeassistant/api.py b/supervisor/homeassistant/api.py index daba82cd2..3d3c5d7e5 100644 --- a/supervisor/homeassistant/api.py +++ b/supervisor/homeassistant/api.py @@ -135,6 +135,7 @@ class HomeAssistantAPI(CoreSysAttributes): """ url = f"{self.sys_homeassistant.api_url}/{path}" headers = headers or {} + client_timeout = aiohttp.ClientTimeout(total=timeout) # Passthrough content type if content_type is not None: @@ -144,10 +145,11 @@ class HomeAssistantAPI(CoreSysAttributes): try: await self.ensure_access_token() headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}" - async with getattr(self.sys_websession, method)( + async with self.sys_websession.request( + method, url, data=data, - timeout=timeout, + timeout=client_timeout, json=json, headers=headers, params=params, diff --git a/tests/addons/test_manager.py b/tests/addons/test_manager.py index b67f85294..9973a9449 100644 --- a/tests/addons/test_manager.py +++ b/tests/addons/test_manager.py @@ -210,12 +210,17 @@ async def test_addon_uninstall_removes_discovery( await coresys.addons.uninstall(TEST_ADDON_SLUG) await asyncio.sleep(0) - coresys.websession.delete.assert_called_once() + + # Find the delete call among all request calls (send also uses request) + delete_calls = [ + c for c in coresys.websession.request.call_args_list if c.args[0] == "delete" + ] + assert len(delete_calls) == 1 assert ( - coresys.websession.delete.call_args.args[0] + delete_calls[0].args[1] == f"http://172.30.32.1:8123/api/hassio_push/discovery/{message.uuid}" ) - assert coresys.websession.delete.call_args.kwargs["json"] == { + assert delete_calls[0].kwargs["json"] == { "addon": TEST_ADDON_SLUG, "service": "mqtt", "uuid": message.uuid, diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py index 7d29805d1..e224e410f 100644 --- a/tests/api/test_auth.py +++ b/tests/api/test_auth.py @@ -95,7 +95,7 @@ async def test_password_reset( days=1 ) - websession.post = MagicMock(return_value=MockResponse(status=200)) + websession.request = MagicMock(return_value=MockResponse(status=200)) resp = await api_client.post( "/auth/reset", json={"username": "john", "password": "doe"} ) @@ -104,7 +104,7 @@ async def test_password_reset( @pytest.mark.parametrize( - ("post_mock", "expected_log"), + ("request_mock", "expected_log"), [ ( MagicMock(return_value=MockResponse(status=400)), @@ -121,7 +121,7 @@ async def test_failed_password_reset( coresys: CoreSys, caplog: pytest.LogCaptureFixture, websession: MagicMock, - post_mock: MagicMock, + request_mock: MagicMock, expected_log: str, ): """Test failed password reset.""" @@ -131,7 +131,7 @@ async def test_failed_password_reset( days=1 ) - websession.post = post_mock + websession.request = request_mock resp = await api_client.post( "/auth/reset", json={"username": "john", "password": "doe"} ) diff --git a/tests/api/test_discovery.py b/tests/api/test_discovery.py index 39efcf36e..e0ec28ad5 100644 --- a/tests/api/test_discovery.py +++ b/tests/api/test_discovery.py @@ -97,12 +97,13 @@ async def test_api_send_del_discovery( assert resp.status == 200 result = await resp.json() uuid = result["data"]["uuid"] - coresys.websession.post.assert_called_once() + coresys.websession.request.assert_called_once() + assert coresys.websession.request.call_args.args[0] == "post" assert ( - coresys.websession.post.call_args.args[0] + coresys.websession.request.call_args.args[1] == f"http://172.30.32.1:8123/api/hassio_push/discovery/{uuid}" ) - assert coresys.websession.post.call_args.kwargs["json"] == { + assert coresys.websession.request.call_args.kwargs["json"] == { "addon": TEST_ADDON_SLUG, "service": "test", "uuid": uuid, @@ -113,15 +114,16 @@ async def test_api_send_del_discovery( assert message.service == "test" assert message.config == {} - coresys.websession.delete = MagicMock() + coresys.websession.request.reset_mock() resp = await api_client.delete(f"/discovery/{uuid}") assert resp.status == 200 - coresys.websession.delete.assert_called_once() + coresys.websession.request.assert_called_once() + assert coresys.websession.request.call_args.args[0] == "delete" assert ( - coresys.websession.delete.call_args.args[0] + coresys.websession.request.call_args.args[1] == f"http://172.30.32.1:8123/api/hassio_push/discovery/{uuid}" ) - assert coresys.websession.delete.call_args.kwargs["json"] == { + assert coresys.websession.request.call_args.kwargs["json"] == { "addon": TEST_ADDON_SLUG, "service": "test", "uuid": uuid,