Compare commits

...

12 Commits

Author SHA1 Message Date
G Johansson
e20bda33a4 Fix entity test 2025-01-19 21:29:42 +01:00
G Johansson
f2125a1133 Reset tts test 2 2025-01-19 21:29:42 +01:00
G Johansson
557caa7ecb Reset tts test 2025-01-19 21:29:42 +01:00
G Johansson
d973e42c97 Reset kira test 2025-01-19 21:29:42 +01:00
G Johansson
428c7ced94 Reset blackbird test 2025-01-19 21:29:42 +01:00
G Johansson
7b14bd89a0 Fix template preview 2025-01-19 21:29:42 +01:00
G Johansson
f1f45d6b5b Fixes 2025-01-19 21:29:42 +01:00
G Johansson
50e4311416 Remove 2025-01-19 21:29:42 +01:00
G Johansson
1a32a2f9c2 Mod tests 2025-01-19 21:29:42 +01:00
G Johansson
f9cecc0cd5 Fix some tests 2025-01-19 21:29:42 +01:00
G Johansson
6fe2612f1d Fix comment 2025-01-19 21:29:42 +01:00
G Johansson
43fbb2ab7b Don't allow entities without platform 2025-01-19 21:29:42 +01:00
8 changed files with 176 additions and 80 deletions

View File

@@ -3,7 +3,9 @@
from __future__ import annotations
from collections.abc import Callable, Coroutine, Mapping
from datetime import timedelta
from functools import partial
import logging
from typing import Any, cast
import voluptuous as vol
@@ -13,6 +15,7 @@ from homeassistant.const import CONF_ENTITIES, CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
@@ -48,6 +51,8 @@ _STATISTIC_MEASURES = [
"sum",
]
_LOGGER = logging.getLogger(__name__)
async def basic_group_options_schema(
domain: str | list[str], handler: SchemaCommonFlowHandler | None
@@ -424,6 +429,15 @@ def ws_start_preview(
)
preview_entity.hass = hass
preview_entity.registry_entry = entity_registry_entry
preview_entity.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=group_type,
platform_name=DOMAIN,
platform=None,
scan_interval=timedelta(hours=1),
entity_namespace=None,
)
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(

View File

@@ -3,15 +3,17 @@
from __future__ import annotations
from collections.abc import Mapping
from datetime import timedelta
from typing import Any, cast
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
from homeassistant.const import CONF_NAME, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
@@ -36,7 +38,7 @@ from .const import (
DEFAULT_NAME,
DOMAIN,
)
from .sensor import MoldIndicator
from .sensor import _LOGGER, MoldIndicator
async def validate_input(
@@ -168,6 +170,15 @@ def ws_start_preview(
None,
)
preview_entity.hass = hass
preview_entity.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=SENSOR_DOMAIN,
platform_name=DOMAIN,
platform=None,
scan_interval=timedelta(hours=1),
entity_namespace=None,
)
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(

View File

@@ -1,5 +1,6 @@
"""OwnTracks Message handlers."""
from datetime import timedelta
import json
import logging
@@ -9,6 +10,7 @@ from nacl.secret import SecretBox
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import SourceType
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, STATE_HOME
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.util import decorator, slugify
from .helper import supports_encryption
@@ -317,6 +319,15 @@ async def async_handle_waypoint(hass, name_base, waypoint):
)
zone.hass = hass
zone.entity_id = entity_id
zone.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain="zone",
platform_name="owntracks",
platform=None,
scan_interval=timedelta(seconds=15),
entity_namespace=None,
)
zone.async_write_ha_state()

View File

