[lvgl] Memoize obj_schema by widget_type (#16615)

This commit is contained in:
J. Nick Koston
2026-05-24 20:17:51 -05:00
committed by GitHub
parent 62b0a93e5e
commit e0167e9bdf
2 changed files with 80 additions and 1 deletions

View File

@@ -462,13 +462,23 @@ def base_update_schema(widget_type: WidgetType | LvType, parts):
return schema
# Widget types are module-level singletons populated at import time, so we
# can cache compiled obj_schemas by widget_type identity for the lifetime of
# the process. The strong reference in the value keeps the key (an id()
# target) from being recycled.
_OBJ_SCHEMA_CACHE: dict[int, tuple[WidgetType, cv.Schema]] = {}
def obj_schema(widget_type: WidgetType):
"""
Create a schema for a widget type itself i.e. no allowance for children
:param widget_type:
:return:
"""
return (
cached = _OBJ_SCHEMA_CACHE.get(id(widget_type))
if cached is not None and cached[0] is widget_type:
return cached[1]
schema = (
part_schema(widget_type.parts)
.extend(ALIGN_TO_SCHEMA)
.extend(automation_schema(widget_type.w_type))
@@ -479,6 +489,8 @@ def obj_schema(widget_type: WidgetType):
}
)
)
_OBJ_SCHEMA_CACHE[id(widget_type)] = (widget_type, schema)
return schema
ALIGN_TO_SCHEMA = {

View File

@@ -0,0 +1,67 @@
"""Tests for obj_schema() memoization."""
from __future__ import annotations
from collections.abc import Generator
import pytest
import esphome.components.lvgl # noqa: F401
from esphome.components.lvgl import schemas as lvgl_schemas
from esphome.components.lvgl.schemas import WIDGET_TYPES, obj_schema
@pytest.fixture(autouse=True)
def _clear_obj_schema_cache() -> Generator[None]:
cache = getattr(lvgl_schemas, "_OBJ_SCHEMA_CACHE", None)
if cache is not None:
cache.clear()
yield
if cache is not None:
cache.clear()
def _widget_type(name: str = "obj"):
wt = WIDGET_TYPES.get(name)
assert wt is not None, f"widget type {name!r} not registered"
return wt
def test_same_widget_type_returns_same_schema() -> None:
wt = _widget_type("obj")
assert obj_schema(wt) is obj_schema(wt)
def test_different_widget_types_return_different_schemas() -> None:
assert obj_schema(_widget_type("obj")) is not obj_schema(_widget_type("label"))
def test_cache_is_populated_after_first_call() -> None:
wt = _widget_type("obj")
assert id(wt) not in lvgl_schemas._OBJ_SCHEMA_CACHE
obj_schema(wt)
assert id(wt) in lvgl_schemas._OBJ_SCHEMA_CACHE
def test_cached_schema_produces_equivalent_output() -> None:
wt = _widget_type("obj")
cached_result = obj_schema(wt)({})
lvgl_schemas._OBJ_SCHEMA_CACHE.clear()
fresh_result = obj_schema(wt)({})
assert cached_result == fresh_result
def test_id_recycling_is_caught_by_identity_guard() -> None:
wt = _widget_type("obj")
real_schema = obj_schema(wt)
cached_widget_type, _ = lvgl_schemas._OBJ_SCHEMA_CACHE[id(wt)]
sentinel_schema = object()
lvgl_schemas._OBJ_SCHEMA_CACHE[id(wt)] = (cached_widget_type, sentinel_schema)
assert obj_schema(wt) is sentinel_schema
other = _widget_type("label")
lvgl_schemas._OBJ_SCHEMA_CACHE[id(wt)] = (other, sentinel_schema)
rebuilt = obj_schema(wt)
assert rebuilt is not sentinel_schema
assert rebuilt is not real_schema