Refactor coordinator for Garages Amsterdam integration (#131054)

This commit is contained in:
Klaas Schoute 2024-11-26 09:25:36 +01:00 committed by GitHub
parent f5b2f9dcbf
commit e7030f5704
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 132 additions and 60 deletions

View File

@ -1,25 +1,32 @@
"""The Garages Amsterdam integration."""
import asyncio
from datetime import timedelta
import logging
from __future__ import annotations
from odp_amsterdam import ODPAmsterdam, VehicleType
from odp_amsterdam import ODPAmsterdam
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
type GaragesAmsterdamConfigEntry = ConfigEntry[GaragesAmsterdamDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Garages Amsterdam from a config entry."""
await get_coordinator(hass)
client = ODPAmsterdam(session=async_get_clientsession(hass))
coordinator = GaragesAmsterdamDataUpdateCoordinator(hass, client)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -31,32 +38,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.pop(DOMAIN)
return unload_ok
async def get_coordinator(
hass: HomeAssistant,
) -> DataUpdateCoordinator:
"""Get the data update coordinator."""
if DOMAIN in hass.data:
return hass.data[DOMAIN]
async def async_get_garages():
async with asyncio.timeout(10):
return {
garage.garage_name: garage
for garage in await ODPAmsterdam(
session=aiohttp_client.async_get_clientsession(hass)
).all_garages(vehicle=VehicleType.CAR)
}
coordinator = DataUpdateCoordinator(
hass,
logging.getLogger(__name__),
name=DOMAIN,
update_method=async_get_garages,
update_interval=timedelta(minutes=10),
)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN] = coordinator
return coordinator

View File

@ -10,7 +10,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import get_coordinator
from .const import DOMAIN
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
from .entity import GaragesAmsterdamEntity
BINARY_SENSORS = {
@ -20,16 +21,16 @@ BINARY_SENSORS = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Defer sensor setup to the shared sensor module."""
coordinator = await get_coordinator(hass)
coordinator: GaragesAmsterdamDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
async_add_entities(
GaragesAmsterdamBinarySensor(
coordinator, config_entry.data["garage_name"], info_type
)
GaragesAmsterdamBinarySensor(coordinator, entry.data["garage_name"], info_type)
for info_type in BINARY_SENSORS
)

View File

@ -1,4 +1,13 @@
"""Constants for the Garages Amsterdam integration."""
DOMAIN = "garages_amsterdam"
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Final
DOMAIN: Final = "garages_amsterdam"
ATTRIBUTION = f'{"Data provided by municipality of Amsterdam"}'
LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(minutes=10)

View File

@ -0,0 +1,34 @@
"""Coordinator for the Garages Amsterdam integration."""
from __future__ import annotations
from odp_amsterdam import Garage, ODPAmsterdam, VehicleType
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
class GaragesAmsterdamDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Garage]]):
"""Class to manage fetching Garages Amsterdam data from single endpoint."""
def __init__(
self,
hass: HomeAssistant,
client: ODPAmsterdam,
) -> None:
"""Initialize global Garages Amsterdam data updater."""
super().__init__(
hass,
LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
self.client = client
async def _async_update_data(self) -> dict[str, Garage]:
return {
garage.garage_name: garage
for garage in await self.client.all_garages(vehicle=VehicleType.CAR)
}

View File

@ -3,22 +3,23 @@
from __future__ import annotations
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTRIBUTION, DOMAIN
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
class GaragesAmsterdamEntity(CoordinatorEntity):
class GaragesAmsterdamEntity(CoordinatorEntity[GaragesAmsterdamDataUpdateCoordinator]):
"""Base Entity for garages amsterdam data."""
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
def __init__(
self, coordinator: DataUpdateCoordinator, garage_name: str, info_type: str
self,
coordinator: GaragesAmsterdamDataUpdateCoordinator,
garage_name: str,
info_type: str,
) -> None:
"""Initialize garages amsterdam entity."""
super().__init__(coordinator)

View File

@ -6,9 +6,9 @@ from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import get_coordinator
from .const import DOMAIN
from .coordinator import GaragesAmsterdamDataUpdateCoordinator
from .entity import GaragesAmsterdamEntity
SENSORS = {
@ -21,16 +21,18 @@ SENSORS = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Defer sensor setup to the shared sensor module."""
coordinator = await get_coordinator(hass)
coordinator: GaragesAmsterdamDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
async_add_entities(
GaragesAmsterdamSensor(coordinator, config_entry.data["garage_name"], info_type)
GaragesAmsterdamSensor(coordinator, entry.data["garage_name"], info_type)
for info_type in SENSORS
if getattr(coordinator.data[config_entry.data["garage_name"]], info_type) != ""
if getattr(coordinator.data[entry.data["garage_name"]], info_type) != ""
)
@ -40,7 +42,10 @@ class GaragesAmsterdamSensor(GaragesAmsterdamEntity, SensorEntity):
_attr_native_unit_of_measurement = "cars"
def __init__(
self, coordinator: DataUpdateCoordinator, garage_name: str, info_type: str
self,
coordinator: GaragesAmsterdamDataUpdateCoordinator,
garage_name: str,
info_type: str,
) -> None:
"""Initialize garages amsterdam sensor."""
super().__init__(coordinator, garage_name, info_type)

View File

@ -1,12 +1,28 @@
"""Test helpers."""
"""Fixtures for Garages Amsterdam integration tests."""
from unittest.mock import Mock, patch
import pytest
from homeassistant.components.garages_amsterdam.const import DOMAIN
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="monitor",
domain=DOMAIN,
data={},
unique_id="unique_thingy",
version=1,
)
@pytest.fixture(autouse=True)
def mock_cases():
def mock_garages_amsterdam():
"""Mock garages_amsterdam garages."""
with patch(
"odp_amsterdam.ODPAmsterdam.all_garages",

View File

@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
async def test_full_flow(hass: HomeAssistant) -> None:
async def test_full_user_flow(hass: HomeAssistant) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(

View File

@ -0,0 +1,28 @@
"""Tests for the Garages Amsterdam integration."""
from unittest.mock import AsyncMock
from homeassistant.components.garages_amsterdam.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def test_load_unload_config_entry(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_garages_amsterdam: AsyncMock,
) -> None:
"""Test the Garages Amsterdam integration loads and unloads correctly."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert not hass.data.get(DOMAIN)
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED