1 """Module to help with parsing and generating configuration files."""
3 from __future__
import annotations
6 from collections
import OrderedDict
7 from collections.abc
import Callable, Hashable, Iterable, Sequence
8 from contextlib
import suppress
9 from dataclasses
import dataclass
10 from enum
import StrEnum
11 from functools
import partial, reduce
15 from pathlib
import Path
18 from types
import ModuleType
19 from typing
import TYPE_CHECKING, Any
21 from awesomeversion
import AwesomeVersion
22 import voluptuous
as vol
23 from voluptuous.humanize
import MAX_VALIDATION_ERROR_ITEM_LENGTH
24 from yaml.error
import MarkedYAMLError
26 from .const
import CONF_PACKAGES, CONF_PLATFORM, __version__
27 from .core
import DOMAIN
as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
28 from .core_config
import _PACKAGE_DEFINITION_SCHEMA, _PACKAGES_CONFIG_SCHEMA
29 from .exceptions
import ConfigValidationError, HomeAssistantError
30 from .helpers
import config_validation
as cv
31 from .helpers.translation
import async_get_exception_message
32 from .helpers.typing
import ConfigType
33 from .loader
import ComponentProtocol, Integration, IntegrationNotFound
34 from .requirements
import RequirementsNotFound, async_get_integration_with_requirements
35 from .util.async_
import create_eager_task
36 from .util.package
import is_docker_env
37 from .util.yaml
import SECRET_YAML, Secrets, YamlTypeError, load_yaml_dict
38 from .util.yaml.objects
import NodeStrClass
40 _LOGGER = logging.getLogger(__name__)
42 RE_YAML_ERROR = re.compile(
r"homeassistant\.util\.yaml")
43 RE_ASCII = re.compile(
r"\033\[[^m]*m")
44 YAML_CONFIG_FILE =
"configuration.yaml"
45 VERSION_FILE =
".HA_VERSION"
46 CONFIG_DIR_NAME =
".homeassistant"
48 AUTOMATION_CONFIG_PATH =
"automations.yaml"
49 SCRIPT_CONFIG_PATH =
"scripts.yaml"
50 SCENE_CONFIG_PATH =
"scenes.yaml"
52 LOAD_EXCEPTIONS = (ImportError, FileNotFoundError)
53 INTEGRATION_LOAD_EXCEPTIONS = (IntegrationNotFound, RequirementsNotFound)
55 SAFE_MODE_FILENAME =
"safe-mode"
58 # Loads default set of integrations. Do not remove.
61 # Load frontend themes from the themes folder
63 themes: !include_dir_merge_named themes
65 automation: !include {AUTOMATION_CONFIG_PATH}
66 script: !include {SCRIPT_CONFIG_PATH}
67 scene: !include {SCENE_CONFIG_PATH}
70 # Use this file to store secrets like usernames and passwords.
71 # Learn more at https://www.home-assistant.io/docs/configuration/secrets/
72 some_password: welcome
80 - platform: google_translate
81 service_name: google_say
86 """Config error translation keys for config errors."""
89 CONFIG_VALIDATION_ERR =
"config_validation_err"
90 PLATFORM_CONFIG_VALIDATION_ERR =
"platform_config_validation_err"
93 COMPONENT_IMPORT_ERR =
"component_import_err"
94 CONFIG_PLATFORM_IMPORT_ERR =
"config_platform_import_err"
95 CONFIG_VALIDATOR_UNKNOWN_ERR =
"config_validator_unknown_err"
96 CONFIG_SCHEMA_UNKNOWN_ERR =
"config_schema_unknown_err"
97 PLATFORM_COMPONENT_LOAD_ERR =
"platform_component_load_err"
98 PLATFORM_COMPONENT_LOAD_EXC =
"platform_component_load_exc"
99 PLATFORM_SCHEMA_VALIDATOR_ERR =
"platform_schema_validator_err"
102 MULTIPLE_INTEGRATION_CONFIG_ERRORS =
"multiple_integration_config_errors"
105 _CONFIG_LOG_SHOW_STACK_TRACE: dict[ConfigErrorTranslationKey, bool] = {
106 ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR:
False,
107 ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR:
False,
108 ConfigErrorTranslationKey.CONFIG_VALIDATOR_UNKNOWN_ERR:
True,
109 ConfigErrorTranslationKey.CONFIG_SCHEMA_UNKNOWN_ERR:
True,
110 ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_ERR:
False,
111 ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC:
True,
112 ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR:
True,
118 """Configuration exception info class."""
121 translation_key: ConfigErrorTranslationKey
124 integration_link: str |
None
129 """Configuration for an integration and exception information."""
131 config: ConfigType |
None
132 exception_info_list: list[ConfigExceptionInfo]
136 """Put together the default configuration directory based on the OS."""
137 data_dir = os.path.expanduser(
"~")
138 return os.path.join(data_dir, CONFIG_DIR_NAME)
142 """Ensure a configuration file exists in given configuration directory.
144 Creating a default one if needed.
145 Return boolean if configuration dir is ready to go.
147 config_path = hass.config.path(YAML_CONFIG_FILE)
149 if os.path.isfile(config_path):
153 "Unable to find configuration. Creating default one in", hass.config.config_dir
159 """Create a default configuration file in given configuration directory.
161 Return if creation was successful.
163 return await hass.async_add_executor_job(
164 _write_default_config, hass.config.config_dir
169 """Write the default config."""
170 config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
171 secret_path = os.path.join(config_dir, SECRET_YAML)
172 version_path = os.path.join(config_dir, VERSION_FILE)
173 automation_yaml_path = os.path.join(config_dir, AUTOMATION_CONFIG_PATH)
174 script_yaml_path = os.path.join(config_dir, SCRIPT_CONFIG_PATH)
175 scene_yaml_path = os.path.join(config_dir, SCENE_CONFIG_PATH)
180 with open(config_path,
"w", encoding=
"utf8")
as config_file:
181 config_file.write(DEFAULT_CONFIG)
183 if not os.path.isfile(secret_path):
184 with open(secret_path,
"w", encoding=
"utf8")
as secret_file:
185 secret_file.write(DEFAULT_SECRETS)
187 with open(version_path,
"w", encoding=
"utf8")
as version_file:
188 version_file.write(__version__)
190 if not os.path.isfile(automation_yaml_path):
191 with open(automation_yaml_path,
"w", encoding=
"utf8")
as automation_file:
192 automation_file.write(
"[]")
194 if not os.path.isfile(script_yaml_path):
195 with open(script_yaml_path,
"w", encoding=
"utf8"):
198 if not os.path.isfile(scene_yaml_path):
199 with open(scene_yaml_path,
"w", encoding=
"utf8"):
203 f
"Unable to create default configuration file {config_path}"
210 """Load YAML from a Home Assistant configuration file.
212 This function allows a component inside the asyncio loop to reload its
213 configuration by itself. Include package merge.
215 secrets = Secrets(Path(hass.config.config_dir))
219 config = await hass.loop.run_in_executor(
221 load_yaml_config_file,
222 hass.config.path(YAML_CONFIG_FILE),
225 except HomeAssistantError
as exc:
226 if not (base_exc := exc.__cause__)
or not isinstance(base_exc, MarkedYAMLError):
230 if base_exc.context_mark
and base_exc.context_mark.name:
231 base_exc.context_mark.name =
_relpath(hass, base_exc.context_mark.name)
232 if base_exc.problem_mark
and base_exc.problem_mark.name:
233 base_exc.problem_mark.name =
_relpath(hass, base_exc.problem_mark.name)
240 except vol.Invalid
as exc:
243 suffix = f
" at {_relpath(hass, annotation[0])}, line {annotation[1]}"
244 _LOGGER.error(
"Invalid domain '%s'%s", key, suffix)
245 invalid_domains.append(key)
246 for invalid_domain
in invalid_domains:
247 config.pop(invalid_domain)
249 core_config = config.get(HOMEASSISTANT_DOMAIN, {})
252 except vol.Invalid
as exc:
255 config, [HOMEASSISTANT_DOMAIN, CONF_PACKAGES, *exc.path]
257 suffix = f
" at {_relpath(hass, annotation[0])}, line {annotation[1]}"
259 "Invalid package configuration '%s'%s: %s", CONF_PACKAGES, suffix, exc
261 core_config[CONF_PACKAGES] = {}
267 config_path: str, secrets: Secrets |
None =
None
269 """Parse a YAML configuration file.
271 Raises FileNotFoundError or HomeAssistantError.
273 This method needs to run in an executor.
277 except YamlTypeError
as exc:
279 f
"The configuration file {os.path.basename(config_path)} "
280 "does not contain a dictionary"
286 for key, value
in conf_dict.items():
287 conf_dict[key] = value
or {}
292 """Upgrade configuration if necessary.
294 This method needs to run in an executor.
296 version_path = hass.config.path(VERSION_FILE)
299 with open(version_path, encoding=
"utf8")
as inp:
300 conf_version = inp.readline().strip()
301 except FileNotFoundError:
303 conf_version =
"0.7.7"
305 if conf_version == __version__:
309 "Upgrading configuration directory from %s to %s", conf_version, __version__
312 version_obj = AwesomeVersion(conf_version)
314 if version_obj < AwesomeVersion(
"0.50"):
316 lib_path = hass.config.path(
"deps")
317 if os.path.isdir(lib_path):
318 shutil.rmtree(lib_path)
320 if version_obj < AwesomeVersion(
"0.92"):
322 config_path = hass.config.path(YAML_CONFIG_FILE)
324 with open(config_path, encoding=
"utf-8")
as config_file:
325 config_raw = config_file.read()
327 if TTS_PRE_92
in config_raw:
328 _LOGGER.info(
"Migrating google tts to google_translate tts")
329 config_raw = config_raw.replace(TTS_PRE_92, TTS_92)
331 with open(config_path,
"w", encoding=
"utf-8")
as config_file:
332 config_file.write(config_raw)
334 _LOGGER.exception(
"Migrating to google_translate tts failed")
339 lib_path = hass.config.path(
"deps")
340 if os.path.isdir(lib_path):
341 shutil.rmtree(lib_path)
343 with open(version_path,
"w", encoding=
"utf8")
as outp:
344 outp.write(__version__)
353 link: str |
None =
None,
355 """Log a schema validation error."""
357 _LOGGER.error(message)
362 exc: vol.Invalid | HomeAssistantError,
366 link: str |
None =
None,
368 """Log an error from a custom config validator."""
369 if isinstance(exc, vol.Invalid):
374 _LOGGER.error(message, exc_info=exc)
378 if not hasattr(item,
"__config_file__"):
381 return (getattr(item,
"__config_file__"), getattr(item,
"__line__",
"?"))
385 """Access a nested object in root by item sequence.
387 Returns None in case of error.
390 return reduce(operator.getitem, items, data)
391 except (KeyError, IndexError, TypeError):
396 config: dict | list, path: list[Hashable]
397 ) -> tuple[str, int | str] |
None:
398 """Find file/line annotation for a node in config pointed to by path.
400 If the node pointed to is a dict or list, prefer the annotation for the key in
401 the key/value pair defining the dict or list.
402 If the node is not annotated, try the parent node.
405 def find_annotation_for_key(
406 item: dict, path: list[Hashable], tail: Hashable
407 ) -> tuple[str, int | str] |
None:
415 def find_annotation_rec(
416 config: dict | list, path: list[Hashable], tail: Hashable |
None
417 ) -> tuple[str, int | str] |
None:
419 if isinstance(item, dict)
and tail
is not None:
420 if tail_annotation := find_annotation_for_key(item, path, tail):
421 return tail_annotation
424 isinstance(item, (dict, list))
427 key_annotation := find_annotation_for_key(
432 return key_annotation
441 if annotation := find_annotation_rec(config, path, tail):
445 return find_annotation_rec(config,
list(path),
None)
448 def _relpath(hass: HomeAssistant, path: str) -> str:
449 """Return path relative to the Home Assistant config dir."""
450 return os.path.relpath(path, hass.config.config_dir)
459 max_sub_error_length: int,
461 """Stringify voluptuous.Invalid.
463 This is an alternative to the custom __str__ implemented in
464 voluptuous.error.Invalid. The modifications are:
465 - Format the path delimited by -> instead of @data[]
466 - Prefix with domain, file and line of the error
467 - Suffix with a link to the documentation
468 - Give a more user friendly output for unknown options
469 - Give a more user friendly output for missing options
472 integration_domain, _, platform_domain = domain.partition(
".")
474 f
"Invalid config for '{platform_domain}' from integration "
475 f
"'{integration_domain}'"
478 message_prefix = f
"Invalid config for '{domain}'"
479 if domain != HOMEASSISTANT_DOMAIN
and link:
480 message_suffix = f
", please check the docs at {link}"
484 message_prefix += f
" at {_relpath(hass, annotation[0])}, line {annotation[1]}"
485 path =
"->".join(
str(m)
for m
in exc.path)
486 if exc.error_message ==
"extra keys not allowed":
488 f
"{message_prefix}: '{exc.path[-1]}' is an invalid option for '{domain}', "
489 f
"check: {path}{message_suffix}"
491 if exc.error_message ==
"required key not provided":
493 f
"{message_prefix}: required key '{exc.path[-1]}' not provided"
499 output = Exception.__str__(exc)
500 if error_type := exc.error_type:
501 output +=
" for " + error_type
502 offending_item_summary = repr(
_get_by_path(config, exc.path))
503 if len(offending_item_summary) > max_sub_error_length:
504 offending_item_summary = (
505 f
"{offending_item_summary[: max_sub_error_length - 3]}..."
508 f
"{message_prefix}: {output} '{path}', got {offending_item_summary}"
515 validation_error: vol.Invalid,
519 max_sub_error_length: int = MAX_VALIDATION_ERROR_ITEM_LENGTH,
521 """Provide a more helpful + complete validation error message.
523 This is a modified version of voluptuous.error.Invalid.__str__,
524 the modifications make some minor changes to the formatting.
526 if isinstance(validation_error, vol.MultipleInvalid):
530 hass, sub_error, domain, config, link, max_sub_error_length
532 for sub_error
in validation_error.errors
536 hass, validation_error, domain, config, link, max_sub_error_length
543 exc: HomeAssistantError,
546 link: str |
None =
None,
548 """Format HomeAssistantError thrown by a custom config validator."""
550 integration_domain, _, platform_domain = domain.partition(
".")
552 f
"Invalid config for '{platform_domain}' from integration "
553 f
"'{integration_domain}'"
556 message_prefix = f
"Invalid config for '{domain}'"
560 message_prefix += f
" at {_relpath(hass, annotation[0])}, line {annotation[1]}"
561 message = f
"{message_prefix}: {str(exc) or repr(exc)}"
562 if domain != HOMEASSISTANT_DOMAIN
and link:
563 message += f
", please check the docs at {link}"
574 link: str |
None =
None,
576 """Format configuration validation error."""
581 hass: HomeAssistant, package: str, component: str |
None, config: dict, message: str
583 """Log an error while merging packages."""
584 message_prefix = f
"Setup of package '{package}'"
586 config, [HOMEASSISTANT_DOMAIN, CONF_PACKAGES, package]
588 message_prefix += f
" at {_relpath(hass, annotation[0])}, line {annotation[1]}"
590 _LOGGER.error(
"%s failed: %s", message_prefix, message)
594 """Extract the schema and identify list or dict based."""
595 if not isinstance(module.CONFIG_SCHEMA, vol.Schema):
598 schema = module.CONFIG_SCHEMA.schema
600 if isinstance(schema, vol.All):
601 for subschema
in schema.validators:
602 if isinstance(subschema, dict):
609 key = next(k
for k
in schema
if k == module.DOMAIN)
610 except (TypeError, AttributeError, StopIteration):
613 _LOGGER.exception(
"Unexpected error identifying config schema")
616 if hasattr(key,
"default")
and not isinstance(
617 key.default, vol.schema_builder.Undefined
619 default_value = module.CONFIG_SCHEMA({module.DOMAIN: key.default()})[
623 if isinstance(default_value, dict):
626 if isinstance(default_value, list):
631 domain_schema = schema[key]
633 t_schema =
str(domain_schema)
634 if t_schema.startswith(
"{")
or "schema_with_slug_keys" in t_schema:
636 if t_schema.startswith((
"[",
"All(<function ensure_list")):
642 """Validate basic package definition properties."""
648 """Merge package into conf, recursively."""
649 duplicate_key: str |
None =
None
650 for key, pack_conf
in package.items():
651 if isinstance(pack_conf, dict):
654 conf[key] = conf.get(key, OrderedDict())
657 elif isinstance(pack_conf, list):
658 conf[key] = cv.remove_falsy(
659 cv.ensure_list(conf.get(key)) + cv.ensure_list(pack_conf)
663 if conf.get(key)
is not None:
665 conf[key] = pack_conf
672 packages: dict[str, Any],
673 _log_pkg_error: Callable[
674 [HomeAssistant, str, str |
None, dict, str],
None
677 """Merge packages into the top-level configuration.
679 Ignores packages that cannot be setup. Mutates config. Raises
680 vol.Invalid if whole package config is invalid.
685 invalid_packages = []
686 for pack_name, pack_conf
in packages.items():
689 except vol.Invalid
as exc:
695 f
"Invalid package definition '{pack_name}': {exc!s}. Package "
696 f
"will not be initialized",
698 invalid_packages.append(pack_name)
701 for comp_name, comp_conf
in pack_conf.items():
702 if comp_name == HOMEASSISTANT_DOMAIN:
705 domain = cv.domain_key(comp_name)
708 hass, pack_name, comp_name, config, f
"Invalid domain '{comp_name}'"
716 component = await integration.async_get_component()
717 except LOAD_EXCEPTIONS
as exc:
723 f
"Integration {comp_name} caused error: {exc!s}",
726 except INTEGRATION_LOAD_EXCEPTIONS
as exc:
733 ) = await integration.async_get_platform(
"config")
735 if not hasattr(config_platform,
"async_validate_config"):
736 config_platform =
None
738 config_platform =
None
743 if config_platform
is not None:
744 merge_list = config_platform.PACKAGE_MERGE_HINT ==
"list"
747 merge_list = hasattr(component,
"PLATFORM_SCHEMA")
749 if not merge_list
and hasattr(component,
"CONFIG_SCHEMA"):
753 config[comp_name] = cv.remove_falsy(
754 cv.ensure_list(config.get(comp_name)) + cv.ensure_list(comp_conf)
758 if comp_conf
is None:
759 comp_conf = OrderedDict()
761 if not isinstance(comp_conf, dict):
767 f
"integration '{comp_name}' cannot be merged, expected a dict",
771 if comp_name
not in config
or config[comp_name]
is None:
772 config[comp_name] = OrderedDict()
774 if not isinstance(config[comp_name], dict):
781 f
"integration '{comp_name}' cannot be merged, dict expected in "
794 f
"integration '{comp_name}' has duplicate key '{duplicate_key}'",
797 for pack_name
in invalid_packages:
798 packages.pop(pack_name, {})
805 hass: HomeAssistant, domain: str, platform_exception: ConfigExceptionInfo
806 ) -> tuple[str |
None, bool, dict[str, str]]:
807 """Get message to log and print stack trace preference."""
808 exception = platform_exception.exception
809 platform_path = platform_exception.platform_path
810 platform_config = platform_exception.config
811 link = platform_exception.integration_link
813 placeholders: dict[str, str] = {
815 "error":
str(exception),
816 "p_name": platform_path,
821 show_stack_trace: bool |
None = _CONFIG_LOG_SHOW_STACK_TRACE.get(
822 platform_exception.translation_key
824 if show_stack_trace
is None:
827 show_stack_trace =
False
828 if isinstance(exception, vol.Invalid):
830 hass, exception, platform_path, platform_config, link
833 placeholders[
"config_file"], line = annotation
834 placeholders[
"line"] =
str(line)
837 assert isinstance(exception, HomeAssistantError)
839 hass, exception, platform_path, platform_config, link
842 placeholders[
"config_file"], line = annotation
843 placeholders[
"line"] =
str(line)
844 show_stack_trace =
True
845 return (log_message, show_stack_trace, placeholders)
849 HOMEASSISTANT_DOMAIN,
850 platform_exception.translation_key,
851 translation_placeholders=placeholders,
854 return (log_message, show_stack_trace, placeholders)
860 integration: Integration,
861 raise_on_failure: bool =
False,
862 ) -> ConfigType |
None:
863 """Process and component configuration and handle errors.
866 - Print the error messages to the log.
867 - Raise a ConfigValidationError if raise_on_failure is set.
869 Returns the integration config or `None`.
872 hass, config, integration
875 hass, integration_config_info, integration, raise_on_failure
882 integration_config_info: IntegrationConfigInfo,
883 integration: Integration,
884 ) -> ConfigType |
None:
885 """Remove file and line annotations from str items in component configuration."""
886 if (config := integration_config_info.config)
is None:
889 def drop_config_annotations_rec(node: Any) -> Any:
890 if isinstance(node, dict):
895 (drop_config_annotations_rec(k), drop_config_annotations_rec(v))
896 for k, v
in tmp.items()
900 if isinstance(node, list):
901 return [drop_config_annotations_rec(v)
for v
in node]
903 if isinstance(node, NodeStrClass):
910 if integration.domain
in config
and integration.domain != HOMEASSISTANT_DOMAIN:
911 drop_config_annotations_rec(config[integration.domain])
918 integration_config_info: IntegrationConfigInfo,
919 integration: Integration,
920 raise_on_failure: bool =
False,
922 """Handle component configuration errors from async_process_component_config.
925 - Print the error messages to the log.
926 - Raise a ConfigValidationError if raise_on_failure is set.
929 if not (config_exception_info := integration_config_info.exception_info_list):
932 platform_exception: ConfigExceptionInfo
933 domain = integration.domain
934 placeholders: dict[str, str]
935 for platform_exception
in config_exception_info:
936 exception = platform_exception.exception
944 exc_info=exception
if show_stack_trace
else None,
947 if not raise_on_failure:
950 if len(config_exception_info) == 1:
951 translation_key = platform_exception.translation_key
953 translation_key = ConfigErrorTranslationKey.MULTIPLE_INTEGRATION_CONFIG_ERRORS
954 errors =
str(len(config_exception_info))
961 [platform_exception.exception
for platform_exception
in config_exception_info],
962 translation_domain=HOMEASSISTANT_DOMAIN,
963 translation_placeholders=placeholders,
968 config: ConfigType, domain: str
969 ) -> Iterable[tuple[str |
None, ConfigType]]:
970 """Break a component config into different platforms.
972 For example, will find 'switch', 'switch 2', 'switch 3', .. etc
976 if not (platform_config := config[config_key]):
979 if not isinstance(platform_config, list):
980 platform_config = [platform_config]
984 for item
in platform_config:
986 platform = item.get(CONF_PLATFORM)
987 except AttributeError:
994 config: ConfigType, domains: set[str]
995 ) -> dict[str, set[str]]:
996 """Find all the platforms in a configuration.
998 Returns a dictionary with domain as key and a set of platforms as value.
1000 platform_integrations: dict[str, set[str]] = {}
1001 for key, domain_config
in config.items():
1003 domain = cv.domain_key(key)
1006 if domain
not in domains:
1009 if not isinstance(domain_config, list):
1010 domain_config = [domain_config]
1012 for item
in domain_config:
1014 platform = item.get(CONF_PLATFORM)
1015 except AttributeError:
1017 if platform
and isinstance(platform, Hashable):
1018 platform_integrations.setdefault(domain, set()).
add(platform)
1019 return platform_integrations
1023 """Extract keys from config for given domain name.
1029 with suppress(vol.Invalid):
1030 if cv.domain_key(key) != domain:
1032 domain_configs.append(key)
1033 return domain_configs
1036 @dataclass(slots=True)
1038 """Class to hold platform integration information."""
1042 integration: Integration
1044 validated_config: ConfigType
1049 integration_docs: str |
None,
1050 config_exceptions: list[ConfigExceptionInfo],
1051 p_integration: _PlatformIntegration,
1052 ) -> ConfigType |
None:
1053 """Load a platform integration and validate its config."""
1055 platform = await p_integration.integration.async_get_platform(domain)
1056 except LOAD_EXCEPTIONS
as exc:
1059 ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC,
1061 p_integration.config,
1064 config_exceptions.append(exc_info)
1069 if not hasattr(platform,
"PLATFORM_SCHEMA"):
1070 return p_integration.validated_config
1074 return platform.PLATFORM_SCHEMA(p_integration.config)
1075 except vol.Invalid
as exc:
1078 ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR,
1080 p_integration.config,
1081 p_integration.integration.documentation,
1083 config_exceptions.append(exc_info)
1084 except Exception
as exc:
1087 ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR,
1089 p_integration.config,
1090 p_integration.integration.documentation,
1092 config_exceptions.append(exc_info)
1098 hass: HomeAssistant,
1100 integration: Integration,
1101 component: ComponentProtocol |
None =
None,
1102 ) -> IntegrationConfigInfo:
1103 """Check component configuration.
1105 Returns processed configuration and exception information.
1107 This method must be run in the event loop.
1109 domain = integration.domain
1110 integration_docs = integration.documentation
1111 config_exceptions: list[ConfigExceptionInfo] = []
1115 component = await integration.async_get_component()
1116 except LOAD_EXCEPTIONS
as exc:
1119 ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR,
1124 config_exceptions.append(exc_info)
1128 config_validator =
None
1132 if integration.platforms_exists((
"config",)):
1135 config_validator = await integration.async_get_platform(
"config")
1136 except ImportError
as err:
1140 if err.name != f
"{integration.pkg_path}.config":
1143 ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR,
1148 config_exceptions.append(exc_info)
1151 if config_validator
is not None and hasattr(
1152 config_validator,
"async_validate_config"
1156 await config_validator.async_validate_config(hass, config), []
1158 except (vol.Invalid, HomeAssistantError)
as exc:
1161 ConfigErrorTranslationKey.CONFIG_VALIDATION_ERR,
1166 config_exceptions.append(exc_info)
1168 except Exception
as exc:
1171 ConfigErrorTranslationKey.CONFIG_VALIDATOR_UNKNOWN_ERR,
1176 config_exceptions.append(exc_info)
1180 if hasattr(component,
"CONFIG_SCHEMA"):
1183 await cv.async_validate(hass, component.CONFIG_SCHEMA, config), []
1185 except vol.Invalid
as exc:
1188 ConfigErrorTranslationKey.CONFIG_VALIDATION_ERR,
1193 config_exceptions.append(exc_info)
1195 except Exception
as exc:
1198 ConfigErrorTranslationKey.CONFIG_SCHEMA_UNKNOWN_ERR,
1203 config_exceptions.append(exc_info)
1206 component_platform_schema = getattr(
1207 component,
"PLATFORM_SCHEMA_BASE", getattr(component,
"PLATFORM_SCHEMA",
None)
1210 if component_platform_schema
is None:
1213 platform_integrations_to_load: list[_PlatformIntegration] = []
1214 platforms: list[ConfigType] = []
1217 platform_path = f
"{p_name}.{domain}"
1219 p_validated = await cv.async_validate(
1220 hass, component_platform_schema, p_config
1222 except vol.Invalid
as exc:
1225 ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR,
1230 config_exceptions.append(exc_info)
1232 except Exception
as exc:
1235 ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR,
1240 config_exceptions.append(exc_info)
1247 platforms.append(p_validated)
1252 except (RequirementsNotFound, IntegrationNotFound)
as exc:
1255 ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_ERR,
1260 config_exceptions.append(exc_info)
1264 platform_path, p_name, p_integration, p_config, p_validated
1266 platform_integrations_to_load.append(platform_integration)
1284 if platform_integrations_to_load:
1285 async_load_and_validate = partial(
1286 _async_load_and_validate_platform_integration,
1293 for validated_config
in await asyncio.gather(
1296 async_load_and_validate(p_integration), loop=hass.loop
1298 for p_integration
in platform_integrations_to_load
1301 if validated_config
is not None
1307 config[domain] = platforms
1314 """Return a config with all configuration for a domain removed."""
1316 return {key: value
for key, value
in config.items()
if key
not in filter_keys}
1320 """Check if Home Assistant configuration file is valid.
1322 This method is a coroutine.
1325 from .helpers
import check_config
1327 res = await check_config.async_check_ha_config_file(hass)
1331 return res.error_str
1335 """Return if safe mode is enabled.
1337 If safe mode is enabled, the safe mode file will be removed.
1339 safe_mode_path = os.path.join(config_dir, SAFE_MODE_FILENAME)
1340 safe_mode = os.path.exists(safe_mode_path)
1342 os.remove(safe_mode_path)
1347 """Enable safe mode."""
1349 def _enable_safe_mode() -> None:
1350 Path(hass.config.path(SAFE_MODE_FILENAME)).touch()
1352 await hass.async_add_executor_job(_enable_safe_mode)
bool add(self, _T matcher)
None open(self, **Any kwargs)
Sequence[str] extract_domain_configs(ConfigType config, str domain)
bool async_ensure_config_exists(HomeAssistant hass)
bool _write_default_config(str config_dir)
tuple[str, int|str]|None _get_annotation(Any item)
str|None _identify_config_schema(ComponentProtocol module)
Iterable[tuple[str|None, ConfigType]] config_per_platform(ConfigType config, str domain)
ConfigType|None _async_load_and_validate_platform_integration(str domain, str|None integration_docs, list[ConfigExceptionInfo] config_exceptions, _PlatformIntegration p_integration)
tuple[str|None, bool, dict[str, str]] _get_log_message_and_stack_print_pref(HomeAssistant hass, str domain, ConfigExceptionInfo platform_exception)
ConfigType|None async_drop_config_annotations(IntegrationConfigInfo integration_config_info, Integration integration)
ConfigType|None async_process_component_and_handle_errors(HomeAssistant hass, ConfigType config, Integration integration, bool raise_on_failure=False)
Any _get_by_path(dict|list data, list[Hashable] items)
None async_log_schema_error(vol.Invalid exc, str domain, dict config, HomeAssistant hass, str|None link=None)
dict[Any, Any] load_yaml_config_file(str config_path, Secrets|None secrets=None)
str get_default_config_dir()
tuple[str, int|str]|None find_annotation(dict|list config, list[Hashable] path)
str|None _recursive_merge(dict[str, Any] conf, dict[str, Any] package)
dict async_hass_config_yaml(HomeAssistant hass)
str format_homeassistant_error(HomeAssistant hass, HomeAssistantError exc, str domain, dict config, str|None link=None)
None _validate_package_definition(str name, Any conf)
str|None async_check_ha_config_file(HomeAssistant hass)
IntegrationConfigInfo async_process_component_config(HomeAssistant hass, ConfigType config, Integration integration, ComponentProtocol|None component=None)
None _log_pkg_error(HomeAssistant hass, str package, str|None component, dict config, str message)
ConfigType config_without_domain(ConfigType config, str domain)
bool safe_mode_enabled(str config_dir)
str _relpath(HomeAssistant hass, str path)
None async_enable_safe_mode(HomeAssistant hass)
dict[str, set[str]] extract_platform_integrations(ConfigType config, set[str] domains)
dict merge_packages_config(HomeAssistant hass, dict config, dict[str, Any] packages, Callable[[HomeAssistant, str, str|None, dict, str], None] _log_pkg_error=_log_pkg_error)
None async_log_config_validator_error(vol.Invalid|HomeAssistantError exc, str domain, dict config, HomeAssistant hass, str|None link=None)
str humanize_error(HomeAssistant hass, vol.Invalid validation_error, str domain, dict config, str|None link, int max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH)
str format_schema_error(HomeAssistant hass, vol.Invalid exc, str domain, dict config, str|None link=None)
None async_handle_component_errors(HomeAssistant hass, IntegrationConfigInfo integration_config_info, Integration integration, bool raise_on_failure=False)
str stringify_invalid(HomeAssistant hass, vol.Invalid exc, str domain, dict config, str|None link, int max_sub_error_length)
bool async_create_default_config(HomeAssistant hass)
None process_ha_config_upgrade(HomeAssistant hass)
_PACKAGE_DEFINITION_SCHEMA
str async_get_exception_message(str translation_domain, str translation_key, dict[str, str]|None translation_placeholders=None)
Integration async_get_integration_with_requirements(HomeAssistant hass, str domain)
dict load_yaml_dict(str|os.PathLike[str] fname, Secrets|None secrets=None)