Add translation_key property to entites (#82701)

* Add translation_key attribute to entity state

* Update accuweather test

* Index entity translation keys by platform

* Store translation key in entity registry
This commit is contained in:
Erik Montnemery 2022-12-01 09:34:09 +01:00 committed by GitHub
parent 843f537109
commit 8e617bbc1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 127 additions and 32 deletions

View file

@ -301,9 +301,9 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
),
AccuWeatherSensorDescription(
key="PressureTendency",
device_class="accuweather__pressure_tendency",
icon="mdi:gauge",
name="Pressure tendency",
translation_key="pressure_tendency",
value_fn=lambda data, _: cast(str, data["LocalizedText"]).lower(),
),
AccuWeatherSensorDescription(

View file

@ -22,6 +22,17 @@
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"entity": {
"sensor": {
"pressure_tendency": {
"state": {
"steady": "Steady",
"rising": "Rising",
"falling": "Falling"
}
}
}
},
"options": {
"step": {
"init": {

View file

@ -1,9 +0,0 @@
{
"state": {
"accuweather__pressure_tendency": {
"steady": "Steady",
"rising": "Rising",
"falling": "Falling"
}
}
}

View file

@ -22,6 +22,17 @@
}
}
},
"entity": {
"sensor": {
"pressure_tendency": {
"state": {
"falling": "Falling",
"rising": "Rising",
"steady": "Steady"
}
}
}
},
"options": {
"step": {
"init": {

View file

@ -247,16 +247,17 @@ def _entry_dict(entry: er.RegistryEntry) -> dict[str, Any]:
"config_entry_id": entry.config_entry_id,
"device_id": entry.device_id,
"disabled_by": entry.disabled_by,
"has_entity_name": entry.has_entity_name,
"entity_category": entry.entity_category,
"entity_id": entry.entity_id,
"has_entity_name": entry.has_entity_name,
"hidden_by": entry.hidden_by,
"icon": entry.icon,
"id": entry.id,
"unique_id": entry.unique_id,
"name": entry.name,
"original_name": entry.original_name,
"platform": entry.platform,
"translation_key": entry.translation_key,
"unique_id": entry.unique_id,
}

View file

@ -223,6 +223,7 @@ class EntityDescription:
icon: str | None = None
has_entity_name: bool = False
name: str | None = None
translation_key: str | None = None
unit_of_measurement: str | None = None
@ -290,6 +291,7 @@ class Entity(ABC):
_attr_should_poll: bool = True
_attr_state: StateType = STATE_UNKNOWN
_attr_supported_features: int | None = None
_attr_translation_key: str | None
_attr_unique_id: str | None = None
_attr_unit_of_measurement: str | None
@ -486,6 +488,15 @@ class Entity(ABC):
return self.entity_description.entity_category
return None
@property
def translation_key(self) -> str | None:
"""Return the translation key to translate the entity's states."""
if hasattr(self, "_attr_translation_key"):
return self._attr_translation_key
if hasattr(self, "entity_description"):
return self.entity_description.translation_key
return None
# 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

View file

@ -616,6 +616,7 @@ class EntityPlatform:
original_name=entity.name,
suggested_object_id=suggested_object_id,
supported_features=entity.supported_features,
translation_key=entity.translation_key,
unit_of_measurement=entity.unit_of_measurement,
)

View file

@ -61,7 +61,7 @@ SAVE_DELAY = 10
_LOGGER = logging.getLogger(__name__)
STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 8
STORAGE_VERSION_MINOR = 9
STORAGE_KEY = "core.entity_registry"
# Attributes relevant to describing entity
@ -125,6 +125,7 @@ class RegistryEntry:
original_icon: str | None = attr.ib(default=None)
original_name: str | None = attr.ib(default=None)
supported_features: int = attr.ib(default=0)
translation_key: str | None = attr.ib(default=None)
unit_of_measurement: str | None = attr.ib(default=None)
@domain.default
@ -234,6 +235,11 @@ class EntityRegistryStore(storage.Store):
continue
entity["device_class"] = None
if old_major_version == 1 and old_minor_version < 9:
# Version 1.9 adds translation_key
for entity in data["entities"]:
entity["translation_key"] = entity.get("translation_key")
if old_major_version > 1:
raise NotImplementedError
return data
@ -412,6 +418,7 @@ class EntityRegistry:
original_icon: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED,
supported_features: int | None | UndefinedType = UNDEFINED,
translation_key: str | None | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
) -> RegistryEntry:
"""Get entity. Create if it doesn't exist."""
@ -437,6 +444,7 @@ class EntityRegistry:
original_icon=original_icon,
original_name=original_name,
supported_features=supported_features,
translation_key=translation_key,
unit_of_measurement=unit_of_measurement,
)
@ -487,6 +495,7 @@ class EntityRegistry:
original_name=none_if_undefined(original_name),
platform=platform,
supported_features=none_if_undefined(supported_features) or 0,
translation_key=none_if_undefined(translation_key),
unique_id=unique_id,
unit_of_measurement=none_if_undefined(unit_of_measurement),
)
@ -592,13 +601,14 @@ class EntityRegistry:
name: str | None | UndefinedType = UNDEFINED,
new_entity_id: str | UndefinedType = UNDEFINED,
new_unique_id: str | UndefinedType = UNDEFINED,
options: EntityOptionsType | UndefinedType = UNDEFINED,
original_device_class: str | None | UndefinedType = UNDEFINED,
original_icon: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED,
supported_features: int | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
platform: str | None | UndefinedType = UNDEFINED,
options: EntityOptionsType | UndefinedType = UNDEFINED,
supported_features: int | UndefinedType = UNDEFINED,
translation_key: str | None | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
) -> RegistryEntry:
"""Private facing update properties method."""
old = self.entities[entity_id]
@ -640,13 +650,14 @@ class EntityRegistry:
("icon", icon),
("has_entity_name", has_entity_name),
("name", name),
("options", options),
("original_device_class", original_device_class),
("original_icon", original_icon),
("original_name", original_name),
("supported_features", supported_features),
("unit_of_measurement", unit_of_measurement),
("platform", platform),
("options", options),
("supported_features", supported_features),
("translation_key", translation_key),
("unit_of_measurement", unit_of_measurement),
):
if value is not UNDEFINED and value != getattr(old, attr_name):
new_values[attr_name] = value
@ -720,6 +731,7 @@ class EntityRegistry:
original_icon: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED,
supported_features: int | UndefinedType = UNDEFINED,
translation_key: str | None | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
) -> RegistryEntry:
"""Update properties of an entity."""
@ -742,6 +754,7 @@ class EntityRegistry:
original_icon=original_icon,
original_name=original_name,
supported_features=supported_features,
translation_key=translation_key,
unit_of_measurement=unit_of_measurement,
)
@ -831,6 +844,7 @@ class EntityRegistry:
original_name=entity["original_name"],
platform=entity["platform"],
supported_features=entity["supported_features"],
translation_key=entity["translation_key"],
unique_id=entity["unique_id"],
unit_of_measurement=entity["unit_of_measurement"],
)
@ -868,6 +882,7 @@ class EntityRegistry:
"original_name": entry.original_name,
"platform": entry.platform,
"supported_features": entry.supported_features,
"translation_key": entry.translation_key,
"unique_id": entry.unique_id,
"unit_of_measurement": entry.unit_of_measurement,
}

