From b7ba7893703c72763794e19f1d529b3c1d94c6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Mon, 23 Sep 2024 13:33:19 +0200 Subject: [PATCH] Code quality improvements at Home Connect (#126323) Added types to all arguments and return values to all functions Defined class members and its types outside the constructor Improved logic at binary sensor --- .../components/home_connect/__init__.py | 2 +- homeassistant/components/home_connect/api.py | 66 +++++++++++-------- .../components/home_connect/binary_sensor.py | 60 +++++++++-------- .../components/home_connect/entity.py | 6 +- .../components/home_connect/light.py | 4 +- .../components/home_connect/sensor.py | 18 ++++- .../components/home_connect/switch.py | 19 +++--- 7 files changed, 100 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index ebfd6f91c76..5f07b8075ce 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -244,7 +244,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @Throttle(SCAN_INTERVAL) -async def update_all_devices(hass, entry): +async def update_all_devices(hass: HomeAssistant, entry: ConfigEntry) -> None: """Update all the devices.""" data = hass.data[DOMAIN] hc_api = data[entry.entry_id] diff --git a/homeassistant/components/home_connect/api.py b/homeassistant/components/home_connect/api.py index 33b1a462e43..f03093b46b9 100644 --- a/homeassistant/components/home_connect/api.py +++ b/homeassistant/components/home_connect/api.py @@ -1,14 +1,15 @@ """API for Home Connect bound to HASS OAuth.""" +from abc import abstractmethod from asyncio import run_coroutine_threadsafe import logging from typing import Any import homeconnect -from homeconnect.api import HomeConnectError +from homeconnect.api import HomeConnectAppliance, HomeConnectError -from homeassistant import config_entries, core from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -17,6 +18,7 @@ from homeassistant.const import ( PERCENTAGE, UnitOfTime, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.dispatcher import dispatcher_send @@ -44,8 +46,8 @@ class ConfigEntryAuth(homeconnect.HomeConnectAPI): def __init__( self, - hass: core.HomeAssistant, - config_entry: config_entries.ConfigEntry, + hass: HomeAssistant, + config_entry: ConfigEntry, implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation, ) -> None: """Initialize Home Connect Auth.""" @@ -65,11 +67,12 @@ class ConfigEntryAuth(homeconnect.HomeConnectAPI): return self.session.token - def get_devices(self): + def get_devices(self) -> list[dict[str, Any]]: """Get a dictionary of devices.""" appl = self.get_appliances() devices = [] for app in appl: + device: HomeConnectDevice if app.type == "Dryer": device = Dryer(self.hass, app) elif app.type == "Washer": @@ -110,13 +113,15 @@ class HomeConnectDevice: # for some devices, this is instead BSH_POWER_STANDBY # see https://developer.home-connect.com/docs/settings/power_state power_off_state = BSH_POWER_OFF + hass: HomeAssistant + appliance: HomeConnectAppliance - def __init__(self, hass, appliance): + def __init__(self, hass: HomeAssistant, appliance: HomeConnectAppliance) -> None: """Initialize the device class.""" self.hass = hass self.appliance = appliance - def initialize(self): + def initialize(self) -> None: """Fetch the info needed to initialize the device.""" try: self.appliance.get_status() @@ -137,17 +142,22 @@ class HomeConnectDevice: } self.appliance.listen_events(callback=self.event_callback) - def event_callback(self, appliance): + def event_callback(self, appliance: HomeConnectAppliance) -> None: """Handle event.""" _LOGGER.debug("Update triggered on %s", appliance.name) _LOGGER.debug(self.appliance.status) dispatcher_send(self.hass, SIGNAL_UPDATE_ENTITIES, appliance.haId) + @abstractmethod + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: + """Get a dictionary with info about the associated entities.""" + raise NotImplementedError + class DeviceWithPrograms(HomeConnectDevice): """Device with programs.""" - def get_programs_available(self): + def get_programs_available(self) -> list: """Get the available programs.""" try: programs_available = self.appliance.get_programs_available() @@ -156,7 +166,7 @@ class DeviceWithPrograms(HomeConnectDevice): programs_available = [] return programs_available - def get_program_switches(self): + def get_program_switches(self) -> list[dict[str, Any]]: """Get a dictionary with info about program switches. There will be one switch for each program. @@ -164,7 +174,7 @@ class DeviceWithPrograms(HomeConnectDevice): programs = self.get_programs_available() return [{ATTR_DEVICE: self, "program_name": p} for p in programs] - def get_program_sensors(self): + def get_program_sensors(self) -> list[dict[str, Any]]: """Get a dictionary with info about program sensors. There will be one of the four types of sensors for each @@ -192,7 +202,7 @@ class DeviceWithPrograms(HomeConnectDevice): class DeviceWithOpState(HomeConnectDevice): """Device that has an operation state sensor.""" - def get_opstate_sensor(self): + def get_opstate_sensor(self) -> list[dict[str, Any]]: """Get a list with info about operation state sensors.""" return [ @@ -211,7 +221,7 @@ class DeviceWithOpState(HomeConnectDevice): class DeviceWithDoor(HomeConnectDevice): """Device that has a door sensor.""" - def get_door_entity(self): + def get_door_entity(self) -> dict[str, Any]: """Get a dictionary with info about the door binary sensor.""" return { ATTR_DEVICE: self, @@ -224,7 +234,7 @@ class DeviceWithDoor(HomeConnectDevice): class DeviceWithLight(HomeConnectDevice): """Device that has lighting.""" - def get_light_entity(self): + def get_light_entity(self) -> dict[str, Any]: """Get a dictionary with info about the lighting.""" return {ATTR_DEVICE: self, ATTR_DESC: "Light", ATTR_AMBIENT: None} @@ -232,7 +242,7 @@ class DeviceWithLight(HomeConnectDevice): class DeviceWithAmbientLight(HomeConnectDevice): """Device that has ambient lighting.""" - def get_ambientlight_entity(self): + def get_ambientlight_entity(self) -> dict[str, Any]: """Get a dictionary with info about the ambient lighting.""" return {ATTR_DEVICE: self, ATTR_DESC: "AmbientLight", ATTR_AMBIENT: True} @@ -240,7 +250,7 @@ class DeviceWithAmbientLight(HomeConnectDevice): class DeviceWithRemoteControl(HomeConnectDevice): """Device that has Remote Control binary sensor.""" - def get_remote_control(self): + def get_remote_control(self) -> dict[str, Any]: """Get a dictionary with info about the remote control sensor.""" return { ATTR_DEVICE: self, @@ -252,7 +262,7 @@ class DeviceWithRemoteControl(HomeConnectDevice): class DeviceWithRemoteStart(HomeConnectDevice): """Device that has a Remote Start binary sensor.""" - def get_remote_start(self): + def get_remote_start(self) -> dict[str, Any]: """Get a dictionary with info about the remote start sensor.""" return { ATTR_DEVICE: self, @@ -270,7 +280,7 @@ class Dryer( ): """Dryer class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() remote_control = self.get_remote_control() @@ -295,7 +305,7 @@ class Dishwasher( ): """Dishwasher class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() remote_control = self.get_remote_control() @@ -321,7 +331,7 @@ class Oven( power_off_state = BSH_POWER_STANDBY - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() remote_control = self.get_remote_control() @@ -345,7 +355,7 @@ class Washer( ): """Washer class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() remote_control = self.get_remote_control() @@ -369,7 +379,7 @@ class WasherDryer( ): """WasherDryer class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() remote_control = self.get_remote_control() @@ -412,7 +422,7 @@ class Hood( ): """Hood class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" remote_control = self.get_remote_control() remote_start = self.get_remote_start() @@ -432,7 +442,7 @@ class Hood( class FridgeFreezer(DeviceWithDoor): """Fridge/Freezer class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() return {"binary_sensor": [door_entity]} @@ -441,7 +451,7 @@ class FridgeFreezer(DeviceWithDoor): class Refrigerator(DeviceWithDoor): """Refrigerator class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() return {"binary_sensor": [door_entity]} @@ -450,7 +460,7 @@ class Refrigerator(DeviceWithDoor): class Freezer(DeviceWithDoor): """Freezer class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() return {"binary_sensor": [door_entity]} @@ -459,7 +469,7 @@ class Freezer(DeviceWithDoor): class Hob(DeviceWithOpState, DeviceWithPrograms, DeviceWithRemoteControl): """Hob class.""" - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" remote_control = self.get_remote_control() op_state_sensor = self.get_opstate_sensor() @@ -477,7 +487,7 @@ class CookProcessor(DeviceWithOpState): power_off_state = BSH_POWER_STANDBY - def get_entity_info(self): + def get_entity_info(self) -> dict[str, list[dict[str, Any]]]: """Get a dictionary with infos about the associated entities.""" op_state_sensor = self.get_opstate_sensor() return {"sensor": op_state_sensor} diff --git a/homeassistant/components/home_connect/binary_sensor.py b/homeassistant/components/home_connect/binary_sensor.py index 758759c135b..c6c43a3119c 100644 --- a/homeassistant/components/home_connect/binary_sensor.py +++ b/homeassistant/components/home_connect/binary_sensor.py @@ -72,8 +72,8 @@ async def async_setup_entry( ) -> None: """Set up the Home Connect binary sensor.""" - def get_entities(): - entities = [] + def get_entities() -> list[BinarySensorEntity]: + entities: list[BinarySensorEntity] = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("binary_sensor", []) @@ -95,55 +95,59 @@ async def async_setup_entry( class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity): """Binary sensor for Home Connect.""" - def __init__(self, device, desc, sensor_type, device_class=None): + def __init__( + self, + device: HomeConnectDevice, + desc: str, + sensor_type: str, + device_class: BinarySensorDeviceClass | None = None, + ) -> None: """Initialize the entity.""" super().__init__(device, desc) - self._state = None - self._device_class = device_class + self._attr_device_class = device_class self._type = sensor_type + self._false_value_list = None + self._true_value_list = None if self._type == "door": self._update_key = BSH_DOOR_STATE - self._false_value_list = (BSH_DOOR_STATE_CLOSED, BSH_DOOR_STATE_LOCKED) + self._false_value_list = [BSH_DOOR_STATE_CLOSED, BSH_DOOR_STATE_LOCKED] self._true_value_list = [BSH_DOOR_STATE_OPEN] elif self._type == "remote_control": self._update_key = BSH_REMOTE_CONTROL_ACTIVATION_STATE - self._false_value_list = [False] - self._true_value_list = [True] elif self._type == "remote_start": self._update_key = BSH_REMOTE_START_ALLOWANCE_STATE - self._false_value_list = [False] - self._true_value_list = [True] - - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return bool(self._state) @property def available(self) -> bool: """Return true if the binary sensor is available.""" - return self._state is not None + return self._attr_is_on is not None async def async_update(self) -> None: """Update the binary sensor's status.""" state = self.device.appliance.status.get(self._update_key, {}) if not state: - self._state = None - elif state.get(ATTR_VALUE) in self._false_value_list: - self._state = False - elif state.get(ATTR_VALUE) in self._true_value_list: - self._state = True + self._attr_is_on = None + return + + value = state.get(ATTR_VALUE) + if self._false_value_list and self._true_value_list: + if value in self._false_value_list: + self._attr_is_on = False + elif value in self._true_value_list: + self._attr_is_on = True + else: + _LOGGER.warning( + "Unexpected value for HomeConnect %s state: %s", self._type, state + ) + self._attr_is_on = None + elif isinstance(value, bool): + self._attr_is_on = value else: _LOGGER.warning( "Unexpected value for HomeConnect %s state: %s", self._type, state ) - self._state = None - _LOGGER.debug("Updated, new state: %s", self._state) - - @property - def device_class(self): - """Return the device class.""" - return self._device_class + self._attr_is_on = None + _LOGGER.debug("Updated, new state: %s", self._attr_is_on) class HomeConnectFridgeDoorBinarySensor(HomeConnectEntity, BinarySensorEntity): diff --git a/homeassistant/components/home_connect/entity.py b/homeassistant/components/home_connect/entity.py index d60f8a96e09..4ed14cd99af 100644 --- a/homeassistant/components/home_connect/entity.py +++ b/homeassistant/components/home_connect/entity.py @@ -30,7 +30,7 @@ class HomeConnectEntity(Entity): name=device.appliance.name, ) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register callbacks.""" self.async_on_remove( async_dispatcher_connect( @@ -39,13 +39,13 @@ class HomeConnectEntity(Entity): ) @callback - def _update_callback(self, ha_id): + def _update_callback(self, ha_id: str) -> None: """Update data.""" if ha_id == self.device.appliance.haId: self.async_entity_update() @callback - def async_entity_update(self): + def async_entity_update(self) -> None: """Update the entity.""" _LOGGER.debug("Entity update triggered on %s", self) self.async_schedule_update_ha_state(True) diff --git a/homeassistant/components/home_connect/light.py b/homeassistant/components/home_connect/light.py index a1556d5caab..b7696493baa 100644 --- a/homeassistant/components/home_connect/light.py +++ b/homeassistant/components/home_connect/light.py @@ -70,9 +70,9 @@ async def async_setup_entry( ) -> None: """Set up the Home Connect light.""" - def get_entities(): + def get_entities() -> list[LightEntity]: """Get a list of entities.""" - entities = [] + entities: list[LightEntity] = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("light", []) diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index c91864c2680..d1635a6bdfa 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -97,9 +97,9 @@ async def async_setup_entry( ) -> None: """Set up the Home Connect sensor.""" - def get_entities(): + def get_entities() -> list[SensorEntity]: """Get a list of entities.""" - entities = [] + entities: list[SensorEntity] = [] hc_api: ConfigEntryAuth = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("sensor", []) @@ -122,7 +122,19 @@ async def async_setup_entry( class HomeConnectSensor(HomeConnectEntity, SensorEntity): """Sensor class for Home Connect.""" - def __init__(self, device, desc, key, unit, icon, device_class, sign=1): + _key: str + _sign: int + + def __init__( + self, + device: HomeConnectDevice, + desc: str, + key: str, + unit: str, + icon: str, + device_class: SensorDeviceClass, + sign: int = 1, + ) -> None: """Initialize the entity.""" super().__init__(device, desc) self._key = key diff --git a/homeassistant/components/home_connect/switch.py b/homeassistant/components/home_connect/switch.py index 80e8e4b2d39..63eabc2e31e 100644 --- a/homeassistant/components/home_connect/switch.py +++ b/homeassistant/components/home_connect/switch.py @@ -61,15 +61,15 @@ async def async_setup_entry( ) -> None: """Set up the Home Connect switch.""" - def get_entities(): + def get_entities() -> list[SwitchEntity]: """Get a list of entities.""" - entities = [] + entities: list[SwitchEntity] = [] hc_api: ConfigEntryAuth = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("switch", []) - entity_list = [HomeConnectProgramSwitch(**d) for d in entity_dicts] - entity_list += [HomeConnectPowerSwitch(device_dict[CONF_DEVICE])] - entity_list += [HomeConnectChildLockSwitch(device_dict[CONF_DEVICE])] + entities.extend(HomeConnectProgramSwitch(**d) for d in entity_dicts) + entities.append(HomeConnectPowerSwitch(device_dict[CONF_DEVICE])) + entities.append(HomeConnectChildLockSwitch(device_dict[CONF_DEVICE])) # Auto-discover entities hc_device: HomeConnectDevice = device_dict[CONF_DEVICE] entities.extend( @@ -77,7 +77,6 @@ async def async_setup_entry( for description in SWITCHES if description.on_key in hc_device.appliance.status ) - entities.extend(entity_list) return entities @@ -88,7 +87,6 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity): """Generic switch class for Home Connect Binary Settings.""" entity_description: HomeConnectSwitchEntityDescription - _attr_available: bool = False def __init__( self, @@ -97,6 +95,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity): ) -> None: """Initialize the entity.""" self.entity_description = entity_description + self._attr_available = False super().__init__(device=device, desc=entity_description.key) async def async_turn_on(self, **kwargs: Any) -> None: @@ -148,7 +147,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity): class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity): """Switch class for Home Connect.""" - def __init__(self, device, program_name): + def __init__(self, device: HomeConnectDevice, program_name: str) -> None: """Initialize the entity.""" desc = " ".join(["Program", program_name.split(".")[-1]]) if device.appliance.type == "WasherDryer": @@ -191,7 +190,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity): class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity): """Power switch class for Home Connect.""" - def __init__(self, device): + def __init__(self, device: HomeConnectDevice) -> None: """Initialize the entity.""" super().__init__(device, "Power") @@ -258,7 +257,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity): class HomeConnectChildLockSwitch(HomeConnectEntity, SwitchEntity): """Child lock switch class for Home Connect.""" - def __init__(self, device) -> None: + def __init__(self, device: HomeConnectDevice) -> None: """Initialize the entity.""" super().__init__(device, "ChildLock")