mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Add entity base class in Habitica integration (#121320)
* Sensor refactoring * Change todo entities to use common base entity * Requested changes * Update button platform to use base class * Update swtich platform to use base entity class
This commit is contained in:
parent
6350c5479b
commit
00aafc0cf7
@ -6,21 +6,19 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HabiticaConfigEntry
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
from .const import DOMAIN
|
||||
from .coordinator import HabiticaData, HabiticaDataUpdateCoordinator
|
||||
from .entity import HabiticaBase
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
@ -90,34 +88,11 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class HabiticaButton(CoordinatorEntity[HabiticaDataUpdateCoordinator], ButtonEntity):
|
||||
class HabiticaButton(HabiticaBase, ButtonEntity):
|
||||
"""Representation of a Habitica button."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: HabiticaButtonEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
entity_description: HabiticaButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Habitica button."""
|
||||
super().__init__(coordinator)
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator.config_entry.unique_id
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}_{entity_description.key}"
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=coordinator.config_entry.data[CONF_NAME],
|
||||
configuration_url=coordinator.config_entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, coordinator.config_entry.unique_id)},
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
try:
|
||||
|
@ -21,3 +21,5 @@ MANUFACTURER = "HabitRPG, Inc."
|
||||
NAME = "Habitica"
|
||||
|
||||
ADDITIONAL_USER_FIELDS: set[str] = {"lastCron"}
|
||||
|
||||
UNIT_TASKS = "tasks"
|
||||
|
42
homeassistant/components/habitica/entity.py
Normal file
42
homeassistant/components/habitica/entity.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Base entity for Habitica."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
|
||||
|
||||
class HabiticaBase(CoordinatorEntity[HabiticaDataUpdateCoordinator]):
|
||||
"""Base Habitica entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
entity_description: EntityDescription,
|
||||
context: Any = None,
|
||||
) -> None:
|
||||
"""Initialize a Habitica entity."""
|
||||
super().__init__(coordinator, context)
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator.config_entry.unique_id
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}_{entity_description.key}"
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=coordinator.config_entry.data[CONF_NAME],
|
||||
configuration_url=coordinator.config_entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, coordinator.config_entry.unique_id)},
|
||||
)
|
@ -64,6 +64,18 @@
|
||||
"wizard": "mdi:wizard-hat",
|
||||
"rogue": "mdi:ninja"
|
||||
}
|
||||
},
|
||||
"todos": {
|
||||
"default": "mdi:checkbox-outline"
|
||||
},
|
||||
"dailys": {
|
||||
"default": "mdi:calendar-month"
|
||||
},
|
||||
"habits": {
|
||||
"default": "mdi:contrast-box"
|
||||
},
|
||||
"rewards": {
|
||||
"default": "mdi:treasure-chest"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import namedtuple
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
@ -14,11 +14,8 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
@ -26,11 +23,11 @@ from homeassistant.helpers.issue_registry import (
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HabiticaConfigEntry
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
from .const import DOMAIN, UNIT_TASKS
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
from .entity import HabiticaBase
|
||||
from .util import entity_used_in
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -40,7 +37,15 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class HabitipySensorEntityDescription(SensorEntityDescription):
|
||||
"""Habitipy Sensor Description."""
|
||||
|
||||
value_path: list[str]
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
ctx: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class HabitipyTaskSensorEntityDescription(SensorEntityDescription):
|
||||
"""Habitipy Task Sensor Description."""
|
||||
|
||||
value_fn: Callable[[list[dict[str, Any]]], list[dict[str, Any]]]
|
||||
|
||||
|
||||
class HabitipySensorEntity(StrEnum):
|
||||
@ -56,87 +61,88 @@ class HabitipySensorEntity(StrEnum):
|
||||
LEVEL = "level"
|
||||
GOLD = "gold"
|
||||
CLASS = "class"
|
||||
HABITS = "habits"
|
||||
DAILIES = "dailys"
|
||||
TODOS = "todos"
|
||||
REWARDS = "rewards"
|
||||
|
||||
|
||||
SENSOR_DESCRIPTIONS: dict[str, HabitipySensorEntityDescription] = {
|
||||
HabitipySensorEntity.DISPLAY_NAME: HabitipySensorEntityDescription(
|
||||
SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.DISPLAY_NAME,
|
||||
translation_key=HabitipySensorEntity.DISPLAY_NAME,
|
||||
value_path=["profile", "name"],
|
||||
value_fn=lambda user: user.get("profile", {}).get("name"),
|
||||
ctx="profile",
|
||||
),
|
||||
HabitipySensorEntity.HEALTH: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.HEALTH,
|
||||
translation_key=HabitipySensorEntity.HEALTH,
|
||||
native_unit_of_measurement="HP",
|
||||
suggested_display_precision=0,
|
||||
value_path=["stats", "hp"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("hp"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.HEALTH_MAX: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.HEALTH_MAX,
|
||||
translation_key=HabitipySensorEntity.HEALTH_MAX,
|
||||
native_unit_of_measurement="HP",
|
||||
entity_registry_enabled_default=False,
|
||||
value_path=["stats", "maxHealth"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("maxHealth"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.MANA: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.MANA,
|
||||
translation_key=HabitipySensorEntity.MANA,
|
||||
native_unit_of_measurement="MP",
|
||||
suggested_display_precision=0,
|
||||
value_path=["stats", "mp"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("mp"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.MANA_MAX: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.MANA_MAX,
|
||||
translation_key=HabitipySensorEntity.MANA_MAX,
|
||||
native_unit_of_measurement="MP",
|
||||
value_path=["stats", "maxMP"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("maxMP"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.EXPERIENCE: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.EXPERIENCE,
|
||||
translation_key=HabitipySensorEntity.EXPERIENCE,
|
||||
native_unit_of_measurement="XP",
|
||||
value_path=["stats", "exp"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("exp"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.EXPERIENCE_MAX: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.EXPERIENCE_MAX,
|
||||
translation_key=HabitipySensorEntity.EXPERIENCE_MAX,
|
||||
native_unit_of_measurement="XP",
|
||||
value_path=["stats", "toNextLevel"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("toNextLevel"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.LEVEL: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.LEVEL,
|
||||
translation_key=HabitipySensorEntity.LEVEL,
|
||||
value_path=["stats", "lvl"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("lvl"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.GOLD: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.GOLD,
|
||||
translation_key=HabitipySensorEntity.GOLD,
|
||||
native_unit_of_measurement="GP",
|
||||
suggested_display_precision=2,
|
||||
value_path=["stats", "gp"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("gp"),
|
||||
ctx="stats",
|
||||
),
|
||||
HabitipySensorEntity.CLASS: HabitipySensorEntityDescription(
|
||||
HabitipySensorEntityDescription(
|
||||
key=HabitipySensorEntity.CLASS,
|
||||
translation_key=HabitipySensorEntity.CLASS,
|
||||
value_path=["stats", "class"],
|
||||
value_fn=lambda user: user.get("stats", {}).get("class"),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["warrior", "healer", "wizard", "rogue"],
|
||||
ctx="stats",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"])
|
||||
TASKS_TYPES = {
|
||||
"habits": SensorType(
|
||||
"Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habit"]
|
||||
),
|
||||
"dailys": SensorType(
|
||||
"Dailys", "mdi:clipboard-list-outline", "n_of_tasks", ["daily"]
|
||||
),
|
||||
"todos": SensorType("TODOs", "mdi:clipboard-list-outline", "n_of_tasks", ["todo"]),
|
||||
"rewards": SensorType(
|
||||
"Rewards", "mdi:clipboard-list-outline", "n_of_tasks", ["reward"]
|
||||
),
|
||||
}
|
||||
|
||||
TASKS_MAP_ID = "id"
|
||||
TASKS_MAP = {
|
||||
@ -166,6 +172,36 @@ TASKS_MAP = {
|
||||
}
|
||||
|
||||
|
||||
TASK_SENSOR_DESCRIPTION: tuple[HabitipyTaskSensorEntityDescription, ...] = (
|
||||
HabitipyTaskSensorEntityDescription(
|
||||
key=HabitipySensorEntity.HABITS,
|
||||
translation_key=HabitipySensorEntity.HABITS,
|
||||
native_unit_of_measurement=UNIT_TASKS,
|
||||
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "habit"],
|
||||
),
|
||||
HabitipyTaskSensorEntityDescription(
|
||||
key=HabitipySensorEntity.DAILIES,
|
||||
translation_key=HabitipySensorEntity.DAILIES,
|
||||
native_unit_of_measurement=UNIT_TASKS,
|
||||
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "daily"],
|
||||
),
|
||||
HabitipyTaskSensorEntityDescription(
|
||||
key=HabitipySensorEntity.TODOS,
|
||||
translation_key=HabitipySensorEntity.TODOS,
|
||||
native_unit_of_measurement=UNIT_TASKS,
|
||||
value_fn=lambda tasks: [
|
||||
r for r in tasks if r.get("type") == "todo" and not r.get("completed")
|
||||
],
|
||||
),
|
||||
HabitipyTaskSensorEntityDescription(
|
||||
key=HabitipySensorEntity.REWARDS,
|
||||
translation_key=HabitipySensorEntity.REWARDS,
|
||||
native_unit_of_measurement=UNIT_TASKS,
|
||||
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "reward"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HabiticaConfigEntry,
|
||||
@ -173,121 +209,70 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the habitica sensors."""
|
||||
|
||||
name = config_entry.data[CONF_NAME]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[SensorEntity] = [
|
||||
HabitipySensor(coordinator, description, config_entry)
|
||||
for description in SENSOR_DESCRIPTIONS.values()
|
||||
HabitipySensor(coordinator, description) for description in SENSOR_DESCRIPTIONS
|
||||
]
|
||||
entities.extend(
|
||||
HabitipyTaskSensor(name, task_type, coordinator, config_entry)
|
||||
for task_type in TASKS_TYPES
|
||||
HabitipyTaskSensor(coordinator, description)
|
||||
for description in TASK_SENSOR_DESCRIPTION
|
||||
)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class HabitipySensor(CoordinatorEntity[HabiticaDataUpdateCoordinator], SensorEntity):
|
||||
class HabitipySensor(HabiticaBase, SensorEntity):
|
||||
"""A generic Habitica sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: HabitipySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
entity_description: HabitipySensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize a generic Habitica sensor."""
|
||||
super().__init__(coordinator, context=entity_description.value_path[0])
|
||||
if TYPE_CHECKING:
|
||||
assert entry.unique_id
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{entry.unique_id}_{entity_description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=entry.data[CONF_NAME],
|
||||
configuration_url=entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, entry.unique_id)},
|
||||
super().__init__(
|
||||
coordinator,
|
||||
entity_description,
|
||||
context=entity_description.ctx,
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the device."""
|
||||
data = self.coordinator.data.user
|
||||
for element in self.entity_description.value_path:
|
||||
data = data[element]
|
||||
return cast(StateType, data)
|
||||
|
||||
return self.entity_description.value_fn(self.coordinator.data.user)
|
||||
|
||||
|
||||
class HabitipyTaskSensor(
|
||||
CoordinatorEntity[HabiticaDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
class HabitipyTaskSensor(HabiticaBase, SensorEntity):
|
||||
"""A Habitica task sensor."""
|
||||
|
||||
def __init__(self, name, task_name, coordinator, entry):
|
||||
"""Initialize a generic Habitica task."""
|
||||
super().__init__(coordinator)
|
||||
self._name = name
|
||||
self._task_name = task_name
|
||||
self._task_type = TASKS_TYPES[task_name]
|
||||
self._state = None
|
||||
self._attr_unique_id = f"{entry.unique_id}_{task_name}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=entry.data[CONF_NAME],
|
||||
configuration_url=entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, entry.unique_id)},
|
||||
)
|
||||
entity_description: HabitipyTaskSensorEntityDescription
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return self._task_type.icon
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the task."""
|
||||
return f"{DOMAIN}_{self._name}_{self._task_name}"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the device."""
|
||||
return len(
|
||||
[
|
||||
task
|
||||
for task in self.coordinator.data.tasks
|
||||
if task.get("type") in self._task_type.path
|
||||
and not task.get("completed")
|
||||
]
|
||||
)
|
||||
|
||||
return len(self.entity_description.value_fn(self.coordinator.data.tasks))
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
"""Return the state attributes of all user tasks."""
|
||||
attrs = {}
|
||||
|
||||
# Map tasks to TASKS_MAP
|
||||
for received_task in self.coordinator.data.tasks:
|
||||
if received_task.get("type") in self._task_type.path:
|
||||
task_id = received_task[TASKS_MAP_ID]
|
||||
task = {}
|
||||
for map_key, map_value in TASKS_MAP.items():
|
||||
if value := received_task.get(map_value):
|
||||
task[map_key] = value
|
||||
attrs[task_id] = task
|
||||
for received_task in self.entity_description.value_fn(
|
||||
self.coordinator.data.tasks
|
||||
):
|
||||
task_id = received_task[TASKS_MAP_ID]
|
||||
task = {}
|
||||
for map_key, map_value in TASKS_MAP.items():
|
||||
if value := received_task.get(map_value):
|
||||
task[map_key] = value
|
||||
attrs[task_id] = task
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return self._task_type.unit
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Raise issue when entity is registered and was not disabled."""
|
||||
if TYPE_CHECKING:
|
||||
@ -297,19 +282,20 @@ class HabitipyTaskSensor(
|
||||
):
|
||||
if (
|
||||
self.enabled
|
||||
and self._task_name in ("todos", "dailys")
|
||||
and self.entity_description.key
|
||||
in (HabitipySensorEntity.TODOS, HabitipySensorEntity.DAILIES)
|
||||
and entity_used_in(self.hass, entity_id)
|
||||
):
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"deprecated_task_entity_{self._task_name}",
|
||||
f"deprecated_task_entity_{self.entity_description.key}",
|
||||
breaks_in_ha_version="2025.2.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_task_entity",
|
||||
translation_placeholders={
|
||||
"task_name": self._task_name,
|
||||
"task_name": str(self.name),
|
||||
"entity": entity_id,
|
||||
},
|
||||
)
|
||||
@ -317,6 +303,6 @@ class HabitipyTaskSensor(
|
||||
async_delete_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"deprecated_task_entity_{self._task_name}",
|
||||
f"deprecated_task_entity_{self.entity_description.key}",
|
||||
)
|
||||
await super().async_added_to_hass()
|
||||
|
@ -70,6 +70,18 @@
|
||||
"wizard": "Mage",
|
||||
"rogue": "Rogue"
|
||||
}
|
||||
},
|
||||
"todos": {
|
||||
"name": "To-Do's"
|
||||
},
|
||||
"dailys": {
|
||||
"name": "Dailies"
|
||||
},
|
||||
"habits": {
|
||||
"name": "Habits"
|
||||
},
|
||||
"rewards": {
|
||||
"name": "Rewards"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
@ -5,22 +5,19 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HabiticaConfigEntry
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
from .coordinator import HabiticaData, HabiticaDataUpdateCoordinator
|
||||
from .entity import HabiticaBase
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
@ -64,34 +61,11 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class HabiticaSwitch(CoordinatorEntity[HabiticaDataUpdateCoordinator], SwitchEntity):
|
||||
class HabiticaSwitch(HabiticaBase, SwitchEntity):
|
||||
"""Representation of a Habitica Switch."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: HabiticaSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
entity_description: HabiticaSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Habitica switch."""
|
||||
super().__init__(coordinator)
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator.config_entry.unique_id
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}_{entity_description.key}"
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=coordinator.config_entry.data[CONF_NAME],
|
||||
configuration_url=coordinator.config_entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, coordinator.config_entry.unique_id)},
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the device."""
|
||||
|
@ -15,17 +15,16 @@ from homeassistant.components.todo import (
|
||||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import HabiticaConfigEntry
|
||||
from .const import ASSETS_URL, DOMAIN, MANUFACTURER, NAME
|
||||
from .const import ASSETS_URL, DOMAIN
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
from .entity import HabiticaBase
|
||||
from .util import next_due_date
|
||||
|
||||
|
||||
@ -63,35 +62,16 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class BaseHabiticaListEntity(
|
||||
CoordinatorEntity[HabiticaDataUpdateCoordinator], TodoListEntity
|
||||
):
|
||||
class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
|
||||
"""Representation of Habitica task lists."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
key: HabiticaTodoList,
|
||||
) -> None:
|
||||
"""Initialize HabiticaTodoListEntity."""
|
||||
entry = coordinator.config_entry
|
||||
if TYPE_CHECKING:
|
||||
assert entry.unique_id
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._attr_unique_id = f"{entry.unique_id}_{key}"
|
||||
self._attr_translation_key = key
|
||||
self.idx = key
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=entry.data[CONF_NAME],
|
||||
configuration_url=entry.data[CONF_URL],
|
||||
identifiers={(DOMAIN, entry.unique_id)},
|
||||
)
|
||||
super().__init__(coordinator, self.entity_description)
|
||||
|
||||
async def async_delete_todo_items(self, uids: list[str]) -> None:
|
||||
"""Delete Habitica tasks."""
|
||||
@ -101,7 +81,7 @@ class BaseHabiticaListEntity(
|
||||
except ClientResponseError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=f"delete_{self.idx}_failed",
|
||||
translation_key=f"delete_{self.entity_description.key}_failed",
|
||||
) from e
|
||||
|
||||
await self.coordinator.async_refresh()
|
||||
@ -129,7 +109,7 @@ class BaseHabiticaListEntity(
|
||||
except ClientResponseError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=f"move_{self.idx}_item_failed",
|
||||
translation_key=f"move_{self.entity_description.key}_item_failed",
|
||||
translation_placeholders={"pos": str(pos)},
|
||||
) from e
|
||||
|
||||
@ -145,7 +125,9 @@ class BaseHabiticaListEntity(
|
||||
assert current_item
|
||||
assert item.due
|
||||
|
||||
if self.idx is HabiticaTodoList.TODOS: # Only todos support a due date.
|
||||
if (
|
||||
self.entity_description.key is HabiticaTodoList.TODOS
|
||||
): # Only todos support a due date.
|
||||
date = item.due.isoformat()
|
||||
else:
|
||||
date = None
|
||||
@ -159,7 +141,7 @@ class BaseHabiticaListEntity(
|
||||
except ClientResponseError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=f"update_{self.idx}_item_failed",
|
||||
translation_key=f"update_{self.entity_description.key}_item_failed",
|
||||
translation_placeholders={"name": item.summary or ""},
|
||||
) from e
|
||||
|
||||
@ -185,7 +167,7 @@ class BaseHabiticaListEntity(
|
||||
except ClientResponseError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=f"score_{self.idx}_item_failed",
|
||||
translation_key=f"score_{self.entity_description.key}_item_failed",
|
||||
translation_placeholders={"name": item.summary or ""},
|
||||
) from e
|
||||
|
||||
@ -212,10 +194,10 @@ class HabiticaTodosListEntity(BaseHabiticaListEntity):
|
||||
| TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
|
||||
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
|
||||
)
|
||||
|
||||
def __init__(self, coordinator: HabiticaDataUpdateCoordinator) -> None:
|
||||
"""Initialize HabiticaTodosListEntity."""
|
||||
super().__init__(coordinator, HabiticaTodoList.TODOS)
|
||||
entity_description = EntityDescription(
|
||||
key=HabiticaTodoList.TODOS,
|
||||
translation_key=HabiticaTodoList.TODOS,
|
||||
)
|
||||
|
||||
@property
|
||||
def todo_items(self) -> list[TodoItem]:
|
||||
@ -258,7 +240,7 @@ class HabiticaTodosListEntity(BaseHabiticaListEntity):
|
||||
except ClientResponseError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=f"create_{self.idx}_item_failed",
|
||||
translation_key=f"create_{self.entity_description.key}_item_failed",
|
||||
translation_placeholders={"name": item.summary or ""},
|
||||
) from e
|
||||
|
||||
@ -274,10 +256,10 @@ class HabiticaDailiesListEntity(BaseHabiticaListEntity):
|
||||
| TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
|
||||
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
|
||||
)
|
||||
|
||||
def __init__(self, coordinator: HabiticaDataUpdateCoordinator) -> None:
|
||||
"""Initialize HabiticaDailiesListEntity."""
|
||||
super().__init__(coordinator, HabiticaTodoList.DAILIES)
|
||||
entity_description = EntityDescription(
|
||||
key=HabiticaTodoList.DAILIES,
|
||||
translation_key=HabiticaTodoList.DAILIES,
|
||||
)
|
||||
|
||||
@property
|
||||
def todo_items(self) -> list[TodoItem]:
|
||||
|
Loading…
x
Reference in New Issue
Block a user