Improve esphome state property decorator typing (#77152)

This commit is contained in:
Marc Mueller 2022-08-26 10:52:05 +02:00 committed by GitHub
parent 563c956539
commit 79bdc02829
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 45 additions and 48 deletions

View file

@ -60,6 +60,7 @@ from .entry_data import RuntimeEntryData
DOMAIN = "esphome" DOMAIN = "esphome"
CONF_NOISE_PSK = "noise_psk" CONF_NOISE_PSK = "noise_psk"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_R = TypeVar("_R")
_DomainDataSelfT = TypeVar("_DomainDataSelfT", bound="DomainData") _DomainDataSelfT = TypeVar("_DomainDataSelfT", bound="DomainData")
STORAGE_VERSION = 1 STORAGE_VERSION = 1
@ -599,20 +600,18 @@ async def platform_async_setup_entry(
) )
_PropT = TypeVar("_PropT", bound=Callable[..., Any]) def esphome_state_property(
func: Callable[[_EntityT], _R]
) -> Callable[[_EntityT], _R | None]:
def esphome_state_property(func: _PropT) -> _PropT:
"""Wrap a state property of an esphome entity. """Wrap a state property of an esphome entity.
This checks if the state object in the entity is set, and This checks if the state object in the entity is set, and
prevents writing NAN values to the Home Assistant state machine. prevents writing NAN values to the Home Assistant state machine.
""" """
@property # type: ignore[misc]
@functools.wraps(func) @functools.wraps(func)
def _wrapper(self): # type: ignore[no-untyped-def] def _wrapper(self: _EntityT) -> _R | None:
# pylint: disable=protected-access # pylint: disable-next=protected-access
if not self._has_state: if not self._has_state:
return None return None
val = func(self) val = func(self)
@ -622,7 +621,7 @@ def esphome_state_property(func: _PropT) -> _PropT:
return None return None
return val return val
return cast(_PropT, _wrapper) return _wrapper
_EnumT = TypeVar("_EnumT", bound=APIIntEnum) _EnumT = TypeVar("_EnumT", bound=APIIntEnum)

View file

