mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
Don't allow adding entities which don't belong to an entity platform
This commit is contained in:
parent
c92873bbff
commit
df446f5240
@ -463,9 +463,6 @@ class Entity(
|
|||||||
# 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 was added without its platform set
|
|
||||||
_no_platform_reported = False
|
|
||||||
|
|
||||||
# If we reported the name translation placeholders do not match the name
|
# If we reported the name translation placeholders do not match the name
|
||||||
_name_translation_placeholders_reported = False
|
_name_translation_placeholders_reported = False
|
||||||
|
|
||||||
@ -721,9 +718,6 @@ 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
|
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
|
||||||
and self.platform
|
|
||||||
):
|
):
|
||||||
name = self._name_internal(
|
name = self._name_internal(
|
||||||
self._object_id_device_class_name,
|
self._object_id_device_class_name,
|
||||||
@ -736,10 +730,6 @@ 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
|
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
|
||||||
if not self.platform:
|
|
||||||
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.platform_translations,
|
||||||
@ -984,7 +974,7 @@ class Entity(
|
|||||||
|
|
||||||
# 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 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]
|
report_issue = self._suggest_report_issue() # type: ignore[unreachable]
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
(
|
(
|
||||||
@ -996,7 +986,7 @@ class Entity(
|
|||||||
type(self),
|
type(self),
|
||||||
report_issue,
|
report_issue,
|
||||||
)
|
)
|
||||||
self._no_platform_reported = True
|
raise HomeAssistantError("Entity does not have a platform")
|
||||||
|
|
||||||
if self.entity_id is None:
|
if self.entity_id is None:
|
||||||
raise NoEntitySpecifiedError(
|
raise NoEntitySpecifiedError(
|
||||||
@ -1015,8 +1005,6 @@ class Entity(
|
|||||||
@callback
|
@callback
|
||||||
def async_write_ha_state(self) -> None:
|
def async_write_ha_state(self) -> None:
|
||||||
"""Write the state to the state machine."""
|
"""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():
|
if self.hass.loop_thread_id != threading.get_ident():
|
||||||
report_non_thread_safe_operation("async_write_ha_state")
|
report_non_thread_safe_operation("async_write_ha_state")
|
||||||
self._async_write_ha_state()
|
self._async_write_ha_state()
|
||||||
@ -1126,6 +1114,9 @@ class Entity(
|
|||||||
# Polling returned after the entity has already been removed
|
# Polling returned after the entity has already been removed
|
||||||
return
|
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 (entry := self.registry_entry) and entry.disabled_by:
|
||||||
if not self._disabled_reported:
|
if not self._disabled_reported:
|
||||||
self._disabled_reported = True
|
self._disabled_reported = True
|
||||||
@ -1486,10 +1477,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
|
del entity_sources(self.hass)[self.entity_id]
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
|
||||||
if self.platform:
|
|
||||||
del entity_sources(self.hass)[self.entity_id]
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_registry_updated(
|
def _async_registry_updated(
|
||||||
@ -1620,7 +1608,7 @@ 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 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
|
platform_name = self.platform.platform_name if self.platform 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__
|
||||||
|
@ -101,6 +101,7 @@ async def test_async_update_support(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
ent = AsyncEntity()
|
ent = AsyncEntity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
await ent.async_update_ha_state(True)
|
await ent.async_update_ha_state(True)
|
||||||
|
|
||||||
@ -125,6 +126,7 @@ async def test_device_class(hass: HomeAssistant) -> None:
|
|||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
ent.entity_id = "test.overwrite_hidden_true"
|
ent.entity_id = "test.overwrite_hidden_true"
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
state = hass.states.get(ent.entity_id)
|
state = hass.states.get(ent.entity_id)
|
||||||
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
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 = entity.Entity()
|
||||||
mock_entity.hass = hass
|
mock_entity.hass = hass
|
||||||
|
mock_entity.platform = MockEntityPlatform(hass)
|
||||||
mock_entity.entity_id = "comp_test.test_entity"
|
mock_entity.entity_id = "comp_test.test_entity"
|
||||||
mock_entity.async_update = async_update
|
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()
|
await test_lock.wait()
|
||||||
|
|
||||||
ent_1 = AsyncEntity("sensor.test_1", 1)
|
ent_1 = AsyncEntity("sensor.test_1", 1)
|
||||||
|
ent_1.platform = MockEntityPlatform(hass)
|
||||||
ent_2 = AsyncEntity("sensor.test_2", 2)
|
ent_2 = AsyncEntity("sensor.test_2", 2)
|
||||||
|
ent_2.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ent_1.async_schedule_update_ha_state(True)
|
ent_1.async_schedule_update_ha_state(True)
|
||||||
@ -385,7 +390,9 @@ async def test_async_parallel_updates_with_zero_on_sync_update(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ent_1.async_schedule_update_ha_state(True)
|
ent_1.async_schedule_update_ha_state(True)
|
||||||
|
ent_1.platform = MockEntityPlatform(hass)
|
||||||
ent_2.async_schedule_update_ha_state(True)
|
ent_2.async_schedule_update_ha_state(True)
|
||||||
|
ent_2.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if len(updates) >= 2:
|
if len(updates) >= 2:
|
||||||
@ -421,8 +428,11 @@ async def test_async_parallel_updates_with_one(hass: HomeAssistant) -> None:
|
|||||||
await test_lock.acquire()
|
await test_lock.acquire()
|
||||||
|
|
||||||
ent_1 = AsyncEntity("sensor.test_1", 1)
|
ent_1 = AsyncEntity("sensor.test_1", 1)
|
||||||
|
ent_1.platform = MockEntityPlatform(hass)
|
||||||
ent_2 = AsyncEntity("sensor.test_2", 2)
|
ent_2 = AsyncEntity("sensor.test_2", 2)
|
||||||
|
ent_2.platform = MockEntityPlatform(hass)
|
||||||
ent_3 = AsyncEntity("sensor.test_3", 3)
|
ent_3 = AsyncEntity("sensor.test_3", 3)
|
||||||
|
ent_3.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
await test_lock.acquire()
|
await test_lock.acquire()
|
||||||
|
|
||||||
@ -497,9 +507,13 @@ async def test_async_parallel_updates_with_two(hass: HomeAssistant) -> None:
|
|||||||
await test_lock.acquire()
|
await test_lock.acquire()
|
||||||
|
|
||||||
ent_1 = AsyncEntity("sensor.test_1", 1)
|
ent_1 = AsyncEntity("sensor.test_1", 1)
|
||||||
|
ent_1.platform = MockEntityPlatform(hass)
|
||||||
ent_2 = AsyncEntity("sensor.test_2", 2)
|
ent_2 = AsyncEntity("sensor.test_2", 2)
|
||||||
|
ent_2.platform = MockEntityPlatform(hass)
|
||||||
ent_3 = AsyncEntity("sensor.test_3", 3)
|
ent_3 = AsyncEntity("sensor.test_3", 3)
|
||||||
|
ent_3.platform = MockEntityPlatform(hass)
|
||||||
ent_4 = AsyncEntity("sensor.test_4", 4)
|
ent_4 = AsyncEntity("sensor.test_4", 4)
|
||||||
|
ent_4.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
await test_lock.acquire()
|
await test_lock.acquire()
|
||||||
|
|
||||||
@ -565,6 +579,8 @@ async def test_async_parallel_updates_with_one_using_executor(
|
|||||||
locked.append(self.parallel_updates.locked())
|
locked.append(self.parallel_updates.locked())
|
||||||
|
|
||||||
entities = [SyncEntity(f"sensor.test_{i}") for i in range(3)]
|
entities = [SyncEntity(f"sensor.test_{i}") for i in range(3)]
|
||||||
|
for ent in entities:
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
@ -579,17 +595,6 @@ async def test_async_parallel_updates_with_one_using_executor(
|
|||||||
assert locked == [True, True, True]
|
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:
|
async def test_async_remove_runs_callbacks(hass: HomeAssistant) -> None:
|
||||||
"""Test async_remove runs on_remove callback."""
|
"""Test async_remove runs on_remove callback."""
|
||||||
result = []
|
result = []
|
||||||
@ -659,6 +664,7 @@ async def test_set_context(hass: HomeAssistant) -> None:
|
|||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
ent.entity_id = "hello.world"
|
ent.entity_id = "hello.world"
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_set_context(context)
|
ent.async_set_context(context)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
assert hass.states.get("hello.world").context == context
|
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 = entity.Entity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
ent.entity_id = "hello.world"
|
ent.entity_id = "hello.world"
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_set_context(context)
|
ent.async_set_context(context)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
|
|
||||||
@ -758,6 +765,7 @@ async def test_capability_attrs(hass: HomeAssistant) -> None:
|
|||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
ent.entity_id = "hello.world"
|
ent.entity_id = "hello.world"
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
|
|
||||||
state = hass.states.get("hello.world")
|
state = hass.states.get("hello.world")
|
||||||
@ -895,6 +903,7 @@ async def test_float_conversion(hass: HomeAssistant) -> None:
|
|||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
ent.entity_id = "hello.world"
|
ent.entity_id = "hello.world"
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
|
|
||||||
state = hass.states.get("hello.world")
|
state = hass.states.get("hello.world")
|
||||||
@ -907,6 +916,7 @@ async def test_attribution_attribute(hass: HomeAssistant) -> None:
|
|||||||
mock_entity = entity.Entity()
|
mock_entity = entity.Entity()
|
||||||
mock_entity.hass = hass
|
mock_entity.hass = hass
|
||||||
mock_entity.entity_id = "hello.world"
|
mock_entity.entity_id = "hello.world"
|
||||||
|
mock_entity.platform = MockEntityPlatform(hass)
|
||||||
mock_entity._attr_attribution = "Home Assistant"
|
mock_entity._attr_attribution = "Home Assistant"
|
||||||
|
|
||||||
mock_entity.async_schedule_update_ha_state(True)
|
mock_entity.async_schedule_update_ha_state(True)
|
||||||
@ -962,10 +972,12 @@ def test_entity_category_schema_error(value) -> None:
|
|||||||
schema(value)
|
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."""
|
"""Test entity description has same defaults as entity."""
|
||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent_with_description = entity.Entity()
|
ent_with_description = entity.Entity()
|
||||||
|
ent_with_description.platform = MockEntityPlatform(hass)
|
||||||
ent_with_description.entity_description = entity.EntityDescription(key="test")
|
ent_with_description.entity_description = entity.EntityDescription(key="test")
|
||||||
|
|
||||||
for field in dataclasses.fields(entity.EntityDescription._dataclass):
|
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
|
assert error_message not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_warn_no_platform(
|
async def test_raise_no_platform(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> 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 = entity.Entity()
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
ent.platform = MockEntityPlatform(hass)
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.entity_id = "hello.world"
|
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()
|
caplog.clear()
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
assert error_message not in caplog.text
|
assert error_message not in caplog.text
|
||||||
@ -1698,6 +1709,7 @@ async def test_invalid_state(
|
|||||||
ent = entity.Entity()
|
ent = entity.Entity()
|
||||||
ent.entity_id = "test.test"
|
ent.entity_id = "test.test"
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
|
|
||||||
ent._attr_state = "x" * 255
|
ent._attr_state = "x" * 255
|
||||||
ent.async_write_ha_state()
|
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.Entity()
|
||||||
ent.entity_id = "test.any"
|
ent.entity_id = "test.any"
|
||||||
ent.hass = hass
|
ent.hass = hass
|
||||||
|
ent.platform = MockEntityPlatform(hass)
|
||||||
ent.async_write_ha_state()
|
ent.async_write_ha_state()
|
||||||
assert hass.states.get(ent.entity_id)
|
assert hass.states.get(ent.entity_id)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user