diff --git a/docs/source/api/util.rst b/docs/source/api/util.rst index 52ae8eacdd3e03..071f4d81cdf475 100644 --- a/docs/source/api/util.rst +++ b/docs/source/api/util.rst @@ -118,14 +118,6 @@ homeassistant.util.pressure :undoc-members: :show-inheritance: -homeassistant.util.ruamel\_yaml -------------------------------- - -.. automodule:: homeassistant.util.ruamel_yaml - :members: - :undoc-members: - :show-inheritance: - homeassistant.util.ssl ---------------------- diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c59fdafdc7a196..d32947244c5c29 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -28,7 +28,6 @@ python-slugify==4.0.1 pyudev==0.22.0 pyyaml==5.4.1 requests==2.25.1 -ruamel.yaml==0.15.100 scapy==2.4.5 sqlalchemy==1.4.23 voluptuous-serialize==2.4.0 diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py deleted file mode 100644 index 8d813eaa5a4f40..00000000000000 --- a/homeassistant/util/ruamel_yaml.py +++ /dev/null @@ -1,152 +0,0 @@ -"""ruamel.yaml utility functions.""" -from __future__ import annotations - -from collections import OrderedDict -from contextlib import suppress -import logging -import os -from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result -from typing import Union - -import ruamel.yaml -from ruamel.yaml import YAML -from ruamel.yaml.compat import StringIO -from ruamel.yaml.constructor import SafeConstructor -from ruamel.yaml.error import YAMLError - -from homeassistant.exceptions import HomeAssistantError -from homeassistant.util.yaml import secret_yaml - -_LOGGER = logging.getLogger(__name__) - -JSON_TYPE = Union[list, dict, str] # pylint: disable=invalid-name - - -class ExtSafeConstructor(SafeConstructor): - """Extended SafeConstructor.""" - - name: str | None = None - - -class UnsupportedYamlError(HomeAssistantError): - """Unsupported YAML.""" - - -class WriteError(HomeAssistantError): - """Error writing the data.""" - - -def _include_yaml( - constructor: ExtSafeConstructor, node: ruamel.yaml.nodes.Node -) -> JSON_TYPE: - """Load another YAML file and embeds it using the !include tag. - - Example: - device_tracker: !include device_tracker.yaml - - """ - if constructor.name is None: - raise HomeAssistantError( - f"YAML include error: filename not set for {node.value}" - ) - fname = os.path.join(os.path.dirname(constructor.name), node.value) - return load_yaml(fname, False) - - -def _yaml_unsupported( - constructor: ExtSafeConstructor, node: ruamel.yaml.nodes.Node -) -> None: - raise UnsupportedYamlError( - f"Unsupported YAML, you can not use {node.tag} in " - f"{os.path.basename(constructor.name or '(None)')}" - ) - - -def object_to_yaml(data: JSON_TYPE) -> str: - """Create yaml string from object.""" - yaml = YAML(typ="rt") - yaml.indent(sequence=4, offset=2) - stream = StringIO() - try: - yaml.dump(data, stream) - result: str = stream.getvalue() - return result - except YAMLError as exc: - _LOGGER.error("YAML error: %s", exc) - raise HomeAssistantError(exc) from exc - - -def yaml_to_object(data: str) -> JSON_TYPE: - """Create object from yaml string.""" - yaml = YAML(typ="rt") - try: - result: list | dict | str = yaml.load(data) - return result - except YAMLError as exc: - _LOGGER.error("YAML error: %s", exc) - raise HomeAssistantError(exc) from exc - - -def load_yaml(fname: str, round_trip: bool = False) -> JSON_TYPE: - """Load a YAML file.""" - if round_trip: - yaml = YAML(typ="rt") - yaml.preserve_quotes = True # type: ignore[assignment] - else: - if ExtSafeConstructor.name is None: - ExtSafeConstructor.name = fname - yaml = YAML(typ="safe") - yaml.Constructor = ExtSafeConstructor - - 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) or OrderedDict() - except YAMLError as exc: - _LOGGER.error("YAML error in %s: %s", fname, exc) - raise HomeAssistantError(exc) from exc - except UnicodeDecodeError as exc: - _LOGGER.error("Unable to read file %s: %s", fname, exc) - raise HomeAssistantError(exc) from exc - - -def save_yaml(fname: str, data: JSON_TYPE) -> None: - """Save a YAML file.""" - yaml = YAML(typ="rt") - yaml.indent(sequence=4, offset=2) - tmp_fname = f"{fname}__TEMP__" - try: - try: - file_stat = os.stat(fname) - except OSError: - file_stat = stat_result((0o644, -1, -1, -1, -1, -1, -1, -1, -1, -1)) - with open( - os.open(tmp_fname, O_WRONLY | O_CREAT | O_TRUNC, file_stat.st_mode), - "w", - encoding="utf-8", - ) as temp_file: - yaml.dump(data, temp_file) - os.replace(tmp_fname, fname) - if hasattr(os, "chown") and file_stat.st_ctime > -1: - with suppress(OSError): - os.chown(fname, file_stat.st_uid, file_stat.st_gid) - except YAMLError as exc: - _LOGGER.error(str(exc)) - raise HomeAssistantError(exc) from exc - except OSError as exc: - _LOGGER.exception("Saving YAML file %s failed: %s", fname, exc) - raise WriteError(exc) from exc - finally: - if os.path.exists(tmp_fname): - try: - os.remove(tmp_fname) - except OSError as exc: - # If we are cleaning up then something else went wrong, so - # we should suppress likely follow-on errors in the cleanup - _LOGGER.error("YAML replacement cleanup failed: %s", exc) - - -ExtSafeConstructor.add_constructor("!secret", secret_yaml) -ExtSafeConstructor.add_constructor("!include", _include_yaml) -ExtSafeConstructor.add_constructor(None, _yaml_unsupported) diff --git a/requirements.txt b/requirements.txt index 70eeccdae1bd18..e6b4b5845c49b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,6 @@ pip>=8.0.3,<20.3 python-slugify==4.0.1 pyyaml==5.4.1 requests==2.25.1 -ruamel.yaml==0.15.100 voluptuous==0.12.1 voluptuous-serialize==2.4.0 yarl==1.6.3 diff --git a/setup.py b/setup.py index 302eadbfcf6cbe..e9ab189406b92b 100755 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ "python-slugify==4.0.1", "pyyaml==5.4.1", "requests==2.25.1", - "ruamel.yaml==0.15.100", "voluptuous==0.12.1", "voluptuous-serialize==2.4.0", "yarl==1.6.3", diff --git a/tests/util/test_ruamel_yaml.py b/tests/util/test_ruamel_yaml.py deleted file mode 100644 index b4e78a883afac7..00000000000000 --- a/tests/util/test_ruamel_yaml.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Test Home Assistant ruamel.yaml loader.""" -import os -from tempfile import mkdtemp - -import pytest -from ruamel.yaml import YAML - -from homeassistant.exceptions import HomeAssistantError -import homeassistant.util.ruamel_yaml as util_yaml - -TEST_YAML_A = """\ -title: My Awesome Home -# Include external resources -resources: - - url: /local/my-custom-card.js - type: js - - url: /local/my-webfont.css - type: css - -# Exclude entities from "Unused entities" view -excluded_entities: - - weblink.router -views: - # View tab title. - - title: Example - # Optional unique id for direct access /lovelace/${id} - id: example - # Optional background (overwrites the global background). - background: radial-gradient(crimson, skyblue) - # Each view can have a different theme applied. - theme: dark-mode - # The cards to show on this view. - cards: - # The filter card will filter entities for their state - - type: entity-filter - entities: - - device_tracker.paulus - - device_tracker.anne_there - state_filter: - - 'home' - card: - type: glance - title: People that are home - - # The picture entity card will represent an entity with a picture - - type: picture-entity - image: https://www.home-assistant.io/images/default-social.png - entity: light.bed_light - - # Specify a tab icon if you want the view tab to be an icon. - - icon: mdi:home-assistant - # Title of the view. Will be used as the tooltip for tab icon - title: Second view - cards: - - id: test - type: entities - title: Test card - # Entities card will take a list of entities and show their state. - - type: entities - # Title of the entities card - title: Example - # The entities here will be shown in the same order as specified. - # Each entry is an entity ID or a map with extra options. - entities: - - light.kitchen - - switch.ac - - entity: light.living_room - # Override the name to use - name: LR Lights - - # The markdown card will render markdown text. - - type: markdown - title: Lovelace - content: > - Welcome to your **Lovelace UI**. -""" - -TEST_YAML_B = """\ -title: Home -views: - - title: Dashboard - id: dashboard - icon: mdi:home - cards: - - id: testid - type: vertical-stack - cards: - - type: picture-entity - entity: group.sample - name: Sample - image: /local/images/sample.jpg - tap_action: toggle -""" - -# Test data that can not be loaded as YAML -TEST_BAD_YAML = """\ -title: Home -views: - - title: Dashboard - icon: mdi:home - cards: - - id: testid - type: vertical-stack -""" - -# Test unsupported YAML -TEST_UNSUP_YAML = """\ -title: Home -views: - - title: Dashboard - icon: mdi:home - cards: !include cards.yaml -""" - -TMP_DIR = None - - -def setup(): - """Set up for tests.""" - global TMP_DIR - TMP_DIR = mkdtemp() - - -def teardown(): - """Clean up after tests.""" - for fname in os.listdir(TMP_DIR): - os.remove(os.path.join(TMP_DIR, fname)) - os.rmdir(TMP_DIR) - - -def _path_for(leaf_name): - return os.path.join(TMP_DIR, f"{leaf_name}.yaml") - - -def test_save_and_load(): - """Test saving and loading back.""" - yaml = YAML(typ="rt") - fname = _path_for("test1") - open(fname, "w+").close() - util_yaml.save_yaml(fname, yaml.load(TEST_YAML_A)) - data = util_yaml.load_yaml(fname, True) - assert data == yaml.load(TEST_YAML_A) - - -def test_overwrite_and_reload(): - """Test that we can overwrite an existing file and read back.""" - yaml = YAML(typ="rt") - fname = _path_for("test2") - open(fname, "w+").close() - util_yaml.save_yaml(fname, yaml.load(TEST_YAML_A)) - util_yaml.save_yaml(fname, yaml.load(TEST_YAML_B)) - data = util_yaml.load_yaml(fname, True) - assert data == yaml.load(TEST_YAML_B) - - -def test_load_bad_data(): - """Test error from trying to load unserialisable data.""" - fname = _path_for("test3") - with open(fname, "w") as fh: - fh.write(TEST_BAD_YAML) - with pytest.raises(HomeAssistantError): - util_yaml.load_yaml(fname, True)