Skip to content

Commit

Permalink
fix: gracefully retries on failed actions (#81)
Browse files Browse the repository at this point in the history
Resolves #79

---------

Co-authored-by: Michał Mrozek <[email protected]>
  • Loading branch information
emerose and Michsior14 authored Jan 24, 2025
1 parent 39a4156 commit 09d6c53
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 6 deletions.
37 changes: 33 additions & 4 deletions custom_components/venta/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

from __future__ import annotations

from typing import List, TypeVar
import asyncio
import logging
from functools import wraps
from typing import Any, Callable, List, TypeVar

from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.const import UnitOfTemperature

_LOGGER = logging.getLogger(__name__)


def skip_zeros(
Expand Down Expand Up @@ -62,3 +65,29 @@ def venta_temperature_unit(value: int | None) -> str | None:
if value is None:
return None
return UnitOfTemperature.CELSIUS if value == 0 else UnitOfTemperature.FAHRENHEIT


def retry_on_timeout(retries: int = 5, timeout: int = 10, delay: int = 0.5) -> Callable:
"""Retry a function on timeout and with a delay."""

def decorator(fun: Callable) -> Callable:
@wraps(fun)
async def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
for attempt in range(retries):
try:
async with asyncio.timeout(timeout):
return await fun(*args, **kwargs)
except asyncio.TimeoutError:
_LOGGER.warning(
"Timeout calling %s, retrying... (attempt %d/%d)",
fun.__name__,
attempt + 1,
retries,
)
await asyncio.sleep(delay)
_LOGGER.error("Retries exhausted calling %s, giving up...", fun.__name__)
return None

return wrapper

return decorator
3 changes: 2 additions & 1 deletion custom_components/venta/venta.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ async def detect_api(self, api_version: int | None = None) -> None:
if data is not None and data.get("Header") is not None:
return
await asyncio.sleep(0.5)
except (asyncio.TimeoutError, ClientError):
except (asyncio.TimeoutError, ClientError) as err:
_LOGGER.debug("Error while detecting api: %s", err)
pass
raise VentaApiVersionError()

Expand Down
3 changes: 3 additions & 0 deletions custom_components/venta/venta_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from aiohttp import ClientSession

from .json import extract_json
from .utils import retry_on_timeout

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -61,6 +62,7 @@ async def send_action(
"""Send action to the Venta device using HTTP protocol."""
return await self._send_request(method, url, json)

@retry_on_timeout()
async def _send_request(
self, method: str, url: str, json_action: dict[str, Any] | None = None
) -> dict[str, Any] | None:
Expand Down Expand Up @@ -151,6 +153,7 @@ def _build_message(
)
return f"{method} /{url}\nContent-Length: {len(body)}\n{body}"

@retry_on_timeout()
async def _send_request(self, message: str) -> dict[str, Any] | None:
"""Request data from the Venta device using TCP protocol."""
writer = None
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "ANN", "PL"]
ignore = ["ANN101", "ANN102"]
ignore = []

0 comments on commit 09d6c53

Please sign in to comment.