From 76e5b07416cf44ddd55bb87911c4eddf71b75ce1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Oct 2026 10:42:44 -1000 Subject: [PATCH] [core] Add final_validate.get_declaring_config helper --- esphome/final_validate.py | 14 ++++++++- tests/unit_tests/test_final_validate.py | 38 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_final_validate.py diff --git a/esphome/final_validate.py b/esphome/final_validate.py index cebd2f1cdad..e6e07516e67 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -3,7 +3,7 @@ import contextvars from typing import Any import esphome.config_validation as cv -from esphome.types import ID, ConfigFragmentType, ConfigPathType +from esphome.types import ID, ConfigFragmentType, ConfigPathType, ConfigType class FinalValidateConfig(ABC): @@ -39,6 +39,18 @@ full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVa ) +def get_declaring_config( + id_: ID, config: FinalValidateConfig | None = None +) -> ConfigType: + """Return the config block that declares id_, such as a hub's own config. + + Uses the full config being validated unless config is given. + Raises KeyError if the id was not declared. + """ + fconf = full_config.get() if config is None else config + return fconf.get_config_for_path(fconf.get_path_for_id(id_)[:-1]) + + def id_declaration_match_schema(schema): """A final-validation schema function that applies a schema to the outer config fragment of an ID declaration. diff --git a/tests/unit_tests/test_final_validate.py b/tests/unit_tests/test_final_validate.py new file mode 100644 index 00000000000..70b28fefeb0 --- /dev/null +++ b/tests/unit_tests/test_final_validate.py @@ -0,0 +1,38 @@ +"""Tests for esphome.final_validate helpers.""" + +import pytest + +from esphome import final_validate as fv +from esphome.config import Config +from esphome.const import CONF_ID +from esphome.core import ID + + +def _config() -> tuple[Config, ID, dict]: + hub_id = ID("my_hub", is_declaration=True) + hub = {CONF_ID: hub_id, "mode": "fast"} + config = Config() + config["other"] = {"x": 1} + config["my_comp"] = [{"y": 2}, hub] + config.declare_ids.append((hub_id, ["my_comp", 1, CONF_ID])) + return config, hub_id, hub + + +def test_returns_the_declaring_block_from_the_full_config() -> None: + config, hub_id, hub = _config() + token = fv.full_config.set(config) + try: + assert fv.get_declaring_config(hub_id) is hub + finally: + fv.full_config.reset(token) + + +def test_uses_the_given_config() -> None: + config, hub_id, hub = _config() + assert fv.get_declaring_config(ID("my_hub"), config) is hub + + +def test_unknown_id_raises_key_error() -> None: + config, _, _ = _config() + with pytest.raises(KeyError): + fv.get_declaring_config(ID("missing"), config)