diff --git a/homeassistant/components/picnic/const.py b/homeassistant/components/picnic/const.py index 85a7acadaeb..7e983321f3d 100644 --- a/homeassistant/components/picnic/const.py +++ b/homeassistant/components/picnic/const.py @@ -1,16 +1,6 @@ """Constants for the Picnic integration.""" from __future__ import annotations -from collections.abc import Callable -from dataclasses import dataclass -from datetime import datetime -from typing import Any, Literal - -from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription -from homeassistant.const import CURRENCY_EURO -from homeassistant.helpers.typing import StateType -from homeassistant.util import dt as dt_util - DOMAIN = "picnic" CONF_API = "api" @@ -49,163 +39,3 @@ SENSOR_NEXT_DELIVERY_ETA_START = "next_delivery_eta_start" SENSOR_NEXT_DELIVERY_ETA_END = "next_delivery_eta_end" SENSOR_NEXT_DELIVERY_SLOT_START = "next_delivery_slot_start" SENSOR_NEXT_DELIVERY_SLOT_END = "next_delivery_slot_end" - - -@dataclass -class PicnicRequiredKeysMixin: - """Mixin for required keys.""" - - data_type: Literal[ - "cart_data", "slot_data", "next_delivery_data", "last_order_data" - ] - value_fn: Callable[[Any], StateType | datetime] - - -@dataclass -class PicnicSensorEntityDescription(SensorEntityDescription, PicnicRequiredKeysMixin): - """Describes Picnic sensor entity.""" - - entity_registry_enabled_default: bool = False - - -SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( - PicnicSensorEntityDescription( - key=SENSOR_CART_ITEMS_COUNT, - icon="mdi:format-list-numbered", - data_type="cart_data", - value_fn=lambda cart: cart.get("total_count", 0), - ), - PicnicSensorEntityDescription( - key=SENSOR_CART_TOTAL_PRICE, - native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:currency-eur", - entity_registry_enabled_default=True, - data_type="cart_data", - value_fn=lambda cart: cart.get("total_price", 0) / 100, - ), - PicnicSensorEntityDescription( - key=SENSOR_SELECTED_SLOT_START, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", - entity_registry_enabled_default=True, - data_type="slot_data", - value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_start"))), - ), - PicnicSensorEntityDescription( - key=SENSOR_SELECTED_SLOT_END, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", - entity_registry_enabled_default=True, - data_type="slot_data", - value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_end"))), - ), - PicnicSensorEntityDescription( - key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-alert-outline", - entity_registry_enabled_default=True, - data_type="slot_data", - value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("cut_off_time"))), - ), - PicnicSensorEntityDescription( - key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, - native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:currency-eur", - entity_registry_enabled_default=True, - data_type="slot_data", - value_fn=lambda slot: ( - slot["minimum_order_value"] / 100 - if slot.get("minimum_order_value") - else None - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_SLOT_START, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("slot", {}).get("window_start")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_SLOT_END, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("slot", {}).get("window_end")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_STATUS, - icon="mdi:list-status", - data_type="last_order_data", - value_fn=lambda last_order: last_order.get("status"), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_MAX_ORDER_TIME, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-alert-outline", - entity_registry_enabled_default=True, - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("slot", {}).get("cut_off_time")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_DELIVERY_TIME, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:timeline-clock", - entity_registry_enabled_default=True, - data_type="last_order_data", - value_fn=lambda last_order: dt_util.parse_datetime( - str(last_order.get("delivery_time", {}).get("start")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_LAST_ORDER_TOTAL_PRICE, - native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:cash-marker", - data_type="last_order_data", - value_fn=lambda last_order: last_order.get("total_price", 0) / 100, - ), - PicnicSensorEntityDescription( - key=SENSOR_NEXT_DELIVERY_ETA_START, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-start", - entity_registry_enabled_default=True, - data_type="next_delivery_data", - value_fn=lambda next_delivery: dt_util.parse_datetime( - str(next_delivery.get("eta", {}).get("start")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_NEXT_DELIVERY_ETA_END, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-end", - entity_registry_enabled_default=True, - data_type="next_delivery_data", - value_fn=lambda next_delivery: dt_util.parse_datetime( - str(next_delivery.get("eta", {}).get("end")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_NEXT_DELIVERY_SLOT_START, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", - data_type="next_delivery_data", - value_fn=lambda next_delivery: dt_util.parse_datetime( - str(next_delivery.get("slot", {}).get("window_start")) - ), - ), - PicnicSensorEntityDescription( - key=SENSOR_NEXT_DELIVERY_SLOT_END, - device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", - data_type="next_delivery_data", - value_fn=lambda next_delivery: dt_util.parse_datetime( - str(next_delivery.get("slot", {}).get("window_end")) - ), - ), -) diff --git a/homeassistant/components/picnic/sensor.py b/homeassistant/components/picnic/sensor.py index e992945c510..6e63cc075d2 100644 --- a/homeassistant/components/picnic/sensor.py +++ b/homeassistant/components/picnic/sensor.py @@ -1,11 +1,18 @@ """Definition of Picnic sensors.""" from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass from datetime import datetime -from typing import Any, cast +from typing import Any, Literal, cast -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CURRENCY_EURO from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo @@ -15,14 +22,189 @@ from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) +from homeassistant.util import dt as dt_util from .const import ( ADDRESS, ATTRIBUTION, CONF_COORDINATOR, DOMAIN, - SENSOR_TYPES, - PicnicSensorEntityDescription, + SENSOR_CART_ITEMS_COUNT, + SENSOR_CART_TOTAL_PRICE, + SENSOR_LAST_ORDER_DELIVERY_TIME, + SENSOR_LAST_ORDER_MAX_ORDER_TIME, + SENSOR_LAST_ORDER_SLOT_END, + SENSOR_LAST_ORDER_SLOT_START, + SENSOR_LAST_ORDER_STATUS, + SENSOR_LAST_ORDER_TOTAL_PRICE, + SENSOR_NEXT_DELIVERY_ETA_END, + SENSOR_NEXT_DELIVERY_ETA_START, + SENSOR_NEXT_DELIVERY_SLOT_END, + SENSOR_NEXT_DELIVERY_SLOT_START, + SENSOR_SELECTED_SLOT_END, + SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, + SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, + SENSOR_SELECTED_SLOT_START, +) + + +@dataclass +class PicnicRequiredKeysMixin: + """Mixin for required keys.""" + + data_type: Literal[ + "cart_data", "slot_data", "next_delivery_data", "last_order_data" + ] + value_fn: Callable[[Any], StateType | datetime] + + +@dataclass +class PicnicSensorEntityDescription(SensorEntityDescription, PicnicRequiredKeysMixin): + """Describes Picnic sensor entity.""" + + entity_registry_enabled_default: bool = False + + +SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( + PicnicSensorEntityDescription( + key=SENSOR_CART_ITEMS_COUNT, + icon="mdi:format-list-numbered", + data_type="cart_data", + value_fn=lambda cart: cart.get("total_count", 0), + ), + PicnicSensorEntityDescription( + key=SENSOR_CART_TOTAL_PRICE, + native_unit_of_measurement=CURRENCY_EURO, + icon="mdi:currency-eur", + entity_registry_enabled_default=True, + data_type="cart_data", + value_fn=lambda cart: cart.get("total_price", 0) / 100, + ), + PicnicSensorEntityDescription( + key=SENSOR_SELECTED_SLOT_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-start", + entity_registry_enabled_default=True, + data_type="slot_data", + value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_start"))), + ), + PicnicSensorEntityDescription( + key=SENSOR_SELECTED_SLOT_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-end", + entity_registry_enabled_default=True, + data_type="slot_data", + value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_end"))), + ), + PicnicSensorEntityDescription( + key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-alert-outline", + entity_registry_enabled_default=True, + data_type="slot_data", + value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("cut_off_time"))), + ), + PicnicSensorEntityDescription( + key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, + native_unit_of_measurement=CURRENCY_EURO, + icon="mdi:currency-eur", + entity_registry_enabled_default=True, + data_type="slot_data", + value_fn=lambda slot: ( + slot["minimum_order_value"] / 100 + if slot.get("minimum_order_value") + else None + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_SLOT_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-start", + data_type="last_order_data", + value_fn=lambda last_order: dt_util.parse_datetime( + str(last_order.get("slot", {}).get("window_start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_SLOT_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-end", + data_type="last_order_data", + value_fn=lambda last_order: dt_util.parse_datetime( + str(last_order.get("slot", {}).get("window_end")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_STATUS, + icon="mdi:list-status", + data_type="last_order_data", + value_fn=lambda last_order: last_order.get("status"), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_MAX_ORDER_TIME, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-alert-outline", + entity_registry_enabled_default=True, + data_type="last_order_data", + value_fn=lambda last_order: dt_util.parse_datetime( + str(last_order.get("slot", {}).get("cut_off_time")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_DELIVERY_TIME, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:timeline-clock", + entity_registry_enabled_default=True, + data_type="last_order_data", + value_fn=lambda last_order: dt_util.parse_datetime( + str(last_order.get("delivery_time", {}).get("start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_LAST_ORDER_TOTAL_PRICE, + native_unit_of_measurement=CURRENCY_EURO, + icon="mdi:cash-marker", + data_type="last_order_data", + value_fn=lambda last_order: last_order.get("total_price", 0) / 100, + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_ETA_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-start", + entity_registry_enabled_default=True, + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("eta", {}).get("start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_ETA_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:clock-end", + entity_registry_enabled_default=True, + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("eta", {}).get("end")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_SLOT_START, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-start", + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("slot", {}).get("window_start")) + ), + ), + PicnicSensorEntityDescription( + key=SENSOR_NEXT_DELIVERY_SLOT_END, + device_class=SensorDeviceClass.TIMESTAMP, + icon="mdi:calendar-end", + data_type="next_delivery_data", + value_fn=lambda next_delivery: dt_util.parse_datetime( + str(next_delivery.get("slot", {}).get("window_end")) + ), + ), ) diff --git a/tests/components/picnic/test_sensor.py b/tests/components/picnic/test_sensor.py index 8d680327bc8..0d003547ef2 100644 --- a/tests/components/picnic/test_sensor.py +++ b/tests/components/picnic/test_sensor.py @@ -9,7 +9,8 @@ import requests from homeassistant import config_entries from homeassistant.components.picnic import const -from homeassistant.components.picnic.const import CONF_COUNTRY_CODE, SENSOR_TYPES +from homeassistant.components.picnic.const import CONF_COUNTRY_CODE +from homeassistant.components.picnic.sensor import SENSOR_TYPES from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( CONF_ACCESS_TOKEN,