From 2e3ff4e215a2cc831ba570d4c9448be39adbcd99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:11:51 +0000 Subject: [PATCH 1/2] Bump cryptography from 46.0.6 to 46.0.7 (#15550) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5c798819a86..31f33ce7ee5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cryptography==46.0.6 +cryptography==46.0.7 voluptuous==0.16.0 PyYAML==6.0.3 paho-mqtt==1.6.1 From 4db82877af35f9f06d1f7c658e01accd6642b0d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Apr 2026 16:27:11 -1000 Subject: [PATCH 2/2] [yaml] Add IncludeFile representer to ESPHomeDumper (#15549) --- esphome/yaml_util.py | 9 ++++++++ tests/unit_tests/test_yaml_util.py | 36 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index c621428196f..520379e51d2 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -754,6 +754,14 @@ class ESPHomeDumper(yaml.SafeDumper): def represent_remove(self, value): return self.represent_scalar(tag="!remove", value=value.value) + def represent_include_file(self, value): + if value.vars: + mapping = {"file": value.file.as_posix(), "vars": value.vars} + return self.represent_mapping( + tag="!include", mapping=mapping, flow_style=False + ) + return self.represent_scalar(tag="!include", value=value.file.as_posix()) + def represent_id(self, value): if is_secret(value.id): return self.represent_secret(value.id) @@ -785,3 +793,4 @@ ESPHomeDumper.add_multi_representer(Remove, ESPHomeDumper.represent_remove) ESPHomeDumper.add_multi_representer(core.ID, ESPHomeDumper.represent_id) ESPHomeDumper.add_multi_representer(uuid.UUID, ESPHomeDumper.represent_stringify) ESPHomeDumper.add_multi_representer(Path, ESPHomeDumper.represent_stringify) +ESPHomeDumper.add_multi_representer(IncludeFile, ESPHomeDumper.represent_include_file) diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 0bd7c9453b5..2c01019abdf 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -508,6 +508,42 @@ def test_represent_remove() -> None: assert yaml_util.dump({"key": Remove("my_id")}) == "key: !remove 'my_id'\n" +def test_represent_include_file() -> None: + """Test that IncludeFile objects are dumped as !include scalars.""" + include = yaml_util.IncludeFile( + Path("/fake/main.yaml"), "path/to/file.yaml", None, lambda _: {} + ) + assert yaml_util.dump({"key": include}) == "key: !include 'path/to/file.yaml'\n" + + +def test_represent_include_file_with_vars() -> None: + """Test that IncludeFile with vars is dumped as !include mapping form.""" + include = yaml_util.IncludeFile( + Path("/fake/main.yaml"), + "path/to/file.yaml", + {"key": "value"}, + lambda _: {}, + ) + result = yaml_util.dump({"key": include}) + assert "!include" in result + assert "file: path/to/file.yaml" in result + assert "key: value" in result + + +def test_represent_include_file_with_data_base_mixin() -> None: + """Test that IncludeFile wrapped with ESPHomeDataBase mixin is also dumped correctly. + + The YAML loader wraps IncludeFile via add_class_to_obj, creating a dynamic + subclass. add_multi_representer must match this subclass through the MRO. + """ + include = yaml_util.IncludeFile( + Path("/fake/main.yaml"), "common/spi.yaml", None, lambda _: {} + ) + wrapped = yaml_util.make_data_base(include) + assert isinstance(wrapped, yaml_util.ESPHomeDataBase) + assert yaml_util.dump({"pkg": wrapped}) == "pkg: !include 'common/spi.yaml'\n" + + # ── IncludeFile unit tests ──────────────────────────────────────────────────