Mqtt fan fail deprecated options for classic speeds (#58992)
* Fail deprecated options * new removed validator * correct module_name - add tests * Add test cant find module cv.removed * module name from stack+1 * Remove error from log. Just throw. * assert on thrown exception text * cleanup formatting remove KeyStyleAdapter * format the replacement_key and update test * deprecated vs removed - add raise_if_present opt * doc string update * is deprecated
This commit is contained in:
parent
7945facf1e
commit
c3fc19915e
3 changed files with 152 additions and 38 deletions
|
@ -175,13 +175,13 @@ PLATFORM_SCHEMA = vol.All(
|
||||||
# CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_LIST, CONF_SPEED_STATE_TOPIC, CONF_SPEED_VALUE_TEMPLATE and
|
# CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_LIST, CONF_SPEED_STATE_TOPIC, CONF_SPEED_VALUE_TEMPLATE and
|
||||||
# Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF,
|
# Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF,
|
||||||
# are deprecated, support will be removed with release 2021.9
|
# are deprecated, support will be removed with release 2021.9
|
||||||
cv.deprecated(CONF_PAYLOAD_HIGH_SPEED),
|
cv.removed(CONF_PAYLOAD_HIGH_SPEED),
|
||||||
cv.deprecated(CONF_PAYLOAD_LOW_SPEED),
|
cv.removed(CONF_PAYLOAD_LOW_SPEED),
|
||||||
cv.deprecated(CONF_PAYLOAD_MEDIUM_SPEED),
|
cv.removed(CONF_PAYLOAD_MEDIUM_SPEED),
|
||||||
cv.deprecated(CONF_SPEED_COMMAND_TOPIC),
|
cv.removed(CONF_SPEED_COMMAND_TOPIC),
|
||||||
cv.deprecated(CONF_SPEED_LIST),
|
cv.removed(CONF_SPEED_LIST),
|
||||||
cv.deprecated(CONF_SPEED_STATE_TOPIC),
|
cv.removed(CONF_SPEED_STATE_TOPIC),
|
||||||
cv.deprecated(CONF_SPEED_VALUE_TEMPLATE),
|
cv.removed(CONF_SPEED_VALUE_TEMPLATE),
|
||||||
_PLATFORM_SCHEMA_BASE,
|
_PLATFORM_SCHEMA_BASE,
|
||||||
valid_speed_range_configuration,
|
valid_speed_range_configuration,
|
||||||
valid_preset_mode_configuration,
|
valid_preset_mode_configuration,
|
||||||
|
@ -191,13 +191,13 @@ DISCOVERY_SCHEMA = vol.All(
|
||||||
# CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_LIST, CONF_SPEED_STATE_TOPIC, CONF_SPEED_VALUE_TEMPLATE and
|
# CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_LIST, CONF_SPEED_STATE_TOPIC, CONF_SPEED_VALUE_TEMPLATE and
|
||||||
# Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF,
|
# Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF,
|
||||||
# are deprecated, support will be removed with release 2021.9
|
# are deprecated, support will be removed with release 2021.9
|
||||||
cv.deprecated(CONF_PAYLOAD_HIGH_SPEED),
|
cv.removed(CONF_PAYLOAD_HIGH_SPEED),
|
||||||
cv.deprecated(CONF_PAYLOAD_LOW_SPEED),
|
cv.removed(CONF_PAYLOAD_LOW_SPEED),
|
||||||
cv.deprecated(CONF_PAYLOAD_MEDIUM_SPEED),
|
cv.removed(CONF_PAYLOAD_MEDIUM_SPEED),
|
||||||
cv.deprecated(CONF_SPEED_COMMAND_TOPIC),
|
cv.removed(CONF_SPEED_COMMAND_TOPIC),
|
||||||
cv.deprecated(CONF_SPEED_LIST),
|
cv.removed(CONF_SPEED_LIST),
|
||||||
cv.deprecated(CONF_SPEED_STATE_TOPIC),
|
cv.removed(CONF_SPEED_STATE_TOPIC),
|
||||||
cv.deprecated(CONF_SPEED_VALUE_TEMPLATE),
|
cv.removed(CONF_SPEED_VALUE_TEMPLATE),
|
||||||
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
||||||
valid_speed_range_configuration,
|
valid_speed_range_configuration,
|
||||||
valid_preset_mode_configuration,
|
valid_preset_mode_configuration,
|
||||||
|
|
|
@ -78,7 +78,6 @@ from homeassistant.helpers import (
|
||||||
script_variables as script_variables_helper,
|
script_variables as script_variables_helper,
|
||||||
template as template_helper,
|
template as template_helper,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.logging import KeywordStyleAdapter
|
|
||||||
from homeassistant.util import raise_if_invalid_path, slugify as util_slugify
|
from homeassistant.util import raise_if_invalid_path, slugify as util_slugify
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
@ -709,23 +708,26 @@ class multi_select:
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
def deprecated(
|
def _deprecated_or_removed(
|
||||||
key: str,
|
key: str,
|
||||||
replacement_key: str | None = None,
|
replacement_key: str | None = None,
|
||||||
default: Any | None = None,
|
default: Any | None = None,
|
||||||
|
raise_if_present: bool | None = False,
|
||||||
|
option_status: str | None = "is deprecated",
|
||||||
) -> Callable[[dict], dict]:
|
) -> Callable[[dict], dict]:
|
||||||
"""
|
"""
|
||||||
Log key as deprecated and provide a replacement (if exists).
|
Log key as deprecated and provide a replacement (if exists) or fail.
|
||||||
|
|
||||||
Expected behavior:
|
Expected behavior:
|
||||||
- Outputs the appropriate deprecation warning if key is detected
|
- Outputs or throws the appropriate deprecation warning if key is detected
|
||||||
|
- Outputs or throws the appropriate error if key is detected and removed from support
|
||||||
- Processes schema moving the value from key to replacement_key
|
- Processes schema moving the value from key to replacement_key
|
||||||
- Processes schema changing nothing if only replacement_key provided
|
- Processes schema changing nothing if only replacement_key provided
|
||||||
- No warning if only replacement_key provided
|
- No warning if only replacement_key provided
|
||||||
- No warning if neither key nor replacement_key are provided
|
- No warning if neither key nor replacement_key are provided
|
||||||
- Adds replacement_key with default value in this case
|
- Adds replacement_key with default value in this case
|
||||||
"""
|
"""
|
||||||
module = inspect.getmodule(inspect.stack(context=0)[1].frame)
|
module = inspect.getmodule(inspect.stack(context=0)[2].frame)
|
||||||
if module is not None:
|
if module is not None:
|
||||||
module_name = module.__name__
|
module_name = module.__name__
|
||||||
else:
|
else:
|
||||||
|
@ -733,36 +735,36 @@ def deprecated(
|
||||||
# will be missing information, so let's guard.
|
# will be missing information, so let's guard.
|
||||||
# https://github.com/home-assistant/core/issues/24982
|
# https://github.com/home-assistant/core/issues/24982
|
||||||
module_name = __name__
|
module_name = __name__
|
||||||
|
|
||||||
if replacement_key:
|
if replacement_key:
|
||||||
warning = (
|
warning = (
|
||||||
"The '{key}' option is deprecated,"
|
"The '{key}' option {option_status},"
|
||||||
" please replace it with '{replacement_key}'"
|
" please replace it with '{replacement_key}'"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
warning = (
|
warning = (
|
||||||
"The '{key}' option is deprecated,"
|
"The '{key}' option {option_status},"
|
||||||
" please remove it from your configuration"
|
" please remove it from your configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
def validator(config: dict) -> dict:
|
def validator(config: dict) -> dict:
|
||||||
"""Check if key is in config and log warning."""
|
"""Check if key is in config and log warning or error."""
|
||||||
if key in config:
|
if key in config:
|
||||||
try:
|
try:
|
||||||
KeywordStyleAdapter(logging.getLogger(module_name)).warning(
|
warning_local = warning.replace(
|
||||||
warning.replace(
|
"'{key}' option",
|
||||||
"'{key}' option",
|
f"'{key}' option near {config.__config_file__}:{config.__line__}", # type: ignore
|
||||||
f"'{key}' option near {config.__config_file__}:{config.__line__}", # type: ignore
|
|
||||||
),
|
|
||||||
key=key,
|
|
||||||
replacement_key=replacement_key,
|
|
||||||
)
|
)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
KeywordStyleAdapter(logging.getLogger(module_name)).warning(
|
warning_local = warning
|
||||||
warning,
|
warning_local = warning_local.format(
|
||||||
key=key,
|
key=key,
|
||||||
replacement_key=replacement_key,
|
replacement_key=replacement_key,
|
||||||
)
|
option_status=option_status,
|
||||||
|
)
|
||||||
|
if raise_if_present:
|
||||||
|
raise vol.Invalid(warning_local)
|
||||||
|
|
||||||
|
logging.getLogger(module_name).warning(warning_local)
|
||||||
value = config[key]
|
value = config[key]
|
||||||
if replacement_key:
|
if replacement_key:
|
||||||
config.pop(key)
|
config.pop(key)
|
||||||
|
@ -782,6 +784,52 @@ def deprecated(
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated(
|
||||||
|
key: str,
|
||||||
|
replacement_key: str | None = None,
|
||||||
|
default: Any | None = None,
|
||||||
|
raise_if_present: bool | None = False,
|
||||||
|
) -> Callable[[dict], dict]:
|
||||||
|
"""
|
||||||
|
Log key as deprecated and provide a replacement (if exists).
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
- Outputs the appropriate deprecation warning if key is detected or raises an exception
|
||||||
|
- Processes schema moving the value from key to replacement_key
|
||||||
|
- Processes schema changing nothing if only replacement_key provided
|
||||||
|
- No warning if only replacement_key provided
|
||||||
|
- No warning if neither key nor replacement_key are provided
|
||||||
|
- Adds replacement_key with default value in this case
|
||||||
|
"""
|
||||||
|
return _deprecated_or_removed(
|
||||||
|
key,
|
||||||
|
replacement_key=replacement_key,
|
||||||
|
default=default,
|
||||||
|
raise_if_present=raise_if_present,
|
||||||
|
option_status="is deprecated",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def removed(
|
||||||
|
key: str,
|
||||||
|
default: Any | None = None,
|
||||||
|
raise_if_present: bool | None = True,
|
||||||
|
) -> Callable[[dict], dict]:
|
||||||
|
"""
|
||||||
|
Log key as deprecated and fail the config validation.
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
- Outputs the appropriate error if key is detected and removed from support or raises an exception
|
||||||
|
"""
|
||||||
|
return _deprecated_or_removed(
|
||||||
|
key,
|
||||||
|
replacement_key=None,
|
||||||
|
default=default,
|
||||||
|
raise_if_present=raise_if_present,
|
||||||
|
option_status="was removed",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def key_value_schemas(
|
def key_value_schemas(
|
||||||
key: str, value_schemas: dict[Hashable, vol.Schema]
|
key: str, value_schemas: dict[Hashable, vol.Schema]
|
||||||
) -> Callable[[Any], dict[Hashable, Any]]:
|
) -> Callable[[Any], dict[Hashable, Any]]:
|
||||||
|
|
|
@ -712,6 +712,46 @@ def test_deprecated_with_no_optionals(caplog, schema):
|
||||||
assert test_data == output
|
assert test_data == output
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecated_or_removed_param_and_raise(caplog, schema):
|
||||||
|
"""
|
||||||
|
Test removed or deprecation options and fail the config validation by raising an exception.
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
- Outputs the appropriate deprecation or removed from support error if key is detected
|
||||||
|
"""
|
||||||
|
removed_schema = vol.All(cv.deprecated("mars", raise_if_present=True), schema)
|
||||||
|
|
||||||
|
test_data = {"mars": True}
|
||||||
|
with pytest.raises(vol.Invalid) as excinfo:
|
||||||
|
removed_schema(test_data)
|
||||||
|
assert (
|
||||||
|
"The 'mars' option is deprecated, please remove it from your configuration"
|
||||||
|
in str(excinfo.value)
|
||||||
|
)
|
||||||
|
assert len(caplog.records) == 0
|
||||||
|
|
||||||
|
test_data = {"venus": True}
|
||||||
|
output = removed_schema(test_data.copy())
|
||||||
|
assert len(caplog.records) == 0
|
||||||
|
assert test_data == output
|
||||||
|
|
||||||
|
deprecated_schema = vol.All(cv.removed("mars"), schema)
|
||||||
|
|
||||||
|
test_data = {"mars": True}
|
||||||
|
with pytest.raises(vol.Invalid) as excinfo:
|
||||||
|
deprecated_schema(test_data)
|
||||||
|
assert (
|
||||||
|
"The 'mars' option was removed, please remove it from your configuration"
|
||||||
|
in str(excinfo.value)
|
||||||
|
)
|
||||||
|
assert len(caplog.records) == 0
|
||||||
|
|
||||||
|
test_data = {"venus": True}
|
||||||
|
output = deprecated_schema(test_data.copy())
|
||||||
|
assert len(caplog.records) == 0
|
||||||
|
assert test_data == output
|
||||||
|
|
||||||
|
|
||||||
def test_deprecated_with_replacement_key(caplog, schema):
|
def test_deprecated_with_replacement_key(caplog, schema):
|
||||||
"""
|
"""
|
||||||
Test deprecation behaves correctly when only a replacement key is provided.
|
Test deprecation behaves correctly when only a replacement key is provided.
|
||||||
|
@ -846,17 +886,43 @@ def test_deprecated_cant_find_module():
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with patch("inspect.getmodule", return_value=None):
|
||||||
|
# This used to raise.
|
||||||
|
cv.removed(
|
||||||
|
"mars",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
def test_deprecated_logger_with_config_attributes(caplog):
|
|
||||||
|
def test_deprecated_or_removed_logger_with_config_attributes(caplog):
|
||||||
"""Test if the logger outputs the correct message if the line and file attribute is available in config."""
|
"""Test if the logger outputs the correct message if the line and file attribute is available in config."""
|
||||||
file: str = "configuration.yaml"
|
file: str = "configuration.yaml"
|
||||||
line: int = 54
|
line: int = 54
|
||||||
replacement = f"'mars' option near {file}:{line} is deprecated"
|
|
||||||
|
# test as deprecated option
|
||||||
|
replacement_key = "jupiter"
|
||||||
|
option_status = "is deprecated"
|
||||||
|
replacement = f"'mars' option near {file}:{line} {option_status}, please replace it with '{replacement_key}'"
|
||||||
config = OrderedDict([("mars", "blah")])
|
config = OrderedDict([("mars", "blah")])
|
||||||
setattr(config, "__config_file__", file)
|
setattr(config, "__config_file__", file)
|
||||||
setattr(config, "__line__", line)
|
setattr(config, "__line__", line)
|
||||||
|
|
||||||
cv.deprecated("mars", replacement_key="jupiter", default=False)(config)
|
cv.deprecated("mars", replacement_key=replacement_key, default=False)(config)
|
||||||
|
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
assert replacement in caplog.text
|
||||||
|
|
||||||
|
caplog.clear()
|
||||||
|
assert len(caplog.records) == 0
|
||||||
|
|
||||||
|
# test as removed option
|
||||||
|
option_status = "was removed"
|
||||||
|
replacement = f"'mars' option near {file}:{line} {option_status}, please remove it from your configuration"
|
||||||
|
config = OrderedDict([("mars", "blah")])
|
||||||
|
setattr(config, "__config_file__", file)
|
||||||
|
setattr(config, "__line__", line)
|
||||||
|
|
||||||
|
cv.removed("mars", default=False, raise_if_present=False)(config)
|
||||||
|
|
||||||
assert len(caplog.records) == 1
|
assert len(caplog.records) == 1
|
||||||
assert replacement in caplog.text
|
assert replacement in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue