From 4e921f8127fa60bd1810c3e9ae5cdd5c79f16104 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2026 07:09:55 -0500 Subject: [PATCH] [cover] Use ApplyField dataclass for the apply-lambda field list --- esphome/components/cover/__init__.py | 41 ++++++++++++++----- esphome/components/template/cover/__init__.py | 10 ++--- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index e6a3c29c1de..954ad7a3457 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -1,3 +1,5 @@ +from collections.abc import Callable +from dataclasses import dataclass import logging from esphome import automation @@ -295,14 +297,31 @@ COVER_CONTROL_ACTION_SCHEMA = cv.Schema( ) +@dataclass(frozen=True) +class ApplyField: + """One field in a folded-lambda action. + + `conf_key` is the YAML key looked up in `config`. When present, the + helper emits `statement_fn(target, value_expr)` into the lambda body. + `target` is whatever the statement function needs to identify the + field (typically a setter name like `"set_position"` or a struct + member like `"position"`). `type_` is the C++ return type for + `cg.process_lambda` when the value is a user lambda. + """ + + conf_key: str + target: str + type_: object + + async def build_apply_lambda_action( config: ConfigType, action_id: ID, template_arg: cg.TemplateArguments, args: TemplateArgsType, - fields: tuple[tuple[str, str, object], ...], + fields: tuple[ApplyField, ...], prefix_args: list[tuple[object, str]], - statement_fn, + statement_fn: Callable[[str, str], str], ) -> MockObj: """Fold configured fields into a single stateless apply lambda action. @@ -315,15 +334,15 @@ async def build_apply_lambda_action( paren = await cg.get_variable(config[CONF_ID]) fwd_args = ", ".join(name for _, name in args) body_lines: list[str] = [] - for conf_key, target, type_ in fields: - if (value := config.get(conf_key)) is None: + for field in fields: + if (value := config.get(field.conf_key)) is None: continue if isinstance(value, Lambda): - inner = await cg.process_lambda(value, args, return_type=type_) + inner = await cg.process_lambda(value, args, return_type=field.type_) value_expr = f"({inner})({fwd_args})" else: value_expr = str(cg.safe_exp(value)) - body_lines.append(statement_fn(target, value_expr)) + body_lines.append(statement_fn(field.target, value_expr)) apply_args = [ *prefix_args, @@ -340,11 +359,11 @@ async def build_apply_lambda_action( # CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most # one is present and both dispatch to set_position. -_COVER_CONTROL_FIELDS = ( - (CONF_STOP, "set_stop", cg.bool_), - (CONF_STATE, "set_position", cg.float_), - (CONF_POSITION, "set_position", cg.float_), - (CONF_TILT, "set_tilt", cg.float_), +_COVER_CONTROL_FIELDS: tuple[ApplyField, ...] = ( + ApplyField(CONF_STOP, "set_stop", cg.bool_), + ApplyField(CONF_STATE, "set_position", cg.float_), + ApplyField(CONF_POSITION, "set_position", cg.float_), + ApplyField(CONF_TILT, "set_tilt", cg.float_), ) diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index 3cf87798473..7cb50df84c5 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -115,11 +115,11 @@ async def to_code(config): # CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most # one is present and both map to the position field. -_COVER_PUBLISH_FIELDS = ( - (CONF_STATE, "position", cg.float_), - (CONF_POSITION, "position", cg.float_), - (CONF_TILT, "tilt", cg.float_), - (CONF_CURRENT_OPERATION, "current_operation", cover.CoverOperation), +_COVER_PUBLISH_FIELDS: tuple[cover.ApplyField, ...] = ( + cover.ApplyField(CONF_STATE, "position", cg.float_), + cover.ApplyField(CONF_POSITION, "position", cg.float_), + cover.ApplyField(CONF_TILT, "tilt", cg.float_), + cover.ApplyField(CONF_CURRENT_OPERATION, "current_operation", cover.CoverOperation), )