@ -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): class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEntity):
"""A climate implementation for ESPHome.""" """A climate implementation for ESPHome."""
@ -219,11 +215,13 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
features |= ClimateEntityFeature.SWING_MODE features |= ClimateEntityFeature.SWING_MODE
return features return features
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def hvac_mode(self) -> str | None: def hvac_mode(self) -> str | None:
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
return _CLIMATE_MODES.from_esphome(self._state.mode) return _CLIMATE_MODES.from_esphome(self._state.mode)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def hvac_action(self) -> str | None: def hvac_action(self) -> str | None:
"""Return current action.""" """Return current action."""
@ -232,6 +230,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
return None return None
return _CLIMATE_ACTIONS.from_esphome(self._state.action) return _CLIMATE_ACTIONS.from_esphome(self._state.action)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def fan_mode(self) -> str | None: def fan_mode(self) -> str | None:
"""Return current fan setting.""" """Return current fan setting."""
@ -239,6 +238,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
self._state.fan_mode self._state.fan_mode
) )
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return current preset mode.""" """Return current preset mode."""
@ -246,26 +246,31 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
self._state.preset_compat(self._api_version) self._state.preset_compat(self._api_version)
) )
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def swing_mode(self) -> str | None: def swing_mode(self) -> str | None:
"""Return current swing mode.""" """Return current swing mode."""
return _SWING_MODES.from_esphome(self._state.swing_mode) return _SWING_MODES.from_esphome(self._state.swing_mode)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
return self._state.current_temperature return self._state.current_temperature
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def target_temperature(self) -> float | None: def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._state.target_temperature return self._state.target_temperature
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def target_temperature_low(self) -> float | None: def target_temperature_low(self) -> float | None:
"""Return the lowbound target temperature we try to reach.""" """Return the lowbound target temperature we try to reach."""
return self._state.target_temperature_low return self._state.target_temperature_low
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def target_temperature_high(self) -> float | None: def target_temperature_high(self) -> float | None:
"""Return the highbound target temperature we try to reach.""" """Return the highbound target temperature we try to reach."""

View file

@ -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): class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity):
"""A cover implementation for ESPHome.""" """A cover implementation for ESPHome."""
@ -69,22 +65,26 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity):
"""Return true if we do optimistic updates.""" """Return true if we do optimistic updates."""
return self._static_info.assumed_state return self._static_info.assumed_state
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_closed(self) -> bool | None: def is_closed(self) -> bool | None:
"""Return if the cover is closed or not.""" """Return if the cover is closed or not."""
# Check closed state with api version due to a protocol change # Check closed state with api version due to a protocol change
return self._state.is_closed(self._api_version) return self._state.is_closed(self._api_version)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_opening(self) -> bool: def is_opening(self) -> bool:
"""Return if the cover is opening or not.""" """Return if the cover is opening or not."""
return self._state.current_operation == CoverOperation.IS_OPENING return self._state.current_operation == CoverOperation.IS_OPENING
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_closing(self) -> bool: def is_closing(self) -> bool:
"""Return if the cover is closing or not.""" """Return if the cover is closing or not."""
return self._state.current_operation == CoverOperation.IS_CLOSING return self._state.current_operation == CoverOperation.IS_CLOSING
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def current_cover_position(self) -> int | None: def current_cover_position(self) -> int | None:
"""Return current position of cover. 0 is closed, 100 is open.""" """Return current position of cover. 0 is closed, 100 is open."""
@ -92,6 +92,7 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity):
return None return None
return round(self._state.position * 100.0) return round(self._state.position * 100.0)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def current_cover_tilt_position(self) -> int | None: def current_cover_tilt_position(self) -> int | None:
"""Return current position of cover tilt. 0 is closed, 100 is open.""" """Return current position of cover tilt. 0 is closed, 100 is open."""

View file

@ -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): class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
"""A fan implementation for ESPHome.""" """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) key=self._static_info.key, direction=_FAN_DIRECTIONS.from_hass(direction)
) )
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
"""Return true if the entity is on.""" """Return true if the entity is on."""
return self._state.state return self._state.state
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def percentage(self) -> int | None: def percentage(self) -> int | None:
"""Return the current speed percentage.""" """Return the current speed percentage."""
@ -143,6 +141,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
return len(ORDERED_NAMED_FAN_SPEEDS) return len(ORDERED_NAMED_FAN_SPEEDS)
return self._static_info.supported_speed_levels return self._static_info.supported_speed_levels
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def oscillating(self) -> bool | None: def oscillating(self) -> bool | None:
"""Return the oscillation state.""" """Return the oscillation state."""
@ -150,6 +149,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity):
return None return None
return self._state.oscillating return self._state.oscillating
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def current_direction(self) -> str | None: def current_direction(self) -> str | None:
"""Return the current fan direction.""" """Return the current fan direction."""

View file

@ -122,10 +122,6 @@ def _filter_color_modes(
return [mode for mode in supported if mode & features] 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): class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
"""A light implementation for ESPHome.""" """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 whether the client supports the new color mode system natively."""
return self._api_version >= APIVersion(1, 6) return self._api_version >= APIVersion(1, 6)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
"""Return true if the light is on.""" """Return true if the light is on."""
@ -263,11 +260,13 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
data["transition_length"] = kwargs[ATTR_TRANSITION] data["transition_length"] = kwargs[ATTR_TRANSITION]
await self._client.light_command(**data) await self._client.light_command(**data)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def brightness(self) -> int | None: def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return round(self._state.brightness * 255) return round(self._state.brightness * 255)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def color_mode(self) -> str | None: def color_mode(self) -> str | None:
"""Return the color mode of the light.""" """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) return _color_mode_to_ha(self._state.color_mode)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def rgb_color(self) -> tuple[int, int, int] | None: def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the rgb color value [int, int, int].""" """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), round(self._state.blue * self._state.color_brightness * 255),
) )
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def rgbw_color(self) -> tuple[int, int, int, int] | None: def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the rgbw color value [int, int, int, int].""" """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) rgb = cast("tuple[int, int, int]", self.rgb_color)
return (*rgb, white) return (*rgb, white)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def rgbww_color(self) -> tuple[int, int, int, int, int] | None: def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
"""Return the rgbww color value [int, int, int, int, int].""" """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), round(self._state.warm_white * 255),
) )
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def color_temp(self) -> float | None: # type: ignore[override] def color_temp(self) -> float | None: # type: ignore[override]
"""Return the CT color value in mireds.""" """Return the CT color value in mireds."""
return self._state.color_temperature return self._state.color_temperature
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def effect(self) -> str | None: def effect(self) -> str | None:
"""Return the current effect.""" """Return the current effect."""

