From 987c2d1612a69422fb0cf6e94cf8ed5b69b6fbd0 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:12:55 +0100 Subject: [PATCH] Type check KNX integration expose (#48055) --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/expose.py | 64 +++++++++++++++--------- homeassistant/components/knx/schema.py | 18 ++++++- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 348eac8f40e..c252572e28e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -195,7 +195,7 @@ SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( ) SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( - ExposeSchema.SCHEMA.extend( + ExposeSchema.EXPOSE_SENSOR_SCHEMA.extend( { vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 8bdd3d1d1d1..3d28c394140 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,6 +1,8 @@ """Exposures to KNX bus.""" from __future__ import annotations +from typing import Callable + from xknx import XKNX from xknx.devices import DateTime, ExposeSensor @@ -11,9 +13,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback from homeassistant.helpers.event import async_track_state_change_event -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ( + ConfigType, + EventType, + HomeAssistantType, + StateType, +) from .const import KNX_ADDRESS from .schema import ExposeSchema @@ -21,19 +28,22 @@ from .schema import ExposeSchema @callback def create_knx_exposure( - hass: HomeAssistant, xknx: XKNX, config: ConfigType + hass: HomeAssistantType, xknx: XKNX, config: ConfigType ) -> KNXExposeSensor | KNXExposeTime: """Create exposures from config.""" address = config[KNX_ADDRESS] + expose_type = config[ExposeSchema.CONF_KNX_EXPOSE_TYPE] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) - entity_id = config.get(CONF_ENTITY_ID) - expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) exposure: KNXExposeSensor | KNXExposeTime - if expose_type.lower() in ["time", "date", "datetime"]: + if ( + isinstance(expose_type, str) + and expose_type.lower() in ExposeSchema.EXPOSE_TIME_TYPES + ): exposure = KNXExposeTime(xknx, expose_type, address) else: + entity_id = config[CONF_ENTITY_ID] exposure = KNXExposeSensor( hass, xknx, @@ -43,14 +53,22 @@ def create_knx_exposure( default, address, ) - exposure.async_register() return exposure class KNXExposeSensor: """Object to Expose Home Assistant entity to KNX bus.""" - def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address): + def __init__( + self, + hass: HomeAssistantType, + xknx: XKNX, + expose_type: int | str, + entity_id: str, + attribute: str | None, + default: StateType, + address: str, + ) -> None: """Initialize of Expose class.""" self.hass = hass self.xknx = xknx @@ -59,17 +77,17 @@ class KNXExposeSensor: self.expose_attribute = attribute self.expose_default = default self.address = address - self.device = None - self._remove_listener = None + self._remove_listener: Callable[[], None] | None = None + self.device: ExposeSensor = self.async_register() @callback - def async_register(self): + def async_register(self) -> ExposeSensor: """Register listener.""" if self.expose_attribute is not None: _name = self.entity_id + "__" + self.expose_attribute else: _name = self.entity_id - self.device = ExposeSensor( + device = ExposeSensor( self.xknx, name=_name, group_address=self.address, @@ -78,6 +96,7 @@ class KNXExposeSensor: self._remove_listener = async_track_state_change_event( self.hass, [self.entity_id], self._async_entity_changed ) + return device @callback def shutdown(self) -> None: @@ -85,10 +104,9 @@ class KNXExposeSensor: if self._remove_listener is not None: self._remove_listener() self._remove_listener = None - if self.device is not None: - self.device.shutdown() + self.device.shutdown() - async def _async_entity_changed(self, event): + async def _async_entity_changed(self, event: EventType) -> None: """Handle entity change.""" new_state = event.data.get("new_state") if new_state is None: @@ -110,8 +128,9 @@ class KNXExposeSensor: return await self._async_set_knx_value(new_attribute) - async def _async_set_knx_value(self, value): + async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" + assert self.device is not None if value is None: if self.expose_default is None: return @@ -129,17 +148,17 @@ class KNXExposeSensor: class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" - def __init__(self, xknx: XKNX, expose_type: str, address: str): + def __init__(self, xknx: XKNX, expose_type: str, address: str) -> None: """Initialize of Expose class.""" self.xknx = xknx self.expose_type = expose_type self.address = address - self.device = None + self.device: DateTime = self.async_register() @callback - def async_register(self): + def async_register(self) -> DateTime: """Register listener.""" - self.device = DateTime( + return DateTime( self.xknx, name=self.expose_type.capitalize(), broadcast_type=self.expose_type.upper(), @@ -148,7 +167,6 @@ class KNXExposeTime: ) @callback - def shutdown(self): + def shutdown(self) -> None: """Prepare for deletion.""" - if self.device is not None: - self.device.shutdown() + self.device.shutdown() diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 376e86cf90c..0e290311f28 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -254,16 +254,30 @@ class ExposeSchema: CONF_KNX_EXPOSE_TYPE = CONF_TYPE CONF_KNX_EXPOSE_ATTRIBUTE = "attribute" CONF_KNX_EXPOSE_DEFAULT = "default" + EXPOSE_TIME_TYPES = [ + "time", + "date", + "datetime", + ] - SCHEMA = vol.Schema( + EXPOSE_TIME_SCHEMA = vol.Schema( + { + vol.Required(CONF_KNX_EXPOSE_TYPE): vol.All( + cv.string, str.lower, vol.In(EXPOSE_TIME_TYPES) + ), + vol.Required(KNX_ADDRESS): ga_validator, + } + ) + EXPOSE_SENSOR_SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): sensor_type_validator, vol.Required(KNX_ADDRESS): ga_validator, - vol.Optional(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, } ) + SCHEMA = vol.Any(EXPOSE_TIME_SCHEMA, EXPOSE_SENSOR_SCHEMA) class FanSchema: