Improve rate limit handling in Habitica integration (#121763)

* Adjustments to requests and update interval due to rate limiting

* Use debounced refresh for to-do lists

* Use debounced refresh in switch and buttons

* Request refresh only if a to-do was changed

* Update task order provisionally in the coordinator
This commit is contained in:
Mr. Bubbles 2024-08-16 11:41:04 +02:00 committed by GitHub
parent 66a8733333
commit 4b62dcfd19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 20 deletions

View File

@ -113,7 +113,7 @@ class HabiticaButton(HabiticaBase, ButtonEntity):
translation_key="service_call_exception",
) from e
else:
await self.coordinator.async_refresh()
await self.coordinator.async_request_refresh()
@property
def available(self) -> bool:

View File

@ -15,6 +15,7 @@ from habitipy.aio import HabitipyAsync
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
@ -41,7 +42,13 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
hass,
_LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=30),
update_interval=timedelta(seconds=60),
request_refresh_debouncer=Debouncer(
hass,
_LOGGER,
cooldown=5,
immediate=False,
),
)
self.api = habitipy
@ -51,6 +58,9 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
tasks_response = await self.api.tasks.user.get()
tasks_response.extend(await self.api.tasks.user.get(type="completedTodos"))
except ClientResponseError as error:
if error.status == HTTPStatus.TOO_MANY_REQUESTS:
_LOGGER.debug("Currently rate limited, skipping update")
return self.data
raise UpdateFailed(f"Error communicating with API: {error}") from error
return HabiticaData(user=user_response, tasks=tasks_response)
@ -73,4 +83,4 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
translation_key="service_call_exception",
) from e
else:
await self.async_refresh()
await self.async_request_refresh()

View File

@ -93,7 +93,7 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
translation_key=f"delete_{self.entity_description.key}_failed",
) from e
await self.coordinator.async_refresh()
await self.coordinator.async_request_refresh()
async def async_move_todo_item(
self, uid: str, previous_uid: str | None = None
@ -121,9 +121,22 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
translation_key=f"move_{self.entity_description.key}_item_failed",
translation_placeholders={"pos": str(pos)},
) from e
else:
# move tasks in the coordinator until we have fresh data
tasks = self.coordinator.data.tasks
new_pos = (
tasks.index(next(task for task in tasks if task["id"] == previous_uid))
+ 1
if previous_uid
else 0
)
old_pos = tasks.index(next(task for task in tasks if task["id"] == uid))
tasks.insert(new_pos, tasks.pop(old_pos))
await self.coordinator.async_request_refresh()
async def async_update_todo_item(self, item: TodoItem) -> None:
"""Update a Habitica todo."""
refresh_required = False
current_item = next(
(task for task in (self.todo_items or []) if task.uid == item.uid),
None,
@ -132,7 +145,6 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
if TYPE_CHECKING:
assert item.uid
assert current_item
assert item.due
if (
self.entity_description.key is HabiticaTodoList.TODOS
@ -142,18 +154,24 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
else:
date = None
try:
await self.coordinator.api.tasks[item.uid].put(
text=item.summary,
notes=item.description or "",
date=date,
)
except ClientResponseError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key=f"update_{self.entity_description.key}_item_failed",
translation_placeholders={"name": item.summary or ""},
) from e
if (
item.summary != current_item.summary
or item.description != current_item.description
or item.due != current_item.due
):
try:
await self.coordinator.api.tasks[item.uid].put(
text=item.summary,
notes=item.description or "",
date=date,
)
refresh_required = True
except ClientResponseError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key=f"update_{self.entity_description.key}_item_failed",
translation_placeholders={"name": item.summary or ""},
) from e
try:
# Score up or down if item status changed
@ -164,6 +182,7 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
score_result = (
await self.coordinator.api.tasks[item.uid].score["up"].post()
)
refresh_required = True
elif (
current_item.status is TodoItemStatus.COMPLETED
and item.status == TodoItemStatus.NEEDS_ACTION
@ -171,6 +190,7 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
score_result = (
await self.coordinator.api.tasks[item.uid].score["down"].post()
)
refresh_required = True
else:
score_result = None
@ -189,8 +209,8 @@ class BaseHabiticaListEntity(HabiticaBase, TodoListEntity):
persistent_notification.async_create(
self.hass, message=msg, title="Habitica"
)
await self.coordinator.async_refresh()
if refresh_required:
await self.coordinator.async_request_refresh()
class HabiticaTodosListEntity(BaseHabiticaListEntity):
@ -254,7 +274,7 @@ class HabiticaTodosListEntity(BaseHabiticaListEntity):
translation_placeholders={"name": item.summary or ""},
) from e
await self.coordinator.async_refresh()
await self.coordinator.async_request_refresh()
class HabiticaDailiesListEntity(BaseHabiticaListEntity):