Refactor Habitica button and switch functions to use habiticalib instance directly (#149602)

This commit is contained in:
Manu 2025-07-30 15:07:22 +02:00 committed by GitHub
parent d8016f7f41
commit 779f0afcc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 94 deletions

View File

@ -7,15 +7,7 @@ from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from typing import Any from typing import Any
from aiohttp import ClientError from habiticalib import Habitica, HabiticaClass, Skill, TaskType
from habiticalib import (
HabiticaClass,
HabiticaException,
NotAuthorizedError,
Skill,
TaskType,
TooManyRequestsError,
)
from homeassistant.components.button import ( from homeassistant.components.button import (
DOMAIN as BUTTON_DOMAIN, DOMAIN as BUTTON_DOMAIN,
@ -23,16 +15,11 @@ from homeassistant.components.button import (
ButtonEntityDescription, ButtonEntityDescription,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import ASSETS_URL, DOMAIN from .const import ASSETS_URL, DOMAIN
from .coordinator import ( from .coordinator import HabiticaConfigEntry, HabiticaData
HabiticaConfigEntry,
HabiticaData,
HabiticaDataUpdateCoordinator,
)
from .entity import HabiticaBase from .entity import HabiticaBase
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -42,7 +29,7 @@ PARALLEL_UPDATES = 1
class HabiticaButtonEntityDescription(ButtonEntityDescription): class HabiticaButtonEntityDescription(ButtonEntityDescription):
"""Describes Habitica button entity.""" """Describes Habitica button entity."""
press_fn: Callable[[HabiticaDataUpdateCoordinator], Any] press_fn: Callable[[Habitica], Any]
available_fn: Callable[[HabiticaData], bool] available_fn: Callable[[HabiticaData], bool]
class_needed: HabiticaClass | None = None class_needed: HabiticaClass | None = None
entity_picture: str | None = None entity_picture: str | None = None
@ -73,13 +60,13 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.RUN_CRON, key=HabiticaButtonEntity.RUN_CRON,
translation_key=HabiticaButtonEntity.RUN_CRON, translation_key=HabiticaButtonEntity.RUN_CRON,
press_fn=lambda coordinator: coordinator.habitica.run_cron(), press_fn=lambda habitica: habitica.run_cron(),
available_fn=lambda data: data.user.needsCron is True, available_fn=lambda data: data.user.needsCron is True,
), ),
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.BUY_HEALTH_POTION, key=HabiticaButtonEntity.BUY_HEALTH_POTION,
translation_key=HabiticaButtonEntity.BUY_HEALTH_POTION, translation_key=HabiticaButtonEntity.BUY_HEALTH_POTION,
press_fn=lambda coordinator: coordinator.habitica.buy_health_potion(), press_fn=lambda habitica: habitica.buy_health_potion(),
available_fn=( available_fn=(
lambda data: (data.user.stats.gp or 0) >= 25 lambda data: (data.user.stats.gp or 0) >= 25
and (data.user.stats.hp or 0) < 50 and (data.user.stats.hp or 0) < 50
@ -89,7 +76,7 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS, key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
translation_key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS, translation_key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
press_fn=lambda coordinator: coordinator.habitica.allocate_stat_points(), press_fn=lambda habitica: habitica.allocate_stat_points(),
available_fn=( available_fn=(
lambda data: data.user.preferences.automaticAllocation is True lambda data: data.user.preferences.automaticAllocation is True
and (data.user.stats.points or 0) > 0 and (data.user.stats.points or 0) > 0
@ -98,7 +85,7 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.REVIVE, key=HabiticaButtonEntity.REVIVE,
translation_key=HabiticaButtonEntity.REVIVE, translation_key=HabiticaButtonEntity.REVIVE,
press_fn=lambda coordinator: coordinator.habitica.revive(), press_fn=lambda habitica: habitica.revive(),
available_fn=lambda data: data.user.stats.hp == 0, available_fn=lambda data: data.user.stats.hp == 0,
), ),
) )
@ -108,9 +95,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.MPHEAL, key=HabiticaButtonEntity.MPHEAL,
translation_key=HabiticaButtonEntity.MPHEAL, translation_key=HabiticaButtonEntity.MPHEAL,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.ETHEREAL_SURGE),
lambda coordinator: coordinator.habitica.cast_skill(Skill.ETHEREAL_SURGE)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12 lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 30 and (data.user.stats.mp or 0) >= 30
@ -121,7 +106,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.EARTH, key=HabiticaButtonEntity.EARTH,
translation_key=HabiticaButtonEntity.EARTH, translation_key=HabiticaButtonEntity.EARTH,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.EARTHQUAKE), press_fn=lambda habitica: habitica.cast_skill(Skill.EARTHQUAKE),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13 lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 35 and (data.user.stats.mp or 0) >= 35
@ -132,9 +117,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.FROST, key=HabiticaButtonEntity.FROST,
translation_key=HabiticaButtonEntity.FROST, translation_key=HabiticaButtonEntity.FROST,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.CHILLING_FROST),
lambda coordinator: coordinator.habitica.cast_skill(Skill.CHILLING_FROST)
),
# chilling frost can only be cast once per day (streaks buff is false) # chilling frost can only be cast once per day (streaks buff is false)
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14 lambda data: (data.user.stats.lvl or 0) >= 14
@ -147,9 +130,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.DEFENSIVE_STANCE, key=HabiticaButtonEntity.DEFENSIVE_STANCE,
translation_key=HabiticaButtonEntity.DEFENSIVE_STANCE, translation_key=HabiticaButtonEntity.DEFENSIVE_STANCE,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.DEFENSIVE_STANCE),
lambda coordinator: coordinator.habitica.cast_skill(Skill.DEFENSIVE_STANCE)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12 lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 25 and (data.user.stats.mp or 0) >= 25
@ -160,9 +141,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.VALOROUS_PRESENCE, key=HabiticaButtonEntity.VALOROUS_PRESENCE,
translation_key=HabiticaButtonEntity.VALOROUS_PRESENCE, translation_key=HabiticaButtonEntity.VALOROUS_PRESENCE,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.VALOROUS_PRESENCE),
lambda coordinator: coordinator.habitica.cast_skill(Skill.VALOROUS_PRESENCE)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13 lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 20 and (data.user.stats.mp or 0) >= 20
@ -173,9 +152,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.INTIMIDATE, key=HabiticaButtonEntity.INTIMIDATE,
translation_key=HabiticaButtonEntity.INTIMIDATE, translation_key=HabiticaButtonEntity.INTIMIDATE,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.INTIMIDATING_GAZE),
lambda coordinator: coordinator.habitica.cast_skill(Skill.INTIMIDATING_GAZE)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14 lambda data: (data.user.stats.lvl or 0) >= 14
and (data.user.stats.mp or 0) >= 15 and (data.user.stats.mp or 0) >= 15
@ -186,11 +163,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.TOOLS_OF_TRADE, key=HabiticaButtonEntity.TOOLS_OF_TRADE,
translation_key=HabiticaButtonEntity.TOOLS_OF_TRADE, translation_key=HabiticaButtonEntity.TOOLS_OF_TRADE,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.TOOLS_OF_THE_TRADE),
lambda coordinator: coordinator.habitica.cast_skill(
Skill.TOOLS_OF_THE_TRADE
)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13 lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 25 and (data.user.stats.mp or 0) >= 25
@ -201,7 +174,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.STEALTH, key=HabiticaButtonEntity.STEALTH,
translation_key=HabiticaButtonEntity.STEALTH, translation_key=HabiticaButtonEntity.STEALTH,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.STEALTH), press_fn=lambda habitica: habitica.cast_skill(Skill.STEALTH),
# Stealth buffs stack and it can only be cast if the amount of # Stealth buffs stack and it can only be cast if the amount of
# buffs is smaller than the amount of unfinished dailies # buffs is smaller than the amount of unfinished dailies
available_fn=( available_fn=(
@ -224,9 +197,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.HEAL, key=HabiticaButtonEntity.HEAL,
translation_key=HabiticaButtonEntity.HEAL, translation_key=HabiticaButtonEntity.HEAL,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.HEALING_LIGHT),
lambda coordinator: coordinator.habitica.cast_skill(Skill.HEALING_LIGHT)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 11 lambda data: (data.user.stats.lvl or 0) >= 11
and (data.user.stats.mp or 0) >= 15 and (data.user.stats.mp or 0) >= 15
@ -238,11 +209,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.BRIGHTNESS, key=HabiticaButtonEntity.BRIGHTNESS,
translation_key=HabiticaButtonEntity.BRIGHTNESS, translation_key=HabiticaButtonEntity.BRIGHTNESS,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.SEARING_BRIGHTNESS),
lambda coordinator: coordinator.habitica.cast_skill(
Skill.SEARING_BRIGHTNESS
)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12 lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 15 and (data.user.stats.mp or 0) >= 15
@ -253,9 +220,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.PROTECT_AURA, key=HabiticaButtonEntity.PROTECT_AURA,
translation_key=HabiticaButtonEntity.PROTECT_AURA, translation_key=HabiticaButtonEntity.PROTECT_AURA,
press_fn=( press_fn=lambda habitica: habitica.cast_skill(Skill.PROTECTIVE_AURA),
lambda coordinator: coordinator.habitica.cast_skill(Skill.PROTECTIVE_AURA)
),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13 lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 30 and (data.user.stats.mp or 0) >= 30
@ -266,7 +231,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription( HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.HEAL_ALL, key=HabiticaButtonEntity.HEAL_ALL,
translation_key=HabiticaButtonEntity.HEAL_ALL, translation_key=HabiticaButtonEntity.HEAL_ALL,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.BLESSING), press_fn=lambda habitica: habitica.cast_skill(Skill.BLESSING),
available_fn=( available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14 lambda data: (data.user.stats.lvl or 0) >= 14
and (data.user.stats.mp or 0) >= 25 and (data.user.stats.mp or 0) >= 25
@ -332,33 +297,9 @@ class HabiticaButton(HabiticaBase, ButtonEntity):
async def async_press(self) -> None: async def async_press(self) -> None:
"""Handle the button press.""" """Handle the button press."""
try:
await self.entity_description.press_fn(self.coordinator) await self.coordinator.execute(self.entity_description.press_fn)
except TooManyRequestsError as e: await self.coordinator.async_request_refresh()
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="setup_rate_limit_exception",
translation_placeholders={"retry_after": str(e.retry_after)},
) from e
except NotAuthorizedError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_unallowed",
) from e
except HabiticaException as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
translation_placeholders={"reason": e.error.message},
) from e
except ClientError as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
translation_placeholders={"reason": str(e)},
) from e
else:
await self.coordinator.async_request_refresh()
@property @property
def available(self) -> bool: def available(self) -> bool:

