Deprecate binary sensor device class constants (#105736)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
c64c1c8f08
commit
a4ccd6e13b
5 changed files with 302 additions and 33 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from datetime import timedelta
|
||||
from enum import StrEnum
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Literal, final
|
||||
|
||||
|
@ -16,6 +17,10 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
check_if_deprecated_constant,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
@ -121,34 +126,92 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(BinarySensorDeviceClass))
|
|||
# DEVICE_CLASS* below are deprecated as of 2021.12
|
||||
# use the BinarySensorDeviceClass enum instead.
|
||||
DEVICE_CLASSES = [cls.value for cls in BinarySensorDeviceClass]
|
||||
DEVICE_CLASS_BATTERY = BinarySensorDeviceClass.BATTERY.value
|
||||
DEVICE_CLASS_BATTERY_CHARGING = BinarySensorDeviceClass.BATTERY_CHARGING.value
|
||||
DEVICE_CLASS_CO = BinarySensorDeviceClass.CO.value
|
||||
DEVICE_CLASS_COLD = BinarySensorDeviceClass.COLD.value
|
||||
DEVICE_CLASS_CONNECTIVITY = BinarySensorDeviceClass.CONNECTIVITY.value
|
||||
DEVICE_CLASS_DOOR = BinarySensorDeviceClass.DOOR.value
|
||||
DEVICE_CLASS_GARAGE_DOOR = BinarySensorDeviceClass.GARAGE_DOOR.value
|
||||
DEVICE_CLASS_GAS = BinarySensorDeviceClass.GAS.value
|
||||
DEVICE_CLASS_HEAT = BinarySensorDeviceClass.HEAT.value
|
||||
DEVICE_CLASS_LIGHT = BinarySensorDeviceClass.LIGHT.value
|
||||
DEVICE_CLASS_LOCK = BinarySensorDeviceClass.LOCK.value
|
||||
DEVICE_CLASS_MOISTURE = BinarySensorDeviceClass.MOISTURE.value
|
||||
DEVICE_CLASS_MOTION = BinarySensorDeviceClass.MOTION.value
|
||||
DEVICE_CLASS_MOVING = BinarySensorDeviceClass.MOVING.value
|
||||
DEVICE_CLASS_OCCUPANCY = BinarySensorDeviceClass.OCCUPANCY.value
|
||||
DEVICE_CLASS_OPENING = BinarySensorDeviceClass.OPENING.value
|
||||
DEVICE_CLASS_PLUG = BinarySensorDeviceClass.PLUG.value
|
||||
DEVICE_CLASS_POWER = BinarySensorDeviceClass.POWER.value
|
||||
DEVICE_CLASS_PRESENCE = BinarySensorDeviceClass.PRESENCE.value
|
||||
DEVICE_CLASS_PROBLEM = BinarySensorDeviceClass.PROBLEM.value
|
||||
DEVICE_CLASS_RUNNING = BinarySensorDeviceClass.RUNNING.value
|
||||
DEVICE_CLASS_SAFETY = BinarySensorDeviceClass.SAFETY.value
|
||||
DEVICE_CLASS_SMOKE = BinarySensorDeviceClass.SMOKE.value
|
||||
DEVICE_CLASS_SOUND = BinarySensorDeviceClass.SOUND.value
|
||||
DEVICE_CLASS_TAMPER = BinarySensorDeviceClass.TAMPER.value
|
||||
DEVICE_CLASS_UPDATE = BinarySensorDeviceClass.UPDATE.value
|
||||
DEVICE_CLASS_VIBRATION = BinarySensorDeviceClass.VIBRATION.value
|
||||
DEVICE_CLASS_WINDOW = BinarySensorDeviceClass.WINDOW.value
|
||||
_DEPRECATED_DEVICE_CLASS_BATTERY = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.BATTERY, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_BATTERY_CHARGING = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.BATTERY_CHARGING, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_CO = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.CO, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_COLD = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.COLD, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_CONNECTIVITY = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.CONNECTIVITY, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.DOOR, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_GARAGE_DOOR = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.GARAGE_DOOR, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_GAS = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.GAS, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_HEAT = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.HEAT, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_LIGHT = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.LIGHT, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_LOCK = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.LOCK, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_MOISTURE = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.MOISTURE, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_MOTION = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.MOTION, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_MOVING = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.MOVING, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_OCCUPANCY = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.OCCUPANCY, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_OPENING = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.OPENING, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_PLUG = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.PLUG, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_POWER = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.POWER, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_PRESENCE = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.PRESENCE, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_PROBLEM = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.PROBLEM, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_RUNNING = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.RUNNING, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_SAFETY = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.SAFETY, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_SMOKE = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.SMOKE, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_SOUND = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.SOUND, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_TAMPER = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.TAMPER, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_UPDATE = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.UPDATE, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_VIBRATION = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.VIBRATION, "2025.1"
|
||||
)
|
||||
_DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.WINDOW, "2025.1"
|
||||
)
|
||||
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Callable
|
||||
from contextlib import suppress
|
||||
from enum import Enum
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
from typing import Any, NamedTuple, ParamSpec, TypeVar
|
||||
|
||||
from homeassistant.core import HomeAssistant, async_get_hass
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
@ -153,7 +154,25 @@ def _print_deprecation_warning(
|
|||
verb: str,
|
||||
breaks_in_ha_version: str | None,
|
||||
) -> None:
|
||||
logger = logging.getLogger(obj.__module__)
|
||||
_print_deprecation_warning_internal(
|
||||
obj.__name__,
|
||||
obj.__module__,
|
||||
replacement,
|
||||
description,
|
||||
verb,
|
||||
breaks_in_ha_version,
|
||||
)
|
||||
|
||||
|
||||
def _print_deprecation_warning_internal(
|
||||
obj_name: str,
|
||||
module_name: str,
|
||||
replacement: str,
|
||||
description: str,
|
||||
verb: str,
|
||||
breaks_in_ha_version: str | None,
|
||||
) -> None:
|
||||
logger = logging.getLogger(module_name)
|
||||
if breaks_in_ha_version:
|
||||
breaks_in = f" which will be removed in HA Core {breaks_in_ha_version}"
|
||||
else:
|
||||
|
@ -163,7 +182,7 @@ def _print_deprecation_warning(
|
|||
except MissingIntegrationFrame:
|
||||
logger.warning(
|
||||
"%s is a deprecated %s%s. Use %s instead",
|
||||
obj.__name__,
|
||||
obj_name,
|
||||
description,
|
||||
breaks_in,
|
||||
replacement,
|
||||
|
@ -183,7 +202,7 @@ def _print_deprecation_warning(
|
|||
"%s was %s from %s, this is a deprecated %s%s. Use %s instead,"
|
||||
" please %s"
|
||||
),
|
||||
obj.__name__,
|
||||
obj_name,
|
||||
verb,
|
||||
integration_frame.integration,
|
||||
description,
|
||||
|
@ -194,10 +213,69 @@ def _print_deprecation_warning(
|
|||
else:
|
||||
logger.warning(
|
||||
"%s was %s from %s, this is a deprecated %s%s. Use %s instead",
|
||||
obj.__name__,
|
||||
obj_name,
|
||||
verb,
|
||||
integration_frame.integration,
|
||||
description,
|
||||
breaks_in,
|
||||
replacement,
|
||||
)
|
||||
|
||||
|
||||
class DeprecatedConstant(NamedTuple):
|
||||
"""Deprecated constant."""
|
||||
|
||||
value: Any
|
||||
replacement: str
|
||||
breaks_in_ha_version: str | None
|
||||
|
||||
|
||||
class DeprecatedConstantEnum(NamedTuple):
|
||||
"""Deprecated constant."""
|
||||
|
||||
enum: Enum
|
||||
breaks_in_ha_version: str | None
|
||||
|
||||
|
||||
def check_if_deprecated_constant(name: str, module_globals: dict[str, Any]) -> Any:
|
||||
"""Check if the not found name is a deprecated constant.
|
||||
|
||||
If it is, print a deprecation warning and return the value of the constant.
|
||||
Otherwise raise AttributeError.
|
||||
"""
|
||||
module_name = module_globals.get("__name__")
|
||||
logger = logging.getLogger(module_name)
|
||||
if (deprecated_const := module_globals.get(f"_DEPRECATED_{name}")) is None:
|
||||
raise AttributeError(f"Module {module_name!r} has no attribute {name!r}")
|
||||
if isinstance(deprecated_const, DeprecatedConstant):
|
||||
value = deprecated_const.value
|
||||
replacement = deprecated_const.replacement
|
||||
breaks_in_ha_version = deprecated_const.breaks_in_ha_version
|
||||
elif isinstance(deprecated_const, DeprecatedConstantEnum):
|
||||
value = deprecated_const.enum.value
|
||||
replacement = (
|
||||
f"{deprecated_const.enum.__class__.__name__}.{deprecated_const.enum.name}"
|
||||
)
|
||||
breaks_in_ha_version = deprecated_const.breaks_in_ha_version
|
||||
else:
|
||||
msg = (
|
||||
f"Value of _DEPRECATED_{name!r} is an instance of {type(deprecated_const)} "
|
||||
"but an instance of DeprecatedConstant or DeprecatedConstantEnum is required"
|
||||
)
|
||||
|
||||
logger.debug(msg)
|
||||
# PEP 562 -- Module __getattr__ and __dir__
|
||||
# specifies that __getattr__ should raise AttributeError if the attribute is not
|
||||
# found.
|
||||
# https://peps.python.org/pep-0562/#specification
|
||||
raise AttributeError(msg) # noqa: TRY004
|
||||
|
||||
_print_deprecation_warning_internal(
|
||||
name,
|
||||
module_name or __name__,
|
||||
replacement,
|
||||
"constant",
|
||||
"used",
|
||||
breaks_in_ha_version,
|
||||
)
|
||||
return value
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""The tests for the Binary sensor component."""
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@ -19,6 +20,9 @@ from tests.common import (
|
|||
mock_platform,
|
||||
)
|
||||
from tests.testing_config.custom_components.test.binary_sensor import MockBinarySensor
|
||||
from tests.testing_config.custom_components.test_constant_deprecation.binary_sensor import (
|
||||
import_deprecated,
|
||||
)
|
||||
|
||||
TEST_DOMAIN = "test"
|
||||
|
||||
|
@ -194,3 +198,26 @@ async def test_entity_category_config_raises_error(
|
|||
"Entity binary_sensor.test2 cannot be added as the entity category is set to config"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_class",
|
||||
list(binary_sensor.BinarySensorDeviceClass),
|
||||
)
|
||||
def test_deprecated_constant_device_class(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
device_class: binary_sensor.BinarySensorDeviceClass,
|
||||
) -> None:
|
||||
"""Test deprecated binary sensor device classes."""
|
||||
import_deprecated(device_class)
|
||||
|
||||
assert (
|
||||
"homeassistant.components.binary_sensor",
|
||||
logging.WARNING,
|
||||
(
|
||||
f"DEVICE_CLASS_{device_class.name} was used from test_constant_deprecation,"
|
||||
" this is a deprecated constant which will be removed in HA Core 2025.1. "
|
||||
f"Use BinarySensorDeviceClass.{device_class.name} instead, please report "
|
||||
"it to the author of the 'test_constant_deprecation' custom integration"
|
||||
),
|
||||
) in caplog.record_tuples
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
"""Test deprecation helpers."""
|
||||
import logging
|
||||
import sys
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstant,
|
||||
DeprecatedConstantEnum,
|
||||
check_if_deprecated_constant,
|
||||
deprecated_class,
|
||||
deprecated_function,
|
||||
deprecated_substitute,
|
||||
|
@ -247,3 +252,92 @@ def test_deprecated_function_called_from_custom_integration(
|
|||
"Use new_function instead, please report it to the author of the "
|
||||
"'hue' custom integration"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("deprecated_constant", "extra_msg"),
|
||||
[
|
||||
(
|
||||
DeprecatedConstant("value", "NEW_CONSTANT", None),
|
||||
". Use NEW_CONSTANT instead",
|
||||
),
|
||||
(
|
||||
DeprecatedConstant(1, "NEW_CONSTANT", "2099.1"),
|
||||
" which will be removed in HA Core 2099.1. Use NEW_CONSTANT instead",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("module_name", "extra_extra_msg"),
|
||||
[
|
||||
("homeassistant.components.hue.light", ""), # builtin integration
|
||||
(
|
||||
"config.custom_components.hue.light",
|
||||
", please report it to the author of the 'hue' custom integration",
|
||||
), # custom component integration
|
||||
],
|
||||
)
|
||||
def test_check_if_deprecated_constant(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
deprecated_constant: DeprecatedConstant | DeprecatedConstantEnum,
|
||||
extra_msg: str,
|
||||
module_name: str,
|
||||
extra_extra_msg: str,
|
||||
) -> None:
|
||||
"""Test check_if_deprecated_constant."""
|
||||
module_globals = {
|
||||
"__name__": module_name,
|
||||
"_DEPRECATED_TEST_CONSTANT": deprecated_constant,
|
||||
}
|
||||
filename = f"/home/paulus/{module_name.replace('.', '/')}.py"
|
||||
|
||||
# mock module for homeassistant/helpers/frame.py#get_integration_frame
|
||||
sys.modules[module_name] = Mock(__file__=filename)
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.frame.extract_stack",
|
||||
return_value=[
|
||||
Mock(
|
||||
filename="/home/paulus/homeassistant/core.py",
|
||||
lineno="23",
|
||||
line="do_something()",
|
||||
),
|
||||
Mock(
|
||||
filename=filename,
|
||||
lineno="23",
|
||||
line="await session.close()",
|
||||
),
|
||||
Mock(
|
||||
filename="/home/paulus/aiohue/lights.py",
|
||||
lineno="2",
|
||||
line="something()",
|
||||
),
|
||||
],
|
||||
):
|
||||
value = check_if_deprecated_constant("TEST_CONSTANT", module_globals)
|
||||
assert value == deprecated_constant.value
|
||||
|
||||
assert (
|
||||
module_name,
|
||||
logging.WARNING,
|
||||
f"TEST_CONSTANT was used from hue, this is a deprecated constant{extra_msg}{extra_extra_msg}",
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_test_check_if_deprecated_constant_invalid(
|
||||
caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test check_if_deprecated_constant will raise an attribute error and create an log entry on an invalid deprecation type."""
|
||||
module_name = "homeassistant.components.hue.light"
|
||||
module_globals = {"__name__": module_name, "_DEPRECATED_TEST_CONSTANT": 1}
|
||||
name = "TEST_CONSTANT"
|
||||
|
||||
excepted_msg = (
|
||||
f"Value of _DEPRECATED_{name!r} is an instance of <class 'int'> "
|
||||
"but an instance of DeprecatedConstant or DeprecatedConstantEnum is required"
|
||||
)
|
||||
|
||||
with pytest.raises(AttributeError, match=excepted_msg):
|
||||
check_if_deprecated_constant(name, module_globals)
|
||||
|
||||
assert (module_name, logging.DEBUG, excepted_msg) in caplog.record_tuples
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
"""Test deprecated binary sensor device classes."""
|
||||
from homeassistant.components import binary_sensor
|
||||
|
||||
|
||||
def import_deprecated(device_class: binary_sensor.BinarySensorDeviceClass):
|
||||
"""Import deprecated device class constant."""
|
||||
getattr(binary_sensor, f"DEVICE_CLASS_{device_class.name}")
|
Loading…
Add table
Add a link
Reference in a new issue