[config] Add --no-defaults flag to config command (#16718)

This commit is contained in:
Jesse Hills
2026-06-04 14:08:36 +12:00
committed by GitHub
parent 0d7d091e71
commit 93f25258ee
4 changed files with 154 additions and 1 deletions

View File

@@ -471,6 +471,88 @@ def test_command_config__show_secrets_skips_redaction(
assert "\\033[8m" not in output
def test_command_config__no_defaults_dumps_user_snapshot(
tmp_path: Path, capfd: CaptureFixture[str]
) -> None:
"""``--no-defaults`` dumps ``config.user_config`` instead of the
validated config, so schema defaults don't leak into the output."""
from esphome.config import Config
setup_core(tmp_path=tmp_path, config={"esphome": {"name": "test"}})
args = MockArgs()
args.show_secrets = True
args.no_defaults = True
validated = Config()
validated["esphome"] = {"name": "test", "build_path": "build/test"}
validated["wifi"] = {"ssid": "MyNet", "reboot_timeout": "15min"}
validated.user_config = {
"esphome": {"name": "test"},
"wifi": {"ssid": "MyNet"},
}
result = command_config(args, validated)
assert result == 0
output = capfd.readouterr().out
assert "ssid: MyNet" in output
# Defaults present on the validated config must not appear.
assert "reboot_timeout" not in output
assert "build_path" not in output
def test_command_config__no_defaults_warns_when_snapshot_missing(
tmp_path: Path,
capfd: CaptureFixture[str],
caplog: pytest.LogCaptureFixture,
) -> None:
"""If the snapshot is unavailable (e.g. a plain dict was passed in),
``--no-defaults`` logs a warning and falls back to the input config."""
setup_core(tmp_path=tmp_path, config={"esphome": {"name": "test"}})
args = MockArgs()
args.show_secrets = True
args.no_defaults = True
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
result = command_config(args, {"wifi": {"ssid": "MyNet"}})
assert result == 0
output = capfd.readouterr().out
assert "ssid: MyNet" in output
assert any(
"user-only config snapshot is unavailable" in rec.message
for rec in caplog.records
)
def test_command_config__no_defaults_skips_strip_default_ids(
tmp_path: Path, capfd: CaptureFixture[str]
) -> None:
"""When ``--no-defaults`` is set, ``strip_default_ids`` isn't run --
the user snapshot is already free of schema-injected IDs."""
from esphome.config import Config
setup_core(tmp_path=tmp_path, config={"esphome": {"name": "test"}})
args = MockArgs()
args.show_secrets = True
args.no_defaults = True
validated = Config()
validated["sensor"] = [{"name": "x", "id": "auto_generated"}]
validated.user_config = {"sensor": [{"name": "x"}]}
with patch(
"esphome.__main__.strip_default_ids", side_effect=AssertionError
) as mock_strip:
result = command_config(args, validated)
assert result == 0
mock_strip.assert_not_called()
output = capfd.readouterr().out
assert "name: x" in output
assert "auto_generated" not in output
def test_choose_upload_log_host_with_string_default() -> None:
"""Test with a single string default device."""
setup_core()

View File

@@ -361,6 +361,47 @@ def test_validate_config_without_command_line_substitutions_maintains_ordered_di
assert result[CONF_SUBSTITUTIONS]["var2"] == "value2"
def test_validate_config_captures_user_config_snapshot(tmp_path: Path) -> None:
"""validate_config stores a deep copy of the user's config -- with
substitutions re-added and no schema defaults applied -- on
``result.user_config`` for ``esphome config --no-defaults``.
"""
test_config = _get_test_minimal_valid_config(tmp_path)
result = config_module.validate_config(test_config, None)
# Snapshot is populated.
assert result.user_config is not None
# Substitutions are re-added and appear first.
assert list(result.user_config.keys())[0] == CONF_SUBSTITUTIONS
assert result.user_config[CONF_SUBSTITUTIONS]["var1"] == "value1"
# User-supplied keys are present without schema-default fields like
# ``build_path`` (which preload_core_config injects on the validated
# result's esphome section).
assert result.user_config["esphome"] == {"name": "test_device"}
assert "build_path" not in result.user_config["esphome"]
assert "min_version" not in result.user_config["esphome"]
assert result.user_config["esp32"] == {"board": "esp32dev"}
def test_validate_config_user_config_snapshot_is_deep_copy(tmp_path: Path) -> None:
"""The snapshot is independent of subsequent mutations to the result
config -- preload_core_config rewrites ``esphome:`` in place, but the
snapshot keeps the user's literal block.
"""
test_config = _get_test_minimal_valid_config(tmp_path)
result = config_module.validate_config(test_config, None)
assert result.user_config is not None
# preload_core_config injected build_path onto the validated config.
assert "build_path" in result["esphome"]
# The snapshot was taken before that and is unaffected.
assert "build_path" not in result.user_config["esphome"]
# And the two are not aliased.
assert result["esphome"] is not result.user_config["esphome"]
def test_merge_config_preserves_ordered_dict() -> None:
"""Test that merge_config preserves OrderedDict type.