Move Meater coordinator to module (#146946)

* Move Meater coordinator to module

* Fix tests
This commit is contained in:
Joost Lekkerkerker 2025-06-16 14:48:44 +02:00 committed by GitHub
parent 38973fe64a
commit add9f4c5ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 86 additions and 75 deletions

View File

@ -1,85 +1,25 @@
"""The Meater Temperature Probe integration."""
import asyncio
from datetime import timedelta
import logging
from meater import (
AuthenticationError,
MeaterApi,
ServiceUnavailableError,
TooManyRequestsError,
)
from meater.MeaterApi import MeaterProbe
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
from .coordinator import MeaterCoordinator
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Meater Temperature Probe from a config entry."""
# Store an API object to access
session = async_get_clientsession(hass)
meater_api = MeaterApi(session)
# Add the credentials
try:
_LOGGER.debug("Authenticating with the Meater API")
await meater_api.authenticate(
entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
)
except (ServiceUnavailableError, TooManyRequestsError) as err:
raise ConfigEntryNotReady from err
except AuthenticationError as err:
raise ConfigEntryAuthFailed(
f"Unable to authenticate with the Meater API: {err}"
) from err
async def async_update_data() -> dict[str, MeaterProbe]:
"""Fetch data from API endpoint."""
try:
# Note: TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with asyncio.timeout(10):
devices: list[MeaterProbe] = await meater_api.get_all_devices()
except AuthenticationError as err:
raise ConfigEntryAuthFailed("The API call wasn't authenticated") from err
except TooManyRequestsError as err:
raise UpdateFailed(
"Too many requests have been made to the API, rate limiting is in place"
) from err
return {device.id: device for device in devices}
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
config_entry=entry,
# Name of the data. For logging purposes.
name="meater_api",
update_method=async_update_data,
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=30),
)
coordinator = MeaterCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault("known_probes", set())
hass.data[DOMAIN][entry.entry_id] = {
"api": meater_api,
"coordinator": coordinator,
}
hass.data[DOMAIN][entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

View File

@ -0,0 +1,75 @@
"""Meater Coordinator."""
import asyncio
from datetime import timedelta
import logging
from meater.MeaterApi import (
AuthenticationError,
MeaterApi,
MeaterProbe,
ServiceUnavailableError,
TooManyRequestsError,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__)
class MeaterCoordinator(DataUpdateCoordinator[dict[str, MeaterProbe]]):
"""Meater Coordinator."""
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
) -> None:
"""Initialize the Meater Coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=entry,
name=f"Meater {entry.title}",
update_interval=timedelta(seconds=30),
)
session = async_get_clientsession(hass)
self.client = MeaterApi(session)
async def _async_setup(self) -> None:
"""Set up the Meater Coordinator."""
try:
_LOGGER.debug("Authenticating with the Meater API")
await self.client.authenticate(
self.config_entry.data[CONF_USERNAME],
self.config_entry.data[CONF_PASSWORD],
)
except (ServiceUnavailableError, TooManyRequestsError) as err:
raise UpdateFailed from err
except AuthenticationError as err:
raise ConfigEntryAuthFailed(
f"Unable to authenticate with the Meater API: {err}"
) from err
async def _async_update_data(self) -> dict[str, MeaterProbe]:
"""Fetch data from API endpoint."""
try:
# Note: TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with asyncio.timeout(10):
devices: list[MeaterProbe] = await self.client.get_all_devices()
except AuthenticationError as err:
raise ConfigEntryAuthFailed("The API call wasn't authenticated") from err
except TooManyRequestsError as err:
raise UpdateFailed(
"Too many requests have been made to the API, rate limiting is in place"
) from err
return {device.id: device for device in devices}

View File

@ -19,12 +19,10 @@ from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from . import MeaterCoordinator
from .const import DOMAIN
@ -141,9 +139,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the entry."""
coordinator: DataUpdateCoordinator[dict[str, MeaterProbe]] = hass.data[DOMAIN][
entry.entry_id
]["coordinator"]
coordinator: MeaterCoordinator = hass.data[DOMAIN][entry.entry_id]
@callback
def async_update_data():
@ -176,9 +172,7 @@ async def async_setup_entry(
coordinator.async_add_listener(async_update_data)
class MeaterProbeTemperature(
SensorEntity, CoordinatorEntity[DataUpdateCoordinator[dict[str, MeaterProbe]]]
):
class MeaterProbeTemperature(SensorEntity, CoordinatorEntity[MeaterCoordinator]):
"""Meater Temperature Sensor Entity."""
entity_description: MeaterSensorEntityDescription

View File

@ -23,7 +23,9 @@ def mock_client():
@pytest.fixture
def mock_meater(mock_client):
"""Mock the meater library."""
with patch("homeassistant.components.meater.MeaterApi.authenticate") as mock_:
with patch(
"homeassistant.components.meater.coordinator.MeaterApi.authenticate"
) as mock_:
mock_.side_effect = mock_client
yield mock_