Add a calendar entity to Ridwell (#86117)

This commit is contained in:
Aaron Bach 2023-02-14 06:58:41 -07:00 committed by GitHub
parent e1a5d5a749
commit 16a5275461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 131 additions and 25 deletions

View File

@ -977,6 +977,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

View File

@ -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:

View 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 aiorecollect 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]
]

View File

@ -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)

View File

@ -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,
)

View File

@ -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()
)

View File

@ -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]:

View File

@ -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:

View File

@ -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

View File

@ -25,7 +25,7 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_config_e
"_async_request": None,
"event_id": "event_123",
"pickup_date": {
"__type": "<class 'datetime.date'>",
"__type": "<class 'freezegun.api.FakeDate'>",
"isoformat": "2022-01-24",
},
"pickups": [