Start deprecation of auxiliary heater in ClimateEntity (#112532)
* Start deprecation of auxiliary heater in ClimateEntity * No issue for core integrations * Remove unneded strings * Move report to state_attributes
This commit is contained in:
parent
834f45397d
commit
c21d508c2d
3 changed files with 371 additions and 0 deletions
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
|
@ -40,6 +41,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue
|
||||||
from homeassistant.util.unit_conversion import TemperatureConverter
|
from homeassistant.util.unit_conversion import TemperatureConverter
|
||||||
|
|
||||||
from . import group as group_pre_import # noqa: F401
|
from . import group as group_pre_import # noqa: F401
|
||||||
|
@ -309,6 +311,8 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
_attr_target_temperature: float | None = None
|
_attr_target_temperature: float | None = None
|
||||||
_attr_temperature_unit: str
|
_attr_temperature_unit: str
|
||||||
|
|
||||||
|
__climate_reported_legacy_aux = False
|
||||||
|
|
||||||
__mod_supported_features: ClimateEntityFeature = ClimateEntityFeature(0)
|
__mod_supported_features: ClimateEntityFeature = ClimateEntityFeature(0)
|
||||||
# Integrations should set `_enable_turn_on_off_backwards_compatibility` to False
|
# Integrations should set `_enable_turn_on_off_backwards_compatibility` to False
|
||||||
# once migrated and set the feature flags TURN_ON/TURN_OFF as needed.
|
# once migrated and set the feature flags TURN_ON/TURN_OFF as needed.
|
||||||
|
@ -404,6 +408,50 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
|
ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _report_legacy_aux(self) -> None:
|
||||||
|
"""Log warning and create an issue if the entity implements legacy auxiliary heater."""
|
||||||
|
|
||||||
|
report_issue = async_suggest_report_issue(
|
||||||
|
self.hass,
|
||||||
|
integration_domain=self.platform.platform_name,
|
||||||
|
module=type(self).__module__,
|
||||||
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"%s::%s implements the `is_aux_heat` property or uses the auxiliary "
|
||||||
|
"heater methods in a subclass of ClimateEntity which is "
|
||||||
|
"deprecated and will be unsupported from Home Assistant 2024.10."
|
||||||
|
" Please %s"
|
||||||
|
),
|
||||||
|
self.platform.platform_name,
|
||||||
|
self.__class__.__name__,
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
|
||||||
|
translation_placeholders = {"platform": self.platform.platform_name}
|
||||||
|
translation_key = "deprecated_climate_aux_no_url"
|
||||||
|
issue_tracker = async_get_issue_tracker(
|
||||||
|
self.hass,
|
||||||
|
integration_domain=self.platform.platform_name,
|
||||||
|
module=type(self).__module__,
|
||||||
|
)
|
||||||
|
if issue_tracker:
|
||||||
|
translation_placeholders["issue_tracker"] = issue_tracker
|
||||||
|
translation_key = "deprecated_climate_aux_url_custom"
|
||||||
|
ir.async_create_issue(
|
||||||
|
self.hass,
|
||||||
|
DOMAIN,
|
||||||
|
f"deprecated_climate_aux_{self.platform.platform_name}",
|
||||||
|
breaks_in_ha_version="2024.10.0",
|
||||||
|
is_fixable=False,
|
||||||
|
is_persistent=False,
|
||||||
|
issue_domain=self.platform.platform_name,
|
||||||
|
severity=ir.IssueSeverity.WARNING,
|
||||||
|
translation_key=translation_key,
|
||||||
|
translation_placeholders=translation_placeholders,
|
||||||
|
)
|
||||||
|
self.__climate_reported_legacy_aux = True
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
|
@ -508,6 +556,11 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
|
|
||||||
if ClimateEntityFeature.AUX_HEAT in supported_features:
|
if ClimateEntityFeature.AUX_HEAT in supported_features:
|
||||||
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
|
data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF
|
||||||
|
if (
|
||||||
|
self.__climate_reported_legacy_aux is False
|
||||||
|
and "custom_components" in type(self).__module__
|
||||||
|
):
|
||||||
|
self._report_legacy_aux()
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"issues": {
|
||||||
|
"deprecated_climate_aux_url_custom": {
|
||||||
|
"title": "The {platform} custom integration is using deprecated climate auxiliary heater",
|
||||||
|
"description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
|
||||||
|
},
|
||||||
|
"deprecated_climate_aux_no_url": {
|
||||||
|
"title": "[%key:component::climate::issues::deprecated_climate_aux_url_custom::title%]",
|
||||||
|
"description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
|
||||||
|
}
|
||||||
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
"not_valid_preset_mode": {
|
"not_valid_preset_mode": {
|
||||||
"message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}."
|
"message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}."
|
||||||
|
|
|
@ -23,12 +23,14 @@ from homeassistant.components.climate.const import (
|
||||||
SERVICE_SET_FAN_MODE,
|
SERVICE_SET_FAN_MODE,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_SWING_MODE,
|
SERVICE_SET_SWING_MODE,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, UnitOfTemperature
|
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
|
@ -771,3 +773,309 @@ async def test_sync_toggle(hass: HomeAssistant) -> None:
|
||||||
await climate.async_toggle()
|
await climate.async_toggle()
|
||||||
|
|
||||||
assert climate.toggle.called
|
assert climate.toggle.called
|
||||||
|
|
||||||
|
|
||||||
|
ISSUE_TRACKER = "https://blablabla.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"manifest_extra",
|
||||||
|
"translation_key",
|
||||||
|
"translation_placeholders_extra",
|
||||||
|
"report",
|
||||||
|
"module",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{},
|
||||||
|
"deprecated_climate_aux_no_url",
|
||||||
|
{},
|
||||||
|
"report it to the author of the 'test' custom integration",
|
||||||
|
"custom_components.test.climate",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"issue_tracker": ISSUE_TRACKER},
|
||||||
|
"deprecated_climate_aux_url_custom",
|
||||||
|
{"issue_tracker": ISSUE_TRACKER},
|
||||||
|
"create a bug report at https://blablabla.com",
|
||||||
|
"custom_components.test.climate",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_issue_aux_property_deprecated(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
config_flow_fixture: None,
|
||||||
|
manifest_extra: dict[str, str],
|
||||||
|
translation_key: str,
|
||||||
|
translation_placeholders_extra: dict[str, str],
|
||||||
|
report: str,
|
||||||
|
module: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test the issue is raised on deprecated auxiliary heater attributes."""
|
||||||
|
|
||||||
|
class MockClimateEntityWithAux(MockClimateEntity):
|
||||||
|
"""Mock climate class with mocked aux heater."""
|
||||||
|
|
||||||
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.AUX_HEAT | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_aux_heat(self) -> bool | None:
|
||||||
|
"""Return true if aux heater.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.AUX_HEAT.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_on(self) -> None:
|
||||||
|
"""Turn auxiliary heater on."""
|
||||||
|
await self.hass.async_add_executor_job(self.turn_aux_heat_on)
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_off(self) -> None:
|
||||||
|
"""Turn auxiliary heater off."""
|
||||||
|
await self.hass.async_add_executor_job(self.turn_aux_heat_off)
|
||||||
|
|
||||||
|
# Fake the module is custom component or built in
|
||||||
|
MockClimateEntityWithAux.__module__ = module
|
||||||
|
|
||||||
|
climate_entity = MockClimateEntityWithAux(
|
||||||
|
name="Testing",
|
||||||
|
entity_id="climate.testing",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_setup_entry_init(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Set up test config entry."""
|
||||||
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_setup_entry_climate_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up test weather platform via config entry."""
|
||||||
|
async_add_entities([climate_entity])
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=async_setup_entry_init,
|
||||||
|
partial_manifest=manifest_extra,
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"test.climate",
|
||||||
|
MockPlatform(async_setup_entry=async_setup_entry_climate_platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain="test")
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert climate_entity.state == HVACMode.HEAT
|
||||||
|
|
||||||
|
issues = ir.async_get(hass)
|
||||||
|
issue = issues.async_get_issue("climate", "deprecated_climate_aux_test")
|
||||||
|
assert issue
|
||||||
|
assert issue.issue_domain == "test"
|
||||||
|
assert issue.issue_id == "deprecated_climate_aux_test"
|
||||||
|
assert issue.translation_key == translation_key
|
||||||
|
assert (
|
||||||
|
issue.translation_placeholders
|
||||||
|
== {"platform": "test"} | translation_placeholders_extra
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses "
|
||||||
|
"the auxiliary heater methods in a subclass of ClimateEntity which is deprecated "
|
||||||
|
f"and will be unsupported from Home Assistant 2024.10. Please {report}"
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
# Assert we only log warning once
|
||||||
|
caplog.clear()
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
{
|
||||||
|
"entity_id": "climate.test",
|
||||||
|
"temperature": "25",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert ("implements the `is_aux_heat` property") not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"manifest_extra",
|
||||||
|
"translation_key",
|
||||||
|
"translation_placeholders_extra",
|
||||||
|
"report",
|
||||||
|
"module",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{"issue_tracker": ISSUE_TRACKER},
|
||||||
|
"deprecated_climate_aux_url",
|
||||||
|
{"issue_tracker": ISSUE_TRACKER},
|
||||||
|
"create a bug report at https://blablabla.com",
|
||||||
|
"homeassistant.components.test.climate",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_no_issue_aux_property_deprecated_for_core(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
config_flow_fixture: None,
|
||||||
|
manifest_extra: dict[str, str],
|
||||||
|
translation_key: str,
|
||||||
|
translation_placeholders_extra: dict[str, str],
|
||||||
|
report: str,
|
||||||
|
module: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test the no issue on deprecated auxiliary heater attributes for core integrations."""
|
||||||
|
|
||||||
|
class MockClimateEntityWithAux(MockClimateEntity):
|
||||||
|
"""Mock climate class with mocked aux heater."""
|
||||||
|
|
||||||
|
_attr_supported_features = ClimateEntityFeature.AUX_HEAT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_aux_heat(self) -> bool | None:
|
||||||
|
"""Return true if aux heater.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.AUX_HEAT.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_on(self) -> None:
|
||||||
|
"""Turn auxiliary heater on."""
|
||||||
|
await self.hass.async_add_executor_job(self.turn_aux_heat_on)
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_off(self) -> None:
|
||||||
|
"""Turn auxiliary heater off."""
|
||||||
|
await self.hass.async_add_executor_job(self.turn_aux_heat_off)
|
||||||
|
|
||||||
|
# Fake the module is custom component or built in
|
||||||
|
MockClimateEntityWithAux.__module__ = module
|
||||||
|
|
||||||
|
climate_entity = MockClimateEntityWithAux(
|
||||||
|
name="Testing",
|
||||||
|
entity_id="climate.testing",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_setup_entry_init(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Set up test config entry."""
|
||||||
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_setup_entry_climate_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up test weather platform via config entry."""
|
||||||
|
async_add_entities([climate_entity])
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=async_setup_entry_init,
|
||||||
|
partial_manifest=manifest_extra,
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"test.climate",
|
||||||
|
MockPlatform(async_setup_entry=async_setup_entry_climate_platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain="test")
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert climate_entity.state == HVACMode.HEAT
|
||||||
|
|
||||||
|
issues = ir.async_get(hass)
|
||||||
|
issue = issues.async_get_issue("climate", "deprecated_climate_aux_test")
|
||||||
|
assert not issue
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses "
|
||||||
|
"the auxiliary heater methods in a subclass of ClimateEntity which is deprecated "
|
||||||
|
f"and will be unsupported from Home Assistant 2024.10. Please {report}"
|
||||||
|
) not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_issue_no_aux_property(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
config_flow_fixture: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test the issue is raised on deprecated auxiliary heater attributes."""
|
||||||
|
|
||||||
|
climate_entity = MockClimateEntity(
|
||||||
|
name="Testing",
|
||||||
|
entity_id="climate.testing",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_setup_entry_init(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Set up test config entry."""
|
||||||
|
await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def async_setup_entry_climate_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up test weather platform via config entry."""
|
||||||
|
async_add_entities([climate_entity])
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"test",
|
||||||
|
async_setup_entry=async_setup_entry_init,
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"test.climate",
|
||||||
|
MockPlatform(async_setup_entry=async_setup_entry_climate_platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(domain="test")
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert climate_entity.state == HVACMode.HEAT
|
||||||
|
|
||||||
|
issues = ir.async_get(hass)
|
||||||
|
assert len(issues.issues) == 0
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses "
|
||||||
|
"the auxiliary heater methods in a subclass of ClimateEntity which is deprecated "
|
||||||
|
"and will be unsupported from Home Assistant 2024.10."
|
||||||
|
) not in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue