mirror of
https://github.com/home-assistant/core.git
synced 2026-02-03 22:05:35 +01:00
Cleanup deprecated vacuum state constants (#161750)
This commit is contained in:
@@ -6,7 +6,7 @@ import asyncio
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, final
|
||||
from typing import Any, final
|
||||
|
||||
from propcache.api import cached_property
|
||||
import voluptuous as vol
|
||||
@@ -22,12 +22,6 @@ from homeassistant.const import ( # noqa: F401 # STATE_PAUSED/IDLE are API
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
@@ -36,16 +30,7 @@ from homeassistant.helpers.icon import icon_for_battery_level
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
from .const import ( # noqa: F401
|
||||
_DEPRECATED_STATE_CLEANING,
|
||||
_DEPRECATED_STATE_DOCKED,
|
||||
_DEPRECATED_STATE_ERROR,
|
||||
_DEPRECATED_STATE_RETURNING,
|
||||
DATA_COMPONENT,
|
||||
DOMAIN,
|
||||
VacuumActivity,
|
||||
VacuumEntityFeature,
|
||||
)
|
||||
from .const import DATA_COMPONENT, DOMAIN, VacuumActivity, VacuumEntityFeature
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -73,11 +58,6 @@ SERVICE_STOP = "stop"
|
||||
|
||||
DEFAULT_NAME = "Vacuum cleaner robot"
|
||||
|
||||
# These STATE_* constants are deprecated as of Home Assistant 2025.1.
|
||||
# Please use the VacuumActivity enum instead.
|
||||
_DEPRECATED_STATE_IDLE = DeprecatedConstantEnum(VacuumActivity.IDLE, "2026.1")
|
||||
_DEPRECATED_STATE_PAUSED = DeprecatedConstantEnum(VacuumActivity.PAUSED, "2026.1")
|
||||
|
||||
_BATTERY_DEPRECATION_IGNORED_PLATFORMS = (
|
||||
"mqtt",
|
||||
"template",
|
||||
@@ -196,7 +176,6 @@ class StateVacuumEntity(
|
||||
_attr_activity: VacuumActivity | None = None
|
||||
_attr_supported_features: VacuumEntityFeature = VacuumEntityFeature(0)
|
||||
|
||||
__vacuum_legacy_state: bool = False
|
||||
__vacuum_legacy_battery_level: bool = False
|
||||
__vacuum_legacy_battery_icon: bool = False
|
||||
__vacuum_legacy_battery_feature: bool = False
|
||||
@@ -204,10 +183,6 @@ class StateVacuumEntity(
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
"""Post initialisation processing."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
if any(method in cls.__dict__ for method in ("_attr_state", "state")):
|
||||
# Integrations should use the 'activity' property instead of
|
||||
# setting the state directly.
|
||||
cls.__vacuum_legacy_state = True
|
||||
if any(
|
||||
method in cls.__dict__
|
||||
for method in ("_attr_battery_level", "battery_level")
|
||||
@@ -223,11 +198,9 @@ class StateVacuumEntity(
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""Set attribute.
|
||||
|
||||
Deprecation warning if setting state, battery icon or battery level
|
||||
Deprecation warning if setting battery icon or battery level
|
||||
attributes directly unless already reported.
|
||||
"""
|
||||
if name == "_attr_state":
|
||||
self._report_deprecated_activity_handling()
|
||||
if name in {"_attr_battery_level", "_attr_battery_icon"}:
|
||||
self._report_deprecated_battery_properties(name[6:])
|
||||
return super().__setattr__(name, value)
|
||||
@@ -241,30 +214,11 @@ class StateVacuumEntity(
|
||||
) -> None:
|
||||
"""Start adding an entity to a platform."""
|
||||
super().add_to_platform_start(hass, platform, parallel_updates)
|
||||
if self.__vacuum_legacy_state:
|
||||
self._report_deprecated_activity_handling()
|
||||
if self.__vacuum_legacy_battery_level:
|
||||
self._report_deprecated_battery_properties("battery_level")
|
||||
if self.__vacuum_legacy_battery_icon:
|
||||
self._report_deprecated_battery_properties("battery_icon")
|
||||
|
||||
@callback
|
||||
def _report_deprecated_activity_handling(self) -> None:
|
||||
"""Report on deprecated handling of vacuum state.
|
||||
|
||||
Integrations should implement activity instead of using state directly.
|
||||
"""
|
||||
report_usage(
|
||||
"is setting state directly."
|
||||
f" Entity {self.entity_id} ({type(self)}) should implement the 'activity'"
|
||||
" property and return its state using the VacuumActivity enum",
|
||||
core_integration_behavior=ReportBehavior.ERROR,
|
||||
custom_integration_behavior=ReportBehavior.LOG,
|
||||
breaks_in_ha_version="2026.1",
|
||||
integration_domain=self.platform.platform_name if self.platform else None,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
|
||||
@callback
|
||||
def _report_deprecated_battery_properties(self, property: str) -> None:
|
||||
"""Report on deprecated use of battery properties.
|
||||
@@ -368,12 +322,6 @@ class StateVacuumEntity(
|
||||
"""Return the state of the vacuum cleaner."""
|
||||
if (activity := self.activity) is not None:
|
||||
return activity
|
||||
if self._attr_state is not None:
|
||||
# Backwards compatibility for integrations that set state directly
|
||||
# Should be removed in 2026.1
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(self._attr_state, str)
|
||||
return self._attr_state
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
@@ -491,13 +439,3 @@ class StateVacuumEntity(
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
await self.hass.async_add_executor_job(self.pause)
|
||||
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -3,15 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntFlag, StrEnum
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
@@ -51,19 +44,3 @@ class VacuumEntityFeature(IntFlag):
|
||||
MAP = 2048
|
||||
STATE = 4096 # Must be set by vacuum platforms derived from StateVacuumEntity
|
||||
START = 8192
|
||||
|
||||
|
||||
# These STATE_* constants are deprecated as of Home Assistant 2025.1.
|
||||
# Please use the VacuumActivity enum instead.
|
||||
_DEPRECATED_STATE_CLEANING = DeprecatedConstantEnum(VacuumActivity.CLEANING, "2026.1")
|
||||
_DEPRECATED_STATE_DOCKED = DeprecatedConstantEnum(VacuumActivity.DOCKED, "2026.1")
|
||||
_DEPRECATED_STATE_RETURNING = DeprecatedConstantEnum(VacuumActivity.RETURNING, "2026.1")
|
||||
_DEPRECATED_STATE_ERROR = DeprecatedConstantEnum(VacuumActivity.ERROR, "2026.1")
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
import logging
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import vacuum
|
||||
from homeassistant.components.vacuum import (
|
||||
DOMAIN,
|
||||
SERVICE_CLEAN_SPOT,
|
||||
@@ -31,47 +28,12 @@ from .common import async_start
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockEntity,
|
||||
MockModule,
|
||||
help_test_all,
|
||||
import_and_test_deprecated_constant_enum,
|
||||
mock_integration,
|
||||
setup_test_component_platform,
|
||||
)
|
||||
|
||||
|
||||
def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, str]]:
|
||||
return [(enum_field, constant_prefix) for enum_field in enum if enum_field]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"module",
|
||||
[vacuum],
|
||||
)
|
||||
def test_all(module: ModuleType) -> None:
|
||||
"""Test module.__all__ is correctly set."""
|
||||
help_test_all(module)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("enum", "constant_prefix"), _create_tuples(vacuum.VacuumActivity, "STATE_")
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"module",
|
||||
[vacuum],
|
||||
)
|
||||
def test_deprecated_constants_for_state(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enum: Enum,
|
||||
constant_prefix: str,
|
||||
module: ModuleType,
|
||||
) -> None:
|
||||
"""Test deprecated constants."""
|
||||
import_and_test_deprecated_constant_enum(
|
||||
caplog, module, enum, constant_prefix, "2026.1"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "expected_state"),
|
||||
[
|
||||
@@ -244,181 +206,6 @@ async def test_send_command(hass: HomeAssistant, config_flow_fixture: None) -> N
|
||||
assert "test" in strings
|
||||
|
||||
|
||||
async def test_vacuum_not_log_deprecated_state_warning(
|
||||
hass: HomeAssistant,
|
||||
mock_vacuum_entity: MockVacuum,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test correctly using activity doesn't log issue or raise repair."""
|
||||
state = hass.states.get(mock_vacuum_entity.entity_id)
|
||||
assert state is not None
|
||||
assert (
|
||||
"should implement the 'activity' property and return its state using the VacuumActivity enum"
|
||||
not in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
||||
async def test_vacuum_log_deprecated_state_warning_using_state_prop(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test incorrectly using state property does log issue and raise repair."""
|
||||
|
||||
class MockLegacyVacuum(MockVacuum):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the state of the entity."""
|
||||
return VacuumActivity.CLEANING
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=False,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
|
||||
assert (
|
||||
"should implement the 'activity' property and return its state using the VacuumActivity enum"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
||||
async def test_vacuum_log_deprecated_state_warning_using_attr_state_attr(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test incorrectly using _attr_state attribute does log issue and raise repair."""
|
||||
|
||||
class MockLegacyVacuum(MockVacuum):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start cleaning."""
|
||||
self._attr_state = VacuumActivity.CLEANING
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=False,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
|
||||
assert (
|
||||
"should implement the 'activity' property and return its state using the VacuumActivity enum"
|
||||
not in caplog.text
|
||||
)
|
||||
|
||||
await async_start(hass, entity.entity_id)
|
||||
|
||||
assert (
|
||||
"should implement the 'activity' property and return its state using the VacuumActivity enum"
|
||||
in caplog.text
|
||||
)
|
||||
caplog.clear()
|
||||
await async_start(hass, entity.entity_id)
|
||||
# Test we only log once
|
||||
assert (
|
||||
"should implement the 'activity' property and return its state using the VacuumActivity enum"
|
||||
not in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
||||
async def test_vacuum_deprecated_state_does_not_break_state(
|
||||
hass: HomeAssistant,
|
||||
config_flow_fixture: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test using _attr_state attribute does not break state."""
|
||||
|
||||
class MockLegacyVacuum(MockEntity, StateVacuumEntity):
|
||||
"""Mocked vacuum entity."""
|
||||
|
||||
_attr_supported_features = VacuumEntityFeature.STATE | VacuumEntityFeature.START
|
||||
|
||||
def __init__(self, **values: Any) -> None:
|
||||
"""Initialize a mock vacuum entity."""
|
||||
super().__init__(**values)
|
||||
self._attr_state = VacuumActivity.DOCKED
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start cleaning."""
|
||||
self._attr_state = VacuumActivity.CLEANING
|
||||
|
||||
entity = MockLegacyVacuum(
|
||||
name="Testing",
|
||||
entity_id="vacuum.test",
|
||||
)
|
||||
config_entry = MockConfigEntry(domain="test")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"test",
|
||||
async_setup_entry=help_async_setup_entry_init,
|
||||
async_unload_entry=help_async_unload_entry,
|
||||
),
|
||||
built_in=False,
|
||||
)
|
||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "docked"
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_START,
|
||||
{
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "cleaning"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
|
||||
async def test_vacuum_log_deprecated_battery_using_properties(
|
||||
hass: HomeAssistant,
|
||||
|
||||
Reference in New Issue
Block a user