Split up yaml loaders into multiple files (#23774)
* Start moving parts of yaml utils to own module Move parts of yaml loader out of the single large file and start to create the structure of the yaml loaders in Ansible [0]. [0]: https://github.com/ansible/ansible/tree/devel/lib/ansible/parsing/yaml * Finish yaml migration, update tests and mocks * Move code around to finish the migration * Update the mocks so that `open` is patched in `homeassistant.util.yaml.loader` instead of `homeassistant.util.yaml`. * Updated mypy ignores * Updated external API of `homeasistant.util.yaml`, see below: Checked what part of the api of `homeassistant.util.yaml` was actually called from outside the tests and added an `__ALL__` that contains only these elements. Updated the tests so that references to internal parts of the API (e.g. the yaml module imported into `homeassistant.util.yaml.loader`) are referenced directly from `homeassistant.util.yaml.loader`. In `tests/test_yaml.py` the import `yaml` refers to `homeassistant.util.yaml` and `yaml_loader` refers to `~.loader`. Future work that remains for the next iteration is to create a custom SafeConstructor and refers to that instead of monkey patching `yaml` with custom loaders. * Update mocks in yaml dumper, check_configpull/23784/head
parent
118d3bc11c
commit
4004867eda
|
@ -17,7 +17,8 @@ from homeassistant.config import (
|
|||
CONF_PACKAGES, merge_packages_config, _format_config_error,
|
||||
find_config_file, load_yaml_config_file,
|
||||
extract_domain_configs, config_per_platform)
|
||||
from homeassistant.util import yaml
|
||||
|
||||
import homeassistant.util.yaml.loader as yaml_loader
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
REQUIREMENTS = ('colorlog==4.0.2',)
|
||||
|
@ -25,12 +26,14 @@ REQUIREMENTS = ('colorlog==4.0.2',)
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
# pylint: disable=protected-access
|
||||
MOCKS = {
|
||||
'load': ("homeassistant.util.yaml.load_yaml", yaml.load_yaml),
|
||||
'load*': ("homeassistant.config.load_yaml", yaml.load_yaml),
|
||||
'secrets': ("homeassistant.util.yaml.secret_yaml", yaml.secret_yaml),
|
||||
'load': ("homeassistant.util.yaml.loader.load_yaml",
|
||||
yaml_loader.load_yaml),
|
||||
'load*': ("homeassistant.config.load_yaml", yaml_loader.load_yaml),
|
||||
'secrets': ("homeassistant.util.yaml.loader.secret_yaml",
|
||||
yaml_loader.secret_yaml),
|
||||
}
|
||||
SILENCE = (
|
||||
'homeassistant.scripts.check_config.yaml.clear_secret_cache',
|
||||
'homeassistant.scripts.check_config.yaml_loader.clear_secret_cache',
|
||||
)
|
||||
|
||||
PATCHES = {}
|
||||
|
@ -195,7 +198,8 @@ def check(config_dir, secrets=False):
|
|||
|
||||
if secrets:
|
||||
# Ensure !secrets point to the patched function
|
||||
yaml.yaml.SafeLoader.add_constructor('!secret', yaml.secret_yaml)
|
||||
yaml_loader.yaml.SafeLoader.add_constructor('!secret',
|
||||
yaml_loader.secret_yaml)
|
||||
|
||||
try:
|
||||
hass = core.HomeAssistant()
|
||||
|
@ -203,7 +207,7 @@ def check(config_dir, secrets=False):
|
|||
|
||||
res['components'] = hass.loop.run_until_complete(
|
||||
check_ha_config_file(hass))
|
||||
res['secret_cache'] = OrderedDict(yaml.__SECRET_CACHE)
|
||||
res['secret_cache'] = OrderedDict(yaml_loader.__SECRET_CACHE)
|
||||
|
||||
for err in res['components'].errors:
|
||||
domain = err.domain or ERROR_STR
|
||||
|
@ -221,7 +225,8 @@ def check(config_dir, secrets=False):
|
|||
pat.stop()
|
||||
if secrets:
|
||||
# Ensure !secrets point to the original function
|
||||
yaml.yaml.SafeLoader.add_constructor('!secret', yaml.secret_yaml)
|
||||
yaml_loader.yaml.SafeLoader.add_constructor(
|
||||
'!secret', yaml_loader.secret_yaml)
|
||||
bootstrap.clear_secret_cache()
|
||||
|
||||
return res
|
||||
|
@ -239,7 +244,7 @@ def line_info(obj, **kwargs):
|
|||
def dump_dict(layer, indent_count=3, listi=False, **kwargs):
|
||||
"""Display a dict.
|
||||
|
||||
A friendly version of print yaml.yaml.dump(config).
|
||||
A friendly version of print yaml_loader.yaml.dump(config).
|
||||
"""
|
||||
def sort_dict_key(val):
|
||||
"""Return the dict key for sorting."""
|
||||
|
@ -311,7 +316,7 @@ async def check_ha_config_file(hass):
|
|||
return result.add_error(
|
||||
"Error loading {}: {}".format(config_path, err))
|
||||
finally:
|
||||
yaml.clear_secret_cache()
|
||||
yaml_loader.clear_secret_cache()
|
||||
|
||||
# Extract and validate core [homeassistant] config
|
||||
try:
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
"""YAML utility functions."""
|
||||
from .const import (
|
||||
SECRET_YAML, _SECRET_NAMESPACE
|
||||
)
|
||||
from .dumper import dump, save_yaml
|
||||
from .loader import (
|
||||
clear_secret_cache, load_yaml, secret_yaml
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'SECRET_YAML', '_SECRET_NAMESPACE',
|
||||
'dump', 'save_yaml',
|
||||
'clear_secret_cache', 'load_yaml', 'secret_yaml',
|
||||
]
|
|
@ -0,0 +1,4 @@
|
|||
"""Constants."""
|
||||
SECRET_YAML = 'secrets.yaml'
|
||||
|
||||
_SECRET_NAMESPACE = 'homeassistant'
|
|
@ -0,0 +1,60 @@
|
|||
"""Custom dumper and representers."""
|
||||
from collections import OrderedDict
|
||||
import yaml
|
||||
|
||||
from .objects import NodeListClass
|
||||
|
||||
|
||||
def dump(_dict: dict) -> str:
|
||||
"""Dump YAML to a string and remove null."""
|
||||
return yaml.safe_dump(
|
||||
_dict, default_flow_style=False, allow_unicode=True) \
|
||||
.replace(': null\n', ':\n')
|
||||
|
||||
|
||||
def save_yaml(path: str, data: dict) -> None:
|
||||
"""Save YAML to a file."""
|
||||
# Dump before writing to not truncate the file if dumping fails
|
||||
str_data = dump(data)
|
||||
with open(path, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(str_data)
|
||||
|
||||
|
||||
# From: https://gist.github.com/miracle2k/3184458
|
||||
# pylint: disable=redefined-outer-name
|
||||
def represent_odict(dump, tag, mapping, # type: ignore
|
||||
flow_style=None) -> yaml.MappingNode:
|
||||
"""Like BaseRepresenter.represent_mapping but does not issue the sort()."""
|
||||
value = [] # type: list
|
||||
node = yaml.MappingNode(tag, value, flow_style=flow_style)
|
||||
if dump.alias_key is not None:
|
||||
dump.represented_objects[dump.alias_key] = node
|
||||
best_style = True
|
||||
if hasattr(mapping, 'items'):
|
||||
mapping = mapping.items()
|
||||
for item_key, item_value in mapping:
|
||||
node_key = dump.represent_data(item_key)
|
||||
node_value = dump.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
|
||||
best_style = False
|
||||
if not (isinstance(node_value, yaml.ScalarNode) and
|
||||
not node_value.style):
|
||||
best_style = False
|
||||
value.append((node_key, node_value))
|
||||
if flow_style is None:
|
||||
if dump.default_flow_style is not None:
|
||||
node.flow_style = dump.default_flow_style
|
||||
else:
|
||||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
|
||||
yaml.SafeDumper.add_representer(
|
||||
OrderedDict,
|
||||
lambda dumper, value:
|
||||
represent_odict(dumper, 'tag:yaml.org,2002:map', value))
|
||||
|
||||
yaml.SafeDumper.add_representer(
|
||||
NodeListClass,
|
||||
lambda dumper, value:
|
||||
dumper.represent_sequence('tag:yaml.org,2002:seq', value))
|
|
@ -1,4 +1,4 @@
|
|||
"""YAML utility functions."""
|
||||
"""Custom loader."""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
@ -7,6 +7,7 @@ from collections import OrderedDict
|
|||
from typing import Union, List, Dict, Iterator, overload, TypeVar
|
||||
|
||||
import yaml
|
||||
|
||||
try:
|
||||
import keyring
|
||||
except ImportError:
|
||||
|
@ -19,25 +20,23 @@ except ImportError:
|
|||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import _SECRET_NAMESPACE, SECRET_YAML
|
||||
from .objects import NodeListClass, NodeStrClass
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_SECRET_NAMESPACE = 'homeassistant'
|
||||
SECRET_YAML = 'secrets.yaml'
|
||||
__SECRET_CACHE = {} # type: Dict[str, JSON_TYPE]
|
||||
|
||||
JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name
|
||||
DICT_T = TypeVar('DICT_T', bound=Dict) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
class NodeListClass(list):
|
||||
"""Wrapper class to be able to add attributes on a list."""
|
||||
def clear_secret_cache() -> None:
|
||||
"""Clear the secret cache.
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NodeStrClass(str):
|
||||
"""Wrapper class to be able to add attributes on a string."""
|
||||
|
||||
pass
|
||||
Async friendly.
|
||||
"""
|
||||
__SECRET_CACHE.clear()
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
|
@ -54,6 +53,21 @@ class SafeLineLoader(yaml.SafeLoader):
|
|||
return node
|
||||
|
||||
|
||||
def load_yaml(fname: str) -> JSON_TYPE:
|
||||
"""Load a YAML file."""
|
||||
try:
|
||||
with open(fname, encoding='utf-8') as conf_file:
|
||||
# If configuration file is empty YAML returns None
|
||||
# We convert that to an empty dict
|
||||
return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict()
|
||||
except yaml.YAMLError as exc:
|
||||
_LOGGER.error(str(exc))
|
||||
raise HomeAssistantError(exc)
|
||||
except UnicodeDecodeError as exc:
|
||||
_LOGGER.error("Unable to read file %s: %s", fname, exc)
|
||||
raise HomeAssistantError(exc)
|
||||
|
||||
|
||||
# pylint: disable=pointless-statement
|
||||
@overload
|
||||
def _add_reference(obj: Union[list, NodeListClass],
|
||||
|
@ -86,44 +100,6 @@ def _add_reference(obj, loader: SafeLineLoader, # type: ignore # noqa: F811
|
|||
return obj
|
||||
|
||||
|
||||
def load_yaml(fname: str) -> JSON_TYPE:
|
||||
"""Load a YAML file."""
|
||||
try:
|
||||
with open(fname, encoding='utf-8') as conf_file:
|
||||
# If configuration file is empty YAML returns None
|
||||
# We convert that to an empty dict
|
||||
return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict()
|
||||
except yaml.YAMLError as exc:
|
||||
_LOGGER.error(str(exc))
|
||||
raise HomeAssistantError(exc)
|
||||
except UnicodeDecodeError as exc:
|
||||
_LOGGER.error("Unable to read file %s: %s", fname, exc)
|
||||
raise HomeAssistantError(exc)
|
||||
|
||||
|
||||
def dump(_dict: dict) -> str:
|
||||
"""Dump YAML to a string and remove null."""
|
||||
return yaml.safe_dump(
|
||||
_dict, default_flow_style=False, allow_unicode=True) \
|
||||
.replace(': null\n', ':\n')
|
||||
|
||||
|
||||
def save_yaml(path: str, data: dict) -> None:
|
||||
"""Save YAML to a file."""
|
||||
# Dump before writing to not truncate the file if dumping fails
|
||||
str_data = dump(data)
|
||||
with open(path, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(str_data)
|
||||
|
||||
|
||||
def clear_secret_cache() -> None:
|
||||
"""Clear the secret cache.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
__SECRET_CACHE.clear()
|
||||
|
||||
|
||||
def _include_yaml(loader: SafeLineLoader,
|
||||
node: yaml.nodes.Node) -> JSON_TYPE:
|
||||
"""Load another YAML file and embeds it using the !include tag.
|
||||
|
@ -331,43 +307,3 @@ yaml.SafeLoader.add_constructor('!include_dir_merge_list',
|
|||
yaml.SafeLoader.add_constructor('!include_dir_named', _include_dir_named_yaml)
|
||||
yaml.SafeLoader.add_constructor('!include_dir_merge_named',
|
||||
_include_dir_merge_named_yaml)
|
||||
|
||||
|
||||
# From: https://gist.github.com/miracle2k/3184458
|
||||
# pylint: disable=redefined-outer-name
|
||||
def represent_odict(dump, tag, mapping, # type: ignore
|
||||
flow_style=None) -> yaml.MappingNode:
|
||||
"""Like BaseRepresenter.represent_mapping but does not issue the sort()."""
|
||||
value = [] # type: list
|
||||
node = yaml.MappingNode(tag, value, flow_style=flow_style)
|
||||
if dump.alias_key is not None:
|
||||
dump.represented_objects[dump.alias_key] = node
|
||||
best_style = True
|
||||
if hasattr(mapping, 'items'):
|
||||
mapping = mapping.items()
|
||||
for item_key, item_value in mapping:
|
||||
node_key = dump.represent_data(item_key)
|
||||
node_value = dump.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
|
||||
best_style = False
|
||||
if not (isinstance(node_value, yaml.ScalarNode) and
|
||||
not node_value.style):
|
||||
best_style = False
|
||||
value.append((node_key, node_value))
|
||||
if flow_style is None:
|
||||
if dump.default_flow_style is not None:
|
||||
node.flow_style = dump.default_flow_style
|
||||
else:
|
||||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
|
||||
yaml.SafeDumper.add_representer(
|
||||
OrderedDict,
|
||||
lambda dumper, value:
|
||||
represent_odict(dumper, 'tag:yaml.org,2002:map', value))
|
||||
|
||||
yaml.SafeDumper.add_representer(
|
||||
NodeListClass,
|
||||
lambda dumper, value:
|
||||
dumper.represent_sequence('tag:yaml.org,2002:seq', value))
|
|
@ -0,0 +1,13 @@
|
|||
"""Custom yaml object types."""
|
||||
|
||||
|
||||
class NodeListClass(list):
|
||||
"""Wrapper class to be able to add attributes on a list."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NodeStrClass(str):
|
||||
"""Wrapper class to be able to add attributes on a string."""
|
||||
|
||||
pass
|
6
mypy.ini
6
mypy.ini
|
@ -17,7 +17,11 @@ disallow_untyped_defs = true
|
|||
[mypy-homeassistant.config_entries]
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[mypy-homeassistant.util.yaml]
|
||||
[mypy-homeassistant.util.yaml.dumper]
|
||||
warn_return_any = false
|
||||
disallow_untyped_calls = false
|
||||
|
||||
[mypy-homeassistant.util.yaml.loader]
|
||||
warn_return_any = false
|
||||
disallow_untyped_calls = false
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ from io import StringIO
|
|||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.util.yaml as yaml
|
||||
import homeassistant.util.yaml.loader as yaml_loader
|
||||
import homeassistant.util.yaml.dumper as yaml_dumper
|
||||
|
||||
from homeassistant import auth, config_entries, core as ha, loader
|
||||
from homeassistant.auth import (
|
||||
|
@ -680,7 +681,8 @@ def patch_yaml_files(files_dict, endswith=True):
|
|||
# Not found
|
||||
raise FileNotFoundError("File not found: {}".format(fname))
|
||||
|
||||
return patch.object(yaml, 'open', mock_open_f, create=True)
|
||||
return patch.object(yaml_loader, 'open', mock_open_f, create=True)
|
||||
return patch.object(yaml_dumper, 'open', mock_open_f, create=True)
|
||||
|
||||
|
||||
def mock_coro(return_value=None, exception=None):
|
||||
|
|
|
@ -4,7 +4,7 @@ import unittest
|
|||
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.components import light, scene
|
||||
from homeassistant.util import yaml
|
||||
from homeassistant.util.yaml import loader as yaml_loader
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
from tests.components.light import common as common_light
|
||||
|
@ -90,7 +90,7 @@ class TestScene(unittest.TestCase):
|
|||
self.light_1.entity_id, self.light_2.entity_id)
|
||||
|
||||
with io.StringIO(config) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.load(file)
|
||||
|
||||
assert setup_component(self.hass, scene.DOMAIN, doc)
|
||||
common.activate(self.hass, 'scene.test')
|
||||
|
|
|
@ -11,7 +11,7 @@ from homeassistant.helpers import entity_registry
|
|||
from tests.common import mock_registry, flush_store
|
||||
|
||||
|
||||
YAML__OPEN_PATH = 'homeassistant.util.yaml.open'
|
||||
YAML__OPEN_PATH = 'homeassistant.util.yaml.loader.open'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -8,7 +8,8 @@ from unittest.mock import patch
|
|||
import pytest
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import yaml
|
||||
from homeassistant.util.yaml import loader as yaml_loader
|
||||
import homeassistant.util.yaml as yaml
|
||||
from homeassistant.config import YAML_CONFIG_FILE, load_yaml_config_file
|
||||
from tests.common import get_test_config_dir, patch_yaml_files
|
||||
|
||||
|
@ -16,7 +17,7 @@ from tests.common import get_test_config_dir, patch_yaml_files
|
|||
@pytest.fixture(autouse=True)
|
||||
def mock_credstash():
|
||||
"""Mock credstash so it doesn't connect to the internet."""
|
||||
with patch.object(yaml, 'credstash') as mock_credstash:
|
||||
with patch.object(yaml_loader, 'credstash') as mock_credstash:
|
||||
mock_credstash.getSecret.return_value = None
|
||||
yield mock_credstash
|
||||
|
||||
|
@ -25,7 +26,7 @@ def test_simple_list():
|
|||
"""Test simple list."""
|
||||
conf = "config:\n - simple\n - list"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc['config'] == ["simple", "list"]
|
||||
|
||||
|
||||
|
@ -33,7 +34,7 @@ def test_simple_dict():
|
|||
"""Test simple dict."""
|
||||
conf = "key: value"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc['key'] == 'value'
|
||||
|
||||
|
||||
|
@ -58,7 +59,7 @@ def test_environment_variable():
|
|||
os.environ["PASSWORD"] = "secret_password"
|
||||
conf = "password: !env_var PASSWORD"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc['password'] == "secret_password"
|
||||
del os.environ["PASSWORD"]
|
||||
|
||||
|
@ -67,7 +68,7 @@ def test_environment_variable_default():
|
|||
"""Test config file with default value for environment variable."""
|
||||
conf = "password: !env_var PASSWORD secret_password"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc['password'] == "secret_password"
|
||||
|
||||
|
||||
|
@ -76,7 +77,7 @@ def test_invalid_environment_variable():
|
|||
conf = "password: !env_var PASSWORD"
|
||||
with pytest.raises(HomeAssistantError):
|
||||
with io.StringIO(conf) as file:
|
||||
yaml.yaml.safe_load(file)
|
||||
yaml_loader.yaml.safe_load(file)
|
||||
|
||||
|
||||
def test_include_yaml():
|
||||
|
@ -84,17 +85,17 @@ def test_include_yaml():
|
|||
with patch_yaml_files({'test.yaml': 'value'}):
|
||||
conf = 'key: !include test.yaml'
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc["key"] == "value"
|
||||
|
||||
with patch_yaml_files({'test.yaml': None}):
|
||||
conf = 'key: !include test.yaml'
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc["key"] == {}
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_list(mock_walk):
|
||||
"""Test include dir list yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -107,11 +108,11 @@ def test_include_dir_list(mock_walk):
|
|||
}):
|
||||
conf = "key: !include_dir_list /tmp"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc["key"] == sorted(["one", "two"])
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_list_recursive(mock_walk):
|
||||
"""Test include dir recursive list yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -129,13 +130,13 @@ def test_include_dir_list_recursive(mock_walk):
|
|||
with io.StringIO(conf) as file:
|
||||
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||
"Expecting .ignore in here"
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||
assert sorted(doc["key"]) == sorted(["zero", "one", "two"])
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_named(mock_walk):
|
||||
"""Test include dir named yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -149,11 +150,11 @@ def test_include_dir_named(mock_walk):
|
|||
conf = "key: !include_dir_named /tmp"
|
||||
correct = {'first': 'one', 'second': 'two'}
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc["key"] == correct
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_named_recursive(mock_walk):
|
||||
"""Test include dir named yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -172,13 +173,13 @@ def test_include_dir_named_recursive(mock_walk):
|
|||
with io.StringIO(conf) as file:
|
||||
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||
"Expecting .ignore in here"
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||
assert doc["key"] == correct
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_merge_list(mock_walk):
|
||||
"""Test include dir merge list yaml."""
|
||||
mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]]
|
||||
|
@ -189,11 +190,11 @@ def test_include_dir_merge_list(mock_walk):
|
|||
}):
|
||||
conf = "key: !include_dir_merge_list /tmp"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert sorted(doc["key"]) == sorted(["one", "two", "three"])
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_merge_list_recursive(mock_walk):
|
||||
"""Test include dir merge list yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -211,14 +212,14 @@ def test_include_dir_merge_list_recursive(mock_walk):
|
|||
with io.StringIO(conf) as file:
|
||||
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||
"Expecting .ignore in here"
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||
assert sorted(doc["key"]) == sorted(["one", "two",
|
||||
"three", "four"])
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_merge_named(mock_walk):
|
||||
"""Test include dir merge named yaml."""
|
||||
mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]]
|
||||
|
@ -231,7 +232,7 @@ def test_include_dir_merge_named(mock_walk):
|
|||
with patch_yaml_files(files):
|
||||
conf = "key: !include_dir_merge_named /tmp"
|
||||
with io.StringIO(conf) as file:
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert doc["key"] == {
|
||||
"key1": "one",
|
||||
"key2": "two",
|
||||
|
@ -239,7 +240,7 @@ def test_include_dir_merge_named(mock_walk):
|
|||
}
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.os.walk')
|
||||
@patch('homeassistant.util.yaml.loader.os.walk')
|
||||
def test_include_dir_merge_named_recursive(mock_walk):
|
||||
"""Test include dir merge named yaml."""
|
||||
mock_walk.return_value = [
|
||||
|
@ -257,7 +258,7 @@ def test_include_dir_merge_named_recursive(mock_walk):
|
|||
with io.StringIO(conf) as file:
|
||||
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||
"Expecting .ignore in here"
|
||||
doc = yaml.yaml.safe_load(file)
|
||||
doc = yaml_loader.yaml.safe_load(file)
|
||||
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||
assert doc["key"] == {
|
||||
|
@ -268,12 +269,12 @@ def test_include_dir_merge_named_recursive(mock_walk):
|
|||
}
|
||||
|
||||
|
||||
@patch('homeassistant.util.yaml.open', create=True)
|
||||
@patch('homeassistant.util.yaml.loader.open', create=True)
|
||||
def test_load_yaml_encoding_error(mock_open):
|
||||
"""Test raising a UnicodeDecodeError."""
|
||||
mock_open.side_effect = UnicodeDecodeError('', b'', 1, 0, '')
|
||||
with pytest.raises(HomeAssistantError):
|
||||
yaml.load_yaml('test')
|
||||
yaml_loader.load_yaml('test')
|
||||
|
||||
|
||||
def test_dump():
|
||||
|
@ -392,16 +393,16 @@ class TestSecrets(unittest.TestCase):
|
|||
|
||||
def test_secrets_keyring(self):
|
||||
"""Test keyring fallback & get_password."""
|
||||
yaml.keyring = None # Ensure its not there
|
||||
yaml_loader.keyring = None # Ensure its not there
|
||||
yaml_str = 'http:\n api_password: !secret http_pw_keyring'
|
||||
with pytest.raises(yaml.HomeAssistantError):
|
||||
with pytest.raises(HomeAssistantError):
|
||||
load_yaml(self._yaml_path, yaml_str)
|
||||
|
||||
yaml.keyring = FakeKeyring({'http_pw_keyring': 'yeah'})
|
||||
yaml_loader.keyring = FakeKeyring({'http_pw_keyring': 'yeah'})
|
||||
_yaml = load_yaml(self._yaml_path, yaml_str)
|
||||
assert {'http': {'api_password': 'yeah'}} == _yaml
|
||||
|
||||
@patch.object(yaml, 'credstash')
|
||||
@patch.object(yaml_loader, 'credstash')
|
||||
def test_secrets_credstash(self, mock_credstash):
|
||||
"""Test credstash fallback & get_password."""
|
||||
mock_credstash.getSecret.return_value = 'yeah'
|
||||
|
@ -413,10 +414,10 @@ class TestSecrets(unittest.TestCase):
|
|||
|
||||
def test_secrets_logger_removed(self):
|
||||
"""Ensure logger: debug was removed."""
|
||||
with pytest.raises(yaml.HomeAssistantError):
|
||||
with pytest.raises(HomeAssistantError):
|
||||
load_yaml(self._yaml_path, 'api_password: !secret logger')
|
||||
|
||||
@patch('homeassistant.util.yaml._LOGGER.error')
|
||||
@patch('homeassistant.util.yaml.loader._LOGGER.error')
|
||||
def test_bad_logger_value(self, mock_error):
|
||||
"""Ensure logger: debug was removed."""
|
||||
yaml.clear_secret_cache()
|
||||
|
|
Loading…
Reference in New Issue