diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index ef8808288f3..2a885ed90ec 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -60,6 +60,7 @@ from .entry_data import RuntimeEntryData DOMAIN = "esphome" CONF_NOISE_PSK = "noise_psk" _LOGGER = logging.getLogger(__name__) +_R = TypeVar("_R") _DomainDataSelfT = TypeVar("_DomainDataSelfT", bound="DomainData") STORAGE_VERSION = 1 @@ -599,20 +600,18 @@ async def platform_async_setup_entry( ) -_PropT = TypeVar("_PropT", bound=Callable[..., Any]) - - -def esphome_state_property(func: _PropT) -> _PropT: +def esphome_state_property( + func: Callable[[_EntityT], _R] +) -> Callable[[_EntityT], _R | None]: """Wrap a state property of an esphome entity. This checks if the state object in the entity is set, and prevents writing NAN values to the Home Assistant state machine. """ - @property # type: ignore[misc] @functools.wraps(func) - def _wrapper(self): # type: ignore[no-untyped-def] - # pylint: disable=protected-access + def _wrapper(self: _EntityT) -> _R | None: + # pylint: disable-next=protected-access if not self._has_state: return None val = func(self) @@ -622,7 +621,7 @@ def esphome_state_property(func: _PropT) -> _PropT: return None return val - return cast(_PropT, _wrapper) + return _wrapper _EnumT = TypeVar("_EnumT", bound=APIIntEnum) diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index 3c0a80e780f..1552bd3775b 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -133,10 +133,6 @@ _PRESETS: EsphomeEnumMapper[ClimatePreset, str] = EsphomeEnumMapper( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEntity): """A climate implementation for ESPHome.""" @@ -219,11 +215,13 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti features |= ClimateEntityFeature.SWING_MODE return features + @property # type: ignore[misc] @esphome_state_property def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" return _CLIMATE_MODES.from_esphome(self._state.mode) + @property # type: ignore[misc] @esphome_state_property def hvac_action(self) -> str | None: """Return current action.""" @@ -232,6 +230,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti return None return _CLIMATE_ACTIONS.from_esphome(self._state.action) + @property # type: ignore[misc] @esphome_state_property def fan_mode(self) -> str | None: """Return current fan setting.""" @@ -239,6 +238,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti self._state.fan_mode ) + @property # type: ignore[misc] @esphome_state_property def preset_mode(self) -> str | None: """Return current preset mode.""" @@ -246,26 +246,31 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti self._state.preset_compat(self._api_version) ) + @property # type: ignore[misc] @esphome_state_property def swing_mode(self) -> str | None: """Return current swing mode.""" return _SWING_MODES.from_esphome(self._state.swing_mode) + @property # type: ignore[misc] @esphome_state_property def current_temperature(self) -> float | None: """Return the current temperature.""" return self._state.current_temperature + @property # type: ignore[misc] @esphome_state_property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._state.target_temperature + @property # type: ignore[misc] @esphome_state_property def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._state.target_temperature_low + @property # type: ignore[misc] @esphome_state_property def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 97ae22dcccc..bf179ff25a9 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -34,10 +34,6 @@ async def async_setup_entry( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity): """A cover implementation for ESPHome.""" @@ -69,22 +65,26 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity): """Return true if we do optimistic updates.""" return self._static_info.assumed_state + @property # type: ignore[misc] @esphome_state_property def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" # Check closed state with api version due to a protocol change return self._state.is_closed(self._api_version) + @property # type: ignore[misc] @esphome_state_property def is_opening(self) -> bool: """Return if the cover is opening or not.""" return self._state.current_operation == CoverOperation.IS_OPENING + @property # type: ignore[misc] @esphome_state_property def is_closing(self) -> bool: """Return if the cover is closing or not.""" return self._state.current_operation == CoverOperation.IS_CLOSING + @property # type: ignore[misc] @esphome_state_property def current_cover_position(self) -> int | None: """Return current position of cover. 0 is closed, 100 is open.""" @@ -92,6 +92,7 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity): return None return round(self._state.position * 100.0) + @property # type: ignore[misc] @esphome_state_property def current_cover_tilt_position(self) -> int | None: """Return current position of cover tilt. 0 is closed, 100 is open.""" diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 41d7e418673..207b9d3f9f8 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -55,10 +55,6 @@ _FAN_DIRECTIONS: EsphomeEnumMapper[FanDirection, str] = EsphomeEnumMapper( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): """A fan implementation for ESPHome.""" @@ -116,11 +112,13 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): key=self._static_info.key, direction=_FAN_DIRECTIONS.from_hass(direction) ) + @property # type: ignore[misc] @esphome_state_property def is_on(self) -> bool | None: """Return true if the entity is on.""" return self._state.state + @property # type: ignore[misc] @esphome_state_property def percentage(self) -> int | None: """Return the current speed percentage.""" @@ -143,6 +141,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): return len(ORDERED_NAMED_FAN_SPEEDS) return self._static_info.supported_speed_levels + @property # type: ignore[misc] @esphome_state_property def oscillating(self) -> bool | None: """Return the oscillation state.""" @@ -150,6 +149,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): return None return self._state.oscillating + @property # type: ignore[misc] @esphome_state_property def current_direction(self) -> str | None: """Return the current fan direction.""" diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index 3eb45d63cac..2f536e82b47 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -122,10 +122,6 @@ def _filter_color_modes( return [mode for mode in supported if mode & features] -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): """A light implementation for ESPHome.""" @@ -134,6 +130,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): """Return whether the client supports the new color mode system natively.""" return self._api_version >= APIVersion(1, 6) + @property # type: ignore[misc] @esphome_state_property def is_on(self) -> bool | None: """Return true if the light is on.""" @@ -263,11 +260,13 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): data["transition_length"] = kwargs[ATTR_TRANSITION] await self._client.light_command(**data) + @property # type: ignore[misc] @esphome_state_property def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" return round(self._state.brightness * 255) + @property # type: ignore[misc] @esphome_state_property def color_mode(self) -> str | None: """Return the color mode of the light.""" @@ -278,6 +277,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): return _color_mode_to_ha(self._state.color_mode) + @property # type: ignore[misc] @esphome_state_property def rgb_color(self) -> tuple[int, int, int] | None: """Return the rgb color value [int, int, int].""" @@ -294,6 +294,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): round(self._state.blue * self._state.color_brightness * 255), ) + @property # type: ignore[misc] @esphome_state_property def rgbw_color(self) -> tuple[int, int, int, int] | None: """Return the rgbw color value [int, int, int, int].""" @@ -301,6 +302,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): rgb = cast("tuple[int, int, int]", self.rgb_color) return (*rgb, white) + @property # type: ignore[misc] @esphome_state_property def rgbww_color(self) -> tuple[int, int, int, int, int] | None: """Return the rgbww color value [int, int, int, int, int].""" @@ -328,11 +330,13 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): round(self._state.warm_white * 255), ) + @property # type: ignore[misc] @esphome_state_property def color_temp(self) -> float | None: # type: ignore[override] """Return the CT color value in mireds.""" return self._state.color_temperature + @property # type: ignore[misc] @esphome_state_property def effect(self) -> str | None: """Return the current effect.""" diff --git a/homeassistant/components/esphome/lock.py b/homeassistant/components/esphome/lock.py index a85411b5744..62c7c6de0dd 100644 --- a/homeassistant/components/esphome/lock.py +++ b/homeassistant/components/esphome/lock.py @@ -29,10 +29,6 @@ async def async_setup_entry( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity): """A lock implementation for ESPHome.""" @@ -53,21 +49,25 @@ class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity): return self._static_info.code_format return None + @property # type: ignore[misc] @esphome_state_property def is_locked(self) -> bool | None: """Return true if the lock is locked.""" return self._state.state == LockState.LOCKED + @property # type: ignore[misc] @esphome_state_property def is_locking(self) -> bool | None: """Return true if the lock is locking.""" return self._state.state == LockState.LOCKING + @property # type: ignore[misc] @esphome_state_property def is_unlocking(self) -> bool | None: """Return true if the lock is unlocking.""" return self._state.state == LockState.UNLOCKING + @property # type: ignore[misc] @esphome_state_property def is_jammed(self) -> bool | None: """Return true if the lock is jammed (incomplete locking).""" diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py index d7ce73976e7..17635157754 100644 --- a/homeassistant/components/esphome/media_player.py +++ b/homeassistant/components/esphome/media_player.py @@ -59,10 +59,6 @@ _STATES: EsphomeEnumMapper[MediaPlayerState, str] = EsphomeEnumMapper( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeMediaPlayer( EsphomeEntity[MediaPlayerInfo, MediaPlayerEntityState], MediaPlayerEntity ): @@ -70,16 +66,19 @@ class EsphomeMediaPlayer( _attr_device_class = MediaPlayerDeviceClass.SPEAKER + @property # type: ignore[misc] @esphome_state_property def state(self) -> str | None: """Return current state.""" return _STATES.from_esphome(self._state.state) + @property # type: ignore[misc] @esphome_state_property def is_volume_muted(self) -> bool: """Return true if volume is muted.""" return self._state.muted + @property # type: ignore[misc] @esphome_state_property def volume_level(self) -> float | None: """Volume level of the media player (0..1).""" diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index bbca463a908..ed721f2db5e 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -44,10 +44,6 @@ NUMBER_MODES: EsphomeEnumMapper[EsphomeNumberMode, NumberMode] = EsphomeEnumMapp ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): """A number implementation for esphome.""" @@ -78,6 +74,7 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): return NUMBER_MODES.from_esphome(self._static_info.mode) return NumberMode.AUTO + @property # type: ignore[misc] @esphome_state_property def native_value(self) -> float | None: """Return the state of the entity.""" diff --git a/homeassistant/components/esphome/select.py b/homeassistant/components/esphome/select.py index f3bfcb982ea..190fca52889 100644 --- a/homeassistant/components/esphome/select.py +++ b/homeassistant/components/esphome/select.py @@ -28,10 +28,6 @@ async def async_setup_entry( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity): """A select implementation for esphome.""" @@ -40,6 +36,7 @@ class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity): """Return a set of selectable options.""" return self._static_info.options + @property # type: ignore[misc] @esphome_state_property def current_option(self) -> str | None: """Return the state of the entity.""" diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 897ab86b18a..f7f137f4592 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -56,10 +56,6 @@ async def async_setup_entry( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - _STATE_CLASSES: EsphomeEnumMapper[ EsphomeSensorStateClass, SensorStateClass | None ] = EsphomeEnumMapper( @@ -80,6 +76,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): """Return if this sensor should force a state update.""" return self._static_info.force_update + @property # type: ignore[misc] @esphome_state_property def native_value(self) -> datetime | str | None: """Return the state of the entity.""" @@ -124,6 +121,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity): """A text sensor implementation for ESPHome.""" + @property # type: ignore[misc] @esphome_state_property def native_value(self) -> str | None: """Return the state of the entity.""" diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index fbb22bb7397..2970edf7af0 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -28,10 +28,6 @@ async def async_setup_entry( ) -# https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property -# pylint: disable=invalid-overridden-method - - class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity): """A switch implementation for ESPHome.""" @@ -40,6 +36,7 @@ class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity): """Return true if we do optimistic updates.""" return self._static_info.assumed_state + @property # type: ignore[misc] @esphome_state_property def is_on(self) -> bool | None: """Return true if the switch is on."""