diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 3e93bf27ffc..5047714e097 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -26,6 +26,10 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA_BASE, make_entity_service_schema, ) +from homeassistant.helpers.deprecation import ( + 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.temperature import display_temp as show_temp @@ -33,6 +37,20 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.util.unit_conversion import TemperatureConverter from .const import ( # noqa: F401 + _DEPRECATED_HVAC_MODE_AUTO, + _DEPRECATED_HVAC_MODE_COOL, + _DEPRECATED_HVAC_MODE_DRY, + _DEPRECATED_HVAC_MODE_FAN_ONLY, + _DEPRECATED_HVAC_MODE_HEAT, + _DEPRECATED_HVAC_MODE_HEAT_COOL, + _DEPRECATED_HVAC_MODE_OFF, + _DEPRECATED_SUPPORT_AUX_HEAT, + _DEPRECATED_SUPPORT_FAN_MODE, + _DEPRECATED_SUPPORT_PRESET_MODE, + _DEPRECATED_SUPPORT_SWING_MODE, + _DEPRECATED_SUPPORT_TARGET_HUMIDITY, + _DEPRECATED_SUPPORT_TARGET_TEMPERATURE, + _DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE, ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, @@ -64,10 +82,6 @@ from .const import ( # noqa: F401 FAN_OFF, FAN_ON, FAN_TOP, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, HVAC_MODES, PRESET_ACTIVITY, PRESET_AWAY, @@ -84,13 +98,6 @@ from .const import ( # noqa: F401 SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, - SUPPORT_AUX_HEAT, - SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, - SUPPORT_SWING_MODE, - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, @@ -128,6 +135,12 @@ SET_TEMPERATURE_SCHEMA = vol.All( ), ) +# 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 +# Both can be removed if no deprecated constant are in this module anymore +__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals()) +__dir__ = ft.partial(dir_with_deprecated_constants, module_globals=globals()) + # mypy: disallow-any-generics diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index 23c76c151d7..615dc7d48dd 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -1,6 +1,13 @@ """Provides the constants needed for component.""" from enum import IntFlag, StrEnum +from functools import partial + +from homeassistant.helpers.deprecation import ( + DeprecatedConstantEnum, + check_if_deprecated_constant, + dir_with_deprecated_constants, +) class HVACMode(StrEnum): @@ -31,13 +38,13 @@ class HVACMode(StrEnum): # These HVAC_MODE_* constants are deprecated as of Home Assistant 2022.5. # Please use the HVACMode enum instead. -HVAC_MODE_OFF = "off" -HVAC_MODE_HEAT = "heat" -HVAC_MODE_COOL = "cool" -HVAC_MODE_HEAT_COOL = "heat_cool" -HVAC_MODE_AUTO = "auto" -HVAC_MODE_DRY = "dry" -HVAC_MODE_FAN_ONLY = "fan_only" +_DEPRECATED_HVAC_MODE_OFF = DeprecatedConstantEnum(HVACMode.OFF, "2025.1") +_DEPRECATED_HVAC_MODE_HEAT = DeprecatedConstantEnum(HVACMode.HEAT, "2025.1") +_DEPRECATED_HVAC_MODE_COOL = DeprecatedConstantEnum(HVACMode.COOL, "2025.1") +_DEPRECATED_HVAC_MODE_HEAT_COOL = DeprecatedConstantEnum(HVACMode.HEAT_COOL, "2025.1") +_DEPRECATED_HVAC_MODE_AUTO = DeprecatedConstantEnum(HVACMode.AUTO, "2025.1") +_DEPRECATED_HVAC_MODE_DRY = DeprecatedConstantEnum(HVACMode.DRY, "2025.1") +_DEPRECATED_HVAC_MODE_FAN_ONLY = DeprecatedConstantEnum(HVACMode.FAN_ONLY, "2025.1") HVAC_MODES = [cls.value for cls in HVACMode] # No preset is active @@ -99,12 +106,12 @@ class HVACAction(StrEnum): # These CURRENT_HVAC_* constants are deprecated as of Home Assistant 2022.5. # Please use the HVACAction enum instead. -CURRENT_HVAC_OFF = "off" -CURRENT_HVAC_HEAT = "heating" -CURRENT_HVAC_COOL = "cooling" -CURRENT_HVAC_DRY = "drying" -CURRENT_HVAC_IDLE = "idle" -CURRENT_HVAC_FAN = "fan" +_DEPRECATED_CURRENT_HVAC_OFF = DeprecatedConstantEnum(HVACAction.OFF, "2025.1") +_DEPRECATED_CURRENT_HVAC_HEAT = DeprecatedConstantEnum(HVACAction.HEATING, "2025.1") +_DEPRECATED_CURRENT_HVAC_COOL = DeprecatedConstantEnum(HVACAction.COOLING, "2025.1") +_DEPRECATED_CURRENT_HVAC_DRY = DeprecatedConstantEnum(HVACAction.DRYING, "2025.1") +_DEPRECATED_CURRENT_HVAC_IDLE = DeprecatedConstantEnum(HVACAction.IDLE, "2025.1") +_DEPRECATED_CURRENT_HVAC_FAN = DeprecatedConstantEnum(HVACAction.FAN, "2025.1") CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction] @@ -159,10 +166,28 @@ class ClimateEntityFeature(IntFlag): # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5. # Please use the ClimateEntityFeature enum instead. -SUPPORT_TARGET_TEMPERATURE = 1 -SUPPORT_TARGET_TEMPERATURE_RANGE = 2 -SUPPORT_TARGET_HUMIDITY = 4 -SUPPORT_FAN_MODE = 8 -SUPPORT_PRESET_MODE = 16 -SUPPORT_SWING_MODE = 32 -SUPPORT_AUX_HEAT = 64 +_DEPRECATED_SUPPORT_TARGET_TEMPERATURE = DeprecatedConstantEnum( + ClimateEntityFeature.TARGET_TEMPERATURE, "2025.1" +) +_DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE = DeprecatedConstantEnum( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, "2025.1" +) +_DEPRECATED_SUPPORT_TARGET_HUMIDITY = DeprecatedConstantEnum( + ClimateEntityFeature.TARGET_HUMIDITY, "2025.1" +) +_DEPRECATED_SUPPORT_FAN_MODE = DeprecatedConstantEnum( + ClimateEntityFeature.FAN_MODE, "2025.1" +) +_DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum( + ClimateEntityFeature.PRESET_MODE, "2025.1" +) +_DEPRECATED_SUPPORT_SWING_MODE = DeprecatedConstantEnum( + ClimateEntityFeature.SWING_MODE, "2025.1" +) +_DEPRECATED_SUPPORT_AUX_HEAT = DeprecatedConstantEnum( + ClimateEntityFeature.AUX_HEAT, "2025.1" +) + +# Both 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=globals()) diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 6714e0bf35a..a920884c252 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -72,7 +72,7 @@ async def async_get_actions( } actions.append({**base_action, CONF_TYPE: "set_hvac_mode"}) - if supported_features & const.SUPPORT_PRESET_MODE: + if supported_features & const.ClimateEntityFeature.PRESET_MODE: actions.append({**base_action, CONF_TYPE: "set_preset_mode"}) return actions diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 57b9654651b..78f358db32e 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -71,7 +71,7 @@ async def async_get_conditions( conditions.append({**base_condition, CONF_TYPE: "is_hvac_mode"}) - if supported_features & const.SUPPORT_PRESET_MODE: + if supported_features & const.ClimateEntityFeature.PRESET_MODE: conditions.append({**base_condition, CONF_TYPE: "is_preset_mode"}) return conditions diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index 8ef73ed4e51..1fc379487ed 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -220,7 +220,7 @@ async def test_action( assert set_hvac_mode_calls[0].service == "set_hvac_mode" assert set_hvac_mode_calls[0].data == { "entity_id": entry.entity_id, - "hvac_mode": const.HVAC_MODE_OFF, + "hvac_mode": const.HVACMode.OFF, } assert set_preset_mode_calls[0].domain == DOMAIN assert set_preset_mode_calls[0].service == "set_preset_mode" @@ -287,7 +287,7 @@ async def test_action_legacy( assert set_hvac_mode_calls[0].service == "set_hvac_mode" assert set_hvac_mode_calls[0].data == { "entity_id": entry.entity_id, - "hvac_mode": const.HVAC_MODE_OFF, + "hvac_mode": const.HVACMode.OFF, } diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 897a7316c95..1181a432ea2 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,11 +1,14 @@ """The tests for the climate component.""" from __future__ import annotations +from enum import Enum +from types import ModuleType from unittest.mock import MagicMock import pytest import voluptuous as vol +from homeassistant.components import climate from homeassistant.components.climate import ( SET_TEMPERATURE_SCHEMA, ClimateEntity, @@ -13,7 +16,11 @@ from homeassistant.components.climate import ( ) from homeassistant.core import HomeAssistant -from tests.common import async_mock_service +from tests.common import ( + async_mock_service, + import_and_test_deprecated_constant, + import_and_test_deprecated_constant_enum, +) async def test_set_temp_schema_no_req( @@ -96,3 +103,58 @@ async def test_sync_turn_off(hass: HomeAssistant) -> None: await climate.async_turn_off() assert climate.turn_off.called + + +def _create_tuples(enum: Enum, constant_prefix: str) -> list[tuple[Enum, str]]: + result = [] + for enum in enum: + result.append((enum, constant_prefix)) + return result + + +@pytest.mark.parametrize( + ("enum", "constant_prefix"), + _create_tuples(climate.ClimateEntityFeature, "SUPPORT_") + + _create_tuples(climate.HVACMode, "HVAC_MODE_"), +) +@pytest.mark.parametrize( + "module", + [climate, climate.const], +) +def test_deprecated_constants( + 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, "2025.1" + ) + + +@pytest.mark.parametrize( + ("enum", "constant_postfix"), + [ + (climate.HVACAction.OFF, "OFF"), + (climate.HVACAction.HEATING, "HEAT"), + (climate.HVACAction.COOLING, "COOL"), + (climate.HVACAction.DRYING, "DRY"), + (climate.HVACAction.IDLE, "IDLE"), + (climate.HVACAction.FAN, "FAN"), + ], +) +def test_deprecated_current_constants( + caplog: pytest.LogCaptureFixture, + enum: climate.HVACAction, + constant_postfix: str, +) -> None: + """Test deprecated current constants.""" + import_and_test_deprecated_constant( + caplog, + climate.const, + "CURRENT_HVAC_" + constant_postfix, + f"{enum.__class__.__name__}.{enum.name}", + enum, + "2025.1", + ) diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 62051bbf244..723881ac182 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -8,11 +8,7 @@ from unittest import mock from aiohomekit import AccessoryNotFoundError from aiohomekit.testing import FakePairing -from homeassistant.components.climate import ( - SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_RANGE, -) +from homeassistant.components.climate import ClimateEntityFeature from homeassistant.components.sensor import SensorStateClass from homeassistant.config_entries import ConfigEntryState from homeassistant.const import UnitOfTemperature @@ -108,9 +104,9 @@ async def test_ecobee3_setup(hass: HomeAssistant) -> None: friendly_name="HomeW", unique_id="00:00:00:00:00:00_1_16", supported_features=( - SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE - | SUPPORT_TARGET_HUMIDITY + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + | ClimateEntityFeature.TARGET_HUMIDITY ), capabilities={ "hvac_modes": ["off", "heat", "cool", "heat_cool"], diff --git a/tests/components/matter/test_climate.py b/tests/components/matter/test_climate.py index ec8453b5c56..81d210ed579 100644 --- a/tests/components/matter/test_climate.py +++ b/tests/components/matter/test_climate.py @@ -6,18 +6,7 @@ from matter_server.client.models.node import MatterNode from matter_server.common.helpers.util import create_attribute_path_from_attribute import pytest -from homeassistant.components.climate import ( - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_HEAT_COOL, - HVACAction, - HVACMode, -) -from homeassistant.components.climate.const import ( - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_OFF, -) +from homeassistant.components.climate import HVACAction, HVACMode from homeassistant.core import HomeAssistant from .common import ( @@ -51,7 +40,7 @@ async def test_thermostat( # test set temperature when target temp is None assert state.attributes["temperature"] is None - assert state.state == HVAC_MODE_COOL + assert state.state == HVACMode.COOL with pytest.raises( ValueError, match="Current target_temperature should not be None" ): @@ -85,7 +74,7 @@ async def test_thermostat( ): state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_HEAT_COOL + assert state.state == HVACMode.HEAT_COOL await hass.services.async_call( "climate", "set_temperature", @@ -119,19 +108,19 @@ async def test_thermostat( await trigger_subscription_callback(hass, matter_client) state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_OFF + assert state.state == HVACMode.OFF set_node_attribute(thermostat, 1, 513, 28, 7) await trigger_subscription_callback(hass, matter_client) state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_FAN_ONLY + assert state.state == HVACMode.FAN_ONLY set_node_attribute(thermostat, 1, 513, 28, 8) await trigger_subscription_callback(hass, matter_client) state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_DRY + assert state.state == HVACMode.DRY # test running state update from device set_node_attribute(thermostat, 1, 513, 41, 1) @@ -188,7 +177,7 @@ async def test_thermostat( state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_HEAT + assert state.state == HVACMode.HEAT # change occupied heating setpoint to 20 set_node_attribute(thermostat, 1, 513, 18, 2000) @@ -225,7 +214,7 @@ async def test_thermostat( state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_COOL + assert state.state == HVACMode.COOL # change occupied cooling setpoint to 18 set_node_attribute(thermostat, 1, 513, 17, 1800) @@ -273,7 +262,7 @@ async def test_thermostat( state = hass.states.get("climate.longan_link_hvac") assert state - assert state.state == HVAC_MODE_HEAT_COOL + assert state.state == HVACMode.HEAT_COOL # change occupied cooling setpoint to 18 set_node_attribute(thermostat, 1, 513, 17, 2500) @@ -340,7 +329,7 @@ async def test_thermostat( "set_hvac_mode", { "entity_id": "climate.longan_link_hvac", - "hvac_mode": HVAC_MODE_HEAT, + "hvac_mode": HVACMode.HEAT, }, blocking=True, )