From 26d139afb44b3fc08f61fb0a1e11922ec303b7b2 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Thu, 26 Dec 2024 20:17:20 +0000 Subject: [PATCH] Remove mqtt publish templates after 6 months of deprecation --- homeassistant/components/mqtt/__init__.py | 105 ++------- homeassistant/components/mqtt/strings.json | 8 - tests/components/mqtt/test_init.py | 248 +-------------------- 3 files changed, 21 insertions(+), 340 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index bcad8747c39466..624f99d350ac42 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -6,14 +6,14 @@ from collections.abc import Callable from datetime import datetime import logging -from typing import TYPE_CHECKING, Any, cast +from typing import Any, cast import voluptuous as vol from homeassistant import config as conf_util from homeassistant.components import websocket_api from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_DISCOVERY, CONF_PAYLOAD, SERVICE_RELOAD +from homeassistant.const import CONF_DISCOVERY, SERVICE_RELOAD from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ( ConfigValidationError, @@ -25,7 +25,6 @@ entity_registry as er, event as ev, issue_registry as ir, - template, ) from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -113,8 +112,6 @@ SERVICE_PUBLISH = "publish" SERVICE_DUMP = "dump" -ATTR_TOPIC_TEMPLATE = "topic_template" -ATTR_PAYLOAD_TEMPLATE = "payload_template" ATTR_EVALUATE_PAYLOAD = "evaluate_payload" MAX_RECONNECT_WAIT = 300 # seconds @@ -155,25 +152,16 @@ extra=vol.ALLOW_EXTRA, ) - -# The use of a topic_template and payload_template in an mqtt publish action call -# have been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 - # Publish action call validation schema -MQTT_PUBLISH_SCHEMA = vol.All( - vol.Schema( - { - vol.Exclusive(ATTR_TOPIC, CONF_TOPIC): valid_publish_topic, - vol.Exclusive(ATTR_TOPIC_TEMPLATE, CONF_TOPIC): cv.string, - vol.Exclusive(ATTR_PAYLOAD, CONF_PAYLOAD): cv.string, - vol.Exclusive(ATTR_PAYLOAD_TEMPLATE, CONF_PAYLOAD): cv.string, - vol.Optional(ATTR_EVALUATE_PAYLOAD): cv.boolean, - vol.Optional(ATTR_QOS, default=DEFAULT_QOS): valid_qos_schema, - vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, - }, - required=True, - ), - cv.has_at_least_one_key(ATTR_TOPIC, ATTR_TOPIC_TEMPLATE), +MQTT_PUBLISH_SCHEMA = vol.Schema( + { + vol.Required(ATTR_TOPIC): valid_publish_topic, + vol.Required(ATTR_PAYLOAD): cv.string, + vol.Optional(ATTR_EVALUATE_PAYLOAD): cv.boolean, + vol.Optional(ATTR_QOS, default=DEFAULT_QOS): valid_qos_schema, + vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + }, + required=True, ) @@ -233,86 +221,25 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_publish_service(call: ServiceCall) -> None: """Handle MQTT publish service calls.""" - msg_topic: str | None = call.data.get(ATTR_TOPIC) - msg_topic_template: str | None = call.data.get(ATTR_TOPIC_TEMPLATE) + msg_topic: str = call.data[ATTR_TOPIC] if not mqtt_config_entry_enabled(hass): raise ServiceValidationError( translation_key="mqtt_not_setup_cannot_publish", translation_domain=DOMAIN, - translation_placeholders={ - "topic": str(msg_topic or msg_topic_template) - }, + translation_placeholders={"topic": msg_topic}, ) mqtt_data = hass.data[DATA_MQTT] - payload: PublishPayloadType = call.data.get(ATTR_PAYLOAD) + payload: PublishPayloadType = call.data[ATTR_PAYLOAD] evaluate_payload: bool = call.data.get(ATTR_EVALUATE_PAYLOAD, False) - payload_template: str | None = call.data.get(ATTR_PAYLOAD_TEMPLATE) qos: int = call.data[ATTR_QOS] retain: bool = call.data[ATTR_RETAIN] - if msg_topic_template is not None: - # The use of a topic_template in an mqtt publish action call - # has been deprecated with HA Core 2024.8.0 - # and will be removed with HA Core 2025.2.0 - rendered_topic: Any = MqttCommandTemplate( - template.Template(msg_topic_template, hass), - ).async_render() - ir.async_create_issue( - hass, - DOMAIN, - f"topic_template_deprecation_{rendered_topic}", - breaks_in_ha_version="2025.2.0", - is_fixable=False, - severity=ir.IssueSeverity.WARNING, - translation_key="topic_template_deprecation", - translation_placeholders={ - "topic_template": msg_topic_template, - "topic": rendered_topic, - }, - ) - try: - msg_topic = valid_publish_topic(rendered_topic) - except vol.Invalid as err: - err_str = str(err) - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="invalid_publish_topic", - translation_placeholders={ - "error": err_str, - "topic": str(rendered_topic), - "topic_template": str(msg_topic_template), - }, - ) from err - - if payload_template is not None: - # The use of a payload_template in an mqtt publish action call - # has been deprecated with HA Core 2024.8.0 - # and will be removed with HA Core 2025.2.0 - if TYPE_CHECKING: - assert msg_topic is not None - ir.async_create_issue( - hass, - DOMAIN, - f"payload_template_deprecation_{msg_topic}", - breaks_in_ha_version="2025.2.0", - is_fixable=False, - severity=ir.IssueSeverity.WARNING, - translation_key="payload_template_deprecation", - translation_placeholders={ - "topic": msg_topic, - "payload_template": payload_template, - }, - ) - payload = MqttCommandTemplate( - template.Template(payload_template, hass) - ).async_render() - elif evaluate_payload: + + if evaluate_payload: # Convert quoted binary literal to raw data payload = convert_outgoing_mqtt_payload(payload) - if TYPE_CHECKING: - assert msg_topic is not None await mqtt_data.client.async_publish(msg_topic, payload, qos, retain) hass.services.async_register( diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 3b337c05d2a346..ddc82a8dc10113 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -11,14 +11,6 @@ "invalid_platform_config": { "title": "Invalid config found for mqtt {domain} item", "description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue." - }, - "payload_template_deprecation": { - "title": "Deprecated option used in mqtt publish action call", - "description": "Deprecated `payload_template` option used in MQTT publish action call to topic `{topic}` from payload template `{payload_template}`. Use the `payload` option instead. In automations templates are supported natively. Update the automation or script to use the `payload` option instead and restart Home Assistant to fix this issue." - }, - "topic_template_deprecation": { - "title": "Deprecated option used in mqtt publish action call", - "description": "Deprecated `topic_template` option used in MQTT publish action call to topic `{topic}` from topic template `{topic_template}`. Use the `topic` option instead. In automations templates are supported natively. Update the automation or script to use the `topic` option instead and restart Home Assistant to fix this issue." } }, "config": { diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 2ab664f5041169..dd2a03ac1f0872 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -275,80 +275,6 @@ async def test_service_call_mqtt_entry_does_not_publish( ) -# The use of a topic_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_mqtt_publish_action_call_with_topic_and_topic_template_does_not_publish( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with topic/topic template. - - If both 'topic' and 'topic_template' are provided then fail. - """ - mqtt_mock = await mqtt_mock_entry() - topic = "test/topic" - topic_template = "test/{{ 'topic' }}" - with pytest.raises(vol.Invalid): - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC: topic, - mqtt.ATTR_TOPIC_TEMPLATE: topic_template, - mqtt.ATTR_PAYLOAD: "payload", - }, - blocking=True, - ) - assert not mqtt_mock.async_publish.called - - -# The use of a topic_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_mqtt_action_call_with_invalid_topic_template_does_not_publish( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with a problematic topic template.""" - mqtt_mock = await mqtt_mock_entry() - with pytest.raises(MqttCommandTemplateException) as exc: - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC_TEMPLATE: "test/{{ 1 | no_such_filter }}", - mqtt.ATTR_PAYLOAD: "payload", - }, - blocking=True, - ) - assert str(exc.value) == ( - "TemplateError: TemplateAssertionError: No filter named 'no_such_filter'. " - "rendering template, template: " - "'test/{{ 1 | no_such_filter }}' and payload: None" - ) - assert not mqtt_mock.async_publish.called - - -# The use of a topic_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_mqtt_publish_action_call_with_template_topic_renders_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with rendered topic template. - - If 'topic_template' is provided and 'topic' is not, then render it. - """ - mqtt_mock = await mqtt_mock_entry() - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC_TEMPLATE: "test/{{ 1+1 }}", - mqtt.ATTR_PAYLOAD: "payload", - }, - blocking=True, - ) - assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][0] == "test/2" - - async def test_service_call_with_template_topic_renders_invalid_topic( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator ) -> None: @@ -357,84 +283,23 @@ async def test_service_call_with_template_topic_renders_invalid_topic( If a wildcard topic is rendered, then fail. """ mqtt_mock = await mqtt_mock_entry() - with pytest.raises(ServiceValidationError) as exc: + with pytest.raises(vol.Invalid) as exc: await hass.services.async_call( mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, { - mqtt.ATTR_TOPIC_TEMPLATE: "test/{{ '+' if True else 'topic' }}/topic", + mqtt.ATTR_TOPIC: "test/{{ '+' if True else 'topic' }}/topic", mqtt.ATTR_PAYLOAD: "payload", }, blocking=True, ) - assert str(exc.value) == ( - "Unable to publish: topic template `test/{{ '+' if True else 'topic' }}/topic` " - "produced an invalid topic `test/+/topic` after rendering " - "(Wildcards cannot be used in topic names)" + assert ( + str(exc.value) == "Wildcards cannot be used in topic names " + "for dictionary value @ data['topic']" ) assert not mqtt_mock.async_publish.called -# The use of a payload_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_action_call_with_invalid_rendered_payload_template_doesnt_render_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the action call with unrendered payload template. - - If both 'payload' and 'payload_template' are provided then fail. - """ - mqtt_mock = await mqtt_mock_entry() - payload = "not a template" - payload_template = "a template" - with pytest.raises(vol.Invalid): - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC: "test/topic", - mqtt.ATTR_PAYLOAD: payload, - mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template, - }, - blocking=True, - ) - assert not mqtt_mock.async_publish.called - - -# The use of a payload_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_mqtt_publish_action_call_with_template_payload_renders_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with rendered template. - - If 'payload_template' is provided and 'payload' is not, then render it. - """ - mqtt_mock = await mqtt_mock_entry() - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - {mqtt.ATTR_TOPIC: "test/topic", mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 4+4 }}"}, - blocking=True, - ) - assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == "8" - mqtt_mock.reset_mock() - - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC: "test/topic", - mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ (4+4) | pack('B') }}", - }, - blocking=True, - ) - assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == b"\x08" - mqtt_mock.reset_mock() - - @pytest.mark.parametrize( ("attr_payload", "payload", "evaluate_payload", "literal_eval_calls"), [ @@ -503,56 +368,6 @@ async def test_mqtt_publish_action_call_with_raw_data( assert len(literal_eval_mock.mock_calls) == literal_eval_calls -# The use of a payload_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_publish_action_call_with_bad_payload_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with a bad template does not publish.""" - mqtt_mock = await mqtt_mock_entry() - with pytest.raises(MqttCommandTemplateException) as exc: - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC: "test/topic", - mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 1 | bad }}", - }, - blocking=True, - ) - assert not mqtt_mock.async_publish.called - assert str(exc.value) == ( - "TemplateError: TemplateAssertionError: No filter named 'bad'. " - "rendering template, template: '{{ 1 | bad }}' and payload: None" - ) - - -# The use of a payload_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_action_call_with_payload_doesnt_render_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the mqtt publish action call with an unrendered template. - - If both 'payload' and 'payload_template' are provided then fail. - """ - mqtt_mock = await mqtt_mock_entry() - payload = "not a template" - payload_template = "a template" - with pytest.raises(vol.Invalid): - await hass.services.async_call( - mqtt.DOMAIN, - mqtt.SERVICE_PUBLISH, - { - mqtt.ATTR_TOPIC: "test/topic", - mqtt.ATTR_PAYLOAD: payload, - mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template, - }, - blocking=True, - ) - assert not mqtt_mock.async_publish.called - - async def test_service_call_with_ascii_qos_retain_flags( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator ) -> None: @@ -1721,59 +1536,6 @@ async def test_debug_info_qos_retain( } in messages -# The use of a payload_template in an mqtt publish action call -# has been deprecated with HA Core 2024.8.0 and will be removed with HA Core 2025.2.0 -async def test_publish_json_from_template( - hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator -) -> None: - """Test the publishing of call to mqtt publish action.""" - mqtt_mock = await mqtt_mock_entry() - - test_str = "{'valid': 'python', 'invalid': 'json'}" - test_str_tpl = "{'valid': '{{ \"python\" }}', 'invalid': 'json'}" - - await async_setup_component( - hass, - "script", - { - "script": { - "test_script_payload": { - "sequence": { - "service": "mqtt.publish", - "data": {"topic": "test-topic", "payload": test_str_tpl}, - } - }, - "test_script_payload_template": { - "sequence": { - "service": "mqtt.publish", - "data": { - "topic": "test-topic", - "payload_template": test_str_tpl, - }, - } - }, - } - }, - ) - - await hass.services.async_call("script", "test_script_payload", blocking=True) - await hass.async_block_till_done() - - assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == test_str - - mqtt_mock.async_publish.reset_mock() - assert not mqtt_mock.async_publish.called - - await hass.services.async_call( - "script", "test_script_payload_template", blocking=True - ) - await hass.async_block_till_done() - - assert mqtt_mock.async_publish.called - assert mqtt_mock.async_publish.call_args[0][1] == test_str - - async def test_subscribe_connection_status( hass: HomeAssistant, mock_debouncer: asyncio.Event,