mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
[image] Only migrate recognised legacy image configs
Guard the legacy `image:` migration so it only rewrites shapes the
pre-platform schema actually accepted. A list with a non-dict or
already-platform-tagged entry, or a dict with no recognised image keys
(e.g. `image: ["bad"]`, `image: {foo: 1}`), now returns None from the
migration hook, so normal platform validation surfaces a proper error
instead of the migration silently dropping the input.
This commit is contained in:
@@ -487,6 +487,32 @@ def _is_new_image_format(config: object) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _is_legacy_image_format(config: object) -> bool:
|
||||
"""True when ``config`` matches a shape the pre-platform schema accepted.
|
||||
|
||||
Only these shapes are migrated. Anything else -- a list containing a
|
||||
non-dict (or already platform-tagged) entry, or a dict with no recognised
|
||||
image keys -- is left untouched so the platform validation surfaces a
|
||||
proper error instead of the migration silently dropping the input.
|
||||
"""
|
||||
if isinstance(config, list):
|
||||
# A bare list of (not-yet-platform-tagged) image dicts.
|
||||
return bool(config) and all(
|
||||
isinstance(entry, dict) and CONF_PLATFORM not in entry for entry in config
|
||||
)
|
||||
if not isinstance(config, dict):
|
||||
return False
|
||||
# A single image dict, or the grouped `defaults:`/`images:`/type-key form.
|
||||
return (
|
||||
CONF_ID in config
|
||||
or CONF_FILE in config
|
||||
or any(
|
||||
key in (CONF_DEFAULTS, CONF_IMAGES) or key.upper() in IMAGE_TYPE
|
||||
for key in config
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _flatten_legacy_image_config(config: object) -> list[dict]:
|
||||
"""Structurally flatten a legacy ``image:`` config into image dicts.
|
||||
|
||||
@@ -543,8 +569,13 @@ def _flatten_legacy_image_config(config: object) -> list[dict]:
|
||||
|
||||
|
||||
def _migrate_legacy_image_config(config: object) -> list[dict] | None:
|
||||
"""Rewrite a legacy ``image:`` config into the ``platform: file`` list."""
|
||||
if _is_new_image_format(config):
|
||||
"""Rewrite a legacy ``image:`` config into the ``platform: file`` list.
|
||||
|
||||
Returns None for the already-migrated platform form and for any shape the
|
||||
pre-platform schema never accepted, so normal platform validation can
|
||||
surface a proper error instead of the migration silently discarding input.
|
||||
"""
|
||||
if _is_new_image_format(config) or not _is_legacy_image_format(config):
|
||||
return None
|
||||
migrated = [
|
||||
{CONF_PLATFORM: PLATFORM_FILE, **entry}
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.components.image import (
|
||||
CONF_TRANSPARENCY,
|
||||
PLATFORM_FILE,
|
||||
_flatten_legacy_image_config,
|
||||
_is_legacy_image_format,
|
||||
_is_new_image_format,
|
||||
_migrate_legacy_image_config,
|
||||
get_all_image_metadata,
|
||||
@@ -323,6 +324,49 @@ def test_migrate_legacy_warns_and_prepends_platform(
|
||||
assert f"platform: {PLATFORM_FILE}" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "expected"),
|
||||
[
|
||||
# Recognised legacy shapes -> migrate.
|
||||
pytest.param([{"id": "a", "file": "x.png"}], True, id="bare_list_of_dicts"),
|
||||
pytest.param({"id": "a", "file": "x.png"}, True, id="single_image_dict"),
|
||||
pytest.param({"file": "x.png"}, True, id="single_dict_file_only"),
|
||||
pytest.param({"defaults": {}, "images": []}, True, id="defaults_images"),
|
||||
pytest.param({"rgb565": [{"id": "a"}]}, True, id="type_grouped"),
|
||||
# Shapes the legacy schema never accepted -> not migrated.
|
||||
pytest.param([], False, id="empty_list"),
|
||||
pytest.param(["bad"], False, id="list_with_non_dict"),
|
||||
pytest.param([{"id": "a"}, "bad"], False, id="list_mixed_dict_and_non_dict"),
|
||||
pytest.param(
|
||||
[{CONF_PLATFORM: "file", "id": "a"}], False, id="already_platform_tagged"
|
||||
),
|
||||
pytest.param({"foo": 1}, False, id="dict_unknown_keys"),
|
||||
pytest.param("a string", False, id="scalar"),
|
||||
],
|
||||
)
|
||||
def test_is_legacy_image_format(config: object, expected: bool) -> None:
|
||||
assert _is_legacy_image_format(config) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
pytest.param(["bad"], id="list_with_non_dict"),
|
||||
pytest.param([{"id": "a"}, "bad"], id="list_mixed"),
|
||||
pytest.param({"foo": 1}, id="dict_unknown_keys"),
|
||||
],
|
||||
)
|
||||
def test_migrate_returns_none_for_invalid_legacy_shapes(
|
||||
config: object, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Unrecognised shapes are not migrated (and emit no warning) so normal
|
||||
platform validation surfaces a proper error instead of silently dropping
|
||||
the offending input."""
|
||||
with caplog.at_level(logging.WARNING):
|
||||
assert _migrate_legacy_image_config(config) is None
|
||||
assert "deprecated" not in caplog.text
|
||||
|
||||
|
||||
# --------------------------- end legacy migration --------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user