[lvgl] Build automation_schema event validators lazily (#16633)

This commit is contained in:
J. Nick Koston
2026-05-26 08:41:54 -05:00
committed by GitHub
parent ceb9d406e1
commit 88b12a1c45
2 changed files with 102 additions and 3 deletions

View File

@@ -22,6 +22,7 @@ from esphome.const import (
)
from esphome.core import TimePeriod
from esphome.core.config import StartupTrigger
from esphome.schema_extractors import EnableSchemaExtraction
from . import defines as df, lv_validation as lvalid
from .defines import (
@@ -407,7 +408,34 @@ def part_schema(parts: tuple[str, ...] | list[str]) -> cv.Schema:
return cv.Schema(part_dict(parts))
def automation_schema(typ: LvType):
def _lazy_validate_automation(extra_schema: dict) -> Callable[[Any], Any]:
"""Return a validator that defers building the validate_automation schema.
validate_automation() runs AUTOMATION_SCHEMA.extend(extra_schema), which
voluptuous compiles eagerly. automation_schema() builds ~60 of these per
widget type, and the vast majority of slots are never invoked by a given
user config. Deferring the build to first use removes that work from
schema-construction time.
When EnableSchemaExtraction is set (build_language_schema.py), fall back
to eager construction so the @schema_extractor("automation") decoration
inside validate_automation is registered.
"""
if EnableSchemaExtraction:
return validate_automation(extra_schema)
cached: Callable[[Any], Any] | None = None
def validator(value: Any) -> Any:
nonlocal cached
if cached is None:
cached = validate_automation(extra_schema)
return cached(value)
return validator
def automation_schema(typ: LvType) -> dict[Any, Any]:
events = df.LV_EVENT_TRIGGERS + df.SWIPE_TRIGGERS
if typ.has_on_value:
events = events + (CONF_ON_VALUE, CONF_ON_UPDATE)
@@ -422,7 +450,7 @@ def automation_schema(typ: LvType):
return {
**{
cv.Optional(event): validate_automation(
cv.Optional(event): _lazy_validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
Trigger.template(*get_trigger_args(event))
@@ -431,7 +459,7 @@ def automation_schema(typ: LvType):
)
for event in events
},
cv.Optional(CONF_ON_BOOT): validate_automation(
cv.Optional(CONF_ON_BOOT): _lazy_validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger)}
),
}

View File

@@ -0,0 +1,71 @@
"""Tests for lvgl automation_schema lazy validate_automation build."""
from __future__ import annotations
from unittest.mock import patch
import esphome.components.lvgl # noqa: F401
from esphome.components.lvgl import schemas as lvgl_schemas
from esphome.components.lvgl.schemas import (
WIDGET_TYPES,
_lazy_validate_automation,
automation_schema,
)
from esphome.components.lvgl.widgets import WidgetType
from esphome.config_validation import GenerateID, declare_id
from esphome.const import CONF_TRIGGER_ID
from esphome.core.config import StartupTrigger
def _widget_type(name: str = "obj") -> WidgetType:
wt = WIDGET_TYPES.get(name)
assert wt is not None, f"widget type {name!r} not registered"
return wt
def _trigger_extra_schema() -> dict:
return {GenerateID(CONF_TRIGGER_ID): declare_id(StartupTrigger)}
def test_lazy_validator_defers_build_until_first_call() -> None:
with patch(
"esphome.components.lvgl.schemas.validate_automation",
wraps=lvgl_schemas.validate_automation,
) as va_mock:
validator = _lazy_validate_automation(_trigger_extra_schema())
assert va_mock.call_count == 0
validator({"then": []})
assert va_mock.call_count == 1
validator({"then": []})
assert va_mock.call_count == 1
def test_eager_build_when_schema_extraction_enabled() -> None:
with (
patch("esphome.components.lvgl.schemas.EnableSchemaExtraction", True),
patch(
"esphome.components.lvgl.schemas.validate_automation",
wraps=lvgl_schemas.validate_automation,
) as va_mock,
):
_lazy_validate_automation(_trigger_extra_schema())
assert va_mock.call_count == 1
def test_lazy_and_eager_produce_equivalent_validation() -> None:
extra = _trigger_extra_schema()
with patch("esphome.components.lvgl.schemas.EnableSchemaExtraction", True):
eager = _lazy_validate_automation(extra)
lazy = _lazy_validate_automation(_trigger_extra_schema())
sample = {"then": []}
assert lazy(sample) == eager(sample)
def test_automation_schema_uses_lazy_validators() -> None:
wt = _widget_type("obj")
with patch(
"esphome.components.lvgl.schemas.validate_automation",
wraps=lvgl_schemas.validate_automation,
) as va_mock:
automation_schema(wt.w_type)
assert va_mock.call_count == 0