From c062c1ddbdb6cf59bd177ea46b2de52c178e558c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 May 2026 17:00:22 -0700 Subject: [PATCH] [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. --- esphome/components/lvgl/helpers.py | 5 +++++ esphome/components/lvgl/widgets/__init__.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/esphome/components/lvgl/helpers.py b/esphome/components/lvgl/helpers.py index 4e41eb900a..2cdbc722d4 100644 --- a/esphome/components/lvgl/helpers.py +++ b/esphome/components/lvgl/helpers.py @@ -16,6 +16,11 @@ def lazy_once(build: Callable[[], T]) -> Callable[[], T]: Used to defer voluptuous schema construction until first validation. Many of the lvgl schemas would otherwise be built at module-import time even for 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] = [] diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index d56aefa769..20e7312b63 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -48,6 +48,7 @@ from ..defines import ( join_enums, literal, ) +from ..helpers import lazy_once from ..lv_validation import lv_int from ..lvcode import ( LvConditional, @@ -80,8 +81,11 @@ def _lazy_update_schema(widget_type: "WidgetType"): 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 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(): from ..schemas import base_update_schema