mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 15:10:51 +00:00
[lvgl] Memoize obj_schema by widget_type (#16615)
This commit is contained in:
@@ -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 = {
|
||||
|
||||
67
tests/component_tests/lvgl/test_obj_schema_cache.py
Normal file
67
tests/component_tests/lvgl/test_obj_schema_cache.py
Normal 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
|
||||
Reference in New Issue
Block a user