Change Entity.name default to UNDEFINED (#94574)
* Change Entity.name default to UNDEFINED * Update typing * Update Pylint plugin * Update TTS test
This commit is contained in:
parent
d369d679c7
commit
334dacc322
17 changed files with 277 additions and 67 deletions
|
@ -1,7 +1,7 @@
|
||||||
"""Support for Adax wifi-enabled home heaters."""
|
"""Support for Adax wifi-enabled home heaters."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
from adax import Adax
|
from adax import Adax
|
||||||
from adax_local import Adax as AdaxLocal
|
from adax_local import Adax as AdaxLocal
|
||||||
|
@ -79,7 +79,10 @@ class AdaxDevice(ClimateEntity):
|
||||||
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, heater_data["id"])},
|
identifiers={(DOMAIN, heater_data["id"])},
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, adax
|
||||||
|
# should be updated to set has_entity_name = True, and set the entity
|
||||||
|
# name to None
|
||||||
|
name=cast(str | None, self.name),
|
||||||
manufacturer="Adax",
|
manufacturer="Adax",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.typing import UndefinedType
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +16,7 @@ def service_signal(service: str, *args: str) -> str:
|
||||||
def log_update_error(
|
def log_update_error(
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
action: str,
|
action: str,
|
||||||
name: str | None,
|
name: str | UndefinedType | None,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
error: Exception,
|
error: Exception,
|
||||||
level: int = logging.ERROR,
|
level: int = logging.ERROR,
|
||||||
|
|
|
@ -330,7 +330,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||||
trace_config: ConfigType,
|
trace_config: ConfigType,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize an automation entity."""
|
"""Initialize an automation entity."""
|
||||||
self._attr_name = name
|
self._name = name
|
||||||
self._trigger_config = trigger_config
|
self._trigger_config = trigger_config
|
||||||
self._async_detach_triggers: CALLBACK_TYPE | None = None
|
self._async_detach_triggers: CALLBACK_TYPE | None = None
|
||||||
self._cond_func = cond_func
|
self._cond_func = cond_func
|
||||||
|
@ -348,6 +348,11 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||||
self._trace_config = trace_config
|
self._trace_config = trace_config
|
||||||
self._attr_unique_id = automation_id
|
self._attr_unique_id = automation_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the entity state attributes."""
|
"""Return the entity state attributes."""
|
||||||
|
|
|
@ -115,10 +115,15 @@ class CupsSensor(SensorEntity):
|
||||||
def __init__(self, data: CupsData, printer_name: str) -> None:
|
def __init__(self, data: CupsData, printer_name: str) -> None:
|
||||||
"""Initialize the CUPS sensor."""
|
"""Initialize the CUPS sensor."""
|
||||||
self.data = data
|
self.data = data
|
||||||
self._attr_name = printer_name
|
self._name = printer_name
|
||||||
self._printer: dict[str, Any] | None = None
|
self._printer: dict[str, Any] | None = None
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
|
@ -149,7 +154,6 @@ class CupsSensor(SensorEntity):
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""Get the latest data and updates the states."""
|
"""Get the latest data and updates the states."""
|
||||||
self.data.update()
|
self.data.update()
|
||||||
assert self.name is not None
|
|
||||||
assert self.data.printers is not None
|
assert self.data.printers is not None
|
||||||
self._printer = self.data.printers.get(self.name)
|
self._printer = self.data.printers.get(self.name)
|
||||||
self._attr_available = self.data.available
|
self._attr_available = self.data.available
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""Base DirecTV Entity."""
|
"""Base DirecTV Entity."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from directv import DIRECTV
|
from directv import DIRECTV
|
||||||
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
@ -24,7 +26,10 @@ class DIRECTVEntity(Entity):
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
identifiers={(DOMAIN, self._device_id)},
|
identifiers={(DOMAIN, self._device_id)},
|
||||||
manufacturer=self.dtv.device.info.brand,
|
manufacturer=self.dtv.device.info.brand,
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, directv
|
||||||
|
# should be updated to set has_entity_name = True, and set the entity
|
||||||
|
# name to None
|
||||||
|
name=cast(str | None, self.name),
|
||||||
sw_version=self.dtv.device.info.version,
|
sw_version=self.dtv.device.info.version,
|
||||||
via_device=(DOMAIN, self.dtv.device.info.receiver_id),
|
via_device=(DOMAIN, self.dtv.device.info.receiver_id),
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable, Coroutine
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Concatenate, ParamSpec, TypeVar
|
from typing import Any, Concatenate, ParamSpec, TypeVar, cast
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from iaqualink.client import AqualinkClient
|
from iaqualink.client import AqualinkClient
|
||||||
|
@ -243,6 +243,8 @@ class AqualinkEntity(Entity):
|
||||||
identifiers={(DOMAIN, self.unique_id)},
|
identifiers={(DOMAIN, self.unique_id)},
|
||||||
manufacturer=self.dev.manufacturer,
|
manufacturer=self.dev.manufacturer,
|
||||||
model=self.dev.model,
|
model=self.dev.model,
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, iaqualink
|
||||||
|
# should be updated to set has_entity_name = True
|
||||||
|
name=cast(str | None, self.name),
|
||||||
via_device=(DOMAIN, self.dev.system.serial),
|
via_device=(DOMAIN, self.dev.system.serial),
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
@ -28,7 +28,9 @@ class KaleidescapeEntity(Entity):
|
||||||
self._attr_name = f"{KALEIDESCAPE_NAME} {device.system.friendly_name}"
|
self._attr_name = f"{KALEIDESCAPE_NAME} {device.system.friendly_name}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(KALEIDESCAPE_DOMAIN, self._device.serial_number)},
|
identifiers={(KALEIDESCAPE_DOMAIN, self._device.serial_number)},
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, kaleidescape
|
||||||
|
# should be updated to set has_entity_name = True
|
||||||
|
name=cast(str | None, self.name),
|
||||||
model=self._device.system.type,
|
model=self._device.system.type,
|
||||||
manufacturer=KALEIDESCAPE_NAME,
|
manufacturer=KALEIDESCAPE_NAME,
|
||||||
sw_version=f"{self._device.system.kos_version}",
|
sw_version=f"{self._device.system.kos_version}",
|
||||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Concatenate, ParamSpec, TypeVar
|
from typing import Any, Concatenate, ParamSpec, TypeVar, cast
|
||||||
|
|
||||||
import plexapi.exceptions
|
import plexapi.exceptions
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
@ -535,7 +535,10 @@ class PlexMediaPlayer(MediaPlayerEntity):
|
||||||
identifiers={(DOMAIN, self.machine_identifier)},
|
identifiers={(DOMAIN, self.machine_identifier)},
|
||||||
manufacturer=self.device_platform or "Plex",
|
manufacturer=self.device_platform or "Plex",
|
||||||
model=self.device_product or self.device_make,
|
model=self.device_product or self.device_make,
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, plex
|
||||||
|
# should be updated to set has_entity_name = True, and set the entity
|
||||||
|
# name to None
|
||||||
|
name=cast(str | None, self.name),
|
||||||
sw_version=self.device_version,
|
sw_version=self.device_version,
|
||||||
via_device=(DOMAIN, self.plex_server.machine_identifier),
|
via_device=(DOMAIN, self.plex_server.machine_identifier),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
from roonapi import split_media_path
|
from roonapi import split_media_path
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -159,7 +159,10 @@ class RoonDevice(MediaPlayerEntity):
|
||||||
dev_model = self.player_data["source_controls"][0].get("display_name")
|
dev_model = self.player_data["source_controls"][0].get("display_name")
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
identifiers={(DOMAIN, self.unique_id)},
|
identifiers={(DOMAIN, self.unique_id)},
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, roon
|
||||||
|
# should be updated to set has_entity_name = True, and set the entity
|
||||||
|
# name to None
|
||||||
|
name=cast(str | None, self.name),
|
||||||
manufacturer="RoonLabs",
|
manufacturer="RoonLabs",
|
||||||
model=dev_model,
|
model=dev_model,
|
||||||
via_device=(DOMAIN, self._server.roon_id),
|
via_device=(DOMAIN, self._server.roon_id),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""Base SamsungTV Entity."""
|
"""Base SamsungTV Entity."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_MAC, CONF_MODEL, CONF_NAME
|
from homeassistant.const import CONF_MAC, CONF_MODEL, CONF_NAME
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
@ -20,7 +22,9 @@ class SamsungTVEntity(Entity):
|
||||||
self._attr_name = config_entry.data.get(CONF_NAME)
|
self._attr_name = config_entry.data.get(CONF_NAME)
|
||||||
self._attr_unique_id = config_entry.unique_id
|
self._attr_unique_id = config_entry.unique_id
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
name=self.name,
|
# Instead of setting the device name to the entity name, samsungtv
|
||||||
|
# should be updated to set has_entity_name = True
|
||||||
|
name=cast(str | None, self.name),
|
||||||
manufacturer=config_entry.data.get(CONF_MANUFACTURER),
|
manufacturer=config_entry.data.get(CONF_MANUFACTURER),
|
||||||
model=config_entry.data.get(CONF_MODEL),
|
model=config_entry.data.get(CONF_MODEL),
|
||||||
)
|
)
|
||||||
|
|
|
@ -44,7 +44,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.network import get_url
|
from homeassistant.helpers.network import get_url
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import UNDEFINED, ConfigType
|
||||||
from homeassistant.util import dt as dt_util, language as language_util
|
from homeassistant.util import dt as dt_util, language as language_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -610,7 +610,7 @@ class SpeechManager:
|
||||||
|
|
||||||
async def get_tts_data() -> str:
|
async def get_tts_data() -> str:
|
||||||
"""Handle data available."""
|
"""Handle data available."""
|
||||||
if engine_instance.name is None:
|
if engine_instance.name is None or engine_instance.name is UNDEFINED:
|
||||||
raise HomeAssistantError("TTS engine name is not set.")
|
raise HomeAssistantError("TTS engine name is not set.")
|
||||||
|
|
||||||
if isinstance(engine_instance, Provider):
|
if isinstance(engine_instance, Provider):
|
||||||
|
|
|
@ -15,6 +15,7 @@ from homeassistant.const import EntityCategory, Platform, UnitOfMass, UnitOfTemp
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import UndefinedType
|
||||||
|
|
||||||
from .core import discovery
|
from .core import discovery
|
||||||
from .core.const import (
|
from .core.const import (
|
||||||
|
@ -334,7 +335,7 @@ class ZhaNumber(ZhaEntity, NumberEntity):
|
||||||
return super().native_step
|
return super().native_step
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str | None:
|
def name(self) -> str | UndefinedType | None:
|
||||||
"""Return the name of the number entity."""
|
"""Return the name of the number entity."""
|
||||||
description = self._analog_output_cluster_handler.description
|
description = self._analog_output_cluster_handler.description
|
||||||
if description is not None and len(description) > 0:
|
if description is not None and len(description) > 0:
|
||||||
|
|
|
@ -258,6 +258,9 @@ class Entity(ABC):
|
||||||
# it should be using async_write_ha_state.
|
# it should be using async_write_ha_state.
|
||||||
_async_update_ha_state_reported = False
|
_async_update_ha_state_reported = False
|
||||||
|
|
||||||
|
# If we reported this entity is implicitly using device name
|
||||||
|
_implicit_device_name_reported = False
|
||||||
|
|
||||||
# If we reported this entity was added without its platform set
|
# If we reported this entity was added without its platform set
|
||||||
_no_platform_reported = False
|
_no_platform_reported = False
|
||||||
|
|
||||||
|
@ -319,6 +322,53 @@ class Entity(ABC):
|
||||||
"""Return a unique ID."""
|
"""Return a unique ID."""
|
||||||
return self._attr_unique_id
|
return self._attr_unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_device_name(self) -> bool:
|
||||||
|
"""Return if this entity does not have its own name.
|
||||||
|
|
||||||
|
Should be True if the entity represents the single main feature of a device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def report_implicit_device_name() -> None:
|
||||||
|
"""Report entities which use implicit device name."""
|
||||||
|
if self._implicit_device_name_reported:
|
||||||
|
return
|
||||||
|
report_issue = self._suggest_report_issue()
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"Entity %s (%s) is implicitly using device name by not setting its "
|
||||||
|
"name. Instead, the name should be set to None, please %s"
|
||||||
|
),
|
||||||
|
self.entity_id,
|
||||||
|
type(self),
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
self._implicit_device_name_reported = True
|
||||||
|
|
||||||
|
if hasattr(self, "_attr_name"):
|
||||||
|
return not self._attr_name
|
||||||
|
|
||||||
|
if name_translation_key := self._name_translation_key():
|
||||||
|
if name_translation_key in self.platform.platform_translations:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if hasattr(self, "entity_description"):
|
||||||
|
if not (name := self.entity_description.name):
|
||||||
|
return True
|
||||||
|
if name is UNDEFINED:
|
||||||
|
# Backwards compatibility with leaving EntityDescription.name unassigned
|
||||||
|
# for device name.
|
||||||
|
# Deprecated in HA Core 2023.6, remove in HA Core 2023.9
|
||||||
|
report_implicit_device_name()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if self.name is UNDEFINED:
|
||||||
|
# Backwards compatibility with not overriding name property for device name.
|
||||||
|
# Deprecated in HA Core 2023.6, remove in HA Core 2023.9
|
||||||
|
report_implicit_device_name()
|
||||||
|
return True
|
||||||
|
return not self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_entity_name(self) -> bool:
|
def has_entity_name(self) -> bool:
|
||||||
"""Return if the name of the entity is describing only the entity itself."""
|
"""Return if the name of the entity is describing only the entity itself."""
|
||||||
|
@ -344,16 +394,23 @@ class Entity(ABC):
|
||||||
"""Return True if an unnamed entity should be named by its device class."""
|
"""Return True if an unnamed entity should be named by its device class."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _name_translation_key(self) -> str | None:
|
||||||
|
"""Return translation key for entity name."""
|
||||||
|
if self.translation_key is None:
|
||||||
|
return None
|
||||||
|
return (
|
||||||
|
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||||
|
f".{self.translation_key}.name"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str | None:
|
def name(self) -> str | UndefinedType | None:
|
||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
if hasattr(self, "_attr_name"):
|
if hasattr(self, "_attr_name"):
|
||||||
return self._attr_name
|
return self._attr_name
|
||||||
if self.translation_key is not None and self.has_entity_name:
|
if self.has_entity_name and (
|
||||||
name_translation_key = (
|
name_translation_key := self._name_translation_key()
|
||||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
):
|
||||||
f".{self.translation_key}.name"
|
|
||||||
)
|
|
||||||
if name_translation_key in self.platform.platform_translations:
|
if name_translation_key in self.platform.platform_translations:
|
||||||
name: str = self.platform.platform_translations[name_translation_key]
|
name: str = self.platform.platform_translations[name_translation_key]
|
||||||
return name
|
return name
|
||||||
|
@ -361,15 +418,13 @@ class Entity(ABC):
|
||||||
description_name = self.entity_description.name
|
description_name = self.entity_description.name
|
||||||
if description_name is UNDEFINED and self._default_to_device_class_name():
|
if description_name is UNDEFINED and self._default_to_device_class_name():
|
||||||
return self._device_class_name()
|
return self._device_class_name()
|
||||||
if description_name is not UNDEFINED:
|
return description_name
|
||||||
return description_name
|
|
||||||
return None
|
|
||||||
|
|
||||||
# The entity has no name set by _attr_name, translation_key or entity_description
|
# The entity has no name set by _attr_name, translation_key or entity_description
|
||||||
# Check if the entity should be named by its device class
|
# Check if the entity should be named by its device class
|
||||||
if self._default_to_device_class_name():
|
if self._default_to_device_class_name():
|
||||||
return self._device_class_name()
|
return self._device_class_name()
|
||||||
return None
|
return UNDEFINED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> StateType:
|
def state(self) -> StateType:
|
||||||
|
@ -653,16 +708,20 @@ class Entity(ABC):
|
||||||
If has_entity_name is False, this returns self.name
|
If has_entity_name is False, this returns self.name
|
||||||
If has_entity_name is True, this returns device.name + self.name
|
If has_entity_name is True, this returns device.name + self.name
|
||||||
"""
|
"""
|
||||||
|
name = self.name
|
||||||
|
if name is UNDEFINED:
|
||||||
|
name = None
|
||||||
|
|
||||||
if not self.has_entity_name or not self.registry_entry:
|
if not self.has_entity_name or not self.registry_entry:
|
||||||
return self.name
|
return name
|
||||||
|
|
||||||
device_registry = dr.async_get(self.hass)
|
device_registry = dr.async_get(self.hass)
|
||||||
if not (device_id := self.registry_entry.device_id) or not (
|
if not (device_id := self.registry_entry.device_id) or not (
|
||||||
device_entry := device_registry.async_get(device_id)
|
device_entry := device_registry.async_get(device_id)
|
||||||
):
|
):
|
||||||
return self.name
|
return name
|
||||||
|
|
||||||
if not (name := self.name):
|
if self.use_device_name:
|
||||||
return device_entry.name_by_user or device_entry.name
|
return device_entry.name_by_user or device_entry.name
|
||||||
return f"{device_entry.name_by_user or device_entry.name} {name}"
|
return f"{device_entry.name_by_user or device_entry.name} {name}"
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ from .device_registry import DeviceRegistry
|
||||||
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
|
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
|
||||||
from .event import async_call_later, async_track_time_interval
|
from .event import async_call_later, async_track_time_interval
|
||||||
from .issue_registry import IssueSeverity, async_create_issue
|
from .issue_registry import IssueSeverity, async_create_issue
|
||||||
from .typing import ConfigType, DiscoveryInfoType
|
from .typing import UNDEFINED, ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .entity import Entity
|
from .entity import Entity
|
||||||
|
@ -552,6 +552,10 @@ class EntityPlatform:
|
||||||
suggested_object_id: str | None = None
|
suggested_object_id: str | None = None
|
||||||
generate_new_entity_id = False
|
generate_new_entity_id = False
|
||||||
|
|
||||||
|
entity_name = entity.name
|
||||||
|
if entity_name is UNDEFINED:
|
||||||
|
entity_name = None
|
||||||
|
|
||||||
# Get entity_id from unique ID registration
|
# Get entity_id from unique ID registration
|
||||||
if entity.unique_id is not None:
|
if entity.unique_id is not None:
|
||||||
registered_entity_id = entity_registry.async_get_entity_id(
|
registered_entity_id = entity_registry.async_get_entity_id(
|
||||||
|
@ -645,12 +649,12 @@ class EntityPlatform:
|
||||||
else:
|
else:
|
||||||
if device and entity.has_entity_name:
|
if device and entity.has_entity_name:
|
||||||
device_name = device.name_by_user or device.name
|
device_name = device.name_by_user or device.name
|
||||||
if not entity.name:
|
if entity.use_device_name:
|
||||||
suggested_object_id = device_name
|
suggested_object_id = device_name
|
||||||
else:
|
else:
|
||||||
suggested_object_id = f"{device_name} {entity.name}"
|
suggested_object_id = f"{device_name} {entity_name}"
|
||||||
if not suggested_object_id:
|
if not suggested_object_id:
|
||||||
suggested_object_id = entity.name
|
suggested_object_id = entity_name
|
||||||
|
|
||||||
if self.entity_namespace is not None:
|
if self.entity_namespace is not None:
|
||||||
suggested_object_id = f"{self.entity_namespace} {suggested_object_id}"
|
suggested_object_id = f"{self.entity_namespace} {suggested_object_id}"
|
||||||
|
@ -678,7 +682,7 @@ class EntityPlatform:
|
||||||
known_object_ids=self.entities.keys(),
|
known_object_ids=self.entities.keys(),
|
||||||
original_device_class=entity.device_class,
|
original_device_class=entity.device_class,
|
||||||
original_icon=entity.icon,
|
original_icon=entity.icon,
|
||||||
original_name=entity.name,
|
original_name=entity_name,
|
||||||
suggested_object_id=suggested_object_id,
|
suggested_object_id=suggested_object_id,
|
||||||
supported_features=entity.supported_features,
|
supported_features=entity.supported_features,
|
||||||
translation_key=entity.translation_key,
|
translation_key=entity.translation_key,
|
||||||
|
@ -705,7 +709,7 @@ class EntityPlatform:
|
||||||
# Generate entity ID
|
# Generate entity ID
|
||||||
if entity.entity_id is None or generate_new_entity_id:
|
if entity.entity_id is None or generate_new_entity_id:
|
||||||
suggested_object_id = (
|
suggested_object_id = (
|
||||||
suggested_object_id or entity.name or DEVICE_DEFAULT_NAME
|
suggested_object_id or entity_name or DEVICE_DEFAULT_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.entity_namespace is not None:
|
if self.entity_namespace is not None:
|
||||||
|
@ -732,7 +736,7 @@ class EntityPlatform:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"Not adding entity %s because it's disabled",
|
"Not adding entity %s because it's disabled",
|
||||||
entry.name
|
entry.name
|
||||||
or entity.name
|
or entity_name
|
||||||
or f'"{self.platform_name} {entity.unique_id}"',
|
or f'"{self.platform_name} {entity.unique_id}"',
|
||||||
)
|
)
|
||||||
entity.add_to_platform_abort()
|
entity.add_to_platform_abort()
|
||||||
|
|
|
@ -573,7 +573,7 @@ _ENTITY_MATCH: list[TypeHintMatch] = [
|
||||||
),
|
),
|
||||||
TypeHintMatch(
|
TypeHintMatch(
|
||||||
function_name="name",
|
function_name="name",
|
||||||
return_type=["str", None],
|
return_type=["str", "UndefinedType", None],
|
||||||
),
|
),
|
||||||
TypeHintMatch(
|
TypeHintMatch(
|
||||||
function_name="state",
|
function_name="state",
|
||||||
|
|
|
@ -22,6 +22,7 @@ from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant, State
|
from homeassistant.core import HomeAssistant, State
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.typing import UNDEFINED
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.network import normalize_url
|
from homeassistant.util.network import normalize_url
|
||||||
|
@ -68,7 +69,7 @@ async def test_default_entity_attributes() -> None:
|
||||||
entity = DefaultEntity()
|
entity = DefaultEntity()
|
||||||
|
|
||||||
assert entity.hass is None
|
assert entity.hass is None
|
||||||
assert entity.name is None
|
assert entity.name is UNDEFINED
|
||||||
assert entity.default_language == DEFAULT_LANG
|
assert entity.default_language == DEFAULT_LANG
|
||||||
assert entity.supported_languages == SUPPORT_LANGUAGES
|
assert entity.supported_languages == SUPPORT_LANGUAGES
|
||||||
assert entity.supported_options is None
|
assert entity.supported_options is None
|
||||||
|
|
|
@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import Context, HomeAssistant, HomeAssistantError
|
from homeassistant.core import Context, HomeAssistant, HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity, entity_registry as er
|
||||||
|
from homeassistant.helpers.typing import UNDEFINED
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
@ -948,39 +949,24 @@ async def test_entity_description_fallback() -> None:
|
||||||
assert getattr(ent, field.name) == getattr(ent_with_description, field.name)
|
assert getattr(ent, field.name) == getattr(ent_with_description, field.name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
async def _test_friendly_name(
|
||||||
("has_entity_name", "entity_name", "expected_friendly_name"),
|
|
||||||
(
|
|
||||||
(False, "Entity Blu", "Entity Blu"),
|
|
||||||
(False, None, None),
|
|
||||||
(True, "Entity Blu", "Device Bla Entity Blu"),
|
|
||||||
(True, None, "Device Bla"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
async def test_friendly_name(
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
ent: entity.Entity,
|
||||||
has_entity_name: bool,
|
has_entity_name: bool,
|
||||||
entity_name: str | None,
|
entity_name: str | None,
|
||||||
expected_friendly_name: str | None,
|
expected_friendly_name: str | None,
|
||||||
|
warn_implicit_name: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test entity_id is influenced by entity name."""
|
"""Test friendly name."""
|
||||||
|
|
||||||
|
expected_warning = (
|
||||||
|
f"Entity {ent.entity_id} ({type(ent)}) is implicitly using device name"
|
||||||
|
)
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Mock setup entry method."""
|
"""Mock setup entry method."""
|
||||||
async_add_entities(
|
async_add_entities([ent])
|
||||||
[
|
|
||||||
MockEntity(
|
|
||||||
unique_id="qwer",
|
|
||||||
device_info={
|
|
||||||
"identifiers": {("hue", "1234")},
|
|
||||||
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
|
||||||
"name": "Device Bla",
|
|
||||||
},
|
|
||||||
has_entity_name=has_entity_name,
|
|
||||||
name=entity_name,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
||||||
|
@ -995,6 +981,132 @@ async def test_friendly_name(
|
||||||
assert len(hass.states.async_entity_ids()) == 1
|
assert len(hass.states.async_entity_ids()) == 1
|
||||||
state = hass.states.async_all()[0]
|
state = hass.states.async_all()[0]
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == expected_friendly_name
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == expected_friendly_name
|
||||||
|
assert (expected_warning in caplog.text) is warn_implicit_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"),
|
||||||
|
(
|
||||||
|
(False, "Entity Blu", "Entity Blu", False),
|
||||||
|
(False, None, None, False),
|
||||||
|
(True, "Entity Blu", "Device Bla Entity Blu", False),
|
||||||
|
(True, None, "Device Bla", False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_friendly_name_attr(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
has_entity_name: bool,
|
||||||
|
entity_name: str | None,
|
||||||
|
expected_friendly_name: str | None,
|
||||||
|
warn_implicit_name: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Test friendly name when the entity uses _attr_*."""
|
||||||
|
|
||||||
|
ent = MockEntity(
|
||||||
|
unique_id="qwer",
|
||||||
|
device_info={
|
||||||
|
"identifiers": {("hue", "1234")},
|
||||||
|
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
||||||
|
"name": "Device Bla",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
ent._attr_has_entity_name = has_entity_name
|
||||||
|
ent._attr_name = entity_name
|
||||||
|
await _test_friendly_name(
|
||||||
|
hass,
|
||||||
|
caplog,
|
||||||
|
ent,
|
||||||
|
has_entity_name,
|
||||||
|
entity_name,
|
||||||
|
expected_friendly_name,
|
||||||
|
warn_implicit_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"),
|
||||||
|
(
|
||||||
|
(False, "Entity Blu", "Entity Blu", False),
|
||||||
|
(False, None, None, False),
|
||||||
|
(False, UNDEFINED, None, False),
|
||||||
|
(True, "Entity Blu", "Device Bla Entity Blu", False),
|
||||||
|
(True, None, "Device Bla", False),
|
||||||
|
(True, UNDEFINED, "Device Bla", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_friendly_name_description(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
has_entity_name: bool,
|
||||||
|
entity_name: str | None,
|
||||||
|
expected_friendly_name: str | None,
|
||||||
|
warn_implicit_name: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Test friendly name when the entity has an entity description."""
|
||||||
|
|
||||||
|
ent = MockEntity(
|
||||||
|
unique_id="qwer",
|
||||||
|
device_info={
|
||||||
|
"identifiers": {("hue", "1234")},
|
||||||
|
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
||||||
|
"name": "Device Bla",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
ent.entity_description = entity.EntityDescription(
|
||||||
|
"test", has_entity_name=has_entity_name, name=entity_name
|
||||||
|
)
|
||||||
|
await _test_friendly_name(
|
||||||
|
hass,
|
||||||
|
caplog,
|
||||||
|
ent,
|
||||||
|
has_entity_name,
|
||||||
|
entity_name,
|
||||||
|
expected_friendly_name,
|
||||||
|
warn_implicit_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"),
|
||||||
|
(
|
||||||
|
(False, "Entity Blu", "Entity Blu", False),
|
||||||
|
(False, None, None, False),
|
||||||
|
(False, UNDEFINED, None, False),
|
||||||
|
(True, "Entity Blu", "Device Bla Entity Blu", False),
|
||||||
|
(True, None, "Device Bla", False),
|
||||||
|
(True, UNDEFINED, "Device Bla", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_friendly_name_property(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
has_entity_name: bool,
|
||||||
|
entity_name: str | None,
|
||||||
|
expected_friendly_name: str | None,
|
||||||
|
warn_implicit_name: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Test friendly name when the entity has overridden the name property."""
|
||||||
|
|
||||||
|
ent = MockEntity(
|
||||||
|
unique_id="qwer",
|
||||||
|
device_info={
|
||||||
|
"identifiers": {("hue", "1234")},
|
||||||
|
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
||||||
|
"name": "Device Bla",
|
||||||
|
},
|
||||||
|
has_entity_name=has_entity_name,
|
||||||
|
name=entity_name,
|
||||||
|
)
|
||||||
|
await _test_friendly_name(
|
||||||
|
hass,
|
||||||
|
caplog,
|
||||||
|
ent,
|
||||||
|
has_entity_name,
|
||||||
|
entity_name,
|
||||||
|
expected_friendly_name,
|
||||||
|
warn_implicit_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1028,7 +1140,7 @@ async def test_friendly_name_updated(
|
||||||
expected_friendly_name2: str,
|
expected_friendly_name2: str,
|
||||||
expected_friendly_name3: str,
|
expected_friendly_name3: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test entity_id is influenced by entity name."""
|
"""Test friendly name is updated when device or entity registry updates."""
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Mock setup entry method."""
|
"""Mock setup entry method."""
|
||||||
|
|
Loading…
Add table
Reference in a new issue