Home Assistant Unofficial Reference 2024.12.1
config.py
Go to the documentation of this file.
1 """Template config validator."""
2 
3 from contextlib import suppress
4 import logging
5 
6 import voluptuous as vol
7 
8 from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
10  BLUEPRINT_INSTANCE_FIELDS,
11  is_blueprint_instance_config,
12 )
13 from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
14 from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
15 from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
16 from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
17 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
18 from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
19 from homeassistant.config import async_log_schema_error, config_without_domain
20 from homeassistant.const import (
21  CONF_BINARY_SENSORS,
22  CONF_NAME,
23  CONF_SENSORS,
24  CONF_UNIQUE_ID,
25  CONF_VARIABLES,
26 )
27 from homeassistant.core import HomeAssistant
28 from homeassistant.helpers import config_validation as cv
29 from homeassistant.helpers.condition import async_validate_conditions_config
30 from homeassistant.helpers.trigger import async_validate_trigger_config
31 from homeassistant.helpers.typing import ConfigType
32 from homeassistant.setup import async_notify_setup_error
33 
34 from . import (
35  binary_sensor as binary_sensor_platform,
36  button as button_platform,
37  image as image_platform,
38  number as number_platform,
39  select as select_platform,
40  sensor as sensor_platform,
41  weather as weather_platform,
42 )
43 from .const import (
44  CONF_ACTION,
45  CONF_CONDITION,
46  CONF_TRIGGER,
47  DOMAIN,
48  PLATFORMS,
49  TemplateConfig,
50 )
51 from .helpers import async_get_blueprints
52 
53 PACKAGE_MERGE_HINT = "list"
54 
55 CONFIG_SECTION_SCHEMA = vol.Schema(
56  {
57  vol.Optional(CONF_UNIQUE_ID): cv.string,
58  vol.Optional(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
59  vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA,
60  vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
61  vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
62  vol.Optional(NUMBER_DOMAIN): vol.All(
63  cv.ensure_list, [number_platform.NUMBER_SCHEMA]
64  ),
65  vol.Optional(SENSOR_DOMAIN): vol.All(
66  cv.ensure_list, [sensor_platform.SENSOR_SCHEMA]
67  ),
68  vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys(
69  sensor_platform.LEGACY_SENSOR_SCHEMA
70  ),
71  vol.Optional(BINARY_SENSOR_DOMAIN): vol.All(
72  cv.ensure_list, [binary_sensor_platform.BINARY_SENSOR_SCHEMA]
73  ),
74  vol.Optional(CONF_BINARY_SENSORS): cv.schema_with_slug_keys(
75  binary_sensor_platform.LEGACY_BINARY_SENSOR_SCHEMA
76  ),
77  vol.Optional(SELECT_DOMAIN): vol.All(
78  cv.ensure_list, [select_platform.SELECT_SCHEMA]
79  ),
80  vol.Optional(BUTTON_DOMAIN): vol.All(
81  cv.ensure_list, [button_platform.BUTTON_SCHEMA]
82  ),
83  vol.Optional(IMAGE_DOMAIN): vol.All(
84  cv.ensure_list, [image_platform.IMAGE_SCHEMA]
85  ),
86  vol.Optional(WEATHER_DOMAIN): vol.All(
87  cv.ensure_list, [weather_platform.WEATHER_SCHEMA]
88  ),
89  },
90 )
91 
92 TEMPLATE_BLUEPRINT_INSTANCE_SCHEMA = vol.Schema(
93  {
94  vol.Optional(CONF_NAME): cv.string,
95  vol.Optional(CONF_UNIQUE_ID): cv.string,
96  }
97 ).extend(BLUEPRINT_INSTANCE_FIELDS.schema)
98 
99 
101  hass: HomeAssistant,
102  config: ConfigType,
103 ) -> TemplateConfig:
104  """If a config item requires a blueprint, resolve that item to an actual config."""
105  raw_config = None
106  raw_blueprint_inputs = None
107 
108  with suppress(ValueError): # Invalid config
109  raw_config = dict(config)
110 
111  if is_blueprint_instance_config(config):
112  config = TEMPLATE_BLUEPRINT_INSTANCE_SCHEMA(config)
113  blueprints = async_get_blueprints(hass)
114 
115  blueprint_inputs = await blueprints.async_inputs_from_config(config)
116  raw_blueprint_inputs = blueprint_inputs.config_with_inputs
117 
118  config = blueprint_inputs.async_substitute()
119 
120  platforms = [platform for platform in PLATFORMS if platform in config]
121  if len(platforms) > 1:
122  raise vol.Invalid("more than one platform defined per blueprint")
123  if len(platforms) == 1:
124  platform = platforms.pop()
125  for prop in (CONF_NAME, CONF_UNIQUE_ID, CONF_VARIABLES):
126  if prop in config:
127  config[platform][prop] = config.pop(prop)
128  raw_config = dict(config)
129 
130  template_config = TemplateConfig(CONFIG_SECTION_SCHEMA(config))
131  template_config.raw_blueprint_inputs = raw_blueprint_inputs
132  template_config.raw_config = raw_config
133 
134  return template_config
135 
136 
138  hass: HomeAssistant, config: ConfigType
139 ) -> TemplateConfig:
140  """Validate an entire config section for the template integration."""
141 
142  validated_config = await _async_resolve_blueprints(hass, config)
143 
144  if CONF_TRIGGER in validated_config:
145  validated_config[CONF_TRIGGER] = await async_validate_trigger_config(
146  hass, validated_config[CONF_TRIGGER]
147  )
148 
149  if CONF_CONDITION in validated_config:
150  validated_config[CONF_CONDITION] = await async_validate_conditions_config(
151  hass, validated_config[CONF_CONDITION]
152  )
153 
154  return validated_config
155 
156 
157 async def async_validate_config(hass: HomeAssistant, config: ConfigType) -> ConfigType:
158  """Validate config."""
159  if DOMAIN not in config:
160  return config
161 
162  config_sections = []
163 
164  for cfg in cv.ensure_list(config[DOMAIN]):
165  try:
166  template_config: TemplateConfig = await async_validate_config_section(
167  hass, cfg
168  )
169  except vol.Invalid as err:
170  async_log_schema_error(err, DOMAIN, cfg, hass)
171  async_notify_setup_error(hass, DOMAIN)
172  continue
173 
174  legacy_warn_printed = False
175 
176  for old_key, new_key, transform in (
177  (
178  CONF_SENSORS,
179  SENSOR_DOMAIN,
180  sensor_platform.rewrite_legacy_to_modern_conf,
181  ),
182  (
183  CONF_BINARY_SENSORS,
184  BINARY_SENSOR_DOMAIN,
185  binary_sensor_platform.rewrite_legacy_to_modern_conf,
186  ),
187  ):
188  if old_key not in template_config:
189  continue
190 
191  if not legacy_warn_printed:
192  legacy_warn_printed = True
193  logging.getLogger(__name__).warning(
194  "The entity definition format under template: differs from the"
195  " platform "
196  "configuration format. See "
197  "https://www.home-assistant.io/integrations/template#configuration-for-trigger-based-template-sensors"
198  )
199 
200  definitions = (
201  list(template_config[new_key]) if new_key in template_config else []
202  )
203  definitions.extend(transform(hass, template_config[old_key]))
204  template_config = TemplateConfig({**template_config, new_key: definitions})
205 
206  config_sections.append(template_config)
207 
208  # Create a copy of the configuration with all config for current
209  # component removed and add validated config back in.
210  config = config_without_domain(config, DOMAIN)
211  config[DOMAIN] = config_sections
212 
213  return config
blueprint.DomainBlueprints async_get_blueprints(HomeAssistant hass)
Definition: helpers.py:29
bool is_blueprint_instance_config(Any config)
Definition: schemas.py:75
ConfigType async_validate_trigger_config(HomeAssistant hass, ConfigType config)
TemplateConfig async_validate_config_section(HomeAssistant hass, ConfigType config)
Definition: config.py:139
TemplateConfig _async_resolve_blueprints(HomeAssistant hass, ConfigType config)
Definition: config.py:103
ConfigType async_validate_config(HomeAssistant hass, ConfigType config)
Definition: config.py:157
None async_log_schema_error(vol.Invalid exc, str domain, dict config, HomeAssistant hass, str|None link=None)
Definition: config.py:354
ConfigType config_without_domain(ConfigType config, str domain)
Definition: config.py:1313
list[ConfigType|Template] async_validate_conditions_config(HomeAssistant hass, list[ConfigType] conditions)
Definition: condition.py:1073
None async_notify_setup_error(HomeAssistant hass, str component, str|None display_link=None)
Definition: setup.py:99