mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Refactor EntityPlatform (#147927)
This commit is contained in:
parent
5a771b501d
commit
dd399ef59f
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
from errno import EHOSTUNREACH, EIO
|
from errno import EHOSTUNREACH, EIO
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
@ -52,9 +52,8 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
from homeassistant.exceptions import HomeAssistantError, TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, template as template_helper
|
from homeassistant.helpers import config_validation as cv, template as template_helper
|
||||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
from homeassistant.helpers.entity_platform import PlatformData
|
||||||
from homeassistant.helpers.httpx_client import get_async_client
|
from homeassistant.helpers.httpx_client import get_async_client
|
||||||
from homeassistant.setup import async_prepare_setup_platform
|
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from .camera import GenericCamera, generate_auth
|
from .camera import GenericCamera, generate_auth
|
||||||
@ -569,18 +568,9 @@ async def ws_start_preview(
|
|||||||
)
|
)
|
||||||
user_input = flow.preview_image_settings
|
user_input = flow.preview_image_settings
|
||||||
|
|
||||||
# Create an EntityPlatform, needed for name translations
|
# Create PlatformData, needed for name translations
|
||||||
platform = await async_prepare_setup_platform(hass, {}, CAMERA_DOMAIN, DOMAIN)
|
platform_data = PlatformData(hass=hass, domain=CAMERA_DOMAIN, platform_name=DOMAIN)
|
||||||
entity_platform = EntityPlatform(
|
await platform_data.async_load_translations()
|
||||||
hass=hass,
|
|
||||||
logger=_LOGGER,
|
|
||||||
domain=CAMERA_DOMAIN,
|
|
||||||
platform_name=DOMAIN,
|
|
||||||
platform=platform,
|
|
||||||
scan_interval=timedelta(seconds=3600),
|
|
||||||
entity_namespace=None,
|
|
||||||
)
|
|
||||||
await entity_platform.async_load_translations()
|
|
||||||
|
|
||||||
ha_still_url = None
|
ha_still_url = None
|
||||||
ha_stream_url = None
|
ha_stream_url = None
|
||||||
|
@ -387,7 +387,9 @@ class NumberEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
|
|
||||||
if (translation_key := self._unit_of_measurement_translation_key) and (
|
if (translation_key := self._unit_of_measurement_translation_key) and (
|
||||||
unit_of_measurement
|
unit_of_measurement
|
||||||
:= self.platform.default_language_platform_translations.get(translation_key)
|
:= self.platform_data.default_language_platform_translations.get(
|
||||||
|
translation_key
|
||||||
|
)
|
||||||
):
|
):
|
||||||
if native_unit_of_measurement is not None:
|
if native_unit_of_measurement is not None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -523,7 +523,9 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
# Fourth priority: Unit translation
|
# Fourth priority: Unit translation
|
||||||
if (translation_key := self._unit_of_measurement_translation_key) and (
|
if (translation_key := self._unit_of_measurement_translation_key) and (
|
||||||
unit_of_measurement
|
unit_of_measurement
|
||||||
:= self.platform.default_language_platform_translations.get(translation_key)
|
:= self.platform_data.default_language_platform_translations.get(
|
||||||
|
translation_key
|
||||||
|
)
|
||||||
):
|
):
|
||||||
if native_unit_of_measurement is not None:
|
if native_unit_of_measurement is not None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -12,7 +11,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
from homeassistant.helpers.entity_platform import PlatformData
|
||||||
from homeassistant.helpers.schema_config_entry_flow import (
|
from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
SchemaCommonFlowHandler,
|
SchemaCommonFlowHandler,
|
||||||
SchemaConfigFlowHandler,
|
SchemaConfigFlowHandler,
|
||||||
@ -24,7 +23,6 @@ from homeassistant.helpers.selector import (
|
|||||||
SelectSelectorConfig,
|
SelectSelectorConfig,
|
||||||
SelectSelectorMode,
|
SelectSelectorMode,
|
||||||
)
|
)
|
||||||
from homeassistant.setup import async_prepare_setup_platform
|
|
||||||
|
|
||||||
from .const import CONF_DISPLAY_OPTIONS, DOMAIN, OPTION_TYPES
|
from .const import CONF_DISPLAY_OPTIONS, DOMAIN, OPTION_TYPES
|
||||||
from .sensor import TimeDateSensor
|
from .sensor import TimeDateSensor
|
||||||
@ -99,18 +97,9 @@ async def ws_start_preview(
|
|||||||
"""Generate a preview."""
|
"""Generate a preview."""
|
||||||
validated = USER_SCHEMA(msg["user_input"])
|
validated = USER_SCHEMA(msg["user_input"])
|
||||||
|
|
||||||
# Create an EntityPlatform, needed for name translations
|
# Create PlatformData, needed for name translations
|
||||||
platform = await async_prepare_setup_platform(hass, {}, SENSOR_DOMAIN, DOMAIN)
|
platform_data = PlatformData(hass=hass, domain=SENSOR_DOMAIN, platform_name=DOMAIN)
|
||||||
entity_platform = EntityPlatform(
|
await platform_data.async_load_translations()
|
||||||
hass=hass,
|
|
||||||
logger=_LOGGER,
|
|
||||||
domain=SENSOR_DOMAIN,
|
|
||||||
platform_name=DOMAIN,
|
|
||||||
platform=platform,
|
|
||||||
scan_interval=timedelta(seconds=3600),
|
|
||||||
entity_namespace=None,
|
|
||||||
)
|
|
||||||
await entity_platform.async_load_translations()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
|
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
|
||||||
@ -123,7 +112,7 @@ async def ws_start_preview(
|
|||||||
|
|
||||||
preview_entity = TimeDateSensor(validated[CONF_DISPLAY_OPTIONS])
|
preview_entity = TimeDateSensor(validated[CONF_DISPLAY_OPTIONS])
|
||||||
preview_entity.hass = hass
|
preview_entity.hass = hass
|
||||||
preview_entity.platform = entity_platform
|
preview_entity.platform_data = platform_data
|
||||||
|
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(
|
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(
|
||||||
|
@ -66,7 +66,7 @@ from .typing import UNDEFINED, StateType, UndefinedType
|
|||||||
timer = time.time
|
timer = time.time
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .entity_platform import EntityPlatform
|
from .entity_platform import EntityPlatform, PlatformData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
SLOW_UPDATE_WARNING = 10
|
SLOW_UPDATE_WARNING = 10
|
||||||
@ -449,6 +449,7 @@ class Entity(
|
|||||||
# While not purely typed, it makes typehinting more useful for us
|
# While not purely typed, it makes typehinting more useful for us
|
||||||
# and removes the need for constant None checks or asserts.
|
# and removes the need for constant None checks or asserts.
|
||||||
platform: EntityPlatform = None # type: ignore[assignment]
|
platform: EntityPlatform = None # type: ignore[assignment]
|
||||||
|
platform_data: PlatformData = None # type: ignore[assignment]
|
||||||
|
|
||||||
# Entity description instance for this Entity
|
# Entity description instance for this Entity
|
||||||
entity_description: EntityDescription
|
entity_description: EntityDescription
|
||||||
@ -593,7 +594,7 @@ class Entity(
|
|||||||
return not self._attr_name
|
return not self._attr_name
|
||||||
if (
|
if (
|
||||||
name_translation_key := self._name_translation_key
|
name_translation_key := self._name_translation_key
|
||||||
) and name_translation_key in self.platform.platform_translations:
|
) and name_translation_key in self.platform_data.platform_translations:
|
||||||
return False
|
return False
|
||||||
if hasattr(self, "entity_description"):
|
if hasattr(self, "entity_description"):
|
||||||
return not self.entity_description.name
|
return not self.entity_description.name
|
||||||
@ -616,9 +617,9 @@ class Entity(
|
|||||||
if not self.has_entity_name:
|
if not self.has_entity_name:
|
||||||
return None
|
return None
|
||||||
device_class_key = self.device_class or "_"
|
device_class_key = self.device_class or "_"
|
||||||
platform = self.platform
|
platform_domain = self.platform_data.domain
|
||||||
name_translation_key = (
|
name_translation_key = (
|
||||||
f"component.{platform.domain}.entity_component.{device_class_key}.name"
|
f"component.{platform_domain}.entity_component.{device_class_key}.name"
|
||||||
)
|
)
|
||||||
return component_translations.get(name_translation_key)
|
return component_translations.get(name_translation_key)
|
||||||
|
|
||||||
@ -626,13 +627,13 @@ class Entity(
|
|||||||
def _object_id_device_class_name(self) -> str | None:
|
def _object_id_device_class_name(self) -> str | None:
|
||||||
"""Return a translated name of the entity based on its device class."""
|
"""Return a translated name of the entity based on its device class."""
|
||||||
return self._device_class_name_helper(
|
return self._device_class_name_helper(
|
||||||
self.platform.object_id_component_translations
|
self.platform_data.object_id_component_translations
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _device_class_name(self) -> str | None:
|
def _device_class_name(self) -> str | None:
|
||||||
"""Return a translated name of the entity based on its device class."""
|
"""Return a translated name of the entity based on its device class."""
|
||||||
return self._device_class_name_helper(self.platform.component_translations)
|
return self._device_class_name_helper(self.platform_data.component_translations)
|
||||||
|
|
||||||
def _default_to_device_class_name(self) -> bool:
|
def _default_to_device_class_name(self) -> bool:
|
||||||
"""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."""
|
||||||
@ -643,9 +644,9 @@ class Entity(
|
|||||||
"""Return translation key for entity name."""
|
"""Return translation key for entity name."""
|
||||||
if self.translation_key is None:
|
if self.translation_key is None:
|
||||||
return None
|
return None
|
||||||
platform = self.platform
|
platform_data = self.platform_data
|
||||||
return (
|
return (
|
||||||
f"component.{platform.platform_name}.entity.{platform.domain}"
|
f"component.{platform_data.platform_name}.entity.{platform_data.domain}"
|
||||||
f".{self.translation_key}.name"
|
f".{self.translation_key}.name"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -654,14 +655,14 @@ class Entity(
|
|||||||
"""Return translation key for unit of measurement."""
|
"""Return translation key for unit of measurement."""
|
||||||
if self.translation_key is None:
|
if self.translation_key is None:
|
||||||
return None
|
return None
|
||||||
if self.platform is None:
|
if self.platform_data is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Entity {type(self)} cannot have a translation key for "
|
f"Entity {type(self)} cannot have a translation key for "
|
||||||
"unit of measurement before being added to the entity platform"
|
"unit of measurement before being added to the entity platform"
|
||||||
)
|
)
|
||||||
platform = self.platform
|
platform_data = self.platform_data
|
||||||
return (
|
return (
|
||||||
f"component.{platform.platform_name}.entity.{platform.domain}"
|
f"component.{platform_data.platform_name}.entity.{platform_data.domain}"
|
||||||
f".{self.translation_key}.unit_of_measurement"
|
f".{self.translation_key}.unit_of_measurement"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -724,13 +725,13 @@ class Entity(
|
|||||||
# value.
|
# value.
|
||||||
type.__getattribute__(self.__class__, "name")
|
type.__getattribute__(self.__class__, "name")
|
||||||
is type.__getattribute__(Entity, "name")
|
is type.__getattribute__(Entity, "name")
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform_data guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2026.8
|
||||||
and self.platform
|
and self.platform_data
|
||||||
):
|
):
|
||||||
name = self._name_internal(
|
name = self._name_internal(
|
||||||
self._object_id_device_class_name,
|
self._object_id_device_class_name,
|
||||||
self.platform.object_id_platform_translations,
|
self.platform_data.object_id_platform_translations,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
name = self.name
|
name = self.name
|
||||||
@ -739,13 +740,13 @@ class Entity(
|
|||||||
@cached_property
|
@cached_property
|
||||||
def name(self) -> str | UndefinedType | None:
|
def name(self) -> str | UndefinedType | None:
|
||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform_data guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2026.8
|
||||||
if not self.platform:
|
if not self.platform_data:
|
||||||
return self._name_internal(None, {})
|
return self._name_internal(None, {})
|
||||||
return self._name_internal(
|
return self._name_internal(
|
||||||
self._device_class_name,
|
self._device_class_name,
|
||||||
self.platform.platform_translations,
|
self.platform_data.platform_translations,
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -986,7 +987,7 @@ class Entity(
|
|||||||
raise RuntimeError(f"Attribute hass is None for {self}")
|
raise RuntimeError(f"Attribute hass is None for {self}")
|
||||||
|
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2026.8
|
||||||
if self.platform is None and not self._no_platform_reported: # type: ignore[unreachable]
|
if self.platform is None and not self._no_platform_reported: # type: ignore[unreachable]
|
||||||
report_issue = self._suggest_report_issue() # type: ignore[unreachable]
|
report_issue = self._suggest_report_issue() # type: ignore[unreachable]
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
@ -1351,6 +1352,7 @@ class Entity(
|
|||||||
|
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
|
self.platform_data = platform.platform_data
|
||||||
self.parallel_updates = parallel_updates
|
self.parallel_updates = parallel_updates
|
||||||
self._platform_state = EntityPlatformState.ADDING
|
self._platform_state = EntityPlatformState.ADDING
|
||||||
|
|
||||||
@ -1494,7 +1496,7 @@ class Entity(
|
|||||||
Not to be extended by integrations.
|
Not to be extended by integrations.
|
||||||
"""
|
"""
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2026.8
|
||||||
if self.platform:
|
if self.platform:
|
||||||
del entity_sources(self.hass)[self.entity_id]
|
del entity_sources(self.hass)[self.entity_id]
|
||||||
|
|
||||||
@ -1626,9 +1628,9 @@ class Entity(
|
|||||||
|
|
||||||
def _suggest_report_issue(self) -> str:
|
def _suggest_report_issue(self) -> str:
|
||||||
"""Suggest to report an issue."""
|
"""Suggest to report an issue."""
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform_data guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2026.8
|
||||||
platform_name = self.platform.platform_name if self.platform else None
|
platform_name = self.platform_data.platform_name if self.platform_data else None
|
||||||
return async_suggest_report_issue(
|
return async_suggest_report_issue(
|
||||||
self.hass, integration_domain=platform_name, module=type(self).__module__
|
self.hass, integration_domain=platform_name, module=type(self).__module__
|
||||||
)
|
)
|
||||||
|
@ -44,6 +44,7 @@ from . import (
|
|||||||
service,
|
service,
|
||||||
translation,
|
translation,
|
||||||
)
|
)
|
||||||
|
from .deprecation import deprecated_function
|
||||||
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
|
from .entity_registry import EntityRegistry, RegistryEntryDisabler, RegistryEntryHider
|
||||||
from .event import async_call_later
|
from .event import async_call_later
|
||||||
from .issue_registry import IssueSeverity, async_create_issue
|
from .issue_registry import IssueSeverity, async_create_issue
|
||||||
@ -126,6 +127,77 @@ class EntityPlatformModule(Protocol):
|
|||||||
"""Set up an integration platform from a config entry."""
|
"""Set up an integration platform from a config entry."""
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformData:
|
||||||
|
"""Information about a platform, used by entities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
*,
|
||||||
|
domain: str,
|
||||||
|
platform_name: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the base entity platform."""
|
||||||
|
self.hass = hass
|
||||||
|
self.domain = domain
|
||||||
|
self.platform_name = platform_name
|
||||||
|
self.component_translations: dict[str, str] = {}
|
||||||
|
self.platform_translations: dict[str, str] = {}
|
||||||
|
self.object_id_component_translations: dict[str, str] = {}
|
||||||
|
self.object_id_platform_translations: dict[str, str] = {}
|
||||||
|
self.default_language_platform_translations: dict[str, str] = {}
|
||||||
|
|
||||||
|
async def _async_get_translations(
|
||||||
|
self, language: str, category: str, integration: str
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Get translations for a language, category, and integration."""
|
||||||
|
try:
|
||||||
|
return await translation.async_get_translations(
|
||||||
|
self.hass, language, category, {integration}
|
||||||
|
)
|
||||||
|
except Exception as err: # noqa: BLE001
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Could not load translations for %s",
|
||||||
|
integration,
|
||||||
|
exc_info=err,
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def async_load_translations(self) -> None:
|
||||||
|
"""Load translations."""
|
||||||
|
hass = self.hass
|
||||||
|
object_id_language = (
|
||||||
|
hass.config.language
|
||||||
|
if hass.config.language in languages.NATIVE_ENTITY_IDS
|
||||||
|
else languages.DEFAULT_LANGUAGE
|
||||||
|
)
|
||||||
|
config_language = hass.config.language
|
||||||
|
self.component_translations = await self._async_get_translations(
|
||||||
|
config_language, "entity_component", self.domain
|
||||||
|
)
|
||||||
|
self.platform_translations = await self._async_get_translations(
|
||||||
|
config_language, "entity", self.platform_name
|
||||||
|
)
|
||||||
|
if object_id_language == config_language:
|
||||||
|
self.object_id_component_translations = self.component_translations
|
||||||
|
self.object_id_platform_translations = self.platform_translations
|
||||||
|
else:
|
||||||
|
self.object_id_component_translations = await self._async_get_translations(
|
||||||
|
object_id_language, "entity_component", self.domain
|
||||||
|
)
|
||||||
|
self.object_id_platform_translations = await self._async_get_translations(
|
||||||
|
object_id_language, "entity", self.platform_name
|
||||||
|
)
|
||||||
|
if config_language == languages.DEFAULT_LANGUAGE:
|
||||||
|
self.default_language_platform_translations = self.platform_translations
|
||||||
|
else:
|
||||||
|
self.default_language_platform_translations = (
|
||||||
|
await self._async_get_translations(
|
||||||
|
languages.DEFAULT_LANGUAGE, "entity", self.platform_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EntityPlatform:
|
class EntityPlatform:
|
||||||
"""Manage the entities for a single platform.
|
"""Manage the entities for a single platform.
|
||||||
|
|
||||||
@ -147,8 +219,6 @@ class EntityPlatform:
|
|||||||
"""Initialize the entity platform."""
|
"""Initialize the entity platform."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.domain = domain
|
|
||||||
self.platform_name = platform_name
|
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
self.scan_interval = scan_interval
|
self.scan_interval = scan_interval
|
||||||
self.scan_interval_seconds = scan_interval.total_seconds()
|
self.scan_interval_seconds = scan_interval.total_seconds()
|
||||||
@ -157,11 +227,6 @@ class EntityPlatform:
|
|||||||
# Storage for entities for this specific platform only
|
# Storage for entities for this specific platform only
|
||||||
# which are indexed by entity_id
|
# which are indexed by entity_id
|
||||||
self.entities: dict[str, Entity] = {}
|
self.entities: dict[str, Entity] = {}
|
||||||
self.component_translations: dict[str, str] = {}
|
|
||||||
self.platform_translations: dict[str, str] = {}
|
|
||||||
self.object_id_component_translations: dict[str, str] = {}
|
|
||||||
self.object_id_platform_translations: dict[str, str] = {}
|
|
||||||
self.default_language_platform_translations: dict[str, str] = {}
|
|
||||||
self._tasks: list[asyncio.Task[None]] = []
|
self._tasks: list[asyncio.Task[None]] = []
|
||||||
# Stop tracking tasks after setup is completed
|
# Stop tracking tasks after setup is completed
|
||||||
self._setup_complete = False
|
self._setup_complete = False
|
||||||
@ -195,6 +260,10 @@ class EntityPlatform:
|
|||||||
DATA_DOMAIN_PLATFORM_ENTITIES, {}
|
DATA_DOMAIN_PLATFORM_ENTITIES, {}
|
||||||
).setdefault(key, {})
|
).setdefault(key, {})
|
||||||
|
|
||||||
|
self.platform_data = PlatformData(
|
||||||
|
hass, domain=domain, platform_name=platform_name
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""Represent an EntityPlatform."""
|
"""Represent an EntityPlatform."""
|
||||||
return (
|
return (
|
||||||
@ -362,7 +431,7 @@ class EntityPlatform:
|
|||||||
hass = self.hass
|
hass = self.hass
|
||||||
full_name = f"{self.platform_name}.{self.domain}"
|
full_name = f"{self.platform_name}.{self.domain}"
|
||||||
|
|
||||||
await self.async_load_translations()
|
await self.platform_data.async_load_translations()
|
||||||
|
|
||||||
logger.info("Setting up %s", full_name)
|
logger.info("Setting up %s", full_name)
|
||||||
warn_task = hass.loop.call_at(
|
warn_task = hass.loop.call_at(
|
||||||
@ -457,56 +526,6 @@ class EntityPlatform:
|
|||||||
finally:
|
finally:
|
||||||
warn_task.cancel()
|
warn_task.cancel()
|
||||||
|
|
||||||
async def _async_get_translations(
|
|
||||||
self, language: str, category: str, integration: str
|
|
||||||
) -> dict[str, str]:
|
|
||||||
"""Get translations for a language, category, and integration."""
|
|
||||||
try:
|
|
||||||
return await translation.async_get_translations(
|
|
||||||
self.hass, language, category, {integration}
|
|
||||||
)
|
|
||||||
except Exception as err: # noqa: BLE001
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Could not load translations for %s",
|
|
||||||
integration,
|
|
||||||
exc_info=err,
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
async def async_load_translations(self) -> None:
|
|
||||||
"""Load translations."""
|
|
||||||
hass = self.hass
|
|
||||||
object_id_language = (
|
|
||||||
hass.config.language
|
|
||||||
if hass.config.language in languages.NATIVE_ENTITY_IDS
|
|
||||||
else languages.DEFAULT_LANGUAGE
|
|
||||||
)
|
|
||||||
config_language = hass.config.language
|
|
||||||
self.component_translations = await self._async_get_translations(
|
|
||||||
config_language, "entity_component", self.domain
|
|
||||||
)
|
|
||||||
self.platform_translations = await self._async_get_translations(
|
|
||||||
config_language, "entity", self.platform_name
|
|
||||||
)
|
|
||||||
if object_id_language == config_language:
|
|
||||||
self.object_id_component_translations = self.component_translations
|
|
||||||
self.object_id_platform_translations = self.platform_translations
|
|
||||||
else:
|
|
||||||
self.object_id_component_translations = await self._async_get_translations(
|
|
||||||
object_id_language, "entity_component", self.domain
|
|
||||||
)
|
|
||||||
self.object_id_platform_translations = await self._async_get_translations(
|
|
||||||
object_id_language, "entity", self.platform_name
|
|
||||||
)
|
|
||||||
if config_language == languages.DEFAULT_LANGUAGE:
|
|
||||||
self.default_language_platform_translations = self.platform_translations
|
|
||||||
else:
|
|
||||||
self.default_language_platform_translations = (
|
|
||||||
await self._async_get_translations(
|
|
||||||
languages.DEFAULT_LANGUAGE, "entity", self.platform_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _schedule_add_entities(
|
def _schedule_add_entities(
|
||||||
self, new_entities: Iterable[Entity], update_before_add: bool = False
|
self, new_entities: Iterable[Entity], update_before_add: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1120,6 +1139,87 @@ class EntityPlatform:
|
|||||||
]:
|
]:
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain(self) -> str:
|
||||||
|
"""Return the domain (e.g. light)."""
|
||||||
|
return self.platform_data.domain
|
||||||
|
|
||||||
|
@property
|
||||||
|
def platform_name(self) -> str:
|
||||||
|
"""Return the platform name (e.g hue)."""
|
||||||
|
return self.platform_data.platform_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.component_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
def component_translations(self) -> dict[str, str]:
|
||||||
|
"""Return the component translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return self.platform_data.component_translations
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.platform_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
def platform_translations(self) -> dict[str, str]:
|
||||||
|
"""Return the platform translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return self.platform_data.platform_translations
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.object_id_component_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
def object_id_component_translations(self) -> dict[str, str]:
|
||||||
|
"""Return the object ID component translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return self.platform_data.object_id_component_translations
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.object_id_platform_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
def object_id_platform_translations(self) -> dict[str, str]:
|
||||||
|
"""Return the object ID platform translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return self.platform_data.object_id_platform_translations
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.default_language_platform_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
def default_language_platform_translations(self) -> dict[str, str]:
|
||||||
|
"""Return the default language platform translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return self.platform_data.default_language_platform_translations
|
||||||
|
|
||||||
|
@deprecated_function(
|
||||||
|
"platform_data.async_load_translations",
|
||||||
|
breaks_in_ha_version="2026.8",
|
||||||
|
)
|
||||||
|
async def async_load_translations(self) -> None:
|
||||||
|
"""Load translations.
|
||||||
|
|
||||||
|
Will be removed in Home Assistant Core 2026.8.
|
||||||
|
"""
|
||||||
|
return await self.platform_data.async_load_translations()
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_calculate_suggested_object_id(
|
def async_calculate_suggested_object_id(
|
||||||
|
@ -685,7 +685,7 @@ async def test_generic_workaround(
|
|||||||
rest_client.get_jpeg_snapshot.return_value = image_bytes
|
rest_client.get_jpeg_snapshot.return_value = image_bytes
|
||||||
camera.set_stream_source("https://my_stream_url.m3u8")
|
camera.set_stream_source("https://my_stream_url.m3u8")
|
||||||
|
|
||||||
with patch.object(camera.platform, "platform_name", "generic"):
|
with patch.object(camera.platform.platform_data, "platform_name", "generic"):
|
||||||
image = await async_get_image(hass, camera.entity_id)
|
image = await async_get_image(hass, camera.entity_id)
|
||||||
assert image.content == image_bytes
|
assert image.content == image_bytes
|
||||||
|
|
||||||
|
@ -781,7 +781,7 @@ async def test_warn_slow_write_state(
|
|||||||
mock_entity = entity.Entity()
|
mock_entity = entity.Entity()
|
||||||
mock_entity.hass = hass
|
mock_entity.hass = hass
|
||||||
mock_entity.entity_id = "comp_test.test_entity"
|
mock_entity.entity_id = "comp_test.test_entity"
|
||||||
mock_entity.platform = MagicMock(platform_name="hue")
|
mock_entity.platform_data = MagicMock(platform_name="hue")
|
||||||
mock_entity._platform_state = entity.EntityPlatformState.ADDED
|
mock_entity._platform_state = entity.EntityPlatformState.ADDED
|
||||||
|
|
||||||
with patch("homeassistant.helpers.entity.timer", side_effect=[0, 10]):
|
with patch("homeassistant.helpers.entity.timer", side_effect=[0, 10]):
|
||||||
@ -809,7 +809,7 @@ async def test_warn_slow_write_state_custom_component(
|
|||||||
mock_entity = CustomComponentEntity()
|
mock_entity = CustomComponentEntity()
|
||||||
mock_entity.hass = hass
|
mock_entity.hass = hass
|
||||||
mock_entity.entity_id = "comp_test.test_entity"
|
mock_entity.entity_id = "comp_test.test_entity"
|
||||||
mock_entity.platform = MagicMock(platform_name="hue")
|
mock_entity.platform_data = MagicMock(platform_name="hue")
|
||||||
mock_entity._platform_state = entity.EntityPlatformState.ADDED
|
mock_entity._platform_state = entity.EntityPlatformState.ADDED
|
||||||
|
|
||||||
with patch("homeassistant.helpers.entity.timer", side_effect=[0, 10]):
|
with patch("homeassistant.helpers.entity.timer", side_effect=[0, 10]):
|
||||||
|
@ -2447,3 +2447,56 @@ async def test_add_entity_unknown_subentry(
|
|||||||
"Can't add entities to unknown subentry unknown-subentry "
|
"Can't add entities to unknown subentry unknown-subentry "
|
||||||
"of config entry super-mock-id"
|
"of config entry super-mock-id"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("integration_frame_path", ["custom_components/my_integration"])
|
||||||
|
@pytest.mark.usefixtures("mock_integration_frame")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"deprecated_attribute",
|
||||||
|
[
|
||||||
|
"component_translations",
|
||||||
|
"platform_translations",
|
||||||
|
"object_id_component_translations",
|
||||||
|
"object_id_platform_translations",
|
||||||
|
"default_language_platform_translations",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_deprecated_attributes(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
deprecated_attribute: str,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test setting the device name based on input info."""
|
||||||
|
|
||||||
|
platform = MockPlatform()
|
||||||
|
entity_platform = MockEntityPlatform(hass, platform_name="test", platform=platform)
|
||||||
|
|
||||||
|
assert getattr(entity_platform, deprecated_attribute) is getattr(
|
||||||
|
entity_platform.platform_data, deprecated_attribute
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
f"The deprecated function {deprecated_attribute} was called from "
|
||||||
|
"my_integration. It will be removed in HA Core 2026.8. Use platform_data."
|
||||||
|
f"{deprecated_attribute} instead, please report it to the author of the "
|
||||||
|
"'my_integration' custom integration" in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("integration_frame_path", ["custom_components/my_integration"])
|
||||||
|
@pytest.mark.usefixtures("mock_integration_frame")
|
||||||
|
async def test_deprecated_async_load_translations(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test setting the device name based on input info."""
|
||||||
|
|
||||||
|
platform = MockPlatform()
|
||||||
|
entity_platform = MockEntityPlatform(hass, platform_name="test", platform=platform)
|
||||||
|
|
||||||
|
await entity_platform.async_load_translations()
|
||||||
|
assert (
|
||||||
|
"The deprecated function async_load_translations was called from "
|
||||||
|
"my_integration. It will be removed in HA Core 2026.8. Use platform_data."
|
||||||
|
"async_load_translations instead, please report it to the author of the "
|
||||||
|
"'my_integration' custom integration" in caplog.text
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user