From c0c02e5c57acb3e7602da0c2d6c03814f11f07f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Mar 2026 07:12:19 -1000 Subject: [PATCH] Make setup_entity work as both decorator and direct call Address review feedback: support both @setup_entity("sensor") decorator mode and await setup_entity(var, config, "camera") direct-call mode to maintain backward compatibility with external components that use the direct-call API. Simplify esp32_camera to use the direct-call form instead of a no-op decorated wrapper function. --- esphome/components/esp32_camera/__init__.py | 7 +--- esphome/core/entity_helpers.py | 43 +++++++++++++-------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 22e14ae9ee8..3a5d87792b6 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -368,16 +368,11 @@ SETTERS = { } -@setup_entity("camera") -async def _setup_esp32_camera(var, config): - pass - - async def to_code(config): cg.add_define("USE_CAMERA") socket.require_wake_loop_threadsafe() var = cg.new_Pvariable(config[CONF_ID]) - await _setup_esp32_camera(var, config) + await setup_entity(var, config, "camera") await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index fd151d82d9f..ac68eaa64b5 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -236,33 +236,44 @@ def get_base_entity_object_id( return sanitize(snake_case(base_str)) -def setup_entity(platform: str) -> Callable: - """Decorator for component setup functions. +def setup_entity(var_or_platform, config=None, platform=None): + """Set up entity properties — works as both decorator and direct call. - Wraps the function to: - 1. Set up common entity properties (name, icon, etc.) - 2. Run the wrapped function (which may call setup_device_class, etc.) - 3. Finalize entity strings (pack dc/uom/icon indices into uint32_t) - - Usage:: + Decorator mode:: @setup_entity("sensor") async def setup_sensor_core_(var, config): setup_device_class(config) setup_unit_of_measurement(config) ... + + Direct call mode (for entities with no extra string properties):: + + await setup_entity(var, config, "camera") """ + if isinstance(var_or_platform, str) and config is None: + # Decorator mode: @setup_entity("sensor") + platform = var_or_platform - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - async def wrapper(var: MockObj, config: ConfigType, *args, **kwargs) -> None: - await _setup_entity_impl(var, config, platform) - await func(var, config, *args, **kwargs) - finalize_entity_strings(var, config) + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + async def wrapper( + var: MockObj, config: ConfigType, *args, **kwargs + ) -> None: + await _setup_entity_impl(var, config, platform) + await func(var, config, *args, **kwargs) + finalize_entity_strings(var, config) - return wrapper + return wrapper - return decorator + return decorator + + # Direct call mode: await setup_entity(var, config, "camera") + async def _do(): + await _setup_entity_impl(var_or_platform, config, platform) + finalize_entity_strings(var_or_platform, config) + + return _do() async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) -> None: