mirror of
https://github.com/esphome/esphome.git
synced 2026-09-03 19:46:02 +00:00
sort so config hash does not change
This commit is contained in:
@@ -693,12 +693,13 @@ class EsphomeCore:
|
||||
"""Get the FNV-1a 32-bit hash of the config.
|
||||
|
||||
The hash is computed lazily and cached for performance.
|
||||
Uses sort_keys=True to ensure deterministic ordering.
|
||||
"""
|
||||
if self._config_hash is None:
|
||||
from esphome import yaml_util
|
||||
from esphome.helpers import fnv1a_32bit_hash
|
||||
|
||||
config_str = yaml_util.dump(self.config, show_secrets=True)
|
||||
config_str = yaml_util.dump(self.config, show_secrets=True, sort_keys=True)
|
||||
self._config_hash = fnv1a_32bit_hash(config_str)
|
||||
return self._config_hash
|
||||
|
||||
|
||||
+10
-2
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from contextlib import suppress
|
||||
import functools
|
||||
import inspect
|
||||
from io import BytesIO, TextIOBase, TextIOWrapper
|
||||
@@ -501,13 +502,17 @@ def _load_yaml_internal_with_type(
|
||||
loader.dispose()
|
||||
|
||||
|
||||
def dump(dict_, show_secrets=False):
|
||||
def dump(dict_, show_secrets=False, sort_keys=False):
|
||||
"""Dump YAML to a string and remove null."""
|
||||
if show_secrets:
|
||||
_SECRET_VALUES.clear()
|
||||
_SECRET_CACHE.clear()
|
||||
return yaml.dump(
|
||||
dict_, default_flow_style=False, allow_unicode=True, Dumper=ESPHomeDumper
|
||||
dict_,
|
||||
default_flow_style=False,
|
||||
allow_unicode=True,
|
||||
Dumper=ESPHomeDumper,
|
||||
sort_keys=sort_keys,
|
||||
)
|
||||
|
||||
|
||||
@@ -543,6 +548,9 @@ class ESPHomeDumper(yaml.SafeDumper):
|
||||
best_style = True
|
||||
if hasattr(mapping, "items"):
|
||||
mapping = list(mapping.items())
|
||||
if self.sort_keys:
|
||||
with suppress(TypeError):
|
||||
mapping = sorted(mapping)
|
||||
for item_key, item_value in mapping:
|
||||
node_key = self.represent_data(item_key)
|
||||
node_value = self.represent_data(item_value)
|
||||
|
||||
@@ -278,3 +278,31 @@ def test_secret_values_tracking(fixture_path: Path) -> None:
|
||||
assert yaml_util._SECRET_VALUES["super_secret_wifi"] == "wifi_password"
|
||||
assert "0123456789abcdef" in yaml_util._SECRET_VALUES
|
||||
assert yaml_util._SECRET_VALUES["0123456789abcdef"] == "api_key"
|
||||
|
||||
|
||||
def test_dump_sort_keys() -> None:
|
||||
"""Test that dump with sort_keys=True produces sorted output."""
|
||||
# Create a dict with unsorted keys
|
||||
data = {
|
||||
"zebra": 1,
|
||||
"alpha": 2,
|
||||
"nested": {
|
||||
"z_key": "z_value",
|
||||
"a_key": "a_value",
|
||||
},
|
||||
}
|
||||
|
||||
# Without sort_keys, keys are in insertion order
|
||||
unsorted = yaml_util.dump(data, sort_keys=False)
|
||||
lines_unsorted = unsorted.strip().split("\n")
|
||||
# First key should be "zebra" (insertion order)
|
||||
assert lines_unsorted[0].startswith("zebra:")
|
||||
|
||||
# With sort_keys, keys are alphabetically sorted
|
||||
sorted_dump = yaml_util.dump(data, sort_keys=True)
|
||||
lines_sorted = sorted_dump.strip().split("\n")
|
||||
# First key should be "alpha" (alphabetical order)
|
||||
assert lines_sorted[0].startswith("alpha:")
|
||||
# nested keys should also be sorted
|
||||
assert "a_key:" in sorted_dump
|
||||
assert sorted_dump.index("a_key:") < sorted_dump.index("z_key:")
|
||||
|
||||
Reference in New Issue
Block a user