From 353cc0b8c279ad8d40c78bcd31eddfcf57450ece Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 May 2022 14:33:17 -0700 Subject: [PATCH] Fix importing blueprints (#71365) Co-authored-by: Shay Levy --- homeassistant/helpers/selector.py | 23 ++++++++++++++++++- tests/components/blueprint/test_importer.py | 2 +- .../blueprint/test_websocket_api.py | 12 +++++++--- tests/helpers/test_config_validation.py | 13 ++++++++++- tests/helpers/test_selector.py | 18 ++++++++++----- .../automation/test_event_service.yaml | 2 ++ 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index eecc66c83322..afe74a4d7af5 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -5,11 +5,13 @@ from collections.abc import Callable, Sequence from typing import Any, TypedDict, cast import voluptuous as vol +import yaml from homeassistant.backports.enum import StrEnum from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.util import decorator +from homeassistant.util.yaml.dumper import represent_odict from . import config_validation as cv @@ -71,7 +73,11 @@ class Selector: def serialize(self) -> Any: """Serialize Selector for voluptuous_serialize.""" - return {"selector": {self.selector_type: self.config}} + return {"selector": {self.selector_type: self.serialize_config()}} + + def serialize_config(self) -> Any: + """Serialize config.""" + return self.config SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema( @@ -623,6 +629,13 @@ class NumberSelector(Selector): """Instantiate a selector.""" super().__init__(config) + def serialize_config(self) -> Any: + """Serialize the selector config.""" + return { + **self.config, + "mode": self.config["mode"].value, + } + def __call__(self, data: Any) -> float: """Validate the passed selection.""" value: float = vol.Coerce(float)(data) @@ -881,3 +894,11 @@ class TimeSelector(Selector): """Validate the passed selection.""" cv.time(data) return cast(str, data) + + +yaml.SafeDumper.add_representer( + Selector, + lambda dumper, value: represent_odict( + dumper, "tag:yaml.org,2002:map", value.serialize() + ), +) diff --git a/tests/components/blueprint/test_importer.py b/tests/components/blueprint/test_importer.py index 0e1e66405e62..806cdb2cb8db 100644 --- a/tests/components/blueprint/test_importer.py +++ b/tests/components/blueprint/test_importer.py @@ -198,7 +198,7 @@ async def test_fetch_blueprint_from_github_url(hass, aioclient_mock, url): assert imported_blueprint.blueprint.domain == "automation" assert imported_blueprint.blueprint.inputs == { "service_to_call": None, - "trigger_event": None, + "trigger_event": {"selector": {"text": {}}}, } assert imported_blueprint.suggested_filename == "balloob/motion_light" assert imported_blueprint.blueprint.metadata["source_url"] == url diff --git a/tests/components/blueprint/test_websocket_api.py b/tests/components/blueprint/test_websocket_api.py index 51b8184354a1..40f24d980165 100644 --- a/tests/components/blueprint/test_websocket_api.py +++ b/tests/components/blueprint/test_websocket_api.py @@ -30,7 +30,10 @@ async def test_list_blueprints(hass, hass_ws_client): "test_event_service.yaml": { "metadata": { "domain": "automation", - "input": {"service_to_call": None, "trigger_event": None}, + "input": { + "service_to_call": None, + "trigger_event": {"selector": {"text": {}}}, + }, "name": "Call service based on event", }, }, @@ -89,7 +92,10 @@ async def test_import_blueprint(hass, aioclient_mock, hass_ws_client): "blueprint": { "metadata": { "domain": "automation", - "input": {"service_to_call": None, "trigger_event": None}, + "input": { + "service_to_call": None, + "trigger_event": {"selector": {"text": {}}}, + }, "name": "Call service based on event", "source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml", }, @@ -123,7 +129,7 @@ async def test_save_blueprint(hass, aioclient_mock, hass_ws_client): assert msg["success"] assert write_mock.mock_calls assert write_mock.call_args[0] == ( - "blueprint:\n name: Call service based on event\n domain: automation\n input:\n trigger_event:\n service_to_call:\n source_url: https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml\ntrigger:\n platform: event\n event_type: !input 'trigger_event'\naction:\n service: !input 'service_to_call'\n entity_id: light.kitchen\n", + "blueprint:\n name: Call service based on event\n domain: automation\n input:\n trigger_event:\n selector:\n text: {}\n service_to_call:\n source_url: https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml\ntrigger:\n platform: event\n event_type: !input 'trigger_event'\naction:\n service: !input 'service_to_call'\n entity_id: light.kitchen\n", ) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 64c838e6c02e..a5d2223a3d2a 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -11,7 +11,7 @@ import pytest import voluptuous as vol import homeassistant -from homeassistant.helpers import config_validation as cv, template +from homeassistant.helpers import config_validation as cv, selector, template def test_boolean(): @@ -720,6 +720,17 @@ def test_string_in_serializer(): } +def test_selector_in_serializer(): + """Test selector with custom_serializer.""" + assert cv.custom_serializer(selector.selector({"text": {}})) == { + "selector": { + "text": { + "multiline": False, + } + } + } + + def test_positive_time_period_dict_in_serializer(): """Test positive_time_period_dict with custom_serializer.""" assert cv.custom_serializer(cv.positive_time_period_dict) == { diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index cb0ad95eb6ba..8c94e3d3c569 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -2,7 +2,8 @@ import pytest import voluptuous as vol -from homeassistant.helpers import config_validation as cv, selector +from homeassistant.helpers import selector +from homeassistant.util import yaml FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411" @@ -48,10 +49,12 @@ def _test_selector( converter = default_converter # Validate selector configuration - selector.validate_selector({selector_type: schema}) + config = {selector_type: schema} + selector.validate_selector(config) + selector_instance = selector.selector(config) # Use selector in schema and validate - vol_schema = vol.Schema({"selection": selector.selector({selector_type: schema})}) + vol_schema = vol.Schema({"selection": selector_instance}) for selection in valid_selections: assert vol_schema({"selection": selection}) == { "selection": converter(selection) @@ -62,9 +65,12 @@ def _test_selector( # Serialize selector selector_instance = selector.selector({selector_type: schema}) - assert cv.custom_serializer(selector_instance) == { - "selector": {selector_type: selector_instance.config} - } + assert ( + selector.selector(selector_instance.serialize()["selector"]).config + == selector_instance.config + ) + # Test serialized selector can be dumped to YAML + yaml.dump(selector_instance.serialize()) @pytest.mark.parametrize( diff --git a/tests/testing_config/blueprints/automation/test_event_service.yaml b/tests/testing_config/blueprints/automation/test_event_service.yaml index ab067b004ace..648cef39b964 100644 --- a/tests/testing_config/blueprints/automation/test_event_service.yaml +++ b/tests/testing_config/blueprints/automation/test_event_service.yaml @@ -3,6 +3,8 @@ blueprint: domain: automation input: trigger_event: + selector: + text: service_to_call: trigger: platform: event