Skip to content

Commit cccbad7

Browse files
serlclaude
andcommitted
fix(glances): cache dedicated httpx client and add timeout regression test
Address PR review feedback: - Cache the per-verify_ssl httpx client in hass.data so config entry reloads reuse the same client instead of leaking a new one (and a new EVENT_HOMEASSISTANT_CLOSE handler) on every reload. - Add a regression test that asserts setup constructs the client with the configured DEFAULT_TIMEOUT and that a reload hits the cache (no second client created). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5f76f36 commit cccbad7

2 files changed

Lines changed: 54 additions & 6 deletions

File tree

homeassistant/components/glances/__init__.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
GlancesApiError,
1010
GlancesApiNoDataAvailable,
1111
)
12+
import httpx
1213

1314
from homeassistant.const import (
1415
CONF_HOST,
@@ -19,18 +20,23 @@
1920
CONF_VERIFY_SSL,
2021
Platform,
2122
)
22-
from homeassistant.core import HomeAssistant
23+
from homeassistant.core import HomeAssistant, callback
2324
from homeassistant.exceptions import (
2425
ConfigEntryAuthFailed,
2526
ConfigEntryError,
2627
ConfigEntryNotReady,
2728
HomeAssistantError,
2829
)
2930
from homeassistant.helpers.httpx_client import create_async_httpx_client
31+
from homeassistant.util.hass_dict import HassKey
3032

3133
from .const import DEFAULT_TIMEOUT
3234
from .coordinator import GlancesConfigEntry, GlancesDataUpdateCoordinator
3335

36+
DATA_HTTPX_CLIENT: HassKey[dict[bool, httpx.AsyncClient]] = HassKey(
37+
"glances_httpx_client"
38+
)
39+
3440
PLATFORMS = [Platform.SENSOR]
3541

3642

@@ -64,11 +70,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: GlancesConfigEntry) ->
6470
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
6571

6672

73+
@callback
74+
def _async_get_httpx_client(hass: HomeAssistant, verify_ssl: bool) -> httpx.AsyncClient:
75+
"""Return a cached Glances httpx client (one per verify_ssl value).
76+
77+
The shared httpx client cannot be used because it has a 5-second timeout
78+
that is too short for slow Glances hosts. Caching here ensures entry
79+
reloads reuse the same client instead of leaking one on every reload.
80+
"""
81+
clients = hass.data.setdefault(DATA_HTTPX_CLIENT, {})
82+
if (client := clients.get(verify_ssl)) is None:
83+
client = clients[verify_ssl] = create_async_httpx_client(
84+
hass, verify_ssl=verify_ssl, timeout=DEFAULT_TIMEOUT
85+
)
86+
return client
87+
88+
6789
async def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances:
6890
"""Return the api from glances_api."""
69-
httpx_client = create_async_httpx_client(
70-
hass, verify_ssl=entry_data[CONF_VERIFY_SSL], timeout=DEFAULT_TIMEOUT
71-
)
91+
httpx_client = _async_get_httpx_client(hass, entry_data[CONF_VERIFY_SSL])
7292
for version in (4, 3):
7393
api = Glances(
7494
host=entry_data[CONF_HOST],

tests/components/glances/test_init.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for Glances integration."""
22

3-
from unittest.mock import MagicMock
3+
from unittest.mock import MagicMock, patch
44

55
from freezegun.api import FrozenDateTimeFactory
66
from glances_api.exceptions import (
@@ -10,7 +10,11 @@
1010
)
1111
import pytest
1212

13-
from homeassistant.components.glances.const import DEFAULT_SCAN_INTERVAL, DOMAIN
13+
from homeassistant.components.glances.const import (
14+
DEFAULT_SCAN_INTERVAL,
15+
DEFAULT_TIMEOUT,
16+
DOMAIN,
17+
)
1418
from homeassistant.config_entries import ConfigEntryState
1519
from homeassistant.core import HomeAssistant
1620
from homeassistant.helpers.update_coordinator import UpdateFailed
@@ -83,6 +87,30 @@ async def test_update_error_includes_message(
8387
)
8488

8589

90+
async def test_dedicated_httpx_client_uses_timeout_and_is_cached(
91+
hass: HomeAssistant,
92+
) -> None:
93+
"""The integration's dedicated httpx client uses DEFAULT_TIMEOUT and is reused on reload."""
94+
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)
95+
entry.add_to_hass(hass)
96+
97+
with patch(
98+
"homeassistant.components.glances.create_async_httpx_client"
99+
) as mock_create:
100+
await hass.config_entries.async_setup(entry.entry_id)
101+
await hass.async_block_till_done()
102+
assert entry.state is ConfigEntryState.LOADED
103+
104+
assert mock_create.call_count == 1
105+
kwargs = mock_create.call_args.kwargs
106+
assert kwargs["timeout"] == DEFAULT_TIMEOUT
107+
assert kwargs["verify_ssl"] == MOCK_USER_INPUT["verify_ssl"]
108+
109+
assert await hass.config_entries.async_reload(entry.entry_id)
110+
await hass.async_block_till_done()
111+
assert mock_create.call_count == 1
112+
113+
86114
async def test_unload_entry(hass: HomeAssistant) -> None:
87115
"""Test removing Glances."""
88116
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)

0 commit comments

Comments
 (0)