diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 39629d07494..f03658520fd 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -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__ diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 706f1a1a806..c38681536b1 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -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)