mirror of
https://github.com/esphome/esphome.git
synced 2026-10-05 02:21:30 +00:00
[core] Derive CORE state from cached config dict, drop apply_to_core
`StorageJSON.apply_to_core` was the inverse of `from_esphome_core`: two enumerations of the same fields that had to stay in sync. When a new CORE attribute joined the validation pipeline, both lists needed an update. Replace it with `_populate_core_from_validated_config(config)` in `compiled_config.py` that reads the cached config dict directly -- same source `read_config` writes into during validation, no separate sidecar schema. The fast path no longer touches StorageJSON at all; the sidecar stays in place for the dashboard's existing use. Drift surface shrinks to: name/friendly_name/build_path under the esphome block (config schema is stable) + target_platform discovered via `_is_target_platform` (the canonical detector) + framework via the `<platform>.framework.type` convention every platform component already follows.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
@@ -25,8 +24,11 @@ _VALIDATED_CONFIG_YAML = """\
|
||||
esphome:
|
||||
name: lite_test
|
||||
friendly_name: Lite Test Device
|
||||
build_path: build/lite_test
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
logger:
|
||||
baud_rate: 115200
|
||||
api:
|
||||
@@ -43,30 +45,6 @@ wifi:
|
||||
"""
|
||||
|
||||
|
||||
def _write_storage(storage_path: Path) -> None:
|
||||
"""Write a vanilla StorageJSON sidecar for the cache tests."""
|
||||
storage_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = {
|
||||
"storage_version": 1,
|
||||
"name": "lite_test",
|
||||
"friendly_name": "Lite Test Device",
|
||||
"comment": None,
|
||||
"esphome_version": "2026.1.0",
|
||||
"src_version": 1,
|
||||
"address": "192.168.1.42",
|
||||
"web_port": None,
|
||||
"esp_platform": "ESP32",
|
||||
"build_path": "/build/lite_test",
|
||||
"firmware_bin_path": "/build/lite_test/firmware.bin",
|
||||
"loaded_integrations": ["api", "logger", "ota", "wifi"],
|
||||
"loaded_platforms": [],
|
||||
"no_mdns": False,
|
||||
"framework": "arduino",
|
||||
"core_platform": "esp32",
|
||||
}
|
||||
storage_path.write_text(json.dumps(data))
|
||||
|
||||
|
||||
def _write_cache(cache_path: Path, body: str = _VALIDATED_CONFIG_YAML) -> Path:
|
||||
"""Write the cache file and return it."""
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -85,14 +63,14 @@ def _set_cache_mtime(cache_path: Path, yaml_path: Path, *, offset: int) -> None:
|
||||
|
||||
@pytest.fixture
|
||||
def fresh_cache_files(tmp_path: Path) -> Path:
|
||||
"""YAML + StorageJSON + cache, all consistent and fresh."""
|
||||
"""YAML + cache, both consistent and fresh."""
|
||||
yaml_path = tmp_path / "lite_test.yaml"
|
||||
yaml_path.write_text("esphome:\n name: lite_test\n")
|
||||
CORE.config_path = yaml_path
|
||||
|
||||
storage_dir = tmp_path / ".esphome" / "storage"
|
||||
_write_storage(storage_dir / "lite_test.yaml.json")
|
||||
cache = _write_cache(storage_dir / "lite_test.yaml.validated.yaml")
|
||||
cache = _write_cache(
|
||||
tmp_path / ".esphome" / "storage" / "lite_test.yaml.validated.yaml"
|
||||
)
|
||||
_set_cache_mtime(cache, yaml_path, offset=5)
|
||||
|
||||
return yaml_path
|
||||
@@ -106,7 +84,7 @@ def test_compiled_config_path_lives_alongside_sidecar(setup_core: Path) -> None:
|
||||
|
||||
|
||||
def test_load_compiled_config_happy_path(fresh_cache_files: Path) -> None:
|
||||
"""Fresh cache + sidecar → returns config and populates CORE."""
|
||||
"""Fresh cache → returns the config and populates CORE from it."""
|
||||
config = load_compiled_config(fresh_cache_files)
|
||||
|
||||
assert config is not None
|
||||
@@ -114,40 +92,37 @@ def test_load_compiled_config_happy_path(fresh_cache_files: Path) -> None:
|
||||
assert config[CONF_API]["encryption"]["key"] == "6dGhpcyBpcyBhIHRlc3Q="
|
||||
assert config["ota"][0]["password"] == "secret"
|
||||
|
||||
# apply_to_core ran as part of the orchestration.
|
||||
# CORE state derives from the cached config dict -- same source
|
||||
# `read_config` uses, no separate sidecar schema.
|
||||
assert CORE.name == "lite_test"
|
||||
assert CORE.build_path == Path("/build/lite_test")
|
||||
assert CORE.friendly_name == "Lite Test Device"
|
||||
assert CORE.build_path == CORE.data_dir / "build/lite_test"
|
||||
assert CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] == "esp32"
|
||||
assert CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] == "arduino"
|
||||
assert "api" in CORE.loaded_integrations
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario",
|
||||
["missing_cache", "stale_cache", "corrupt_cache", "missing_sidecar"],
|
||||
["missing_cache", "stale_cache", "corrupt_cache", "missing_esphome_block"],
|
||||
)
|
||||
def test_load_compiled_config_falls_back(tmp_path: Path, scenario: str) -> None:
|
||||
"""All non-happy cases return None so the caller falls back."""
|
||||
yaml_path = tmp_path / "lite_test.yaml"
|
||||
yaml_path.write_text("esphome:\n name: lite_test\n")
|
||||
CORE.config_path = yaml_path
|
||||
storage_dir = tmp_path / ".esphome" / "storage"
|
||||
cache_path = storage_dir / "lite_test.yaml.validated.yaml"
|
||||
sidecar_path = storage_dir / "lite_test.yaml.json"
|
||||
cache_path = tmp_path / ".esphome" / "storage" / "lite_test.yaml.validated.yaml"
|
||||
|
||||
if scenario == "missing_cache":
|
||||
pass # no cache, no sidecar
|
||||
pass # no cache
|
||||
elif scenario == "stale_cache":
|
||||
_write_storage(sidecar_path)
|
||||
_set_cache_mtime(_write_cache(cache_path), yaml_path, offset=-60)
|
||||
elif scenario == "corrupt_cache":
|
||||
_write_storage(sidecar_path)
|
||||
_set_cache_mtime(
|
||||
_write_cache(cache_path, "not: valid: yaml: ["), yaml_path, offset=5
|
||||
)
|
||||
elif scenario == "missing_sidecar":
|
||||
# Cache fresh + parseable, but no StorageJSON → can't populate CORE.
|
||||
_set_cache_mtime(_write_cache(cache_path), yaml_path, offset=5)
|
||||
elif scenario == "missing_esphome_block":
|
||||
# Parseable YAML but no esphome: block -- can't populate CORE.
|
||||
_set_cache_mtime(_write_cache(cache_path, "logger:\n"), yaml_path, offset=5)
|
||||
|
||||
assert load_compiled_config(yaml_path) is None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user