View file

@ -255,6 +255,13 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
),
)
},
vol.Optional("entity"): {
str: {
str: vol.Schema(
{vol.Optional("state"): {str: cv.string_with_no_html}}
)
}
},
}
)

View file

@ -1113,6 +1113,11 @@ class MockEntity(entity.Entity):
"""Info about supported features."""
return self._handle("supported_features")
@property
def translation_key(self):
"""Return the translation key."""
return self._handle("translation_key")
@property
def unique_id(self):
"""Return the unique ID of the entity."""

View file

@ -73,12 +73,13 @@ async def test_sensor_without_forecast(hass):
assert state.state == "falling"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:gauge"
assert state.attributes.get(ATTR_DEVICE_CLASS) == "accuweather__pressure_tendency"
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = registry.async_get("sensor.home_pressure_tendency")
assert entry
assert entry.unique_id == "0123456-pressuretendency"
assert entry.translation_key == "pressure_tendency"
state = hass.states.get("sensor.home_realfeel_temperature")
assert state

View file

@ -70,10 +70,11 @@ async def test_list_entities(hass, client):
"hidden_by": None,
"icon": None,
"id": ANY,
"unique_id": ANY,
"name": "Hello World",
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": ANY,
},
{
"area_id": None,
@ -86,10 +87,11 @@ async def test_list_entities(hass, client):
"hidden_by": None,
"icon": None,
"id": ANY,
"unique_id": ANY,
"name": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": ANY,
},
]
@ -124,10 +126,11 @@ async def test_list_entities(hass, client):
"hidden_by": None,
"icon": None,
"id": ANY,
"unique_id": ANY,
"name": "Hello World",
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": ANY,
},
]
@ -165,16 +168,17 @@ async def test_get_entity(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.name",
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"has_entity_name": False,
"name": "Hello World",
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
}
@ -196,16 +200,17 @@ async def test_get_entity(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.no_name",
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"has_entity_name": False,
"name": None,
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "6789",
}
@ -260,16 +265,17 @@ async def test_update_entity(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.world",
"has_entity_name": False,
"hidden_by": "user", # We exchange strings over the WS API, not enums
"icon": "icon:after update",
"id": ANY,
"has_entity_name": False,
"name": "after update",
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
}
}
@ -333,16 +339,17 @@ async def test_update_entity(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.world",
"has_entity_name": False,
"hidden_by": "user", # We exchange strings over the WS API, not enums
"icon": "icon:after update",
"id": ANY,
"has_entity_name": False,
"name": "after update",
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
},
"require_restart": True,
@ -371,16 +378,17 @@ async def test_update_entity(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.world",
"has_entity_name": False,
"hidden_by": "user", # We exchange strings over the WS API, not enums
"icon": "icon:after update",
"id": ANY,
"has_entity_name": False,
"name": "after update",
"options": {"sensor": {"unit_of_measurement": "beard_second"}},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
},
}
@ -421,16 +429,17 @@ async def test_update_entity_require_restart(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": entity_id,
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"hidden_by": None,
"has_entity_name": False,
"name": None,
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
},
"require_restart": True,
@ -527,16 +536,17 @@ async def test_update_entity_no_changes(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.world",
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"has_entity_name": False,
"name": "name of entity",
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
}
}
@ -614,16 +624,17 @@ async def test_update_entity_id(hass, client):
"disabled_by": None,
"entity_category": None,
"entity_id": "test_domain.planet",
"has_entity_name": False,
"hidden_by": None,
"icon": None,
"id": ANY,
"has_entity_name": False,
"name": None,
"options": {},
"original_device_class": None,
"original_icon": None,
"original_name": None,
"platform": "test_platform",
"translation_key": None,
"unique_id": "1234",
}
}

