Fix Glances to use dedicated httpx client with longer timeout and surface API error messages (#169689)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Sergio Livi
2026-09-16 11:09:48 +00:00
committed by GitHub
co-authored by Claude Opus 4.7 Joostlek
parent 10aebaf423
commit 99ab58e58a
4 changed files with 61 additions and 6 deletions
+7 -2
View File
@@ -26,8 +26,9 @@ from homeassistant.exceptions import (
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.httpx_client import create_async_httpx_client
from .const import DEFAULT_TIMEOUT
from .coordinator import GlancesConfigEntry, GlancesDataUpdateCoordinator
PLATFORMS = [Platform.SENSOR]
@@ -65,7 +66,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: GlancesConfigEntry) ->
async def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances:
"""Return the api from glances_api."""
httpx_client = get_async_client(hass, verify_ssl=entry_data[CONF_VERIFY_SSL])
# The shared httpx client cannot be used because its 5-second timeout
# is too short for slow Glances hosts.
httpx_client = create_async_httpx_client(
hass, verify_ssl=entry_data[CONF_VERIFY_SSL], timeout=DEFAULT_TIMEOUT
)
for version in (4, 3):
api = Glances(
host=entry_data[CONF_HOST],
@@ -9,5 +9,6 @@ CONF_VERSION = "version"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 61208
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_TIMEOUT = 30
CPU_ICON = f"mdi:cpu-{64 if sys.maxsize > 2**32 else 32}-bit"
@@ -47,7 +47,7 @@ class GlancesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except exceptions.GlancesApiAuthorizationError as err:
raise ConfigEntryAuthFailed from err
except exceptions.GlancesApiError as err:
raise UpdateFailed from err
raise UpdateFailed(str(err)) from err
# Update computed values
uptime: datetime | None = None
up_duration: timedelta | None = None
+52 -3
View File
@@ -1,7 +1,8 @@
"""Tests for Glances integration."""
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from glances_api.exceptions import (
GlancesApiAuthorizationError,
GlancesApiConnectionError,
@@ -9,13 +10,18 @@ from glances_api.exceptions import (
)
import pytest
from homeassistant.components.glances.const import DOMAIN
from homeassistant.components.glances.const import (
DEFAULT_SCAN_INTERVAL,
DEFAULT_TIMEOUT,
DOMAIN,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from . import MOCK_USER_INPUT
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_successful_config_entry(hass: HomeAssistant) -> None:
@@ -53,6 +59,49 @@ async def test_setup_error(
assert entry.state is entry_state
async def test_entity_unavailable_on_update_error(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_api: MagicMock,
) -> None:
"""Test that entities become unavailable when a data update fails."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
assert hass.states.get("sensor.0_0_0_0_ssl_disk_used").state != STATE_UNAVAILABLE
mock_api.return_value.get_ha_sensor_data.side_effect = GlancesApiConnectionError(
"Connection to http://localhost:61209/api/4/all failed"
)
freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("sensor.0_0_0_0_ssl_disk_used").state == STATE_UNAVAILABLE
async def test_dedicated_httpx_client_uses_timeout(
hass: HomeAssistant,
) -> None:
"""The integration's dedicated httpx client uses DEFAULT_TIMEOUT."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.glances.create_async_httpx_client"
) as mock_create:
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
kwargs = mock_create.call_args.kwargs
assert kwargs["timeout"] == DEFAULT_TIMEOUT
assert kwargs["verify_ssl"] == MOCK_USER_INPUT["verify_ssl"]
async def test_unload_entry(hass: HomeAssistant) -> None:
"""Test removing Glances."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)