mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Remove unneeded checks for Entity.platform (#94321)
* Remove unneeded checks for Entity.platform * Update tests * Prevent breaking integrations without an EntityComponent * Warn when entity has no platform
This commit is contained in:
parent
239e2d9820
commit
59f5b8f2d6
@ -373,7 +373,6 @@ class ScannerEntity(BaseTrackerEntity):
|
||||
# Entities without a unique ID don't have a device
|
||||
if (
|
||||
not self.registry_entry
|
||||
or not self.platform
|
||||
or not self.platform.config_entry
|
||||
or not self.mac_address
|
||||
or (device_entry := self.find_device_entry()) is None
|
||||
|
@ -247,11 +247,7 @@ class MeteoFranceSensor(CoordinatorEntity[DataUpdateCoordinator[_DataT]], Sensor
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
assert (
|
||||
self.platform
|
||||
and self.platform.config_entry
|
||||
and self.platform.config_entry.unique_id
|
||||
)
|
||||
assert self.platform.config_entry and self.platform.config_entry.unique_id
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, self.platform.config_entry.unique_id)},
|
||||
|
@ -109,11 +109,7 @@ class MeteoFranceWeather(
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
assert (
|
||||
self.platform
|
||||
and self.platform.config_entry
|
||||
and self.platform.config_entry.unique_id
|
||||
)
|
||||
assert self.platform.config_entry and self.platform.config_entry.unique_id
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, self.platform.config_entry.unique_id)},
|
||||
|
@ -227,7 +227,6 @@ class MySensorsEntity(MySensorsDevice, Entity):
|
||||
"""Return entity specific state attributes."""
|
||||
attr = self._extra_attributes
|
||||
|
||||
assert self.platform
|
||||
assert self.platform.config_entry
|
||||
attr[ATTR_DEVICE] = self.platform.config_entry.data[CONF_DEVICE]
|
||||
|
||||
|
@ -128,7 +128,6 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
||||
_LOGGER.debug("Update stream URL for camera %s", self.camera_data.name)
|
||||
self.stream.update_source(url)
|
||||
|
||||
assert self.platform
|
||||
assert self.platform.config_entry
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
|
@ -165,7 +165,6 @@ class TTSMediaSource(MediaSource):
|
||||
raise BrowseError("Unknown provider")
|
||||
|
||||
if isinstance(engine_instance, TextToSpeechEntity):
|
||||
assert engine_instance.platform is not None
|
||||
engine_domain = engine_instance.platform.domain
|
||||
else:
|
||||
engine_domain = engine
|
||||
|
@ -622,7 +622,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
||||
)
|
||||
if self._debounced_member_refresh is not None:
|
||||
self.debug("transition complete - refreshing group member states")
|
||||
assert self.platform and self.platform.config_entry
|
||||
assert self.platform.config_entry
|
||||
self.platform.config_entry.async_create_background_task(
|
||||
self.hass,
|
||||
self._debounced_member_refresh.async_call(),
|
||||
|
@ -258,6 +258,9 @@ class Entity(ABC):
|
||||
# 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
|
||||
|
||||
# Protect for multiple updates
|
||||
_update_staged = False
|
||||
|
||||
@ -331,7 +334,6 @@ class Entity(ABC):
|
||||
if hasattr(self, "_attr_name"):
|
||||
return self._attr_name
|
||||
if self.translation_key is not None and self.has_entity_name:
|
||||
assert self.platform
|
||||
name_translation_key = (
|
||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||
f".{self.translation_key}.name"
|
||||
@ -584,6 +586,22 @@ class Entity(ABC):
|
||||
if self.hass is None:
|
||||
raise RuntimeError(f"Attribute hass is None for {self}")
|
||||
|
||||
# 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]
|
||||
report_issue = self._suggest_report_issue() # type: ignore[unreachable]
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s (%s) does not have a platform, this may be caused by "
|
||||
"adding it manually instead of with an EntityComponent helper"
|
||||
", please %s"
|
||||
),
|
||||
self.entity_id,
|
||||
type(self),
|
||||
report_issue,
|
||||
)
|
||||
self._no_platform_reported = True
|
||||
|
||||
if self.entity_id is None:
|
||||
raise NoEntitySpecifiedError(
|
||||
f"No entity id specified for entity {self.name}"
|
||||
@ -636,7 +654,6 @@ class Entity(ABC):
|
||||
if entry and entry.disabled_by:
|
||||
if not self._disabled_reported:
|
||||
self._disabled_reported = True
|
||||
assert self.platform is not None
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s is incorrectly being triggered for updates while it"
|
||||
@ -861,6 +878,8 @@ class Entity(ABC):
|
||||
If the entity doesn't have a non disabled entry in the entity registry,
|
||||
or if force_remove=True, its state will be removed.
|
||||
"""
|
||||
# The check for self.platform guards against integrations not using an
|
||||
# EntityComponent and can be removed in HA Core 2024.1
|
||||
if self.platform and self._platform_state != EntityPlatformState.ADDED:
|
||||
raise HomeAssistantError(
|
||||
f"Entity {self.entity_id} async_remove called twice"
|
||||
@ -908,19 +927,18 @@ class Entity(ABC):
|
||||
|
||||
Not to be extended by integrations.
|
||||
"""
|
||||
if self.platform:
|
||||
info = {
|
||||
"domain": self.platform.platform_name,
|
||||
"custom_component": "custom_components" in type(self).__module__,
|
||||
}
|
||||
info = {
|
||||
"domain": self.platform.platform_name,
|
||||
"custom_component": "custom_components" in type(self).__module__,
|
||||
}
|
||||
|
||||
if self.platform.config_entry:
|
||||
info["source"] = SOURCE_CONFIG_ENTRY
|
||||
info["config_entry"] = self.platform.config_entry.entry_id
|
||||
else:
|
||||
info["source"] = SOURCE_PLATFORM_CONFIG
|
||||
if self.platform.config_entry:
|
||||
info["source"] = SOURCE_CONFIG_ENTRY
|
||||
info["config_entry"] = self.platform.config_entry.entry_id
|
||||
else:
|
||||
info["source"] = SOURCE_PLATFORM_CONFIG
|
||||
|
||||
self.hass.data[DATA_ENTITY_SOURCE][self.entity_id] = info
|
||||
self.hass.data[DATA_ENTITY_SOURCE][self.entity_id] = info
|
||||
|
||||
if self.registry_entry is not None:
|
||||
# This is an assert as it should never happen, but helps in tests
|
||||
@ -940,6 +958,8 @@ class Entity(ABC):
|
||||
|
||||
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:
|
||||
self.hass.data[DATA_ENTITY_SOURCE].pop(self.entity_id)
|
||||
|
||||
@ -974,7 +994,6 @@ class Entity(ABC):
|
||||
|
||||
await self.async_remove(force_remove=True)
|
||||
|
||||
assert self.platform is not None
|
||||
self.entity_id = self.registry_entry.entity_id
|
||||
await self.platform.async_add_entities([self])
|
||||
|
||||
@ -1048,6 +1067,8 @@ class Entity(ABC):
|
||||
"create a bug report at "
|
||||
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||
)
|
||||
# The check for self.platform guards against integrations not using an
|
||||
# EntityComponent and can be removed in HA Core 2024.1
|
||||
if self.platform:
|
||||
report_issue += (
|
||||
f"+label%3A%22integration%3A+{self.platform.platform_name}%22"
|
||||
|
@ -9,7 +9,7 @@ from homeassistant.components.arcam_fmj.const import DEFAULT_NAME
|
||||
from homeassistant.components.arcam_fmj.media_player import ArcamFmj
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, MockEntityPlatform
|
||||
|
||||
MOCK_HOST = "127.0.0.1"
|
||||
MOCK_PORT = 50000
|
||||
@ -75,6 +75,7 @@ def player_fixture(hass, state):
|
||||
player = ArcamFmj(MOCK_NAME, state, MOCK_UUID)
|
||||
player.entity_id = MOCK_ENTITY_ID
|
||||
player.hass = hass
|
||||
player.platform = MockEntityPlatform(hass)
|
||||
player.async_write_ha_state = Mock()
|
||||
return player
|
||||
|
||||
|
@ -35,6 +35,7 @@ from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM
|
||||
|
||||
from tests.common import (
|
||||
MockEntityPlatform,
|
||||
async_mock_restore_state_shutdown_restart,
|
||||
mock_restore_cache_with_extra_data,
|
||||
)
|
||||
@ -246,6 +247,7 @@ async def test_deprecation_warnings(
|
||||
"""Test overriding the deprecated attributes is possible and warnings are logged."""
|
||||
number = MockDefaultNumberEntityDeprecated()
|
||||
number.hass = hass
|
||||
number.platform = MockEntityPlatform(hass)
|
||||
assert number.max_value == 100.0
|
||||
assert number.min_value == 0.0
|
||||
assert number.step == 1.0
|
||||
@ -254,6 +256,7 @@ async def test_deprecation_warnings(
|
||||
|
||||
number_2 = MockNumberEntityDeprecated()
|
||||
number_2.hass = hass
|
||||
number_2.platform = MockEntityPlatform(hass)
|
||||
assert number_2.max_value == 0.5
|
||||
assert number_2.min_value == -0.5
|
||||
assert number_2.step == 0.1
|
||||
@ -262,6 +265,7 @@ async def test_deprecation_warnings(
|
||||
|
||||
number_3 = MockNumberEntityAttrDeprecated()
|
||||
number_3.hass = hass
|
||||
number_3.platform = MockEntityPlatform(hass)
|
||||
assert number_3.max_value == 1000.0
|
||||
assert number_3.min_value == -1000.0
|
||||
assert number_3.step == 100.0
|
||||
@ -270,6 +274,7 @@ async def test_deprecation_warnings(
|
||||
|
||||
number_4 = MockNumberEntityDescrDeprecated()
|
||||
number_4.hass = hass
|
||||
number_4.platform = MockEntityPlatform(hass)
|
||||
assert number_4.max_value == 10.0
|
||||
assert number_4.min_value == -10.0
|
||||
assert number_4.step == 2.0
|
||||
|
@ -578,12 +578,14 @@ async def test_async_remove_no_platform(hass: HomeAssistant) -> None:
|
||||
|
||||
|
||||
async def test_async_remove_runs_callbacks(hass: HomeAssistant) -> None:
|
||||
"""Test async_remove method when no platform set."""
|
||||
"""Test async_remove runs on_remove callback."""
|
||||
result = []
|
||||
|
||||
platform = MockEntityPlatform(hass, domain="test")
|
||||
ent = entity.Entity()
|
||||
ent.hass = hass
|
||||
ent.entity_id = "test.test"
|
||||
await platform.async_add_entities([ent])
|
||||
ent.async_on_remove(lambda: result.append(1))
|
||||
await ent.async_remove()
|
||||
assert len(result) == 1
|
||||
@ -593,11 +595,12 @@ async def test_async_remove_ignores_in_flight_polling(hass: HomeAssistant) -> No
|
||||
"""Test in flight polling is ignored after removing."""
|
||||
result = []
|
||||
|
||||
platform = MockEntityPlatform(hass, domain="test")
|
||||
ent = entity.Entity()
|
||||
ent.hass = hass
|
||||
ent.entity_id = "test.test"
|
||||
ent.async_on_remove(lambda: result.append(1))
|
||||
ent.async_write_ha_state()
|
||||
await platform.async_add_entities([ent])
|
||||
assert hass.states.get("test.test").state == STATE_UNKNOWN
|
||||
await ent.async_remove()
|
||||
assert len(result) == 1
|
||||
@ -798,18 +801,18 @@ async def test_setup_source(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_removing_entity_unavailable(hass: HomeAssistant) -> None:
|
||||
"""Test removing an entity that is still registered creates an unavailable state."""
|
||||
entry = er.RegistryEntry(
|
||||
er.RegistryEntry(
|
||||
entity_id="hello.world",
|
||||
unique_id="test-unique-id",
|
||||
platform="test-platform",
|
||||
disabled_by=None,
|
||||
)
|
||||
|
||||
platform = MockEntityPlatform(hass, domain="hello")
|
||||
ent = entity.Entity()
|
||||
ent.hass = hass
|
||||
ent.entity_id = "hello.world"
|
||||
ent.registry_entry = entry
|
||||
ent.async_write_ha_state()
|
||||
ent._attr_unique_id = "test-unique-id"
|
||||
await platform.async_add_entities([ent])
|
||||
|
||||
state = hass.states.get("hello.world")
|
||||
assert state is not None
|
||||
@ -1112,19 +1115,48 @@ async def test_warn_using_async_update_ha_state(
|
||||
"""Test we warn once when using async_update_ha_state without force_update."""
|
||||
ent = entity.Entity()
|
||||
ent.hass = hass
|
||||
ent.platform = MockEntityPlatform(hass)
|
||||
ent.entity_id = "hello.world"
|
||||
error_message = "is using self.async_update_ha_state()"
|
||||
|
||||
# When forcing, it should not trigger the warning
|
||||
caplog.clear()
|
||||
await ent.async_update_ha_state(force_refresh=True)
|
||||
assert "is using self.async_update_ha_state()" not in caplog.text
|
||||
assert error_message not in caplog.text
|
||||
|
||||
# When not forcing, it should trigger the warning
|
||||
caplog.clear()
|
||||
await ent.async_update_ha_state()
|
||||
assert "is using self.async_update_ha_state()" in caplog.text
|
||||
assert error_message in caplog.text
|
||||
|
||||
# When not forcing, it should not trigger the warning again
|
||||
caplog.clear()
|
||||
await ent.async_update_ha_state()
|
||||
assert "is using self.async_update_ha_state()" not in caplog.text
|
||||
assert error_message not in caplog.text
|
||||
|
||||
|
||||
async def test_warn_no_platform(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test we warn am entity does not have a platform."""
|
||||
ent = entity.Entity()
|
||||
ent.hass = hass
|
||||
ent.platform = MockEntityPlatform(hass)
|
||||
ent.entity_id = "hello.world"
|
||||
error_message = "does not have a platform"
|
||||
|
||||
# No warning if the entity has a platform
|
||||
caplog.clear()
|
||||
ent.async_write_ha_state()
|
||||
assert error_message not in caplog.text
|
||||
|
||||
# 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
|
||||
|
@ -27,6 +27,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import (
|
||||
MockEntityPlatform,
|
||||
MockModule,
|
||||
MockPlatform,
|
||||
async_fire_time_changed,
|
||||
@ -266,15 +267,16 @@ async def test_dump_data(hass: HomeAssistant) -> None:
|
||||
State("input_boolean.b5", "unavailable", {"restored": True}),
|
||||
]
|
||||
|
||||
platform = MockEntityPlatform(hass, domain="input_boolean")
|
||||
entity = Entity()
|
||||
entity.hass = hass
|
||||
entity.entity_id = "input_boolean.b0"
|
||||
await entity.async_internal_added_to_hass()
|
||||
await platform.async_add_entities([entity])
|
||||
|
||||
entity = RestoreEntity()
|
||||
entity.hass = hass
|
||||
entity.entity_id = "input_boolean.b1"
|
||||
await entity.async_internal_added_to_hass()
|
||||
await platform.async_add_entities([entity])
|
||||
|
||||
data = async_get(hass)
|
||||
now = dt_util.utcnow()
|
||||
@ -340,15 +342,16 @@ async def test_dump_error(hass: HomeAssistant) -> None:
|
||||
State("input_boolean.b2", "on"),
|
||||
]
|
||||
|
||||
platform = MockEntityPlatform(hass, domain="input_boolean")
|
||||
entity = Entity()
|
||||
entity.hass = hass
|
||||
entity.entity_id = "input_boolean.b0"
|
||||
await entity.async_internal_added_to_hass()
|
||||
await platform.async_add_entities([entity])
|
||||
|
||||
entity = RestoreEntity()
|
||||
entity.hass = hass
|
||||
entity.entity_id = "input_boolean.b1"
|
||||
await entity.async_internal_added_to_hass()
|
||||
await platform.async_add_entities([entity])
|
||||
|
||||
data = async_get(hass)
|
||||
|
||||
@ -378,10 +381,11 @@ async def test_load_error(hass: HomeAssistant) -> None:
|
||||
|
||||
async def test_state_saved_on_remove(hass: HomeAssistant) -> None:
|
||||
"""Test that we save entity state on removal."""
|
||||
platform = MockEntityPlatform(hass, domain="input_boolean")
|
||||
entity = RestoreEntity()
|
||||
entity.hass = hass
|
||||
entity.entity_id = "input_boolean.b0"
|
||||
await entity.async_internal_added_to_hass()
|
||||
await platform.async_add_entities([entity])
|
||||
|
||||
now = dt_util.utcnow()
|
||||
hass.states.async_set(
|
||||
|
Loading…
x
Reference in New Issue
Block a user