@@ -14,6 +14,7 @@ from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
@@ -37,6 +38,7 @@ from homeassistant.helpers.selector import (
from . import DOMAIN
from .sensor import (
_LOGGER,
CONF_KEEP_LAST_SAMPLE,
CONF_MAX_AGE,
CONF_PERCENTILE,
@@ -232,6 +234,15 @@ async def ws_start_preview(
msg["user_input"].get(CONF_PERCENTILE),
)
preview_entity.hass = hass
preview_entity.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=SENSOR_DOMAIN,
platform_name=DOMAIN,
platform=None,
scan_interval=timedelta(hours=1),
entity_namespace=None,
)
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = await preview_entity.async_start_preview(

View File

@@ -3,7 +3,9 @@
from __future__ import annotations
from collections.abc import Callable, Coroutine, Mapping
from datetime import timedelta
from functools import partial
import logging
from typing import Any, cast
import voluptuous as vol
@@ -32,6 +34,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
@@ -68,6 +71,8 @@ from .sensor import async_create_preview_sensor
from .switch import async_create_preview_switch
from .template_entity import TemplateEntity
_LOGGER = logging.getLogger(__name__)
_SCHEMA_STATE: dict[vol.Marker, Any] = {
vol.Required(CONF_STATE): selector.TemplateSelector(),
}
@@ -524,6 +529,15 @@ def ws_start_preview(
preview_entity = CREATE_PREVIEW_ENTITY[template_type](hass, name, msg["user_input"])
preview_entity.hass = hass
preview_entity.registry_entry = entity_registry_entry
preview_entity.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=template_type,
platform_name=DOMAIN,
platform=None,
scan_interval=timedelta(hours=1),
entity_namespace=None,
)
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
from collections.abc import Mapping
from datetime import timedelta
from typing import Any
import voluptuous as vol
@@ -13,6 +14,7 @@ from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
@@ -20,7 +22,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
SchemaFlowFormStep,
)
from .binary_sensor import ThresholdSensor
from .binary_sensor import _LOGGER, ThresholdSensor
from .const import CONF_HYSTERESIS, CONF_LOWER, CONF_UPPER, DEFAULT_HYSTERESIS, DOMAIN
@@ -140,6 +142,15 @@ def ws_start_preview(
None,
)
preview_entity.hass = hass
preview_entity.platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=SENSOR_DOMAIN,
platform_name=DOMAIN,
platform=None,
scan_interval=timedelta(hours=1),
entity_namespace=None,
)
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(

View File

@@ -64,7 +64,7 @@ from .event import (
async_track_device_registry_updated_event,
async_track_entity_registry_updated_event,
)
from .frame import report_non_thread_safe_operation
from .frame import ReportBehavior, report_non_thread_safe_operation, report_usage
from .typing import UNDEFINED, StateType, UndefinedType
timer = time.time
@@ -464,9 +464,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
@@ -722,9 +719,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,
@@ -737,10 +731,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,
@@ -983,21 +973,14 @@ class Entity(
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,
# Break if entity is not loaded using EntityComponent
# behavior changed from logging in in 2025.1
if self.platform is None:
report_usage( # type: ignore[unreachable]
f"Entity {self.entity_id} ({type(self)}) does not have a platform,"
"this may be caused by adding it manually instead of with an EntityComponent helper",
core_behavior=ReportBehavior.ERROR,
)
self._no_platform_reported = True
if self.entity_id is None:
raise NoEntitySpecifiedError(
@@ -1499,10 +1482,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(
@@ -1632,9 +1612,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
platform_name = self.platform.platform_name if self.platform else None
platform_name = self.platform.platform_name
return async_suggest_report_issue(
self.hass, integration_domain=platform_name, module=type(self).__module__
)

View File

@@ -6,6 +6,7 @@ import dataclasses
from datetime import timedelta
from enum import IntFlag
import logging
import re
import threading
from typing import Any
from unittest.mock import MagicMock, PropertyMock, patch
@@ -100,6 +101,8 @@ async def test_async_update_support(hass: HomeAssistant) -> None:
ent = AsyncEntity()
ent.hass = hass
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
await ent.async_update_ha_state(True)
@@ -124,6 +127,8 @@ async def test_device_class(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.entity_id = "test.overwrite_hidden_true"
ent.hass = hass
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_write_ha_state()
state = hass.states.get(ent.entity_id)
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
@@ -151,6 +156,9 @@ async def test_warn_slow_update(
mock_entity.entity_id = "comp_test.test_entity"
mock_entity.async_update = async_update
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([mock_entity])
fast_update_time = 0.0000001
with patch.object(entity, "SLOW_UPDATE_WARNING", fast_update_time):
@@ -341,6 +349,9 @@ async def test_async_parallel_updates_with_zero(hass: HomeAssistant) -> None:
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_2 = AsyncEntity("sensor.test_2", 2)
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent_1, ent_2])
try:
ent_1.async_schedule_update_ha_state(True)
ent_2.async_schedule_update_ha_state(True)
@@ -382,6 +393,9 @@ async def test_async_parallel_updates_with_zero_on_sync_update(
ent_1 = AsyncEntity("sensor.test_1", 1)
ent_2 = AsyncEntity("sensor.test_2", 2)
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent_1, ent_2])
try:
ent_1.async_schedule_update_ha_state(True)
ent_2.async_schedule_update_ha_state(True)
@@ -412,7 +426,6 @@ async def test_async_parallel_updates_with_one(hass: HomeAssistant) -> None:
self.entity_id = entity_id
self.hass = hass
self._count = count
self.parallel_updates = test_semaphore
async def async_update(self) -> None:
"""Test update."""
@@ -423,6 +436,10 @@ async def test_async_parallel_updates_with_one(hass: HomeAssistant) -> None:
ent_2 = AsyncEntity("sensor.test_2", 2)
ent_3 = AsyncEntity("sensor.test_3", 3)
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
platform.parallel_updates = test_semaphore
await platform.async_add_entities([ent_1, ent_2, ent_3])
await test_lock.acquire()
try:
@@ -488,7 +505,6 @@ async def test_async_parallel_updates_with_two(hass: HomeAssistant) -> None:
self.entity_id = entity_id
self.hass = hass
self._count = count
self.parallel_updates = test_semaphore
async def async_update(self) -> None:
"""Test update."""
@@ -500,6 +516,10 @@ async def test_async_parallel_updates_with_two(hass: HomeAssistant) -> None:
ent_3 = AsyncEntity("sensor.test_3", 3)
ent_4 = AsyncEntity("sensor.test_4", 4)
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
platform.parallel_updates = test_semaphore
await platform.async_add_entities([ent_1, ent_2, ent_3, ent_4])
await test_lock.acquire()
try:
@@ -557,7 +577,6 @@ async def test_async_parallel_updates_with_one_using_executor(
"""Initialize sync test entity."""
self.entity_id = entity_id
self.hass = hass
self.parallel_updates = test_semaphore
def update(self) -> None:
"""Test update."""
@@ -565,6 +584,10 @@ async def test_async_parallel_updates_with_one_using_executor(
entities = [SyncEntity(f"sensor.test_{i}") for i in range(3)]
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
platform.parallel_updates = test_semaphore
await platform.async_add_entities(entities)
await asyncio.gather(
*[
hass.async_create_task(
@@ -583,6 +606,8 @@ async def test_async_remove_no_platform(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "test.test"
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_write_ha_state()
assert len(hass.states.async_entity_ids()) == 1
await ent.async_remove()
@@ -659,6 +684,8 @@ async def test_set_context(hass: HomeAssistant) -> None:
ent.hass = hass
ent.entity_id = "hello.world"
ent.async_set_context(context)
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_write_ha_state()
assert hass.states.get("hello.world").context == context
@@ -671,6 +698,8 @@ async def test_set_context_expired(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_set_context(context)
ent.async_write_ha_state()
@@ -757,6 +786,8 @@ async def test_capability_attrs(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_write_ha_state()
state = hass.states.get("hello.world")
@@ -896,6 +927,8 @@ async def test_float_conversion(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.hass = hass
ent.entity_id = "hello.world"
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent.async_write_ha_state()
state = hass.states.get("hello.world")
@@ -910,6 +943,9 @@ async def test_attribution_attribute(hass: HomeAssistant) -> None:
mock_entity.entity_id = "hello.world"
mock_entity._attr_attribution = "Home Assistant"
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([mock_entity])
mock_entity.async_schedule_update_ha_state(True)
await hass.async_block_till_done()
@@ -963,12 +999,15 @@ 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_with_description = entity.Entity()
ent_with_description.entity_description = entity.EntityDescription(key="test")
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent, ent_with_description])
for field in dataclasses.fields(entity.EntityDescription._dataclass):
if field.name == "key":
continue
@@ -1665,31 +1704,23 @@ async def test_warn_using_async_update_ha_state(
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."""
async def test_raise_if_no_platform(hass: HomeAssistant) -> None:
"""Test we raise if 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"
# 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
# Without a platform, it should raise
error = re.escape(
"Detected code that Entity hello.world (<class 'homeassistant.helpers.entity.Entity'>)"
" does not have a platform,this may be caused by adding it manually instead"
" of with an EntityComponent helper. Please report this issue"
)
with pytest.raises(
RuntimeError,
match=error,
):
ent.async_write_ha_state()
async def test_invalid_state(
@@ -1700,6 +1731,9 @@ async def test_invalid_state(
ent.entity_id = "test.test"
ent.hass = hass
platform = MockEntityPlatform(hass, domain="test", platform_name="test")
await platform.async_add_entities([ent])
ent._attr_state = "x" * 255
ent.async_write_ha_state()
assert hass.states.get("test.test").state == "x" * 255
@@ -1726,13 +1760,6 @@ async def test_suggest_report_issue_built_in(
mock_entity = entity.Entity()
mock_entity.entity_id = "comp_test.test_entity"
suggestion = mock_entity._suggest_report_issue()
assert suggestion == (
"create a bug report at https://github.com/home-assistant/core/issues"
"?q=is%3Aopen+is%3Aissue"
)
mock_integration(hass, MockModule(domain="test"), built_in=True)
platform = MockEntityPlatform(hass, domain="comp_test", platform_name="test")
await platform.async_add_entities([mock_entity])
@@ -1756,20 +1783,27 @@ async def test_suggest_report_issue_custom_component(
mock_entity = CustomComponentEntity()
mock_entity.entity_id = "comp_test.test_entity"
suggestion = mock_entity._suggest_report_issue()
assert suggestion == "report it to the custom integration author"
mock_integration(
hass,
MockModule(
domain="test", partial_manifest={"issue_tracker": "https://some_url"}
),
built_in=False,
)
platform = MockEntityPlatform(hass, domain="comp_test", platform_name="test")
await platform.async_add_entities([mock_entity])
suggestion = mock_entity._suggest_report_issue()
assert suggestion == "report it to the author of the 'test' custom integration"
mock_entity2 = CustomComponentEntity()
mock_entity2.entity_id = "comp_test.test_entity2"
mock_integration(
hass,
MockModule(
domain="test2", partial_manifest={"issue_tracker": "https://some_url"}
),
built_in=False,
)
platform = MockEntityPlatform(hass, domain="comp_test2", platform_name="test2")
await platform.async_add_entities([mock_entity2])
await hass.async_block_till_done()
suggestion = mock_entity2._suggest_report_issue()
assert suggestion == "create a bug report at https://some_url"
@@ -2487,10 +2521,13 @@ async def test_cached_entity_property_override(hass: HomeAssistant) -> None:
async def test_entity_report_deprecated_supported_features_values(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test reporting deprecated supported feature values only happens once."""
ent = entity.Entity()
ent.hass = hass
ent.platform = MockEntityPlatform(hass)
class MockEntityFeatures(IntFlag):
VALUE1 = 1
@@ -2629,18 +2666,27 @@ async def test_async_write_ha_state_thread_safety(hass: HomeAssistant) -> None:
ent = entity.Entity()
ent.entity_id = "test.any"
ent.hass = hass
ent.async_write_ha_state()
assert hass.states.get(ent.entity_id)
ent2 = entity.Entity()
ent2.entity_id = "test.any2"
ent2.hass = hass
platform = MockEntityPlatform(hass, domain="test")
await platform.async_add_entities([ent, ent2])
ent._attr_state = "test"
ent2._attr_state = "test"
ent.async_write_ha_state()
assert hass.states.get(ent.entity_id).state == "test"
with pytest.raises(
RuntimeError,
match="Detected code that calls async_write_ha_state from a thread.",
):
await hass.async_add_executor_job(ent2.async_write_ha_state)
assert not hass.states.get(ent2.entity_id)
await hass.async_block_till_done()
assert hass.states.get(ent2.entity_id).state != "test"
async def test_async_write_ha_state_thread_safety_always(