Improve esphome state property decorator typing (#77152)
This commit is contained in:
parent
563c956539
commit
79bdc02829
11 changed files with 45 additions and 48 deletions
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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)."""
|
||||
|
|
|
@ -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)."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
Loading…
Add table
Reference in a new issue