Fix check config (#43663)
This commit is contained in:
parent
edf70e9f06
commit
e1de36fda8
4 changed files with 85 additions and 21 deletions
|
@ -212,7 +212,13 @@ class DomainBlueprints:
|
||||||
"""Load a blueprint."""
|
"""Load a blueprint."""
|
||||||
try:
|
try:
|
||||||
blueprint_data = yaml.load_yaml(self.blueprint_folder / blueprint_path)
|
blueprint_data = yaml.load_yaml(self.blueprint_folder / blueprint_path)
|
||||||
except (HomeAssistantError, FileNotFoundError) as err:
|
except FileNotFoundError as err:
|
||||||
|
raise FailedToLoad(
|
||||||
|
self.domain,
|
||||||
|
blueprint_path,
|
||||||
|
FileNotFoundError(f"Unable to find {blueprint_path}"),
|
||||||
|
) from err
|
||||||
|
except HomeAssistantError as err:
|
||||||
raise FailedToLoad(self.domain, blueprint_path, err) from err
|
raise FailedToLoad(self.domain, blueprint_path, err) from err
|
||||||
|
|
||||||
return Blueprint(
|
return Blueprint(
|
||||||
|
@ -251,13 +257,25 @@ class DomainBlueprints:
|
||||||
|
|
||||||
async def async_get_blueprint(self, blueprint_path: str) -> Blueprint:
|
async def async_get_blueprint(self, blueprint_path: str) -> Blueprint:
|
||||||
"""Get a blueprint."""
|
"""Get a blueprint."""
|
||||||
|
|
||||||
|
def load_from_cache():
|
||||||
|
"""Load blueprint from cache."""
|
||||||
|
blueprint = self._blueprints[blueprint_path]
|
||||||
|
if blueprint is None:
|
||||||
|
raise FailedToLoad(
|
||||||
|
self.domain,
|
||||||
|
blueprint_path,
|
||||||
|
FileNotFoundError(f"Unable to find {blueprint_path}"),
|
||||||
|
)
|
||||||
|
return blueprint
|
||||||
|
|
||||||
if blueprint_path in self._blueprints:
|
if blueprint_path in self._blueprints:
|
||||||
return self._blueprints[blueprint_path]
|
return load_from_cache()
|
||||||
|
|
||||||
async with self._load_lock:
|
async with self._load_lock:
|
||||||
# Check it again
|
# Check it again
|
||||||
if blueprint_path in self._blueprints:
|
if blueprint_path in self._blueprints:
|
||||||
return self._blueprints[blueprint_path]
|
return load_from_cache()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
blueprint = await self.hass.async_add_executor_job(
|
blueprint = await self.hass.async_add_executor_job(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""Helper to check the configuration file."""
|
"""Helper to check the configuration file."""
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import List, NamedTuple, Optional
|
from typing import List, NamedTuple, Optional
|
||||||
|
|
||||||
import attr
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import loader
|
from homeassistant import loader
|
||||||
|
@ -36,11 +36,13 @@ class CheckConfigError(NamedTuple):
|
||||||
config: Optional[ConfigType]
|
config: Optional[ConfigType]
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
|
||||||
class HomeAssistantConfig(OrderedDict):
|
class HomeAssistantConfig(OrderedDict):
|
||||||
"""Configuration result with errors attribute."""
|
"""Configuration result with errors attribute."""
|
||||||
|
|
||||||
errors: List[CheckConfigError] = attr.ib(factory=list)
|
def __init__(self) -> None:
|
||||||
|
"""Initialize HA config."""
|
||||||
|
super().__init__()
|
||||||
|
self.errors: List[CheckConfigError] = []
|
||||||
|
|
||||||
def add_error(
|
def add_error(
|
||||||
self,
|
self,
|
||||||
|
@ -139,14 +141,24 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> HomeAssistantConfig
|
||||||
config_validator, "async_validate_config"
|
config_validator, "async_validate_config"
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return await config_validator.async_validate_config( # type: ignore
|
result[domain] = (
|
||||||
hass, config
|
await config_validator.async_validate_config( # type: ignore
|
||||||
)
|
hass, config
|
||||||
|
)
|
||||||
|
)[domain]
|
||||||
|
continue
|
||||||
except (vol.Invalid, HomeAssistantError) as ex:
|
except (vol.Invalid, HomeAssistantError) as ex:
|
||||||
_comp_error(ex, domain, config)
|
_comp_error(ex, domain, config)
|
||||||
continue
|
continue
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
result.add_error("Unknown error calling %s config validator", domain)
|
logging.getLogger(__name__).exception(
|
||||||
|
"Unexpected error validating config"
|
||||||
|
)
|
||||||
|
result.add_error(
|
||||||
|
f"Unexpected error calling config validator: {err}",
|
||||||
|
domain,
|
||||||
|
config.get(domain),
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config_schema = getattr(component, "CONFIG_SCHEMA", None)
|
config_schema = getattr(component, "CONFIG_SCHEMA", None)
|
||||||
|
|
|
@ -203,8 +203,8 @@ async def test_domain_blueprints_get_blueprint_errors(hass, domain_bps):
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"}
|
"homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"}
|
||||||
):
|
), pytest.raises(errors.FailedToLoad):
|
||||||
assert await domain_bps.async_get_blueprint("non-existing-path") is None
|
await domain_bps.async_get_blueprint("non-existing-path")
|
||||||
|
|
||||||
|
|
||||||
async def test_domain_blueprints_caching(domain_bps):
|
async def test_domain_blueprints_caching(domain_bps):
|
||||||
|
@ -258,3 +258,11 @@ async def test_domain_blueprints_add_blueprint(domain_bps, blueprint_1):
|
||||||
with patch.object(domain_bps, "_load_blueprint") as mock_load:
|
with patch.object(domain_bps, "_load_blueprint") as mock_load:
|
||||||
assert await domain_bps.async_get_blueprint("something.yaml") == blueprint_1
|
assert await domain_bps.async_get_blueprint("something.yaml") == blueprint_1
|
||||||
assert not mock_load.mock_calls
|
assert not mock_load.mock_calls
|
||||||
|
|
||||||
|
|
||||||
|
async def test_inputs_from_config_nonexisting_blueprint(domain_bps):
|
||||||
|
"""Test referring non-existing blueprint."""
|
||||||
|
with pytest.raises(errors.FailedToLoad):
|
||||||
|
await domain_bps.async_inputs_from_config(
|
||||||
|
{"use_blueprint": {"path": "non-existing.yaml"}}
|
||||||
|
)
|
||||||
|
|
|
@ -7,8 +7,8 @@ from homeassistant.helpers.check_config import (
|
||||||
async_check_ha_config_file,
|
async_check_ha_config_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import Mock, patch
|
||||||
from tests.common import patch_yaml_files
|
from tests.common import mock_platform, patch_yaml_files
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ def log_ha_config(conf):
|
||||||
_LOGGER.debug("error[%s] = %s", cnt, err)
|
_LOGGER.debug("error[%s] = %s", cnt, err)
|
||||||
|
|
||||||
|
|
||||||
async def test_bad_core_config(hass, loop):
|
async def test_bad_core_config(hass):
|
||||||
"""Test a bad core config setup."""
|
"""Test a bad core config setup."""
|
||||||
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
|
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
|
||||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||||
|
@ -53,7 +53,7 @@ async def test_bad_core_config(hass, loop):
|
||||||
assert not res.errors
|
assert not res.errors
|
||||||
|
|
||||||
|
|
||||||
async def test_config_platform_valid(hass, loop):
|
async def test_config_platform_valid(hass):
|
||||||
"""Test a valid platform setup."""
|
"""Test a valid platform setup."""
|
||||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
|
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
|
||||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||||
|
@ -65,7 +65,7 @@ async def test_config_platform_valid(hass, loop):
|
||||||
assert not res.errors
|
assert not res.errors
|
||||||
|
|
||||||
|
|
||||||
async def test_component_platform_not_found(hass, loop):
|
async def test_component_platform_not_found(hass):
|
||||||
"""Test errors if component or platform not found."""
|
"""Test errors if component or platform not found."""
|
||||||
# Make sure they don't exist
|
# Make sure they don't exist
|
||||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"}
|
files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"}
|
||||||
|
@ -83,7 +83,7 @@ async def test_component_platform_not_found(hass, loop):
|
||||||
assert not res.errors
|
assert not res.errors
|
||||||
|
|
||||||
|
|
||||||
async def test_component_platform_not_found_2(hass, loop):
|
async def test_component_platform_not_found_2(hass):
|
||||||
"""Test errors if component or platform not found."""
|
"""Test errors if component or platform not found."""
|
||||||
# Make sure they don't exist
|
# Make sure they don't exist
|
||||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"}
|
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"}
|
||||||
|
@ -103,7 +103,7 @@ async def test_component_platform_not_found_2(hass, loop):
|
||||||
assert not res.errors
|
assert not res.errors
|
||||||
|
|
||||||
|
|
||||||
async def test_package_invalid(hass, loop):
|
async def test_package_invalid(hass):
|
||||||
"""Test a valid platform setup."""
|
"""Test a valid platform setup."""
|
||||||
files = {
|
files = {
|
||||||
YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]')
|
YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]')
|
||||||
|
@ -121,7 +121,7 @@ async def test_package_invalid(hass, loop):
|
||||||
assert res.keys() == {"homeassistant"}
|
assert res.keys() == {"homeassistant"}
|
||||||
|
|
||||||
|
|
||||||
async def test_bootstrap_error(hass, loop):
|
async def test_bootstrap_error(hass):
|
||||||
"""Test a valid platform setup."""
|
"""Test a valid platform setup."""
|
||||||
files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"}
|
files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"}
|
||||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||||
|
@ -146,6 +146,7 @@ automation:
|
||||||
input:
|
input:
|
||||||
trigger_event: blueprint_event
|
trigger_event: blueprint_event
|
||||||
service_to_call: test.automation
|
service_to_call: test.automation
|
||||||
|
input_datetime:
|
||||||
""",
|
""",
|
||||||
hass.config.path(
|
hass.config.path(
|
||||||
"blueprints/automation/test_event_service.yaml"
|
"blueprints/automation/test_event_service.yaml"
|
||||||
|
@ -166,3 +167,28 @@ action:
|
||||||
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||||
res = await async_check_ha_config_file(hass)
|
res = await async_check_ha_config_file(hass)
|
||||||
assert len(res.get("automation", [])) == 1
|
assert len(res.get("automation", [])) == 1
|
||||||
|
assert len(res.errors) == 0
|
||||||
|
assert "input_datetime" in res
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_platform_raise(hass):
|
||||||
|
"""Test bad config validation platform."""
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"bla.config",
|
||||||
|
Mock(async_validate_config=Mock(side_effect=Exception("Broken"))),
|
||||||
|
)
|
||||||
|
files = {
|
||||||
|
YAML_CONFIG_FILE: BASE_CONFIG
|
||||||
|
+ """
|
||||||
|
bla:
|
||||||
|
value: 1
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
with patch("os.path.isfile", return_value=True), patch_yaml_files(files):
|
||||||
|
res = await async_check_ha_config_file(hass)
|
||||||
|
assert len(res.errors) == 1
|
||||||
|
err = res.errors[0]
|
||||||
|
assert err.domain == "bla"
|
||||||
|
assert err.message == "Unexpected error calling config validator: Broken"
|
||||||
|
assert err.config == {"value": 1}
|
||||||
|
|
Loading…
Add table
Reference in a new issue