From 25ab6f0297fcf3a21e07644854db5083ba5a0eed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Jan 2024 19:11:03 -1000 Subject: [PATCH] Ensure filename is shown when YAML raises an error (#6139) * Ensure filename is shown when YAML raises an error fixes #5423 fixes #5377 * Ensure filename is shown when YAML raises an error fixes #5423 fixes #5377 * Ensure filename is shown when YAML raises an error fixes #5423 fixes #5377 * Ensure filename is shown when YAML raises an error fixes #5423 fixes #5377 * Ensure filename is shown when YAML raises an error fixes #5423 fixes #5377 --- esphome/yaml_util.py | 24 ++++++++++++------- .../fixtures/yaml_util/missing_comp.yaml | 12 ++++++++++ tests/unit_tests/test_yaml_util.py | 20 ++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 tests/unit_tests/fixtures/yaml_util/missing_comp.yaml diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index f5e36b79e7..60705082b6 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -7,6 +7,7 @@ import logging import math import os import uuid +from io import TextIOWrapper from typing import Any import yaml @@ -19,7 +20,7 @@ except ImportError: FastestAvailableSafeLoader = PurePythonLoader from esphome import core -from esphome.config_helpers import Extend, Remove, read_config_file +from esphome.config_helpers import Extend, Remove from esphome.core import ( CORE, DocumentRange, @@ -418,19 +419,26 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any: def _load_yaml_internal(fname: str) -> Any: """Load a YAML file.""" - content = read_config_file(fname) try: - return _load_yaml_internal_with_type(ESPHomeLoader, fname, content) - except EsphomeError: - # Loading failed, so we now load with the Python loader which has more - # readable exceptions - return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content) + with open(fname, encoding="utf-8") as f_handle: + try: + return _load_yaml_internal_with_type(ESPHomeLoader, fname, f_handle) + except EsphomeError: + # Loading failed, so we now load with the Python loader which has more + # readable exceptions + # Rewind the stream so we can try again + f_handle.seek(0, 0) + return _load_yaml_internal_with_type( + ESPHomePurePythonLoader, fname, f_handle + ) + except (UnicodeDecodeError, OSError) as err: + raise EsphomeError(f"Error reading file {fname}: {err}") from err def _load_yaml_internal_with_type( loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader], fname: str, - content: str, + content: TextIOWrapper, ) -> Any: """Load a YAML file.""" loader = loader_type(content) diff --git a/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml new file mode 100644 index 0000000000..d065901ed9 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + board: esp32dev + +wifi: + ap: ~ + +image: + - id: its_a_bug + file: "mdi:bug" diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 78b6a2ad84..9178726247 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -22,3 +22,23 @@ def test_loading_a_broken_yaml_file(fixture_path): yaml_util.load_yaml(yaml_file) except EsphomeError as err: assert "broken_included.yaml" in str(err) + + +def test_loading_a_yaml_file_with_a_missing_component(fixture_path): + """Ensure we show the filename for a yaml file with a missing component.""" + yaml_file = fixture_path / "yaml_util" / "missing_comp.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing_comp.yaml" in str(err) + + +def test_loading_a_missing_file(fixture_path): + """We throw EsphomeError when loading a missing file.""" + yaml_file = fixture_path / "yaml_util" / "missing.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing.yaml" in str(err)