Compare commits

...

1 Commits

Author SHA1 Message Date
Erik 78493b3f7e Deprecate device tracker TrackerEntity location_name property 2026-05-22 08:47:24 +02:00
2 changed files with 145 additions and 2 deletions
@@ -1,6 +1,7 @@
"""Code to set up a device tracker platform using a config entry."""
import asyncio
import logging
from typing import Any, final
from propcache.api import cached_property
@@ -16,7 +17,13 @@ from homeassistant.const import (
STATE_NOT_HOME,
EntityCategory,
)
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.core import (
Event,
HomeAssistant,
State,
async_get_hass_or_none,
callback,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import (
DeviceInfo,
@@ -27,6 +34,7 @@ from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.typing import StateType
from homeassistant.loader import async_suggest_report_issue
from homeassistant.util.hass_dict import HassKey
from .const import (
@@ -41,6 +49,8 @@ from .const import (
SourceType,
)
_LOGGER = logging.getLogger(__name__)
DATA_COMPONENT: HassKey[EntityComponent[BaseTrackerEntity]] = HassKey(DOMAIN)
DATA_KEY: HassKey[dict[str, tuple[str, str]]] = HassKey(f"{DOMAIN}_mac")
@@ -222,13 +232,34 @@ class TrackerEntity(
entity_description: TrackerEntityDescription
_attr_latitude: float | None = None
_attr_location_accuracy: float = 0
# _attr_location_name is deprecated and will be removed in Home Assistant 2027.6
_attr_location_name: str | None = None
_attr_longitude: float | None = None
_attr_source_type: SourceType = SourceType.GPS
__active_zone: State | None = None
# If we reported setting deprecated _attr_location_name
__deprecated_attr_location_name_reported = False
__in_zones: list[str] | None = None
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing."""
super().__init_subclass__(**kwargs)
if "location_name" in cls.__dict__:
report_issue = async_suggest_report_issue(
async_get_hass_or_none(), module=cls.__module__
)
_LOGGER.warning(
(
"%s::%s is overriding the deprecated location_name method on "
"an instance of TrackerEntity, this will be unsupported from "
"Home Assistant 2027.6, please %s"
),
cls.__module__,
cls.__name__,
report_issue,
)
@cached_property
def should_poll(self) -> bool:
"""No polling for entities that have location pushed."""
@@ -249,7 +280,27 @@ class TrackerEntity(
@cached_property
def location_name(self) -> str | None:
"""Return a location name for the current location of the device."""
"""Return a location name for the current location of the device.
The property is deprecated and will be removed in Home Assistant 2027.6.
"""
if (location_name := self._attr_location_name) is not None:
if not self.__deprecated_attr_location_name_reported:
report_issue = async_suggest_report_issue(
self.hass, module=self.__class__.__module__
)
_LOGGER.warning(
(
"%s::%s is setting the deprecated _attr_location_name property "
"on an instance of TrackerEntity, this will be unsupported from "
"Home Assistant 2027.6, please %s"
),
self.__class__.__module__,
self.__class__.__name__,
report_issue,
)
self.__deprecated_attr_location_name_reported = True
return location_name
return self._attr_location_name
@cached_property
@@ -760,6 +760,98 @@ def test_base_tracker_entity() -> None:
entity.state_attributes # noqa: B018
async def test_attr_location_name_deprecation_warning(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that setting _attr_location_name logs a deprecation warning."""
error_message = "is setting the deprecated _attr_location_name property"
# No warning when _attr_location_name is unset (default None)
entity_no_attr = TrackerEntity()
entity_no_attr.hass = hass
assert entity_no_attr.location_name is None
assert error_message not in caplog.text
# Warning fires when _attr_location_name has a non-None value
entity = TrackerEntity()
entity.hass = hass
entity._attr_location_name = "the_zone"
caplog.clear()
assert entity.location_name == "the_zone"
assert error_message in caplog.text
# Warning does not fire again on subsequent access for the same instance
caplog.clear()
assert entity.location_name == "the_zone"
assert error_message not in caplog.text
# Warning is suppressed for this instance even after the cached value is
# invalidated by a subsequent _attr_location_name assignment.
entity._attr_location_name = "another_zone"
caplog.clear()
assert entity.location_name == "another_zone"
assert error_message not in caplog.text
# A fresh instance warns once again
entity_new = TrackerEntity()
entity_new.hass = hass
entity_new._attr_location_name = "the_zone"
caplog.clear()
assert entity_new.location_name == "the_zone"
assert error_message in caplog.text
def test_location_name_override_deprecation_warning(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that overriding location_name in a subclass logs a warning."""
error_message = "is overriding the deprecated location_name method"
caplog.clear()
class _SubclassWithOverride(TrackerEntity):
@property
def location_name(self) -> str | None:
return "custom"
assert error_message in caplog.text
assert _SubclassWithOverride.__name__ in caplog.text
# No warning for a subclass that does not override location_name
caplog.clear()
class _SubclassWithoutOverride(TrackerEntity):
pass
assert error_message not in caplog.text
def test_battery_level_override_deprecation_warning(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that overriding battery_level in a subclass logs a warning."""
error_message = "is overriding the deprecated battery_level method"
caplog.clear()
class _SubclassWithOverride(TrackerEntity):
@property
def battery_level(self) -> int | None:
return 50
assert error_message in caplog.text
assert _SubclassWithOverride.__name__ in caplog.text
# No warning for a subclass that does not override battery_level
caplog.clear()
class _SubclassWithoutOverride(TrackerEntity):
pass
assert error_message not in caplog.text
@pytest.mark.parametrize(
("mac_address", "unique_id"), [(TEST_MAC_ADDRESS, f"{TEST_MAC_ADDRESS}_yo1")]
)