View file

@ -934,3 +934,23 @@ async def test_friendly_name(
assert len(hass.states.async_entity_ids()) == 1
state = hass.states.async_all()[0]
assert state.attributes.get(ATTR_FRIENDLY_NAME) == expected_friendly_name
async def test_translation_key(hass):
"""Test translation key property."""
mock_entity1 = entity.Entity()
mock_entity1.hass = hass
mock_entity1.entity_description = entity.EntityDescription(
key="abc", translation_key="from_entity_description"
)
mock_entity1.entity_id = "hello.world"
mock_entity1._attr_translation_key = "from_attr"
assert mock_entity1.translation_key == "from_attr"
mock_entity2 = entity.Entity()
mock_entity2.hass = hass
mock_entity2.entity_description = entity.EntityDescription(
key="abc", translation_key="from_entity_description"
)
mock_entity2.entity_id = "hello.world"
assert mock_entity2.translation_key == "from_entity_description"

View file

@ -1227,6 +1227,7 @@ async def test_entity_info_added_to_entity_registry(hass):
icon="nice:icon",
name="best name",
supported_features=5,
translation_key="my_translation_key",
unique_id="default",
unit_of_measurement=PERCENTAGE,
)
@ -1251,6 +1252,7 @@ async def test_entity_info_added_to_entity_registry(hass):
original_icon="nice:icon",
original_name="best name",
supported_features=5,
translation_key="my_translation_key",
unit_of_measurement=PERCENTAGE,
)

View file

@ -84,6 +84,7 @@ def test_get_or_create_updates_data(registry):
original_icon="initial-original_icon",
original_name="initial-original_name",
supported_features=5,
translation_key="initial-translation_key",
unit_of_measurement="initial-unit_of_measurement",
)
@ -106,6 +107,7 @@ def test_get_or_create_updates_data(registry):
original_icon="initial-original_icon",
original_name="initial-original_name",
supported_features=5,
translation_key="initial-translation_key",
unit_of_measurement="initial-unit_of_measurement",
)
@ -126,6 +128,7 @@ def test_get_or_create_updates_data(registry):
original_icon="updated-original_icon",
original_name="updated-original_name",
supported_features=10,
translation_key="updated-translation_key",
unit_of_measurement="updated-unit_of_measurement",
)
@ -149,6 +152,7 @@ def test_get_or_create_updates_data(registry):
original_icon="updated-original_icon",
original_name="updated-original_name",
supported_features=10,
translation_key="updated-translation_key",
unit_of_measurement="updated-unit_of_measurement",
)
@ -167,6 +171,7 @@ def test_get_or_create_updates_data(registry):
original_icon=None,
original_name=None,
supported_features=None,
translation_key=None,
unit_of_measurement=None,
)
@ -190,6 +195,7 @@ def test_get_or_create_updates_data(registry):
original_icon=None,
original_name=None,
supported_features=0, # supported_features is stored as an int
translation_key=None,
unit_of_measurement=None,
)
@ -242,6 +248,7 @@ async def test_loading_saving_data(hass, registry):
original_icon="hass:original-icon",
original_name="Original Name",
supported_features=5,
translation_key="initial-translation_key",
unit_of_measurement="initial-unit_of_measurement",
)
registry.async_update_entity(
@ -287,6 +294,7 @@ async def test_loading_saving_data(hass, registry):
assert new_entry2.original_icon == "hass:original-icon"
assert new_entry2.original_name == "Original Name"
assert new_entry2.supported_features == 5
assert new_entry2.translation_key == "initial-translation_key"
assert new_entry2.unit_of_measurement == "initial-unit_of_measurement"