From 38d095aa18ccb0901f817409df4a1a8fe0fa3d5b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 22 May 2021 18:13:50 +0200 Subject: [PATCH] Define entity attributes as entity class variables (#50925) * Define entity attributes as entity class variables * Example coronavirus integration * Example verisure * Cleanup/typing fixes * Fix Coronavirus * Revert "Fix Coronavirus" This reverts commit 060843860fe300f8448d0d2476de8963d5ddf5a2. * Revert "Cleanup/typing fixes" This reverts commit 659b79e75a97007f7181064e446c3e988c2d54bb. * Define entity attributes as entity class variables (attr alternative) * Example coronavirus * Example nut * Example verisure * Mark private * Cleanup after all reverting/cherrypicking/merging * Implement all entity properties * Update coronavirus example * Update nut example * Update verisure example * Lets not talk about this one... * Fix multiple class attribute --- .../components/coronavirus/sensor.py | 29 ++++------- homeassistant/components/nut/sensor.py | 31 ++--------- .../verisure/alarm_control_panel.py | 21 ++------ .../components/verisure/binary_sensor.py | 30 +++-------- homeassistant/components/verisure/camera.py | 13 ++--- homeassistant/components/verisure/lock.py | 14 ++--- homeassistant/components/verisure/sensor.py | 51 ++++--------------- homeassistant/components/verisure/switch.py | 14 ++--- homeassistant/helpers/entity.py | 50 ++++++++++++------ 9 files changed, 80 insertions(+), 173 deletions(-) diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 472b8bc8d1c..b467a5fee12 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -27,17 +27,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class CoronavirusSensor(CoordinatorEntity, SensorEntity): """Sensor representing corona virus data.""" - name = None - unique_id = None + _attr_unit_of_measurement = "people" def __init__(self, coordinator, country, info_type): """Initialize coronavirus sensor.""" super().__init__(coordinator) + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._attr_icon = SENSORS[info_type] + self._attr_unique_id = f"{country}-{info_type}" if country == OPTION_WORLDWIDE: - self.name = f"Worldwide Coronavirus {info_type}" + self._attr_name = f"Worldwide Coronavirus {info_type}" else: - self.name = f"{coordinator.data[country].country} Coronavirus {info_type}" - self.unique_id = f"{country}-{info_type}" + self._attr_name = ( + f"{coordinator.data[country].country} Coronavirus {info_type}" + ) + self.country = country self.info_type = info_type @@ -62,18 +66,3 @@ class CoronavirusSensor(CoordinatorEntity, SensorEntity): return sum_cases return getattr(self.coordinator.data[self.country], self.info_type) - - @property - def icon(self): - """Return the icon.""" - return SENSORS[self.info_type] - - @property - def unit_of_measurement(self): - """Return unit of measurement.""" - return "people" - - @property - def extra_state_attributes(self): - """Return device attributes.""" - return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index 2e3826935fe..1eb67e45aa5 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -98,11 +98,14 @@ class NUTSensor(CoordinatorEntity, SensorEntity): self._firmware = firmware self._model = model self._device_name = name - self._name = f"{name} {SENSOR_TYPES[sensor_type][SENSOR_NAME]}" - self._unit = SENSOR_TYPES[sensor_type][SENSOR_UNIT] self._data = data self._unique_id = unique_id + self._attr_device_class = SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS] + self._attr_icon = SENSOR_TYPES[self._type][SENSOR_ICON] + self._attr_name = f"{name} {SENSOR_TYPES[sensor_type][SENSOR_NAME]}" + self._attr_unit_of_measurement = SENSOR_TYPES[sensor_type][SENSOR_UNIT] + @property def device_info(self): """Device info for the ups.""" @@ -127,25 +130,6 @@ class NUTSensor(CoordinatorEntity, SensorEntity): return None return f"{self._unique_id}_{self._type}" - @property - def name(self): - """Return the name of the UPS sensor.""" - return self._name - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - if SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS]: - # The UI will assign an icon - # if it has a class - return None - return SENSOR_TYPES[self._type][SENSOR_ICON] - - @property - def device_class(self): - """Device class of the sensor.""" - return SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS] - @property def state(self): """Return entity state from ups.""" @@ -155,11 +139,6 @@ class NUTSensor(CoordinatorEntity, SensorEntity): return _format_display_state(self._data.status) return self._data.status.get(self._type) - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit - @property def extra_state_attributes(self): """Return the sensor attributes.""" diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 64ee024dfd7..3c77541cea8 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -35,18 +35,10 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): coordinator: VerisureDataUpdateCoordinator + _attr_name = "Verisure Alarm" + _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY + _changed_by: str | None = None - _state: str | None = None - - @property - def name(self) -> str: - """Return the name of the entity.""" - return "Verisure Alarm" - - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return self.coordinator.entry.data[CONF_GIID] @property def device_info(self) -> DeviceInfo: @@ -58,11 +50,6 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, } - @property - def state(self) -> str | None: - """Return the state of the entity.""" - return self._state - @property def supported_features(self) -> int: """Return the list of supported features.""" @@ -109,7 +96,7 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - self._state = ALARM_STATE_TO_HA.get( + self._attr_state = ALARM_STATE_TO_HA.get( self.coordinator.data["alarm"]["statusType"] ) self._changed_by = self.coordinator.data["alarm"].get("name") diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index ab79c6f45fe..4d9b1e84770 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -39,23 +39,17 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): coordinator: VerisureDataUpdateCoordinator + _attr_device_class = DEVICE_CLASS_OPENING + def __init__( self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure door window sensor.""" super().__init__(coordinator) + self._attr_name = coordinator.data["door_window"][serial_number]["area"] + self._attr_unique_id = f"{serial_number}_door_window" self.serial_number = serial_number - @property - def name(self) -> str: - """Return the name of this entity.""" - return self.coordinator.data["door_window"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return f"{self.serial_number}_door_window" - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" @@ -69,11 +63,6 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), } - @property - def device_class(self) -> str: - """Return the class of this entity.""" - return DEVICE_CLASS_OPENING - @property def is_on(self) -> bool: """Return the state of the sensor.""" @@ -95,10 +84,8 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): coordinator: VerisureDataUpdateCoordinator - @property - def name(self) -> str: - """Return the name of this entity.""" - return "Verisure Ethernet status" + _attr_name = "Verisure Ethernet status" + _attr_device_class = DEVICE_CLASS_CONNECTIVITY @property def unique_id(self) -> str: @@ -124,8 +111,3 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): def available(self) -> bool: """Return True if entity is available.""" return super().available and self.coordinator.data["ethernet"] is not None - - @property - def device_class(self) -> str: - """Return the class of this entity.""" - return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 0dfda45999a..a137f61d98f 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -58,21 +58,14 @@ class VerisureSmartcam(CoordinatorEntity, Camera): super().__init__(coordinator) Camera.__init__(self) + self._attr_name = coordinator.data["cameras"][serial_number]["area"] + self._attr_unique_id = serial_number + self.serial_number = serial_number self._directory_path = directory_path self._image = None self._image_id = None - @property - def name(self) -> str: - """Return the name of this entity.""" - return self.coordinator.data["cameras"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return self.serial_number - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index c33ccda208a..e645bf3f8c1 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -65,22 +65,16 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): ) -> None: """Initialize the Verisure lock.""" super().__init__(coordinator) + + self._attr_name = coordinator.data["locks"][serial_number]["area"] + self._attr_unique_id = serial_number + self.serial_number = serial_number self._state = None self._digits = coordinator.entry.options.get( CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS ) - @property - def name(self) -> str: - """Return the name of this entity.""" - return self.coordinator.data["locks"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return self.serial_number - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 028e5877e51..d39c235e9d5 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -50,11 +50,15 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity): coordinator: VerisureDataUpdateCoordinator + _attr_device_class = DEVICE_CLASS_TEMPERATURE + _attr_unit_of_measurement = TEMP_CELSIUS + def __init__( self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) + self._attr_unique_id = f"{serial_number}_temperature" self.serial_number = serial_number @property @@ -63,16 +67,6 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity): name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] return f"{name} Temperature" - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return f"{self.serial_number}_temperature" - - @property - def device_class(self) -> str: - """Return the class of this entity.""" - return DEVICE_CLASS_TEMPERATURE - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" @@ -103,22 +97,21 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity): and "temperature" in self.coordinator.data["climate"][self.serial_number] ) - @property - def unit_of_measurement(self) -> str: - """Return the unit of measurement of this entity.""" - return TEMP_CELSIUS - class VerisureHygrometer(CoordinatorEntity, SensorEntity): """Representation of a Verisure hygrometer.""" coordinator: VerisureDataUpdateCoordinator + _attr_device_class = DEVICE_CLASS_HUMIDITY + _attr_unit_of_measurement = PERCENTAGE + def __init__( self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) + self._attr_unique_id = f"{serial_number}_humidity" self.serial_number = serial_number @property @@ -127,16 +120,6 @@ class VerisureHygrometer(CoordinatorEntity, SensorEntity): name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] return f"{name} Humidity" - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return f"{self.serial_number}_humidity" - - @property - def device_class(self) -> str: - """Return the class of this entity.""" - return DEVICE_CLASS_HUMIDITY - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" @@ -167,22 +150,20 @@ class VerisureHygrometer(CoordinatorEntity, SensorEntity): and "humidity" in self.coordinator.data["climate"][self.serial_number] ) - @property - def unit_of_measurement(self) -> str: - """Return the unit of measurement of this entity.""" - return PERCENTAGE - class VerisureMouseDetection(CoordinatorEntity, SensorEntity): """Representation of a Verisure mouse detector.""" coordinator: VerisureDataUpdateCoordinator + _attr_unit_of_measurement = "Mice" + def __init__( self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) + self._attr_unique_id = f"{serial_number}_mice" self.serial_number = serial_number @property @@ -191,11 +172,6 @@ class VerisureMouseDetection(CoordinatorEntity, SensorEntity): name = self.coordinator.data["mice"][self.serial_number]["area"] return f"{name} Mouse" - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return f"{self.serial_number}_mice" - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" @@ -222,8 +198,3 @@ class VerisureMouseDetection(CoordinatorEntity, SensorEntity): and self.serial_number in self.coordinator.data["mice"] and "detections" in self.coordinator.data["mice"][self.serial_number] ) - - @property - def unit_of_measurement(self) -> str: - """Return the unit of measurement of this entity.""" - return "Mice" diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 97b2ff0186a..f428f70cda6 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -37,20 +37,14 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): ) -> None: """Initialize the Verisure device.""" super().__init__(coordinator) + + self._attr_name = coordinator.data["smart_plugs"][serial_number]["area"] + self._attr_unique_id = serial_number + self.serial_number = serial_number self._change_timestamp = 0 self._state = False - @property - def name(self) -> str: - """Return the name of this entity.""" - return self.coordinator.data["smart_plugs"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this entity.""" - return self.serial_number - @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 2e2c1b3b3f9..724280b19c9 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -168,28 +168,46 @@ class Entity(ABC): # If entity is added to an entity platform _added = False + # Entity Properties + _attr_assumed_state: bool = False + _attr_available: bool = True + _attr_context_recent_time: timedelta = timedelta(seconds=5) + _attr_device_class: str | None = None + _attr_device_info: DeviceInfo | None = None + _attr_entity_picture: str | None = None + _attr_entity_registry_enabled_default: bool = True + _attr_extra_state_attributes: Mapping[str, Any] | None = None + _attr_force_update: bool = False + _attr_icon: str | None = None + _attr_name: str | None = None + _attr_should_poll: bool = True + _attr_state: StateType = STATE_UNKNOWN + _attr_supported_features: int | None = None + _attr_unique_id: str | None = None + _attr_unit_of_measurement: str | None = None + @property def should_poll(self) -> bool: """Return True if entity has to be polled for state. False if entity pushes its state to HA. """ - return True + return self._attr_should_poll @property def unique_id(self) -> str | None: """Return a unique ID.""" - return None + return self._attr_unique_id @property def name(self) -> str | None: """Return the name of the entity.""" - return None + return self._attr_name @property def state(self) -> StateType: """Return the state of the entity.""" - return STATE_UNKNOWN + return self._attr_state @property def capability_attributes(self) -> Mapping[str, Any] | None: @@ -227,7 +245,7 @@ class Entity(ABC): Implemented by platform classes. Convention for attribute names is lowercase snake_case. """ - return None + return self._attr_extra_state_attributes @property def device_info(self) -> DeviceInfo | None: @@ -235,37 +253,37 @@ class Entity(ABC): Implemented by platform classes. """ - return None + return self._attr_device_info @property def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" - return None + return self._attr_device_class @property def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" - return None + return self._attr_unit_of_measurement @property def icon(self) -> str | None: """Return the icon to use in the frontend, if any.""" - return None + return self._attr_icon @property def entity_picture(self) -> str | None: """Return the entity picture to use in the frontend, if any.""" - return None + return self._attr_entity_picture @property def available(self) -> bool: """Return True if entity is available.""" - return True + return self._attr_available @property def assumed_state(self) -> bool: """Return True if unable to access real state of the entity.""" - return False + return self._attr_assumed_state @property def force_update(self) -> bool: @@ -274,22 +292,22 @@ class Entity(ABC): If True, a state change will be triggered anytime the state property is updated, not just when the value changes. """ - return False + return self._attr_force_update @property def supported_features(self) -> int | None: """Flag supported features.""" - return None + return self._attr_supported_features @property def context_recent_time(self) -> timedelta: """Time that a context is considered recent.""" - return timedelta(seconds=5) + return self._attr_context_recent_time @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - return True + return self._attr_entity_registry_enabled_default # DO NOT OVERWRITE # These properties and methods are either managed by Home Assistant or they