diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 5fe4e95a480..27b2738871c 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -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: diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py new file mode 100644 index 00000000000..da797a23074 --- /dev/null +++ b/homeassistant/util/yaml/__init__.py @@ -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', +] diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py new file mode 100644 index 00000000000..9bd08d99326 --- /dev/null +++ b/homeassistant/util/yaml/const.py @@ -0,0 +1,4 @@ +"""Constants.""" +SECRET_YAML = 'secrets.yaml' + +_SECRET_NAMESPACE = 'homeassistant' diff --git a/homeassistant/util/yaml/dumper.py b/homeassistant/util/yaml/dumper.py new file mode 100644 index 00000000000..d8f766c6c2b --- /dev/null +++ b/homeassistant/util/yaml/dumper.py @@ -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)) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml/loader.py similarity index 83% rename from homeassistant/util/yaml.py rename to homeassistant/util/yaml/loader.py index f6d967b6e5a..7d228490c4c 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml/loader.py @@ -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)) diff --git a/homeassistant/util/yaml/objects.py b/homeassistant/util/yaml/objects.py new file mode 100644 index 00000000000..183c6c171d6 --- /dev/null +++ b/homeassistant/util/yaml/objects.py @@ -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 diff --git a/mypy.ini b/mypy.ini index ee893476eed..2599eb079e0 100644 --- a/mypy.ini +++ b/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 diff --git a/tests/common.py b/tests/common.py index 46e30187d45..8b28d9db047 100644 --- a/tests/common.py +++ b/tests/common.py @@ -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): diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 940999c2dbe..99364d51e6c 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -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') diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 624adbb8ea3..3af9394a202 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -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 diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index c7d1be3d58c..01a64f17b86 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -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()