View File

@ -28,6 +28,7 @@ from homeassistant.exceptions import (
ConfigEntryAuthFailed, ConfigEntryAuthFailed,
ConfigEntryNotReady, ConfigEntryNotReady,
HomeAssistantError, HomeAssistantError,
ServiceValidationError,
) )
from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -130,19 +131,22 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
else: else:
return HabiticaData(user=user, tasks=tasks + completed_todos) return HabiticaData(user=user, tasks=tasks + completed_todos)
async def execute( async def execute(self, func: Callable[[Habitica], Any]) -> None:
self, func: Callable[[HabiticaDataUpdateCoordinator], Any]
) -> None:
"""Execute an API call.""" """Execute an API call."""
try: try:
await func(self) await func(self.habitica)
except TooManyRequestsError as e: except TooManyRequestsError as e:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="setup_rate_limit_exception", translation_key="setup_rate_limit_exception",
translation_placeholders={"retry_after": str(e.retry_after)}, translation_placeholders={"retry_after": str(e.retry_after)},
) from e ) from e
except NotAuthorizedError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_unallowed",
) from e
except HabiticaException as e: except HabiticaException as e:
raise HomeAssistantError( raise HomeAssistantError(
translation_domain=DOMAIN, translation_domain=DOMAIN,

View File

@ -7,6 +7,8 @@ from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from typing import Any from typing import Any
from habiticalib import Habitica
from homeassistant.components.switch import ( from homeassistant.components.switch import (
SwitchDeviceClass, SwitchDeviceClass,
SwitchEntity, SwitchEntity,
@ -15,11 +17,7 @@ from homeassistant.components.switch import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ( from .coordinator import HabiticaConfigEntry, HabiticaData
HabiticaConfigEntry,
HabiticaData,
HabiticaDataUpdateCoordinator,
)
from .entity import HabiticaBase from .entity import HabiticaBase
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -29,8 +27,8 @@ PARALLEL_UPDATES = 1
class HabiticaSwitchEntityDescription(SwitchEntityDescription): class HabiticaSwitchEntityDescription(SwitchEntityDescription):
"""Describes Habitica switch entity.""" """Describes Habitica switch entity."""
turn_on_fn: Callable[[HabiticaDataUpdateCoordinator], Any] turn_on_fn: Callable[[Habitica], Any]
turn_off_fn: Callable[[HabiticaDataUpdateCoordinator], Any] turn_off_fn: Callable[[Habitica], Any]
is_on_fn: Callable[[HabiticaData], bool | None] is_on_fn: Callable[[HabiticaData], bool | None]
@ -45,8 +43,8 @@ SWTICH_DESCRIPTIONS: tuple[HabiticaSwitchEntityDescription, ...] = (
key=HabiticaSwitchEntity.SLEEP, key=HabiticaSwitchEntity.SLEEP,
translation_key=HabiticaSwitchEntity.SLEEP, translation_key=HabiticaSwitchEntity.SLEEP,
device_class=SwitchDeviceClass.SWITCH, device_class=SwitchDeviceClass.SWITCH,
turn_on_fn=lambda coordinator: coordinator.habitica.toggle_sleep(), turn_on_fn=lambda habitica: habitica.toggle_sleep(),
turn_off_fn=lambda coordinator: coordinator.habitica.toggle_sleep(), turn_off_fn=lambda habitica: habitica.toggle_sleep(),
is_on_fn=lambda data: data.user.preferences.sleep, is_on_fn=lambda data: data.user.preferences.sleep,
), ),
) )