View file

@ -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): class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity):
"""A lock implementation for ESPHome.""" """A lock implementation for ESPHome."""
@ -53,21 +49,25 @@ class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity):
return self._static_info.code_format return self._static_info.code_format
return None return None
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_locked(self) -> bool | None: def is_locked(self) -> bool | None:
"""Return true if the lock is locked.""" """Return true if the lock is locked."""
return self._state.state == LockState.LOCKED return self._state.state == LockState.LOCKED
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_locking(self) -> bool | None: def is_locking(self) -> bool | None:
"""Return true if the lock is locking.""" """Return true if the lock is locking."""
return self._state.state == LockState.LOCKING return self._state.state == LockState.LOCKING
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_unlocking(self) -> bool | None: def is_unlocking(self) -> bool | None:
"""Return true if the lock is unlocking.""" """Return true if the lock is unlocking."""
return self._state.state == LockState.UNLOCKING return self._state.state == LockState.UNLOCKING
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_jammed(self) -> bool | None: def is_jammed(self) -> bool | None:
"""Return true if the lock is jammed (incomplete locking).""" """Return true if the lock is jammed (incomplete locking)."""

View file

@ -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( class EsphomeMediaPlayer(
EsphomeEntity[MediaPlayerInfo, MediaPlayerEntityState], MediaPlayerEntity EsphomeEntity[MediaPlayerInfo, MediaPlayerEntityState], MediaPlayerEntity
): ):
@ -70,16 +66,19 @@ class EsphomeMediaPlayer(
_attr_device_class = MediaPlayerDeviceClass.SPEAKER _attr_device_class = MediaPlayerDeviceClass.SPEAKER
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def state(self) -> str | None: def state(self) -> str | None:
"""Return current state.""" """Return current state."""
return _STATES.from_esphome(self._state.state) return _STATES.from_esphome(self._state.state)
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_volume_muted(self) -> bool: def is_volume_muted(self) -> bool:
"""Return true if volume is muted.""" """Return true if volume is muted."""
return self._state.muted return self._state.muted
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def volume_level(self) -> float | None: def volume_level(self) -> float | None:
"""Volume level of the media player (0..1).""" """Volume level of the media player (0..1)."""

View file

@ -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): class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity):
"""A number implementation for esphome.""" """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 NUMBER_MODES.from_esphome(self._static_info.mode)
return NumberMode.AUTO return NumberMode.AUTO
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return the state of the entity.""" """Return the state of the entity."""

View file

@ -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): class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity):
"""A select implementation for esphome.""" """A select implementation for esphome."""
@ -40,6 +36,7 @@ class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity):
"""Return a set of selectable options.""" """Return a set of selectable options."""
return self._static_info.options return self._static_info.options
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def current_option(self) -> str | None: def current_option(self) -> str | None:
"""Return the state of the entity.""" """Return the state of the entity."""

View file

@ -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[ _STATE_CLASSES: EsphomeEnumMapper[
EsphomeSensorStateClass, SensorStateClass | None EsphomeSensorStateClass, SensorStateClass | None
] = EsphomeEnumMapper( ] = EsphomeEnumMapper(
@ -80,6 +76,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
"""Return if this sensor should force a state update.""" """Return if this sensor should force a state update."""
return self._static_info.force_update return self._static_info.force_update
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def native_value(self) -> datetime | str | None: def native_value(self) -> datetime | str | None:
"""Return the state of the entity.""" """Return the state of the entity."""
@ -124,6 +121,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity):
class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity): class EsphomeTextSensor(EsphomeEntity[TextSensorInfo, TextSensorState], SensorEntity):
"""A text sensor implementation for ESPHome.""" """A text sensor implementation for ESPHome."""
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def native_value(self) -> str | None: def native_value(self) -> str | None:
"""Return the state of the entity.""" """Return the state of the entity."""

View file

@ -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): class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity):
"""A switch implementation for ESPHome.""" """A switch implementation for ESPHome."""
@ -40,6 +36,7 @@ class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity):
"""Return true if we do optimistic updates.""" """Return true if we do optimistic updates."""
return self._static_info.assumed_state return self._static_info.assumed_state
@property # type: ignore[misc]
@esphome_state_property @esphome_state_property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
"""Return true if the switch is on.""" """Return true if the switch is on."""