diff --git a/homeassistant/components/garages_amsterdam/__init__.py b/homeassistant/components/garages_amsterdam/__init__.py index 81ec72d9fbf..4cdcc3f06be 100644 --- a/homeassistant/components/garages_amsterdam/__init__.py +++ b/homeassistant/components/garages_amsterdam/__init__.py @@ -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 diff --git a/homeassistant/components/garages_amsterdam/binary_sensor.py b/homeassistant/components/garages_amsterdam/binary_sensor.py index 0aebe36baeb..2be8aaeffc0 100644 --- a/homeassistant/components/garages_amsterdam/binary_sensor.py +++ b/homeassistant/components/garages_amsterdam/binary_sensor.py @@ -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 ) diff --git a/homeassistant/components/garages_amsterdam/const.py b/homeassistant/components/garages_amsterdam/const.py index ae7801a9abd..0f1e6505f9f 100644 --- a/homeassistant/components/garages_amsterdam/const.py +++ b/homeassistant/components/garages_amsterdam/const.py @@ -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) diff --git a/homeassistant/components/garages_amsterdam/coordinator.py b/homeassistant/components/garages_amsterdam/coordinator.py new file mode 100644 index 00000000000..3d06aba79e2 --- /dev/null +++ b/homeassistant/components/garages_amsterdam/coordinator.py @@ -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) + } diff --git a/homeassistant/components/garages_amsterdam/entity.py b/homeassistant/components/garages_amsterdam/entity.py index 671405235d4..a8b030157bc 100644 --- a/homeassistant/components/garages_amsterdam/entity.py +++ b/homeassistant/components/garages_amsterdam/entity.py @@ -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) diff --git a/homeassistant/components/garages_amsterdam/sensor.py b/homeassistant/components/garages_amsterdam/sensor.py index b6fc950a843..87c72f4a248 100644 --- a/homeassistant/components/garages_amsterdam/sensor.py +++ b/homeassistant/components/garages_amsterdam/sensor.py @@ -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) diff --git a/tests/components/garages_amsterdam/conftest.py b/tests/components/garages_amsterdam/conftest.py index fb59ba26569..8d7eb8752b0 100644 --- a/tests/components/garages_amsterdam/conftest.py +++ b/tests/components/garages_amsterdam/conftest.py @@ -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", diff --git a/tests/components/garages_amsterdam/test_config_flow.py b/tests/components/garages_amsterdam/test_config_flow.py index 729d31e413c..9c5b10f9ecc 100644 --- a/tests/components/garages_amsterdam/test_config_flow.py +++ b/tests/components/garages_amsterdam/test_config_flow.py @@ -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( diff --git a/tests/components/garages_amsterdam/test_init.py b/tests/components/garages_amsterdam/test_init.py new file mode 100644 index 00000000000..ff3166183a1 --- /dev/null +++ b/tests/components/garages_amsterdam/test_init.py @@ -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