Mark base components' state_attribute @final, rename others to extra_state_attributes (#48161)

* Mark base state_attributes @final, rename others to extra_state_attributes

* Fix calendar, update tests
This commit is contained in:
Erik Montnemery 2021-03-21 10:38:24 +01:00 committed by GitHub
parent 668d018e9c
commit 346a724ac3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 106 additions and 71 deletions

View file

@ -132,7 +132,7 @@ class AcerSwitch(SwitchEntity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return state attributes."""
return self._attributes

View file

@ -1,6 +1,7 @@
"""Component for handling Air Quality data for your location."""
from datetime import timedelta
import logging
from typing import final
from homeassistant.const import (
ATTR_ATTRIBUTION,
@ -131,6 +132,7 @@ class AirQualityEntity(Entity):
"""Return the NO2 (nitrogen dioxide) level."""
return None
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -2,6 +2,7 @@
from abc import abstractmethod
from datetime import timedelta
import logging
from typing import final
import voluptuous as vol
@ -172,6 +173,7 @@ class AlarmControlPanelEntity(Entity):
def supported_features(self) -> int:
"""Return the list of supported features."""
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -154,7 +154,7 @@ class ArwnSensor(Entity):
return self._uid
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return all the state attributes."""
return self.event

View file

@ -274,7 +274,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
return False
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the entity state attributes."""
attrs = {
ATTR_LAST_TRIGGERED: self.action_script.last_triggered,

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
import re
from typing import cast
from typing import cast, final
from aiohttp import web
@ -129,13 +129,14 @@ def is_offset_reached(event):
class CalendarEventDevice(Entity):
"""A calendar event device."""
"""Base class for calendar event entities."""
@property
def event(self):
"""Return the next upcoming event."""
raise NotImplementedError()
@final
@property
def state_attributes(self):
"""Return the entity state attributes."""

View file

@ -8,6 +8,7 @@ import hashlib
import logging
import os
from random import SystemRandom
from typing import final
from aiohttp import web
import async_timeout
@ -441,6 +442,7 @@ class Camera(Entity):
"""Call the job and disable motion detection."""
await self.hass.async_add_executor_job(self.disable_motion_detection)
@final
@property
def state_attributes(self):
"""Return the camera state attributes."""

View file

@ -5,7 +5,7 @@ from abc import abstractmethod
from datetime import timedelta
import functools as ft
import logging
from typing import Any
from typing import Any, final
import voluptuous as vol
@ -167,7 +167,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry):
class ClimateEntity(Entity):
"""Representation of a climate entity."""
"""Base class for climate entities."""
@property
def state(self) -> str:
@ -213,6 +213,7 @@ class ClimateEntity(Entity):
return data
@final
@property
def state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes."""

View file

@ -208,7 +208,7 @@ class Counter(RestoreEntity):
return self._state
@property
def state_attributes(self) -> dict:
def extra_state_attributes(self) -> dict:
"""Return the state attributes."""
ret = {
ATTR_EDITABLE: self.editable,

View file

@ -2,7 +2,7 @@
from datetime import timedelta
import functools as ft
import logging
from typing import Any
from typing import Any, final
import voluptuous as vol
@ -165,7 +165,7 @@ async def async_unload_entry(hass, entry):
class CoverEntity(Entity):
"""Representation of a cover."""
"""Base class for cover entities."""
@property
def current_cover_position(self):
@ -196,6 +196,7 @@ class CoverEntity(Entity):
return STATE_CLOSED if closed else STATE_OPEN
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -1,6 +1,8 @@
"""Code to set up a device tracker platform using a config entry."""
from __future__ import annotations
from typing import final
from homeassistant.components import zone
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
@ -59,7 +61,7 @@ class BaseTrackerEntity(Entity):
class TrackerEntity(BaseTrackerEntity):
"""Represent a tracked device."""
"""Base class for a tracked device."""
@property
def should_poll(self):
@ -114,6 +116,7 @@ class TrackerEntity(BaseTrackerEntity):
return None
@final
@property
def state_attributes(self):
"""Return the device state attributes."""
@ -128,7 +131,7 @@ class TrackerEntity(BaseTrackerEntity):
class ScannerEntity(BaseTrackerEntity):
"""Represent a tracked device that is on a scanned network."""
"""Base class for a tracked device that is on a scanned network."""
@property
def ip_address(self) -> str:
@ -157,6 +160,7 @@ class ScannerEntity(BaseTrackerEntity):
"""Return true if the device is connected to the network."""
raise NotImplementedError
@final
@property
def state_attributes(self):
"""Return the device state attributes."""

View file

@ -5,7 +5,7 @@ import asyncio
from datetime import timedelta
import hashlib
from types import ModuleType
from typing import Any, Callable, Sequence
from typing import Any, Callable, Sequence, final
import attr
import voluptuous as vol
@ -588,7 +588,7 @@ class DeviceTracker:
class Device(RestoreEntity):
"""Represent a tracked device."""
"""Base class for a tracked device."""
host_name: str = None
location_name: str = None
@ -661,6 +661,7 @@ class Device(RestoreEntity):
"""Return the picture of the device."""
return self.config_picture
@final
@property
def state_attributes(self):
"""Return the device state attributes."""

View file

@ -66,7 +66,7 @@ class BanSensor(Entity):
return self._name
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes of the fail2ban sensor."""
return self.ban_dict

View file

@ -5,6 +5,7 @@ from datetime import timedelta
import functools as ft
import logging
import math
from typing import final
import voluptuous as vol
@ -220,7 +221,7 @@ def _fan_native(method):
class FanEntity(ToggleEntity):
"""Representation of a fan."""
"""Base class for fan entities."""
@_fan_native
def set_speed(self, speed: str) -> None:
@ -586,6 +587,7 @@ class FanEntity(ToggleEntity):
f"The speed_list {speed_list} does not contain any valid speeds."
) from ex
@final
@property
def state_attributes(self) -> dict:
"""Return optional state attributes."""

View file

@ -93,7 +93,7 @@ class FritzboxMonitorSensor(Entity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the device state attributes."""
# Don't return attributes if FritzBox is unreachable
if self._state == STATE_UNAVAILABLE:

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
from typing import final
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
from homeassistant.helpers.config_validation import ( # noqa: F401
@ -46,7 +47,7 @@ async def async_unload_entry(hass, entry):
class GeolocationEvent(Entity):
"""This represents an external event with an associated geolocation."""
"""Base class for an external event with an associated geolocation."""
@property
def state(self):
@ -75,6 +76,7 @@ class GeolocationEvent(Entity):
"""Return longitude value of this external event."""
return None
@final
@property
def state_attributes(self):
"""Return the state attributes of this external event."""

View file

@ -547,7 +547,7 @@ class Group(Entity):
self._icon = value
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes for the group."""
data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order}
if not self.user_defined:

View file

@ -231,7 +231,7 @@ class HMHub(Entity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return self._variables.copy()

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from typing import Any, final
import voluptuous as vol
@ -99,7 +99,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo
class HumidifierEntity(ToggleEntity):
"""Representation of a humidifier device."""
"""Base class for humidifier entities."""
@property
def capability_attributes(self) -> dict[str, Any]:
@ -115,6 +115,7 @@ class HumidifierEntity(ToggleEntity):
return data
@final
@property
def state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes."""

View file

@ -502,6 +502,6 @@ class IcloudDevice:
return self._location
@property
def state_attributes(self) -> dict[str, any]:
def exta_state_attributes(self) -> dict[str, any]:
"""Return the attributes."""
return self._attrs

View file

@ -2,6 +2,7 @@
import asyncio
from datetime import timedelta
import logging
from typing import final
import voluptuous as vol
@ -175,6 +176,7 @@ class ImageProcessingFaceEntity(ImageProcessingEntity):
"""Return the class of this device, from component DEVICE_CLASSES."""
return "face"
@final
@property
def state_attributes(self):
"""Return device specific state attributes."""

View file

@ -169,7 +169,7 @@ class InputBoolean(ToggleEntity, RestoreEntity):
return self._config.get(CONF_NAME)
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes of the entity."""
return {ATTR_EDITABLE: self.editable}

View file

@ -319,7 +319,7 @@ class InputDatetime(RestoreEntity):
return self._current_datetime.strftime(FMT_TIME)
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
attrs = {
ATTR_EDITABLE: self.editable,

View file

@ -256,7 +256,7 @@ class InputNumber(RestoreEntity):
return self._config[CONF_ID]
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_INITIAL: self._config.get(CONF_INITIAL),

View file

@ -253,7 +253,7 @@ class InputSelect(RestoreEntity):
return self._current_option
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return {ATTR_OPTIONS: self._config[ATTR_OPTIONS], ATTR_EDITABLE: self.editable}

View file

@ -245,7 +245,7 @@ class InputText(RestoreEntity):
return self._config[CONF_ID]
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_EDITABLE: self.editable,

View file

@ -6,7 +6,7 @@ import dataclasses
from datetime import timedelta
import logging
import os
from typing import cast
from typing import cast, final
import voluptuous as vol
@ -478,7 +478,7 @@ class Profiles:
class LightEntity(ToggleEntity):
"""Representation of a light."""
"""Base class for light entities."""
@property
def brightness(self) -> int | None:
@ -634,6 +634,7 @@ class LightEntity(ToggleEntity):
data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
return data
@final
@property
def state_attributes(self):
"""Return state attributes."""

View file

@ -2,6 +2,7 @@
from datetime import timedelta
import functools as ft
import logging
from typing import final
import voluptuous as vol
@ -76,7 +77,7 @@ async def async_unload_entry(hass, entry):
class LockEntity(Entity):
"""Representation of a lock."""
"""Base class for lock entities."""
@property
def changed_by(self):
@ -117,6 +118,7 @@ class LockEntity(Entity):
"""Open the door latch."""
await self.hass.async_add_executor_job(ft.partial(self.open, **kwargs))
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -9,6 +9,7 @@ import functools as ft
import hashlib
import logging
import secrets
from typing import final
from urllib.parse import urlparse
from aiohttp import web
@ -853,6 +854,7 @@ class MediaPlayerEntity(Entity):
return data
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -169,7 +169,7 @@ class NetioSwitch(SwitchEntity):
self.netio.update()
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return optional state attributes."""
return {
ATTR_TOTAL_CONSUMPTION_KWH: self.cumulated_consumption_kwh,

View file

@ -99,7 +99,7 @@ class ImageProcessingAlprEntity(ImageProcessingEntity):
return "alpr"
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return device specific state attributes."""
return {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles}

View file

@ -152,7 +152,7 @@ class OpenCVImageProcessor(ImageProcessingEntity):
return self._total_matches
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return device specific state attributes."""
return {ATTR_MATCHES: self._matches, ATTR_TOTAL_MATCHES: self._total_matches}

View file

@ -73,8 +73,8 @@ class OpenHardwareMonitorDevice(Entity):
return self.value
@property
def state_attributes(self):
"""Return the state attributes of the sun."""
def extra_state_attributes(self):
"""Return the state attributes of the entity."""
return self.attributes
@classmethod

View file

@ -400,7 +400,7 @@ class Person(RestoreEntity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes of the person."""
data = {ATTR_EDITABLE: self.editable, ATTR_ID: self.unique_id}
if self._latitude is not None:

View file

@ -348,7 +348,7 @@ class Plant(Entity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the attributes of the entity.
Provide the individual measurements from the

View file

@ -148,7 +148,7 @@ class Proximity(Entity):
return self._unit_of_measurement
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return {ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest}

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import timedelta
import functools as ft
import logging
from typing import Any, Iterable, cast
from typing import Any, Iterable, cast, final
import voluptuous as vol
@ -141,7 +141,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo
class RemoteEntity(ToggleEntity):
"""Representation of a remote."""
"""Base class for remote entities."""
@property
def supported_features(self) -> int:
@ -158,6 +158,7 @@ class RemoteEntity(ToggleEntity):
"""List of available activities."""
return None
@final
@property
def state_attributes(self) -> dict[str, Any] | None:
"""Return optional state attributes."""

View file

@ -151,7 +151,7 @@ class RMVDepartureSensor(Entity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
try:
return {

View file

@ -284,7 +284,7 @@ class ScriptEntity(ToggleEntity):
return self.script.name
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
attrs = {
ATTR_LAST_TRIGGERED: self.script.last_triggered,

View file

@ -65,7 +65,7 @@ class SonyProjector(SwitchEntity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return state attributes."""
return self._attributes

View file

@ -124,7 +124,7 @@ class Sun(Entity):
return STATE_BELOW_HORIZON
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes of the sun."""
return {
STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(),

View file

@ -1,6 +1,7 @@
"""Component to interface with switches that can be controlled remotely."""
from datetime import timedelta
import logging
from typing import final
import voluptuous as vol
@ -79,7 +80,7 @@ async def async_unload_entry(hass, entry):
class SwitchEntity(ToggleEntity):
"""Representation of a switch."""
"""Base class for switch entities."""
@property
def current_power_w(self):
@ -96,6 +97,7 @@ class SwitchEntity(ToggleEntity):
"""Return true if device is in standby."""
return None
@final
@property
def state_attributes(self):
"""Return the optional state attributes."""

View file

@ -232,7 +232,7 @@ class Timer(RestoreEntity):
return self._state
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
attrs = {
ATTR_DURATION: _format_timedelta(self._duration),

View file

@ -129,7 +129,7 @@ class TwinklyLight(LightEntity):
return self._brightness
@property
def state_attributes(self) -> dict:
def extra_state_attributes(self) -> dict:
"""Return device specific state attributes."""
attributes = self._attributes

View file

@ -165,7 +165,7 @@ class TariffSelect(RestoreEntity):
return self._current_tariff
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the state attributes."""
return {ATTR_TARIFFS: self._tariffs}

View file

@ -2,6 +2,7 @@
from datetime import timedelta
from functools import partial
import logging
from typing import final
import voluptuous as vol
@ -271,6 +272,7 @@ class VacuumEntity(_BaseVacuum, ToggleEntity):
battery_level=self.battery_level, charging=charging
)
@final
@property
def state_attributes(self):
"""Return the state attributes of the vacuum cleaner."""

View file

@ -2,6 +2,7 @@
from datetime import timedelta
import functools as ft
import logging
from typing import final
import voluptuous as vol
@ -129,7 +130,7 @@ async def async_unload_entry(hass, entry):
class WaterHeaterEntity(Entity):
"""Representation of a water_heater device."""
"""Base class for water heater entities."""
@property
def state(self):
@ -162,6 +163,7 @@ class WaterHeaterEntity(Entity):
return data
@final
@property
def state_attributes(self):
"""Return the optional state attributes."""

View file

@ -1,6 +1,7 @@
"""Weather component that handles meteorological data for your location."""
from datetime import timedelta
import logging
from typing import final
from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.helpers.config_validation import ( # noqa: F401
@ -138,6 +139,7 @@ class WeatherEntity(Entity):
else PRECISION_WHOLE
)
@final
@property
def state_attributes(self):
"""Return the state attributes."""

View file

@ -171,7 +171,7 @@ class IsWorkdaySensor(BinarySensorEntity):
return False
@property
def state_attributes(self):
def extra_state_attributes(self):
"""Return the attributes of the entity."""
# return self._attributes
return {

View file

@ -315,7 +315,7 @@ class Zone(entity.Entity):
return self._config.get(CONF_ICON)
@property
def state_attributes(self) -> dict | None:
def extra_state_attributes(self) -> dict | None:
"""Return the state attributes of the zone."""
return self._attrs

View file

@ -89,8 +89,8 @@ async def test_single_ban(hass):
sensor.update()
assert sensor.state == "111.111.111.111"
assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]
async def test_ipv6_ban(hass):
@ -103,8 +103,8 @@ async def test_ipv6_ban(hass):
sensor.update()
assert sensor.state == "2607:f0d0:1002:51::4"
assert sensor.state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"]
assert sensor.state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"]
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"]
assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"]
async def test_multiple_ban(hass):
@ -117,11 +117,11 @@ async def test_multiple_ban(hass):
sensor.update()
assert sensor.state == "222.222.222.222"
assert sensor.state_attributes[STATE_CURRENT_BANS] == [
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == [
"111.111.111.111",
"222.222.222.222",
]
assert sensor.state_attributes[STATE_ALL_BANS] == [
assert sensor.extra_state_attributes[STATE_ALL_BANS] == [
"111.111.111.111",
"222.222.222.222",
]
@ -137,8 +137,8 @@ async def test_unban_all(hass):
sensor.update()
assert sensor.state == "None"
assert sensor.state_attributes[STATE_CURRENT_BANS] == []
assert sensor.state_attributes[STATE_ALL_BANS] == [
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == []
assert sensor.extra_state_attributes[STATE_ALL_BANS] == [
"111.111.111.111",
"222.222.222.222",
]
@ -154,8 +154,8 @@ async def test_unban_one(hass):
sensor.update()
assert sensor.state == "222.222.222.222"
assert sensor.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"]
assert sensor.state_attributes[STATE_ALL_BANS] == [
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"]
assert sensor.extra_state_attributes[STATE_ALL_BANS] == [
"111.111.111.111",
"222.222.222.222",
]
@ -174,11 +174,11 @@ async def test_multi_jail(hass):
sensor2.update()
assert sensor1.state == "111.111.111.111"
assert sensor1.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor1.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]
assert sensor1.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor1.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]
assert sensor2.state == "222.222.222.222"
assert sensor2.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"]
assert sensor2.state_attributes[STATE_ALL_BANS] == ["222.222.222.222"]
assert sensor2.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"]
assert sensor2.extra_state_attributes[STATE_ALL_BANS] == ["222.222.222.222"]
async def test_ban_active_after_update(hass):
@ -192,5 +192,5 @@ async def test_ban_active_after_update(hass):
assert sensor.state == "111.111.111.111"
sensor.update()
assert sensor.state == "111.111.111.111"
assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]
assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"]
assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"]

View file

@ -58,7 +58,7 @@ async def test_valid_data(hass):
State(GOOD_CONFIG["sensors"][reading], value),
)
assert sensor.state == "ok"
attrib = sensor.state_attributes
attrib = sensor.extra_state_attributes
for reading, value in GOOD_DATA.items():
# battery level has a different name in
# the JSON format than in hass
@ -70,13 +70,13 @@ async def test_low_battery(hass):
sensor = plant.Plant("other plant", GOOD_CONFIG)
sensor.entity_id = "sensor.mqtt_plant_battery"
sensor.hass = hass
assert sensor.state_attributes["problem"] == "none"
assert sensor.extra_state_attributes["problem"] == "none"
sensor.state_changed(
"sensor.mqtt_plant_battery",
State("sensor.mqtt_plant_battery", 10),
)
assert sensor.state == "problem"
assert sensor.state_attributes["problem"] == "battery low"
assert sensor.extra_state_attributes["problem"] == "battery low"
async def test_initial_states(hass):