Don't allow adding entities which don't belong to an entity platform

This commit is contained in:
Erik 2025-07-01 16:27:10 +02:00
parent c92873bbff
commit df446f5240
2 changed files with 48 additions and 47 deletions

View File

@ -463,9 +463,6 @@ class Entity(
# it should be using async_write_ha_state.
_async_update_ha_state_reported = False
# If we reported this entity was added without its platform set
_no_platform_reported = False
# If we reported the name translation placeholders do not match the name
_name_translation_placeholders_reported = False
@ -721,9 +718,6 @@ class Entity(
# value.
type.__getattribute__(self.__class__, "name")
is type.__getattribute__(Entity, "name")
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
and self.platform
):
name = self._name_internal(
self._object_id_device_class_name,
@ -736,10 +730,6 @@ class Entity(
@cached_property
def name(self) -> str | UndefinedType | None:
"""Return the name of the entity."""
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
if not self.platform:
return self._name_internal(None, {})
return self._name_internal(
self._device_class_name,
self.platform.platform_translations,
@ -984,7 +974,7 @@ class Entity(
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
if self.platform is None and not self._no_platform_reported: # type: ignore[unreachable]
if self.platform is None:
report_issue = self._suggest_report_issue() # type: ignore[unreachable]
_LOGGER.warning(
(
@ -996,7 +986,7 @@ class Entity(
type(self),
report_issue,
)
self._no_platform_reported = True
raise HomeAssistantError("Entity does not have a platform")
if self.entity_id is None:
raise NoEntitySpecifiedError(
@ -1015,8 +1005,6 @@ class Entity(
@callback
def async_write_ha_state(self) -> None:
"""Write the state to the state machine."""
if not self.hass or not self._verified_state_writable:
self._async_verify_state_writable()
if self.hass.loop_thread_id != threading.get_ident():
report_non_thread_safe_operation("async_write_ha_state")
self._async_write_ha_state()
@ -1126,6 +1114,9 @@ class Entity(
# Polling returned after the entity has already been removed
return
if not self.hass or not self._verified_state_writable:
self._async_verify_state_writable()
if (entry := self.registry_entry) and entry.disabled_by:
if not self._disabled_reported:
self._disabled_reported = True
@ -1486,10 +1477,7 @@ class Entity(
Not to be extended by integrations.
"""
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
if self.platform:
del entity_sources(self.hass)[self.entity_id]
del entity_sources(self.hass)[self.entity_id]
@callback
def _async_registry_updated(
@ -1620,7 +1608,7 @@ class Entity(
def _suggest_report_issue(self) -> str:
"""Suggest to report an issue."""
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
# EntityComponent which has not been allowed since HA Core 2024.1
platform_name = self.platform.platform_name if self.platform else None
return async_suggest_report_issue(
self.hass, integration_domain=platform_name, module=type(self).__module__

View File

@ -101,6 +101,7 @@ async def test_async_update_support(hass: HomeAssistant) -> None:
ent = AsyncEntity()
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
await ent.async_update_ha_state(True)
@ -125,6 +126,7 @@ async def test_device_class(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.entity_id = "test.overwrite_hidden_true"
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
ent.async_write_ha_state()
state = hass.states.get(ent.entity_id)
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
@ -149,6 +151,7 @@ async def test_warn_slow_update(
mock_entity = entity.Entity()
mock_entity.hass = hass
mock_entity.platform = MockEntityPlatform(hass)
mock_entity.entity_id = "comp_test.test_entity"
mock_entity.async_update = async_update
@ -340,7 +343,9 @@ async def test_async_parallel_updates_with_zero(hass: HomeAssistant) -> None:
await test_lock.wait()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_1.platform = MockEntityPlatform(hass)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_2.platform = MockEntityPlatform(hass)
try:
ent_1.async_schedule_update_ha_state(True)
@ -385,7 +390,9 @@ async def test_async_parallel_updates_with_zero_on_sync_update(
try:
ent_1.async_schedule_update_ha_state(True)
ent_1.platform = MockEntityPlatform(hass)
ent_2.async_schedule_update_ha_state(True)
ent_2.platform = MockEntityPlatform(hass)
while True:
if len(updates) >= 2:
@ -421,8 +428,11 @@ async def test_async_parallel_updates_with_one(hass: HomeAssistant) -> None:
await test_lock.acquire()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_1.platform = MockEntityPlatform(hass)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_2.platform = MockEntityPlatform(hass)
ent_3 = AsyncEntity("sensor.test_3", 3)
ent_3.platform = MockEntityPlatform(hass)
await test_lock.acquire()
@ -497,9 +507,13 @@ async def test_async_parallel_updates_with_two(hass: HomeAssistant) -> None:
await test_lock.acquire()
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_1.platform = MockEntityPlatform(hass)
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_2.platform = MockEntityPlatform(hass)
ent_3 = AsyncEntity("sensor.test_3", 3)
ent_3.platform = MockEntityPlatform(hass)
ent_4 = AsyncEntity("sensor.test_4", 4)
ent_4.platform = MockEntityPlatform(hass)
await test_lock.acquire()
@ -565,6 +579,8 @@ async def test_async_parallel_updates_with_one_using_executor(
locked.append(self.parallel_updates.locked())
entities = [SyncEntity(f"sensor.test_{i}") for i in range(3)]
for ent in entities:
ent.platform = MockEntityPlatform(hass)
await asyncio.gather(
*[
@ -579,17 +595,6 @@ async def test_async_parallel_updates_with_one_using_executor(
assert locked == [True, True, True]
async def test_async_remove_no_platform(hass: HomeAssistant) -> None:
"""Test async_remove method when no platform set."""
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "test.test"
ent.async_write_ha_state()
assert len(hass.states.async_entity_ids()) == 1
await ent.async_remove()
assert len(hass.states.async_entity_ids()) == 0
async def test_async_remove_runs_callbacks(hass: HomeAssistant) -> None:
"""Test async_remove runs on_remove callback."""
result = []
@ -659,6 +664,7 @@ async def test_set_context(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
ent.platform = MockEntityPlatform(hass)
ent.async_set_context(context)
ent.async_write_ha_state()
assert hass.states.get("hello.world").context == context
@ -672,6 +678,7 @@ async def test_set_context_expired(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
ent.platform = MockEntityPlatform(hass)
ent.async_set_context(context)
ent.async_write_ha_state()
@ -758,6 +765,7 @@ async def test_capability_attrs(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
ent.platform = MockEntityPlatform(hass)
ent.async_write_ha_state()
state = hass.states.get("hello.world")
@ -895,6 +903,7 @@ async def test_float_conversion(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
ent.platform = MockEntityPlatform(hass)
ent.async_write_ha_state()
state = hass.states.get("hello.world")
@ -907,6 +916,7 @@ async def test_attribution_attribute(hass: HomeAssistant) -> None:
mock_entity = entity.Entity()
mock_entity.hass = hass
mock_entity.entity_id = "hello.world"
mock_entity.platform = MockEntityPlatform(hass)
mock_entity._attr_attribution = "Home Assistant"
mock_entity.async_schedule_update_ha_state(True)
@ -962,10 +972,12 @@ def test_entity_category_schema_error(value) -> None:
schema(value)
async def test_entity_description_fallback() -> None:
async def test_entity_description_fallback(hass: HomeAssistant) -> None:
"""Test entity description has same defaults as entity."""
ent = entity.Entity()
ent.platform = MockEntityPlatform(hass)
ent_with_description = entity.Entity()
ent_with_description.platform = MockEntityPlatform(hass)
ent_with_description.entity_description = entity.EntityDescription(key="test")
for field in dataclasses.fields(entity.EntityDescription._dataclass):
@ -1664,28 +1676,27 @@ async def test_warn_using_async_update_ha_state(
assert error_message not in caplog.text
async def test_warn_no_platform(
async def test_raise_no_platform(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test we warn am entity does not have a platform."""
"""Test we raise if an entity does not have a platform."""
ent = entity.Entity()
ent.hass = hass
ent.platform = None
ent.entity_id = "hello.world"
error_message = "does not have a platform"
# Without a platform, it should log a warning and raise an error
caplog.clear()
with pytest.raises(HomeAssistantError, match="Entity does not have a platform"):
ent.async_write_ha_state()
assert error_message in caplog.text
# No warning if the entity has a platform
ent = entity.Entity()
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
ent.entity_id = "hello.world"
error_message = "does not have a platform"
# Without a platform, it should trigger the warning
ent.platform = None
caplog.clear()
ent.async_write_ha_state()
assert error_message in caplog.text
# Without a platform, it should not trigger the warning again
caplog.clear()
ent.async_write_ha_state()
assert error_message not in caplog.text
# No warning if the entity has a platform
caplog.clear()
ent.async_write_ha_state()
assert error_message not in caplog.text
@ -1698,6 +1709,7 @@ async def test_invalid_state(
ent = entity.Entity()
ent.entity_id = "test.test"
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
ent._attr_state = "x" * 255
ent.async_write_ha_state()
@ -2605,6 +2617,7 @@ async def test_async_write_ha_state_thread_safety(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.entity_id = "test.any"
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
ent.async_write_ha_state()
assert hass.states.get(ent.entity_id)