mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Use !input instead of !placeholder (#43820)
* Use !input instead of !placeholder * Update input name * Lint * Move tests around
This commit is contained in:
parent
7d23ff6511
commit
1c9c99571e
@ -26,7 +26,7 @@
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"yaml.customTags": [
|
||||
"!placeholder scalar",
|
||||
"!input scalar",
|
||||
"!secret scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
|
@ -31,18 +31,18 @@ max_exceeded: silent
|
||||
|
||||
trigger:
|
||||
platform: state
|
||||
entity_id: !placeholder motion_entity
|
||||
entity_id: !input motion_entity
|
||||
from: "off"
|
||||
to: "on"
|
||||
|
||||
action:
|
||||
- service: homeassistant.turn_on
|
||||
target: !placeholder light_target
|
||||
target: !input light_target
|
||||
- wait_for_trigger:
|
||||
platform: state
|
||||
entity_id: !placeholder motion_entity
|
||||
entity_id: !input motion_entity
|
||||
from: "on"
|
||||
to: "off"
|
||||
- delay: !placeholder no_motion_wait
|
||||
- delay: !input no_motion_wait
|
||||
- service: homeassistant.turn_off
|
||||
target: !placeholder light_target
|
||||
target: !input light_target
|
||||
|
@ -18,10 +18,10 @@ blueprint:
|
||||
|
||||
trigger:
|
||||
platform: state
|
||||
entity_id: !placeholder person_entity
|
||||
entity_id: !input person_entity
|
||||
|
||||
variables:
|
||||
zone_entity: !placeholder zone_entity
|
||||
zone_entity: !input zone_entity
|
||||
zone_state: "{{ states[zone_entity].name }}"
|
||||
|
||||
condition:
|
||||
@ -29,6 +29,6 @@ condition:
|
||||
value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
|
||||
|
||||
action:
|
||||
- service: !placeholder notify_service
|
||||
- service: !input notify_service
|
||||
data:
|
||||
message: "{{ trigger.to_state.name }} has left {{ zone_state }}"
|
||||
|
@ -7,7 +7,7 @@ from .errors import ( # noqa
|
||||
FailedToLoad,
|
||||
InvalidBlueprint,
|
||||
InvalidBlueprintInputs,
|
||||
MissingPlaceholder,
|
||||
MissingInput,
|
||||
)
|
||||
from .models import Blueprint, BlueprintInputs, DomainBlueprints # noqa
|
||||
from .schemas import is_blueprint_instance_config # noqa
|
||||
|
@ -66,17 +66,17 @@ class InvalidBlueprintInputs(BlueprintException):
|
||||
)
|
||||
|
||||
|
||||
class MissingPlaceholder(BlueprintWithNameException):
|
||||
"""When we miss a placeholder."""
|
||||
class MissingInput(BlueprintWithNameException):
|
||||
"""When we miss an input."""
|
||||
|
||||
def __init__(
|
||||
self, domain: str, blueprint_name: str, placeholder_names: Iterable[str]
|
||||
self, domain: str, blueprint_name: str, input_names: Iterable[str]
|
||||
) -> None:
|
||||
"""Initialize blueprint exception."""
|
||||
super().__init__(
|
||||
domain,
|
||||
blueprint_name,
|
||||
f"Missing placeholder {', '.join(sorted(placeholder_names))}",
|
||||
f"Missing input {', '.join(sorted(input_names))}",
|
||||
)
|
||||
|
||||
|
||||
|
@ -19,7 +19,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import placeholder
|
||||
from homeassistant.util import yaml
|
||||
|
||||
from .const import (
|
||||
@ -38,7 +37,7 @@ from .errors import (
|
||||
FileAlreadyExists,
|
||||
InvalidBlueprint,
|
||||
InvalidBlueprintInputs,
|
||||
MissingPlaceholder,
|
||||
MissingInput,
|
||||
)
|
||||
from .schemas import BLUEPRINT_INSTANCE_FIELDS, BLUEPRINT_SCHEMA
|
||||
|
||||
@ -59,8 +58,6 @@ class Blueprint:
|
||||
except vol.Invalid as err:
|
||||
raise InvalidBlueprint(expected_domain, path, data, err) from err
|
||||
|
||||
self.placeholders = placeholder.extract_placeholders(data)
|
||||
|
||||
# In future, we will treat this as "incorrect" and allow to recover from this
|
||||
data_domain = data[CONF_BLUEPRINT][CONF_DOMAIN]
|
||||
if expected_domain is not None and data_domain != expected_domain:
|
||||
@ -73,7 +70,7 @@ class Blueprint:
|
||||
|
||||
self.domain = data_domain
|
||||
|
||||
missing = self.placeholders - set(data[CONF_BLUEPRINT][CONF_INPUT])
|
||||
missing = yaml.extract_inputs(data) - set(data[CONF_BLUEPRINT][CONF_INPUT])
|
||||
|
||||
if missing:
|
||||
raise InvalidBlueprint(
|
||||
@ -143,7 +140,7 @@ class BlueprintInputs:
|
||||
@property
|
||||
def inputs_with_default(self):
|
||||
"""Return the inputs and fallback to defaults."""
|
||||
no_input = self.blueprint.placeholders - set(self.inputs)
|
||||
no_input = set(self.blueprint.inputs) - set(self.inputs)
|
||||
|
||||
inputs_with_default = dict(self.inputs)
|
||||
|
||||
@ -156,12 +153,10 @@ class BlueprintInputs:
|
||||
|
||||
def validate(self) -> None:
|
||||
"""Validate the inputs."""
|
||||
missing = self.blueprint.placeholders - set(self.inputs_with_default)
|
||||
missing = set(self.blueprint.inputs) - set(self.inputs_with_default)
|
||||
|
||||
if missing:
|
||||
raise MissingPlaceholder(
|
||||
self.blueprint.domain, self.blueprint.name, missing
|
||||
)
|
||||
raise MissingInput(self.blueprint.domain, self.blueprint.name, missing)
|
||||
|
||||
# In future we can see if entities are correct domain, areas exist etc
|
||||
# using the new selector helper.
|
||||
@ -169,9 +164,7 @@ class BlueprintInputs:
|
||||
@callback
|
||||
def async_substitute(self) -> dict:
|
||||
"""Get the blueprint value with the inputs substituted."""
|
||||
processed = placeholder.substitute(
|
||||
self.blueprint.data, self.inputs_with_default
|
||||
)
|
||||
processed = yaml.substitute(self.blueprint.data, self.inputs_with_default)
|
||||
combined = {**processed, **self.config_with_inputs}
|
||||
# From config_with_inputs
|
||||
combined.pop(CONF_USE_BLUEPRINT)
|
||||
|
@ -1,17 +1,21 @@
|
||||
"""YAML utility functions."""
|
||||
from .const import _SECRET_NAMESPACE, SECRET_YAML
|
||||
from .dumper import dump, save_yaml
|
||||
from .input import UndefinedSubstitution, extract_inputs, substitute
|
||||
from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml
|
||||
from .objects import Placeholder
|
||||
from .objects import Input
|
||||
|
||||
__all__ = [
|
||||
"SECRET_YAML",
|
||||
"_SECRET_NAMESPACE",
|
||||
"Placeholder",
|
||||
"Input",
|
||||
"dump",
|
||||
"save_yaml",
|
||||
"clear_secret_cache",
|
||||
"load_yaml",
|
||||
"secret_yaml",
|
||||
"parse_yaml",
|
||||
"UndefinedSubstitution",
|
||||
"extract_inputs",
|
||||
"substitute",
|
||||
]
|
||||
|
@ -3,7 +3,7 @@ from collections import OrderedDict
|
||||
|
||||
import yaml
|
||||
|
||||
from .objects import NodeListClass, Placeholder
|
||||
from .objects import Input, NodeListClass
|
||||
|
||||
# mypy: allow-untyped-calls, no-warn-return-any
|
||||
|
||||
@ -62,6 +62,6 @@ yaml.SafeDumper.add_representer(
|
||||
)
|
||||
|
||||
yaml.SafeDumper.add_representer(
|
||||
Placeholder,
|
||||
lambda dumper, value: dumper.represent_scalar("!placeholder", value.name),
|
||||
Input,
|
||||
lambda dumper, value: dumper.represent_scalar("!input", value.name),
|
||||
)
|
||||
|
@ -1,45 +1,46 @@
|
||||
"""Placeholder helpers."""
|
||||
"""Deal with YAML input."""
|
||||
|
||||
from typing import Any, Dict, Set
|
||||
|
||||
from homeassistant.util.yaml import Placeholder
|
||||
from .objects import Input
|
||||
|
||||
|
||||
class UndefinedSubstitution(Exception):
|
||||
"""Error raised when we find a substitution that is not defined."""
|
||||
|
||||
def __init__(self, placeholder: str) -> None:
|
||||
def __init__(self, input_name: str) -> None:
|
||||
"""Initialize the undefined substitution exception."""
|
||||
super().__init__(f"No substitution found for placeholder {placeholder}")
|
||||
self.placeholder = placeholder
|
||||
super().__init__(f"No substitution found for input {input_name}")
|
||||
self.input = input
|
||||
|
||||
|
||||
def extract_placeholders(obj: Any) -> Set[str]:
|
||||
"""Extract placeholders from a structure."""
|
||||
def extract_inputs(obj: Any) -> Set[str]:
|
||||
"""Extract input from a structure."""
|
||||
found: Set[str] = set()
|
||||
_extract_placeholders(obj, found)
|
||||
_extract_inputs(obj, found)
|
||||
return found
|
||||
|
||||
|
||||
def _extract_placeholders(obj: Any, found: Set[str]) -> None:
|
||||
"""Extract placeholders from a structure."""
|
||||
if isinstance(obj, Placeholder):
|
||||
def _extract_inputs(obj: Any, found: Set[str]) -> None:
|
||||
"""Extract input from a structure."""
|
||||
if isinstance(obj, Input):
|
||||
found.add(obj.name)
|
||||
return
|
||||
|
||||
if isinstance(obj, list):
|
||||
for val in obj:
|
||||
_extract_placeholders(val, found)
|
||||
_extract_inputs(val, found)
|
||||
return
|
||||
|
||||
if isinstance(obj, dict):
|
||||
for val in obj.values():
|
||||
_extract_placeholders(val, found)
|
||||
_extract_inputs(val, found)
|
||||
return
|
||||
|
||||
|
||||
def substitute(obj: Any, substitutions: Dict[str, Any]) -> Any:
|
||||
"""Substitute values."""
|
||||
if isinstance(obj, Placeholder):
|
||||
if isinstance(obj, Input):
|
||||
if obj.name not in substitutions:
|
||||
raise UndefinedSubstitution(obj.name)
|
||||
return substitutions[obj.name]
|
@ -11,7 +11,7 @@ import yaml
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import _SECRET_NAMESPACE, SECRET_YAML
|
||||
from .objects import NodeListClass, NodeStrClass, Placeholder
|
||||
from .objects import Input, NodeListClass, NodeStrClass
|
||||
|
||||
try:
|
||||
import keyring
|
||||
@ -331,4 +331,4 @@ yaml.SafeLoader.add_constructor("!include_dir_named", _include_dir_named_yaml)
|
||||
yaml.SafeLoader.add_constructor(
|
||||
"!include_dir_merge_named", _include_dir_merge_named_yaml
|
||||
)
|
||||
yaml.SafeLoader.add_constructor("!placeholder", Placeholder.from_node)
|
||||
yaml.SafeLoader.add_constructor("!input", Input.from_node)
|
||||
|
@ -13,12 +13,12 @@ class NodeStrClass(str):
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Placeholder:
|
||||
"""A placeholder that should be substituted."""
|
||||
class Input:
|
||||
"""Input that should be substituted."""
|
||||
|
||||
name: str
|
||||
|
||||
@classmethod
|
||||
def from_node(cls, loader: yaml.Loader, node: yaml.nodes.Node) -> "Placeholder":
|
||||
def from_node(cls, loader: yaml.Loader, node: yaml.nodes.Node) -> "Input":
|
||||
"""Create a new placeholder from a node."""
|
||||
return cls(node.value)
|
||||
|
@ -57,9 +57,9 @@ def test_extract_blueprint_from_community_topic(community_post):
|
||||
)
|
||||
assert imported_blueprint is not None
|
||||
assert imported_blueprint.blueprint.domain == "automation"
|
||||
assert imported_blueprint.blueprint.placeholders == {
|
||||
"service_to_call",
|
||||
"trigger_event",
|
||||
assert imported_blueprint.blueprint.inputs == {
|
||||
"service_to_call": None,
|
||||
"trigger_event": None,
|
||||
}
|
||||
|
||||
|
||||
@ -103,9 +103,9 @@ async def test_fetch_blueprint_from_community_url(hass, aioclient_mock, communit
|
||||
)
|
||||
assert isinstance(imported_blueprint, importer.ImportedBlueprint)
|
||||
assert imported_blueprint.blueprint.domain == "automation"
|
||||
assert imported_blueprint.blueprint.placeholders == {
|
||||
"service_to_call",
|
||||
"trigger_event",
|
||||
assert imported_blueprint.blueprint.inputs == {
|
||||
"service_to_call": None,
|
||||
"trigger_event": None,
|
||||
}
|
||||
assert imported_blueprint.suggested_filename == "balloob/test-topic"
|
||||
assert (
|
||||
@ -133,9 +133,9 @@ async def test_fetch_blueprint_from_github_url(hass, aioclient_mock, url):
|
||||
imported_blueprint = await importer.fetch_blueprint_from_url(hass, url)
|
||||
assert isinstance(imported_blueprint, importer.ImportedBlueprint)
|
||||
assert imported_blueprint.blueprint.domain == "automation"
|
||||
assert imported_blueprint.blueprint.placeholders == {
|
||||
"service_to_call",
|
||||
"trigger_event",
|
||||
assert imported_blueprint.blueprint.inputs == {
|
||||
"service_to_call": None,
|
||||
"trigger_event": None,
|
||||
}
|
||||
assert imported_blueprint.suggested_filename == "balloob/motion_light"
|
||||
assert imported_blueprint.blueprint.metadata["source_url"] == url
|
||||
@ -152,9 +152,14 @@ async def test_fetch_blueprint_from_github_gist_url(hass, aioclient_mock):
|
||||
imported_blueprint = await importer.fetch_blueprint_from_url(hass, url)
|
||||
assert isinstance(imported_blueprint, importer.ImportedBlueprint)
|
||||
assert imported_blueprint.blueprint.domain == "automation"
|
||||
assert imported_blueprint.blueprint.placeholders == {
|
||||
"motion_entity",
|
||||
"light_entity",
|
||||
assert imported_blueprint.blueprint.inputs == {
|
||||
"motion_entity": {
|
||||
"name": "Motion Sensor",
|
||||
"selector": {
|
||||
"entity": {"domain": "binary_sensor", "device_class": "motion"}
|
||||
},
|
||||
},
|
||||
"light_entity": {"name": "Light", "selector": {"entity": {"domain": "light"}}},
|
||||
}
|
||||
assert imported_blueprint.suggested_filename == "balloob/motion_light"
|
||||
assert imported_blueprint.blueprint.metadata["source_url"] == url
|
||||
|
@ -4,7 +4,7 @@ import logging
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.blueprint import errors, models
|
||||
from homeassistant.util.yaml import Placeholder
|
||||
from homeassistant.util.yaml import Input
|
||||
|
||||
from tests.async_mock import patch
|
||||
|
||||
@ -18,18 +18,16 @@ def blueprint_1():
|
||||
"name": "Hello",
|
||||
"domain": "automation",
|
||||
"source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml",
|
||||
"input": {
|
||||
"test-placeholder": {"name": "Name", "description": "Description"}
|
||||
},
|
||||
"input": {"test-input": {"name": "Name", "description": "Description"}},
|
||||
},
|
||||
"example": Placeholder("test-placeholder"),
|
||||
"example": Input("test-input"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def blueprint_2():
|
||||
"""Blueprint fixture with default placeholder."""
|
||||
"""Blueprint fixture with default inputs."""
|
||||
return models.Blueprint(
|
||||
{
|
||||
"blueprint": {
|
||||
@ -37,12 +35,12 @@ def blueprint_2():
|
||||
"domain": "automation",
|
||||
"source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml",
|
||||
"input": {
|
||||
"test-placeholder": {"name": "Name", "description": "Description"},
|
||||
"test-placeholder-default": {"default": "test"},
|
||||
"test-input": {"name": "Name", "description": "Description"},
|
||||
"test-input-default": {"default": "test"},
|
||||
},
|
||||
},
|
||||
"example": Placeholder("test-placeholder"),
|
||||
"example-default": Placeholder("test-placeholder-default"),
|
||||
"example": Input("test-input"),
|
||||
"example-default": Input("test-input-default"),
|
||||
}
|
||||
)
|
||||
|
||||
@ -72,7 +70,7 @@ def test_blueprint_model_init():
|
||||
"domain": "automation",
|
||||
"input": {"something": None},
|
||||
},
|
||||
"trigger": {"platform": Placeholder("non-existing")},
|
||||
"trigger": {"platform": Input("non-existing")},
|
||||
}
|
||||
)
|
||||
|
||||
@ -83,11 +81,13 @@ def test_blueprint_properties(blueprint_1):
|
||||
"name": "Hello",
|
||||
"domain": "automation",
|
||||
"source_url": "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml",
|
||||
"input": {"test-placeholder": {"name": "Name", "description": "Description"}},
|
||||
"input": {"test-input": {"name": "Name", "description": "Description"}},
|
||||
}
|
||||
assert blueprint_1.domain == "automation"
|
||||
assert blueprint_1.name == "Hello"
|
||||
assert blueprint_1.placeholders == {"test-placeholder"}
|
||||
assert blueprint_1.inputs == {
|
||||
"test-input": {"name": "Name", "description": "Description"}
|
||||
}
|
||||
|
||||
|
||||
def test_blueprint_update_metadata():
|
||||
@ -140,13 +140,13 @@ def test_blueprint_inputs(blueprint_2):
|
||||
{
|
||||
"use_blueprint": {
|
||||
"path": "bla",
|
||||
"input": {"test-placeholder": 1, "test-placeholder-default": 12},
|
||||
"input": {"test-input": 1, "test-input-default": 12},
|
||||
},
|
||||
"example-default": {"overridden": "via-config"},
|
||||
},
|
||||
)
|
||||
inputs.validate()
|
||||
assert inputs.inputs == {"test-placeholder": 1, "test-placeholder-default": 12}
|
||||
assert inputs.inputs == {"test-input": 1, "test-input-default": 12}
|
||||
assert inputs.async_substitute() == {
|
||||
"example": 1,
|
||||
"example-default": {"overridden": "via-config"},
|
||||
@ -159,7 +159,7 @@ def test_blueprint_inputs_validation(blueprint_1):
|
||||
blueprint_1,
|
||||
{"use_blueprint": {"path": "bla", "input": {"non-existing-placeholder": 1}}},
|
||||
)
|
||||
with pytest.raises(errors.MissingPlaceholder):
|
||||
with pytest.raises(errors.MissingInput):
|
||||
inputs.validate()
|
||||
|
||||
|
||||
@ -167,13 +167,13 @@ def test_blueprint_inputs_default(blueprint_2):
|
||||
"""Test blueprint inputs."""
|
||||
inputs = models.BlueprintInputs(
|
||||
blueprint_2,
|
||||
{"use_blueprint": {"path": "bla", "input": {"test-placeholder": 1}}},
|
||||
{"use_blueprint": {"path": "bla", "input": {"test-input": 1}}},
|
||||
)
|
||||
inputs.validate()
|
||||
assert inputs.inputs == {"test-placeholder": 1}
|
||||
assert inputs.inputs == {"test-input": 1}
|
||||
assert inputs.inputs_with_default == {
|
||||
"test-placeholder": 1,
|
||||
"test-placeholder-default": "test",
|
||||
"test-input": 1,
|
||||
"test-input-default": "test",
|
||||
}
|
||||
assert inputs.async_substitute() == {"example": 1, "example-default": "test"}
|
||||
|
||||
@ -185,18 +185,18 @@ def test_blueprint_inputs_override_default(blueprint_2):
|
||||
{
|
||||
"use_blueprint": {
|
||||
"path": "bla",
|
||||
"input": {"test-placeholder": 1, "test-placeholder-default": "custom"},
|
||||
"input": {"test-input": 1, "test-input-default": "custom"},
|
||||
}
|
||||
},
|
||||
)
|
||||
inputs.validate()
|
||||
assert inputs.inputs == {
|
||||
"test-placeholder": 1,
|
||||
"test-placeholder-default": "custom",
|
||||
"test-input": 1,
|
||||
"test-input-default": "custom",
|
||||
}
|
||||
assert inputs.inputs_with_default == {
|
||||
"test-placeholder": 1,
|
||||
"test-placeholder-default": "custom",
|
||||
"test-input": 1,
|
||||
"test-input-default": "custom",
|
||||
}
|
||||
assert inputs.async_substitute() == {"example": 1, "example-default": "custom"}
|
||||
|
||||
@ -238,7 +238,7 @@ async def test_domain_blueprints_inputs_from_config(domain_bps, blueprint_1):
|
||||
with pytest.raises(errors.InvalidBlueprintInputs):
|
||||
await domain_bps.async_inputs_from_config({"not-referencing": "use_blueprint"})
|
||||
|
||||
with pytest.raises(errors.MissingPlaceholder), patch.object(
|
||||
with pytest.raises(errors.MissingInput), patch.object(
|
||||
domain_bps, "async_get_blueprint", return_value=blueprint_1
|
||||
):
|
||||
await domain_bps.async_inputs_from_config(
|
||||
@ -247,10 +247,10 @@ async def test_domain_blueprints_inputs_from_config(domain_bps, blueprint_1):
|
||||
|
||||
with patch.object(domain_bps, "async_get_blueprint", return_value=blueprint_1):
|
||||
inputs = await domain_bps.async_inputs_from_config(
|
||||
{"use_blueprint": {"path": "bla.yaml", "input": {"test-placeholder": None}}}
|
||||
{"use_blueprint": {"path": "bla.yaml", "input": {"test-input": None}}}
|
||||
)
|
||||
assert inputs.blueprint is blueprint_1
|
||||
assert inputs.inputs == {"test-placeholder": None}
|
||||
assert inputs.inputs == {"test-input": None}
|
||||
|
||||
|
||||
async def test_domain_blueprints_add_blueprint(domain_bps, blueprint_1):
|
||||
|
@ -124,7 +124,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: !placeholder 'trigger_event'\naction:\n service: !placeholder 'service_to_call'\n",
|
||||
"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",
|
||||
)
|
||||
|
||||
|
||||
|
2
tests/fixtures/blueprint/community_post.json
vendored
2
tests/fixtures/blueprint/community_post.json
vendored
@ -7,7 +7,7 @@
|
||||
"username": "balloob",
|
||||
"avatar_template": "/user_avatar/community.home-assistant.io/balloob/{size}/21956_2.png",
|
||||
"created_at": "2020-10-16T12:20:12.688Z",
|
||||
"cooked": "\u003cp\u003ehere a test topic.\u003cbr\u003e\nhere a test topic.\u003cbr\u003e\nhere a test topic.\u003cbr\u003e\nhere a test topic.\u003c/p\u003e\n\u003ch1\u003eBlock without syntax\u003c/h1\u003e\n\u003cpre\u003e\u003ccode class=\"lang-auto\"\u003eblueprint:\n domain: automation\n name: Example Blueprint from post\n input:\n trigger_event:\n service_to_call:\ntrigger:\n platform: event\n event_type: !placeholder trigger_event\naction:\n service: !placeholder service_to_call\n\u003c/code\u003e\u003c/pre\u003e",
|
||||
"cooked": "\u003cp\u003ehere a test topic.\u003cbr\u003e\nhere a test topic.\u003cbr\u003e\nhere a test topic.\u003cbr\u003e\nhere a test topic.\u003c/p\u003e\n\u003ch1\u003eBlock without syntax\u003c/h1\u003e\n\u003cpre\u003e\u003ccode class=\"lang-auto\"\u003eblueprint:\n domain: automation\n name: Example Blueprint from post\n input:\n trigger_event:\n service_to_call:\ntrigger:\n platform: event\n event_type: !input trigger_event\naction:\n service: !input service_to_call\n\u003c/code\u003e\u003c/pre\u003e",
|
||||
"post_number": 1,
|
||||
"post_type": 1,
|
||||
"updated_at": "2020-10-20T08:24:14.189Z",
|
||||
|
2
tests/fixtures/blueprint/github_gist.json
vendored
2
tests/fixtures/blueprint/github_gist.json
vendored
@ -15,7 +15,7 @@
|
||||
"raw_url": "https://gist.githubusercontent.com/balloob/e717ce85dd0d2f1bdcdfc884ea25a344/raw/d3cede19ffed75443934325177cd78d26b1700ad/motion_light.yaml",
|
||||
"size": 803,
|
||||
"truncated": false,
|
||||
"content": "blueprint:\n name: Motion-activated Light\n domain: automation\n input:\n motion_entity:\n name: Motion Sensor\n selector:\n entity:\n domain: binary_sensor\n device_class: motion\n light_entity:\n name: Light\n selector:\n entity:\n domain: light\n\n# If motion is detected within the 120s delay,\n# we restart the script.\nmode: restart\nmax_exceeded: silent\n\ntrigger:\n platform: state\n entity_id: !placeholder motion_entity\n from: \"off\"\n to: \"on\"\n\naction:\n - service: homeassistant.turn_on\n entity_id: !placeholder light_entity\n - wait_for_trigger:\n platform: state\n entity_id: !placeholder motion_entity\n from: \"on\"\n to: \"off\"\n - delay: 120\n - service: homeassistant.turn_off\n entity_id: !placeholder light_entity\n"
|
||||
"content": "blueprint:\n name: Motion-activated Light\n domain: automation\n input:\n motion_entity:\n name: Motion Sensor\n selector:\n entity:\n domain: binary_sensor\n device_class: motion\n light_entity:\n name: Light\n selector:\n entity:\n domain: light\n\n# If motion is detected within the 120s delay,\n# we restart the script.\nmode: restart\nmax_exceeded: silent\n\ntrigger:\n platform: state\n entity_id: !input motion_entity\n from: \"off\"\n to: \"on\"\n\naction:\n - service: homeassistant.turn_on\n entity_id: !input light_entity\n - wait_for_trigger:\n platform: state\n entity_id: !input motion_entity\n from: \"on\"\n to: \"off\"\n - delay: 120\n - service: homeassistant.turn_off\n entity_id: !input light_entity\n"
|
||||
}
|
||||
},
|
||||
"public": false,
|
||||
|
@ -159,9 +159,9 @@ blueprint:
|
||||
service_to_call:
|
||||
trigger:
|
||||
platform: event
|
||||
event_type: !placeholder trigger_event
|
||||
event_type: !input trigger_event
|
||||
action:
|
||||
service: !placeholder service_to_call
|
||||
service: !input service_to_call
|
||||
""",
|
||||
}
|
||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||
|
@ -1,29 +0,0 @@
|
||||
"""Test placeholders."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.helpers import placeholder
|
||||
from homeassistant.util.yaml import Placeholder
|
||||
|
||||
|
||||
def test_extract_placeholders():
|
||||
"""Test extracting placeholders from data."""
|
||||
assert placeholder.extract_placeholders(Placeholder("hello")) == {"hello"}
|
||||
assert placeholder.extract_placeholders(
|
||||
{"info": [1, Placeholder("hello"), 2, Placeholder("world")]}
|
||||
) == {"hello", "world"}
|
||||
|
||||
|
||||
def test_substitute():
|
||||
"""Test we can substitute."""
|
||||
assert placeholder.substitute(Placeholder("hello"), {"hello": 5}) == 5
|
||||
|
||||
with pytest.raises(placeholder.UndefinedSubstitution):
|
||||
placeholder.substitute(Placeholder("hello"), {})
|
||||
|
||||
assert (
|
||||
placeholder.substitute(
|
||||
{"info": [1, Placeholder("hello"), 2, Placeholder("world")]},
|
||||
{"hello": 5, "world": 10},
|
||||
)
|
||||
== {"info": [1, 5, 2, 10]}
|
||||
)
|
@ -4,5 +4,5 @@ blueprint:
|
||||
input:
|
||||
trigger:
|
||||
action:
|
||||
trigger: !placeholder trigger
|
||||
action: !placeholder action
|
||||
trigger: !input trigger
|
||||
action: !input action
|
||||
|
@ -6,6 +6,6 @@ blueprint:
|
||||
service_to_call:
|
||||
trigger:
|
||||
platform: event
|
||||
event_type: !placeholder trigger_event
|
||||
event_type: !input trigger_event
|
||||
action:
|
||||
service: !placeholder service_to_call
|
||||
service: !input service_to_call
|
||||
|
1
tests/util/yaml/__init__.py
Normal file
1
tests/util/yaml/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for YAML util."""
|
@ -463,18 +463,18 @@ def test_duplicate_key(caplog):
|
||||
assert "contains duplicate key" in caplog.text
|
||||
|
||||
|
||||
def test_placeholder_class():
|
||||
"""Test placeholder class."""
|
||||
placeholder = yaml_loader.Placeholder("hello")
|
||||
placeholder2 = yaml_loader.Placeholder("hello")
|
||||
def test_input_class():
|
||||
"""Test input class."""
|
||||
input = yaml_loader.Input("hello")
|
||||
input2 = yaml_loader.Input("hello")
|
||||
|
||||
assert placeholder.name == "hello"
|
||||
assert placeholder == placeholder2
|
||||
assert input.name == "hello"
|
||||
assert input == input2
|
||||
|
||||
assert len({placeholder, placeholder2}) == 1
|
||||
assert len({input, input2}) == 1
|
||||
|
||||
|
||||
def test_placeholder():
|
||||
"""Test loading placeholders."""
|
||||
data = {"hello": yaml.Placeholder("test_name")}
|
||||
def test_input():
|
||||
"""Test loading inputs."""
|
||||
data = {"hello": yaml.Input("test_name")}
|
||||
assert yaml.parse_yaml(yaml.dump(data)) == data
|
34
tests/util/yaml/test_input.py
Normal file
34
tests/util/yaml/test_input.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Test inputs."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.util.yaml import (
|
||||
Input,
|
||||
UndefinedSubstitution,
|
||||
extract_inputs,
|
||||
substitute,
|
||||
)
|
||||
|
||||
|
||||
def test_extract_inputs():
|
||||
"""Test extracting inputs from data."""
|
||||
assert extract_inputs(Input("hello")) == {"hello"}
|
||||
assert extract_inputs({"info": [1, Input("hello"), 2, Input("world")]}) == {
|
||||
"hello",
|
||||
"world",
|
||||
}
|
||||
|
||||
|
||||
def test_substitute():
|
||||
"""Test we can substitute."""
|
||||
assert substitute(Input("hello"), {"hello": 5}) == 5
|
||||
|
||||
with pytest.raises(UndefinedSubstitution):
|
||||
substitute(Input("hello"), {})
|
||||
|
||||
assert (
|
||||
substitute(
|
||||
{"info": [1, Input("hello"), 2, Input("world")]},
|
||||
{"hello": 5, "world": 10},
|
||||
)
|
||||
== {"info": [1, 5, 2, 10]}
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user