diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index b5f979b45cf..77d604a8f6c 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -5,7 +5,7 @@ from typing import Any, cast from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TEMPERATURE +from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -59,6 +59,7 @@ async def async_setup_entry( class AccuWeatherSensor(CoordinatorEntity, SensorEntity): """Define an AccuWeather entity.""" + _attr_attribution = ATTRIBUTION coordinator: AccuWeatherDataUpdateCoordinator entity_description: AccuWeatherSensorDescription @@ -75,7 +76,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity): self._sensor_data = _get_sensor_data( coordinator.data, forecast_day, description.key ) - self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} + self._attrs: dict[str, Any] = {} if forecast_day is not None: self._attr_name = f"{name} {description.name} {forecast_day}d" self._attr_unique_id = ( diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 1b8ab5f9c30..1e38bad55a8 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -6,10 +6,7 @@ import logging from typing import Final, final from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_ATTRIBUTION, - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, -) +from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER from homeassistant.core import HomeAssistant from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, @@ -41,7 +38,6 @@ SCAN_INTERVAL: Final = timedelta(seconds=30) PROP_TO_ATTR: Final[dict[str, str]] = { "air_quality_index": ATTR_AQI, - "attribution": ATTR_ATTRIBUTION, "carbon_dioxide": ATTR_CO2, "carbon_monoxide": ATTR_CO, "nitrogen_oxide": ATTR_N2O, @@ -114,11 +110,6 @@ class AirQualityEntity(Entity): """Return the CO2 (carbon dioxide) level.""" return None - @property - def attribution(self) -> StateType: - """Return the attribution.""" - return None - @property def sulphur_dioxide(self) -> StateType: """Return the SO2 (sulphur dioxide) level.""" diff --git a/homeassistant/components/met/const.py b/homeassistant/components/met/const.py index 0f4c22dbba3..93f9e3414dd 100644 --- a/homeassistant/components/met/const.py +++ b/homeassistant/components/met/const.py @@ -18,7 +18,6 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -184,7 +183,6 @@ FORECAST_MAP = { } ATTR_MAP = { - ATTR_WEATHER_ATTRIBUTION: "attribution", ATTR_WEATHER_HUMIDITY: "humidity", ATTR_WEATHER_PRESSURE: "pressure", ATTR_WEATHER_TEMPERATURE: "temperature", diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 7d5f7a99d40..d4965be841d 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -47,7 +47,6 @@ ATTR_FORECAST_TEMP_LOW: Final = "templow" ATTR_FORECAST_TIME: Final = "datetime" ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing" ATTR_FORECAST_WIND_SPEED: Final = "wind_speed" -ATTR_WEATHER_ATTRIBUTION = "attribution" ATTR_WEATHER_HUMIDITY = "humidity" ATTR_WEATHER_OZONE = "ozone" ATTR_WEATHER_PRESSURE = "pressure" @@ -107,7 +106,6 @@ class WeatherEntity(Entity): """ABC for weather data.""" entity_description: WeatherEntityDescription - _attr_attribution: str | None = None _attr_condition: str | None _attr_forecast: list[Forecast] | None = None _attr_humidity: float | None = None @@ -156,11 +154,6 @@ class WeatherEntity(Entity): """Return the ozone level.""" return self._attr_ozone - @property - def attribution(self) -> str | None: - """Return the attribution.""" - return self._attr_attribution - @property def visibility(self) -> float | None: """Return the visibility.""" @@ -216,10 +209,6 @@ class WeatherEntity(Entity): if visibility is not None: data[ATTR_WEATHER_VISIBILITY] = visibility - attribution = self.attribution - if attribution is not None: - data[ATTR_WEATHER_ATTRIBUTION] = attribution - if self.forecast is not None: forecast = [] for forecast_entry in self.forecast: diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index f04949c0caa..122d04b0bf9 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -16,6 +16,7 @@ from typing import Any, TypedDict, final from homeassistant.config import DATA_CUSTOMIZE from homeassistant.const import ( ATTR_ASSUMED_STATE, + ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, @@ -232,6 +233,7 @@ class Entity(ABC): # Entity Properties _attr_assumed_state: bool = False + _attr_attribution: str | None = None _attr_available: bool = True _attr_context_recent_time: timedelta = timedelta(seconds=5) _attr_device_class: str | None @@ -397,6 +399,11 @@ class Entity(ABC): return self.entity_description.entity_registry_enabled_default return True + @property + def attribution(self) -> str | None: + """Return the attribution.""" + return self._attr_attribution + # DO NOT OVERWRITE # These properties and methods are either managed by Home Assistant or they # are used to perform a very specific function. Overwriting these may @@ -520,6 +527,9 @@ class Entity(ABC): if (device_class := self.device_class) is not None: attr[ATTR_DEVICE_CLASS] = str(device_class) + if (attribution := self.attribution) is not None: + attr[ATTR_ATTRIBUTION] = attribution + end = timer() if end - start > 0.4 and not self._slow_reported: diff --git a/tests/components/air_quality/test_air_quality.py b/tests/components/air_quality/test_air_quality.py index e0692931e1c..e0133e3ecc3 100644 --- a/tests/components/air_quality/test_air_quality.py +++ b/tests/components/air_quality/test_air_quality.py @@ -1,11 +1,7 @@ """The tests for the Air Quality component.""" -from homeassistant.components.air_quality import ( - ATTR_ATTRIBUTION, - ATTR_N2O, - ATTR_OZONE, - ATTR_PM_10, -) +from homeassistant.components.air_quality import ATTR_N2O, ATTR_OZONE, ATTR_PM_10 from homeassistant.const import ( + ATTR_ATTRIBUTION, ATTR_UNIT_OF_MEASUREMENT, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, ) diff --git a/tests/components/homematicip_cloud/test_weather.py b/tests/components/homematicip_cloud/test_weather.py index e3370e77ffe..4861a4d2696 100644 --- a/tests/components/homematicip_cloud/test_weather.py +++ b/tests/components/homematicip_cloud/test_weather.py @@ -1,13 +1,13 @@ """Tests for HomematicIP Cloud weather.""" from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN from homeassistant.components.weather import ( - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, DOMAIN as WEATHER_DOMAIN, ) +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.setup import async_setup_component from .helper import async_manipulate_test_data, get_and_check_entity_basics @@ -38,7 +38,7 @@ async def test_hmip_weather_sensor(hass, default_mock_hap_factory): assert ha_state.attributes[ATTR_WEATHER_TEMPERATURE] == 4.3 assert ha_state.attributes[ATTR_WEATHER_HUMIDITY] == 97 assert ha_state.attributes[ATTR_WEATHER_WIND_SPEED] == 15.0 - assert ha_state.attributes[ATTR_WEATHER_ATTRIBUTION] == "Powered by Homematic IP" + assert ha_state.attributes[ATTR_ATTRIBUTION] == "Powered by Homematic IP" await async_manipulate_test_data(hass, hmip_device, "actualTemperature", 12.1) ha_state = hass.states.get(entity_id) @@ -63,7 +63,7 @@ async def test_hmip_weather_sensor_pro(hass, default_mock_hap_factory): assert ha_state.attributes[ATTR_WEATHER_HUMIDITY] == 65 assert ha_state.attributes[ATTR_WEATHER_WIND_SPEED] == 2.6 assert ha_state.attributes[ATTR_WEATHER_WIND_BEARING] == 295.0 - assert ha_state.attributes[ATTR_WEATHER_ATTRIBUTION] == "Powered by Homematic IP" + assert ha_state.attributes[ATTR_ATTRIBUTION] == "Powered by Homematic IP" await async_manipulate_test_data(hass, hmip_device, "actualTemperature", 12.1) ha_state = hass.states.get(entity_id) @@ -86,7 +86,7 @@ async def test_hmip_home_weather(hass, default_mock_hap_factory): assert ha_state.attributes[ATTR_WEATHER_HUMIDITY] == 54 assert ha_state.attributes[ATTR_WEATHER_WIND_SPEED] == 8.6 assert ha_state.attributes[ATTR_WEATHER_WIND_BEARING] == 294 - assert ha_state.attributes[ATTR_WEATHER_ATTRIBUTION] == "Powered by Homematic IP" + assert ha_state.attributes[ATTR_ATTRIBUTION] == "Powered by Homematic IP" await async_manipulate_test_data( hass, mock_hap.home.weather, "temperature", 28.3, fire_device=mock_hap.home diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 33214be0ae3..c890ad62216 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -19,7 +19,6 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -27,7 +26,7 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, ) -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.util.dt import utcnow @@ -60,7 +59,7 @@ async def test_setup_hass( assert state.attributes[ATTR_SMHI_CLOUDINESS] == 50 assert state.attributes[ATTR_SMHI_THUNDER_PROBABILITY] == 33 assert state.attributes[ATTR_SMHI_WIND_GUST_SPEED] == 17 - assert state.attributes[ATTR_WEATHER_ATTRIBUTION].find("SMHI") >= 0 + assert state.attributes[ATTR_ATTRIBUTION].find("SMHI") >= 0 assert state.attributes[ATTR_WEATHER_HUMIDITY] == 55 assert state.attributes[ATTR_WEATHER_PRESSURE] == 1024 assert state.attributes[ATTR_WEATHER_TEMPERATURE] == 17 @@ -94,9 +93,7 @@ async def test_properties_no_data(hass: HomeAssistant) -> None: assert state assert state.name == "test" assert state.state == STATE_UNKNOWN - assert ( - state.attributes[ATTR_WEATHER_ATTRIBUTION] == "Swedish weather institute (SMHI)" - ) + assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)" assert ATTR_WEATHER_HUMIDITY not in state.attributes assert ATTR_WEATHER_PRESSURE not in state.attributes assert ATTR_WEATHER_TEMPERATURE not in state.attributes diff --git a/tests/components/template/test_weather.py b/tests/components/template/test_weather.py index c112473ecd6..dbd3e5706c3 100644 --- a/tests/components/template/test_weather.py +++ b/tests/components/template/test_weather.py @@ -2,7 +2,6 @@ import pytest from homeassistant.components.weather import ( - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, ATTR_WEATHER_PRESSURE, @@ -12,6 +11,7 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_SPEED, DOMAIN, ) +from homeassistant.const import ATTR_ATTRIBUTION @pytest.mark.parametrize("count,domain", [(1, DOMAIN)]) @@ -44,7 +44,7 @@ async def test_template_state_text(hass, start_ha): for attr, v_attr, value in [ ( "sensor.attribution", - ATTR_WEATHER_ATTRIBUTION, + ATTR_ATTRIBUTION, "The custom attribution", ), ("sensor.temperature", ATTR_WEATHER_TEMPERATURE, 22.3), diff --git a/tests/components/weather/test_weather.py b/tests/components/weather/test_weather.py index c32c4d09523..3057532668a 100644 --- a/tests/components/weather/test_weather.py +++ b/tests/components/weather/test_weather.py @@ -7,7 +7,6 @@ from homeassistant.components.weather import ( ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, - ATTR_WEATHER_ATTRIBUTION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, ATTR_WEATHER_PRESSURE, @@ -15,6 +14,7 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_SPEED, ) +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.setup import async_setup_component from homeassistant.util.unit_system import METRIC_SYSTEM @@ -39,7 +39,7 @@ async def test_attributes(hass): assert data.get(ATTR_WEATHER_WIND_SPEED) == 0.5 assert data.get(ATTR_WEATHER_WIND_BEARING) is None assert data.get(ATTR_WEATHER_OZONE) is None - assert data.get(ATTR_WEATHER_ATTRIBUTION) == "Powered by Home Assistant" + assert data.get(ATTR_ATTRIBUTION) == "Powered by Home Assistant" assert data.get(ATTR_FORECAST)[0].get(ATTR_FORECAST_CONDITION) == "rainy" assert data.get(ATTR_FORECAST)[0].get(ATTR_FORECAST_PRECIPITATION) == 1 assert data.get(ATTR_FORECAST)[0].get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 60 diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 21811c3bfdc..bdb7a2782a3 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -7,7 +7,12 @@ from unittest.mock import MagicMock, PropertyMock, patch import pytest -from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_DEVICE_CLASS, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) from homeassistant.core import Context, HomeAssistantError from homeassistant.helpers import entity, entity_registry @@ -790,3 +795,17 @@ async def test_float_conversion(hass): state = hass.states.get("hello.world") assert state is not None assert state.state == "3.6" + + +async def test_attribution_attribute(hass): + """Test attribution attribute.""" + mock_entity = entity.Entity() + mock_entity.hass = hass + mock_entity.entity_id = "hello.world" + mock_entity._attr_attribution = "Home Assistant" + + mock_entity.async_schedule_update_ha_state(True) + await hass.async_block_till_done() + + state = hass.states.get(mock_entity.entity_id) + assert state.attributes.get(ATTR_ATTRIBUTION) == "Home Assistant"