Add a calendar entity to Ridwell (#88108)
* Subclass a `DataUpdateCoordinator` for Ridwell * Add a calendar entity to Ridwell * Simpler unique ID * Fix tests * Docstring
This commit is contained in:
parent
0666a4750c
commit
ff135ecdc6
10 changed files with 131 additions and 25 deletions
|
@ -997,6 +997,7 @@ omit =
|
|||
homeassistant/components/rest/notify.py
|
||||
homeassistant/components/rest/switch.py
|
||||
homeassistant/components/ridwell/__init__.py
|
||||
homeassistant/components/ridwell/calendar.py
|
||||
homeassistant/components/ridwell/coordinator.py
|
||||
homeassistant/components/ridwell/switch.py
|
||||
homeassistant/components/ring/camera.py
|
||||
|
|
|
@ -11,7 +11,7 @@ from homeassistant.helpers import entity_registry as er
|
|||
from .const import DOMAIN, LOGGER, SENSOR_TYPE_NEXT_PICKUP
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.SWITCH]
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
|
78
homeassistant/components/ridwell/calendar.py
Normal file
78
homeassistant/components/ridwell/calendar.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
"""Support for Ridwell calendars."""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from aioridwell.model import RidwellAccount, RidwellPickupEvent
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RidwellDataUpdateCoordinator
|
||||
from .entity import RidwellEntity
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_calendar_event_from_pickup_event(
|
||||
pickup_event: RidwellPickupEvent,
|
||||
) -> CalendarEvent:
|
||||
"""Get a HASS CalendarEvent from an aioridwell PickupEvent."""
|
||||
pickup_type_string = ", ".join(
|
||||
[
|
||||
f"{pickup.name} (quantity: {pickup.quantity})"
|
||||
for pickup in pickup_event.pickups
|
||||
]
|
||||
)
|
||||
return CalendarEvent(
|
||||
summary=f"Ridwell Pickup ({pickup_event.state.value})",
|
||||
description=f"Pickup types: {pickup_type_string}",
|
||||
start=pickup_event.pickup_date,
|
||||
end=pickup_event.pickup_date + datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up Ridwell calendars based on a config entry."""
|
||||
coordinator: RidwellDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
RidwellCalendar(coordinator, account)
|
||||
for account in coordinator.accounts.values()
|
||||
)
|
||||
|
||||
|
||||
class RidwellCalendar(RidwellEntity, CalendarEntity):
|
||||
"""Define a Ridwell calendar."""
|
||||
|
||||
_attr_icon = "mdi:delete-empty"
|
||||
|
||||
def __init__(
|
||||
self, coordinator: RidwellDataUpdateCoordinator, account: RidwellAccount
|
||||
) -> None:
|
||||
"""Initialize the Ridwell entity."""
|
||||
super().__init__(coordinator, account)
|
||||
|
||||
self._attr_unique_id = self._account.account_id
|
||||
self._event: CalendarEvent | None = None
|
||||
|
||||
@property
|
||||
def event(self) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
return async_get_calendar_event_from_pickup_event(self.next_pickup_event)
|
||||
|
||||
async def async_get_events(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
start_date: datetime.datetime,
|
||||
end_date: datetime.datetime,
|
||||
) -> list[CalendarEvent]:
|
||||
"""Return calendar events within a datetime range."""
|
||||
return [
|
||||
async_get_calendar_event_from_pickup_event(event)
|
||||
for event in self.coordinator.data[self._account.account_id]
|
||||
]
|
|
@ -22,14 +22,14 @@ UPDATE_INTERVAL = timedelta(hours=1)
|
|||
|
||||
|
||||
class RidwellDataUpdateCoordinator(
|
||||
DataUpdateCoordinator[dict[str, RidwellPickupEvent]]
|
||||
DataUpdateCoordinator[dict[str, list[RidwellPickupEvent]]]
|
||||
):
|
||||
"""Class to manage fetching data from single endpoint."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, *, name: str) -> None:
|
||||
"""Initialize global data updater."""
|
||||
"""Initialize."""
|
||||
# These will be filled in by async_initialize; we give them these defaults to
|
||||
# avoid arduous typing checks down the line:
|
||||
self.accounts: dict[str, RidwellAccount] = {}
|
||||
|
@ -38,13 +38,13 @@ class RidwellDataUpdateCoordinator(
|
|||
|
||||
super().__init__(hass, LOGGER, name=name, update_interval=UPDATE_INTERVAL)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, RidwellPickupEvent]:
|
||||
async def _async_update_data(self) -> dict[str, list[RidwellPickupEvent]]:
|
||||
"""Fetch the latest data from the source."""
|
||||
data = {}
|
||||
|
||||
async def async_get_pickups(account: RidwellAccount) -> None:
|
||||
"""Get the latest pickups for an account."""
|
||||
data[account.account_id] = await account.async_get_next_pickup_event()
|
||||
data[account.account_id] = await account.async_get_pickup_events()
|
||||
|
||||
tasks = [async_get_pickups(account) for account in self.accounts.values()]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
|
|
@ -32,7 +32,11 @@ async def async_get_config_entry_diagnostics(
|
|||
return async_redact_data(
|
||||
{
|
||||
"entry": entry.as_dict(),
|
||||
"data": [dataclasses.asdict(event) for event in coordinator.data.values()],
|
||||
"data": [
|
||||
dataclasses.asdict(event)
|
||||
for events in coordinator.data.values()
|
||||
for event in events
|
||||
],
|
||||
},
|
||||
TO_REDACT,
|
||||
)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
"""Define a base Ridwell entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from aioridwell.model import RidwellAccount, RidwellPickupEvent
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
@ -18,7 +22,6 @@ class RidwellEntity(CoordinatorEntity[RidwellDataUpdateCoordinator]):
|
|||
self,
|
||||
coordinator: RidwellDataUpdateCoordinator,
|
||||
account: RidwellAccount,
|
||||
description: EntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
@ -31,10 +34,12 @@ class RidwellEntity(CoordinatorEntity[RidwellDataUpdateCoordinator]):
|
|||
manufacturer="Ridwell",
|
||||
name="Ridwell",
|
||||
)
|
||||
self._attr_unique_id = f"{account.account_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def next_pickup_event(self) -> RidwellPickupEvent:
|
||||
"""Get the next pickup event."""
|
||||
return self.coordinator.data[self._account.account_id]
|
||||
return next(
|
||||
event
|
||||
for event in self.coordinator.data[self._account.account_id]
|
||||
if event.pickup_date >= date.today()
|
||||
)
|
||||
|
|
|
@ -27,7 +27,7 @@ ATTR_QUANTITY = "quantity"
|
|||
|
||||
SENSOR_DESCRIPTION = SensorEntityDescription(
|
||||
key=SENSOR_TYPE_NEXT_PICKUP,
|
||||
name="Ridwell pickup",
|
||||
name="Next Ridwell pickup",
|
||||
device_class=SensorDeviceClass.DATE,
|
||||
)
|
||||
|
||||
|
@ -54,9 +54,10 @@ class RidwellSensor(RidwellEntity, SensorEntity):
|
|||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, account, description)
|
||||
super().__init__(coordinator, account)
|
||||
|
||||
self._attr_name = f"{description.name} ({account.address['street1']})"
|
||||
self._attr_unique_id = f"{account.account_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, Any]:
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
|
||||
from aioridwell.errors import RidwellError
|
||||
from aioridwell.model import EventState
|
||||
from aioridwell.model import EventState, RidwellAccount
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -38,7 +38,19 @@ async def async_setup_entry(
|
|||
|
||||
|
||||
class RidwellSwitch(RidwellEntity, SwitchEntity):
|
||||
"""Define a Ridwell button."""
|
||||
"""Define a Ridwell switch."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RidwellDataUpdateCoordinator,
|
||||
account: RidwellAccount,
|
||||
description: SwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, account)
|
||||
|
||||
self._attr_unique_id = f"{account.account_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
|
|
|
@ -3,6 +3,7 @@ from datetime import date
|
|||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aioridwell.model import EventState, RidwellPickup, RidwellPickupEvent
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.ridwell.const import DOMAIN
|
||||
|
@ -28,14 +29,16 @@ def account_fixture():
|
|||
"state": "New York",
|
||||
"postal_code": "10001",
|
||||
},
|
||||
async_get_next_pickup_event=AsyncMock(
|
||||
return_value=RidwellPickupEvent(
|
||||
None,
|
||||
"event_123",
|
||||
date(2022, 1, 24),
|
||||
[RidwellPickup("Plastic Film", "offer_123", 1, "product_123", 1)],
|
||||
EventState.INITIALIZED,
|
||||
)
|
||||
async_get_pickup_events=AsyncMock(
|
||||
return_value=[
|
||||
RidwellPickupEvent(
|
||||
None,
|
||||
"event_123",
|
||||
date(2022, 1, 24),
|
||||
[RidwellPickup("Plastic Film", "offer_123", 1, "product_123", 1)],
|
||||
EventState.INITIALIZED,
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -77,6 +80,8 @@ async def mock_aioridwell_fixture(hass, client, config):
|
|||
), patch(
|
||||
"homeassistant.components.ridwell.coordinator.async_get_client",
|
||||
return_value=client,
|
||||
), freeze_time(
|
||||
"2022-01-01"
|
||||
):
|
||||
yield
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ async def test_entry_diagnostics(
|
|||
"_async_request": None,
|
||||
"event_id": "event_123",
|
||||
"pickup_date": {
|
||||
"__type": "<class 'datetime.date'>",
|
||||
"__type": "<class 'freezegun.api.FakeDate'>",
|
||||
"isoformat": "2022-01-24",
|
||||
},
|
||||
"pickups": [
|
||||
|
|
Loading…
Add table
Reference in a new issue