From 711526fc6c91e87a4f69f1d9e3e0a77f3a9927d6 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Sun, 19 Oct 2025 22:13:20 +0200 Subject: [PATCH] Remove brackets from decorator in Husqvarna Automower (#154042) --- .../components/husqvarna_automower/button.py | 2 +- .../components/husqvarna_automower/entity.py | 56 +++++++++++++++---- .../husqvarna_automower/lawn_mower.py | 10 ++-- .../components/husqvarna_automower/select.py | 2 +- .../components/husqvarna_automower/switch.py | 4 +- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/button.py b/homeassistant/components/husqvarna_automower/button.py index b39f2138ab43..83526fb20ef7 100644 --- a/homeassistant/components/husqvarna_automower/button.py +++ b/homeassistant/components/husqvarna_automower/button.py @@ -112,7 +112,7 @@ class AutomowerButtonEntity(AutomowerControlEntity, ButtonEntity): self.mower_attributes ) - @handle_sending_exception() + @handle_sending_exception async def async_press(self) -> None: """Send a command to the mower.""" await self.entity_description.press_fn(self.coordinator.api, self.mower_id) diff --git a/homeassistant/components/husqvarna_automower/entity.py b/homeassistant/components/husqvarna_automower/entity.py index c1f20e8e7d37..cd8d00ac8f16 100644 --- a/homeassistant/components/husqvarna_automower/entity.py +++ b/homeassistant/components/husqvarna_automower/entity.py @@ -6,7 +6,7 @@ import asyncio from collections.abc import Callable, Coroutine import functools import logging -from typing import TYPE_CHECKING, Any, Concatenate +from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, overload from aioautomower.exceptions import ApiError from aioautomower.model import MowerActivities, MowerAttributes, MowerStates, WorkArea @@ -37,23 +37,42 @@ ERROR_STATES = [ ] -@callback -def _work_area_translation_key(work_area_id: int, key: str) -> str: - """Return the translation key.""" - if work_area_id == 0: - return f"my_lawn_{key}" - return f"work_area_{key}" +_Entity = TypeVar("_Entity", bound="AutomowerBaseEntity") +_P = ParamSpec("_P") -type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Coroutine[Any, Any, _R]] +@overload +def handle_sending_exception( + _func: Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, Any]], +) -> Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, None]]: ... -def handle_sending_exception[_Entity: AutomowerBaseEntity, **_P]( +@overload +def handle_sending_exception( + *, poll_after_sending: bool = False, -) -> Callable[[_FuncType[_Entity, _P, Any]], _FuncType[_Entity, _P, None]]: +) -> Callable[ + [Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, Any]]], + Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, None]], +]: ... + + +def handle_sending_exception( + _func: Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, Any]] | None = None, + *, + poll_after_sending: bool = False, +) -> ( + Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, None]] + | Callable[ + [Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, Any]]], + Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, None]], + ] +): """Handle exceptions while sending a command and optionally refresh coordinator.""" - def decorator(func: _FuncType[_Entity, _P, Any]) -> _FuncType[_Entity, _P, None]: + def decorator( + func: Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, Any]], + ) -> Callable[Concatenate[_Entity, _P], Coroutine[Any, Any, None]]: @functools.wraps(func) async def wrapper(self: _Entity, *args: _P.args, **kwargs: _P.kwargs) -> None: try: @@ -73,7 +92,20 @@ def handle_sending_exception[_Entity: AutomowerBaseEntity, **_P]( return wrapper - return decorator + if _func is None: + # call with brackets: @handle_sending_exception(...) + return decorator + + # call without brackets: @handle_sending_exception + return decorator(_func) + + +@callback +def _work_area_translation_key(work_area_id: int, key: str) -> str: + """Return the translation key.""" + if work_area_id == 0: + return f"my_lawn_{key}" + return f"work_area_{key}" class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]): diff --git a/homeassistant/components/husqvarna_automower/lawn_mower.py b/homeassistant/components/husqvarna_automower/lawn_mower.py index df312ae4ffde..978bf459aaa6 100644 --- a/homeassistant/components/husqvarna_automower/lawn_mower.py +++ b/homeassistant/components/husqvarna_automower/lawn_mower.py @@ -135,22 +135,22 @@ class AutomowerLawnMowerEntity(AutomowerBaseEntity, LawnMowerEntity): """Return the work areas of the mower.""" return self.mower_attributes.work_areas - @handle_sending_exception() + @handle_sending_exception async def async_start_mowing(self) -> None: """Resume schedule.""" await self.coordinator.api.commands.resume_schedule(self.mower_id) - @handle_sending_exception() + @handle_sending_exception async def async_pause(self) -> None: """Pauses the mower.""" await self.coordinator.api.commands.pause_mowing(self.mower_id) - @handle_sending_exception() + @handle_sending_exception async def async_dock(self) -> None: """Parks the mower until next schedule.""" await self.coordinator.api.commands.park_until_next_schedule(self.mower_id) - @handle_sending_exception() + @handle_sending_exception async def async_override_schedule( self, override_mode: str, duration: timedelta ) -> None: @@ -160,7 +160,7 @@ class AutomowerLawnMowerEntity(AutomowerBaseEntity, LawnMowerEntity): if override_mode == PARK: await self.coordinator.api.commands.park_for(self.mower_id, duration) - @handle_sending_exception() + @handle_sending_exception async def async_override_schedule_work_area( self, work_area_id: int, duration: timedelta ) -> None: diff --git a/homeassistant/components/husqvarna_automower/select.py b/homeassistant/components/husqvarna_automower/select.py index 1dde9e16295e..354ae7b7e9bf 100644 --- a/homeassistant/components/husqvarna_automower/select.py +++ b/homeassistant/components/husqvarna_automower/select.py @@ -67,7 +67,7 @@ class AutomowerSelectEntity(AutomowerControlEntity, SelectEntity): """Return the current option for the entity.""" return cast(HeadlightModes, self.mower_attributes.settings.headlight.mode) - @handle_sending_exception() + @handle_sending_exception async def async_select_option(self, option: str) -> None: """Change the selected option.""" await self.coordinator.api.commands.set_headlight_mode( diff --git a/homeassistant/components/husqvarna_automower/switch.py b/homeassistant/components/husqvarna_automower/switch.py index 1cfc79d5a710..4311ccb6a5a1 100644 --- a/homeassistant/components/husqvarna_automower/switch.py +++ b/homeassistant/components/husqvarna_automower/switch.py @@ -108,12 +108,12 @@ class AutomowerScheduleSwitchEntity(AutomowerControlEntity, SwitchEntity): """Return the state of the switch.""" return self.mower_attributes.mower.mode != MowerModes.HOME - @handle_sending_exception() + @handle_sending_exception async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" await self.coordinator.api.commands.park_until_further_notice(self.mower_id) - @handle_sending_exception() + @handle_sending_exception async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" await self.coordinator.api.commands.resume_schedule(self.mower_id)