Skip to content

Commit

Permalink
Fix importing blueprints (#71365)
Browse files Browse the repository at this point in the history
Co-authored-by: Shay Levy <levyshay1@gmail.com>
  • Loading branch information
balloob and thecode committed May 5, 2022
1 parent 9f8111c commit 61a3873
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 12 deletions.
23 changes: 22 additions & 1 deletion homeassistant/helpers/selector.py
Expand Up @@ -5,11 +5,13 @@
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

Expand Down Expand Up @@ -71,7 +73,11 @@ def __init__(self, config: Any = None) -> None:

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(
Expand Down Expand Up @@ -623,6 +629,13 @@ def __init__(self, config: NumberSelectorConfig | None = None) -> None:
"""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)
Expand Down Expand Up @@ -881,3 +894,11 @@ def __call__(self, data: Any) -> str:
"""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()
),
)
2 changes: 1 addition & 1 deletion tests/components/blueprint/test_importer.py
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions tests/components/blueprint/test_websocket_api.py
Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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",
)


Expand Down
13 changes: 12 additions & 1 deletion tests/helpers/test_config_validation.py
Expand Up @@ -11,7 +11,7 @@
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():
Expand Down Expand Up @@ -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) == {
Expand Down
18 changes: 12 additions & 6 deletions tests/helpers/test_selector.py
Expand Up @@ -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"

Expand Down Expand Up @@ -48,10 +49,12 @@ def default_converter(x):
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)
Expand All @@ -62,9 +65,12 @@ def default_converter(x):

# 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(
Expand Down
Expand Up @@ -3,6 +3,8 @@ blueprint:
domain: automation
input:
trigger_event:
selector:
text:
service_to_call:
trigger:
platform: event
Expand Down

0 comments on commit 61a3873

Please sign in to comment.