[lvgl] Hoist lazy_once import, document thread-safety

Addresses two stylistic notes on the PR:

- Move `from ..helpers import lazy_once` to module scope in
  widgets/__init__.py. helpers.py only depends on esphome and
  esphome.const, so there's no circular-import risk that would justify
  the function-local import. Keep the `from ..schemas import
  base_update_schema` deferred because schemas.py imports WidgetType
  from this module.
- Document that lazy_once is single-threaded and retries build() on
  exception (no negative-cache), so future callers know the contract
  if the helper migrates to esphome/core/helpers.py.
This commit is contained in:
J. Nick Koston
2026-05-16 17:00:22 -07:00
parent fabc1b584f
commit c062c1ddbd
2 changed files with 10 additions and 1 deletions

View File

@@ -16,6 +16,11 @@ def lazy_once(build: Callable[[], T]) -> Callable[[], T]:
Used to defer voluptuous schema construction until first validation. Many Used to defer voluptuous schema construction until first validation. Many
of the lvgl schemas would otherwise be built at module-import time even for of the lvgl schemas would otherwise be built at module-import time even for
YAMLs that never reach them. YAMLs that never reach them.
Not thread-safe — concurrent first-callers would each run ``build``. esphome
config validation is single-threaded so this is fine in practice. If
``build`` raises, the cache stays empty and the next call retries; there is
no negative-cache.
""" """
cached: list[T] = [] cached: list[T] = []

View File

@@ -48,6 +48,7 @@ from ..defines import (
join_enums, join_enums,
literal, literal,
) )
from ..helpers import lazy_once
from ..lv_validation import lv_int from ..lv_validation import lv_int
from ..lvcode import ( from ..lvcode import (
LvConditional, LvConditional,
@@ -80,8 +81,11 @@ def _lazy_update_schema(widget_type: "WidgetType"):
voluptuous schemas and is invoked once per WidgetType at import time. The voluptuous schemas and is invoked once per WidgetType at import time. The
caller (register_action) only needs a validator, so wrap the build in a caller (register_action) only needs a validator, so wrap the build in a
closure that materialises on the first validation and caches the result. closure that materialises on the first validation and caches the result.
The ``from ..schemas import base_update_schema`` import stays inside the
closure because ``schemas`` imports ``WidgetType`` from this module — top-
level would deadlock the import.
""" """
from ..helpers import lazy_once
def build(): def build():
from ..schemas import base_update_schema from ..schemas import base_update_schema