From 23207f00743e86a8d0ea4f31c418229e96a1ecf6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 26 Oct 2025 01:03:15 -0700 Subject: [PATCH] dry --- esphome/automation.py | 46 +++++++++++--------- esphome/components/binary_sensor/__init__.py | 5 +-- esphome/components/logger/__init__.py | 16 ++----- esphome/components/sensor/__init__.py | 5 +-- esphome/components/text_sensor/__init__.py | 5 +-- tests/component_tests/text/test_text.py | 4 +- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/esphome/automation.py b/esphome/automation.py index 462489f590..cfe0af1b59 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -16,7 +16,12 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, ) from esphome.core import ID -from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType +from esphome.cpp_generator import ( + LambdaExpression, + MockObj, + MockObjClass, + TemplateArgsType, +) from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.types import ConfigType from esphome.util import Registry @@ -102,25 +107,34 @@ StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Cond ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) -def use_stateless_lambda_if_applicable(id_obj, lambda_expr, stateless_class): - """Return appropriate ID for lambda based on whether it has capture. +def new_lambda_pvariable( + id_obj: ID, + lambda_expr: LambdaExpression, + stateless_class: MockObjClass, + template_arg: cg.TemplateArguments | None = None, +) -> MockObj: + """Create Pvariable for lambda, using stateless class if applicable. - For stateless lambdas (empty capture), returns a copy of id_obj with type - set to stateless_class to use function pointer instead of std::function. - Otherwise returns the original ID unchanged. + Combines ID selection and Pvariable creation in one call. For stateless + lambdas (empty capture), uses function pointer instead of std::function. Args: id_obj: The ID object (action_id, condition_id, or filter_id) lambda_expr: The lambda expression object - stateless_class: The stateless class to use (StatelessLambdaAction, StatelessLambdaCondition, or StatelessLambdaFilter) + stateless_class: The stateless class to use for stateless lambdas + template_arg: Optional template arguments (for actions/conditions) Returns: - ID to use with cg.new_Pvariable() - either original or modified copy + The created Pvariable """ + # For stateless lambdas, use function pointer instead of std::function if lambda_expr.capture == "": id_obj = id_obj.copy() id_obj.type = stateless_class - return id_obj + + if template_arg is not None: + return cg.new_Pvariable(id_obj, template_arg, lambda_expr) + return cg.new_Pvariable(id_obj, lambda_expr) def validate_automation(extra_schema=None, extra_validators=None, single=False): @@ -263,12 +277,8 @@ async def lambda_condition_to_code( args: TemplateArgsType, ) -> MockObj: lambda_ = await cg.process_lambda(config, args, return_type=bool) - return cg.new_Pvariable( - use_stateless_lambda_if_applicable( - condition_id, lambda_, StatelessLambdaCondition - ), - template_arg, - lambda_, + return new_lambda_pvariable( + condition_id, lambda_, StatelessLambdaCondition, template_arg ) @@ -435,11 +445,7 @@ async def lambda_action_to_code( args: TemplateArgsType, ) -> MockObj: lambda_ = await cg.process_lambda(config, args, return_type=cg.void) - return cg.new_Pvariable( - use_stateless_lambda_if_applicable(action_id, lambda_, StatelessLambdaAction), - template_arg, - lambda_, - ) + return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg) @register_action( diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 5fb1ee6667..8892b57e6e 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -300,10 +300,7 @@ async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( config, [(bool, "x")], return_type=cg.optional.template(bool) ) - filter_id = automation.use_stateless_lambda_if_applicable( - filter_id, lambda_, StatelessLambdaFilter - ) - return cg.new_Pvariable(filter_id, lambda_) + return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) @register_filter( diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index e7715cfc10..22bf3d2f4c 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -430,12 +430,8 @@ async def logger_log_action_to_code(config, action_id, template_arg, args): text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) - return cg.new_Pvariable( - automation.use_stateless_lambda_if_applicable( - action_id, lambda_, StatelessLambdaAction - ), - template_arg, - lambda_, + return automation.new_lambda_pvariable( + action_id, lambda_, StatelessLambdaAction, template_arg ) @@ -461,12 +457,8 @@ async def logger_set_level_to_code(config, action_id, template_arg, args): text = str(cg.statement(logger.set_log_level(level))) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) - return cg.new_Pvariable( - automation.use_stateless_lambda_if_applicable( - action_id, lambda_, StatelessLambdaAction - ), - template_arg, - lambda_, + return automation.new_lambda_pvariable( + action_id, lambda_, StatelessLambdaAction, template_arg ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ffa33d521c..41ac3516b9 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -574,10 +574,7 @@ async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( config, [(float, "x")], return_type=cg.optional.template(float) ) - filter_id = automation.use_stateless_lambda_if_applicable( - filter_id, lambda_, StatelessLambdaFilter - ) - return cg.new_Pvariable(filter_id, lambda_) + return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) DELTA_SCHEMA = cv.Schema( diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 47259667f4..adc8a76fcd 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -71,10 +71,7 @@ async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) ) - filter_id = automation.use_stateless_lambda_if_applicable( - filter_id, lambda_, StatelessLambdaFilter - ) - return cg.new_Pvariable(filter_id, lambda_) + return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 75f1c4b88b..6eec86de9f 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main): def test_text_config_lamda_is_set(generate_main): """ - Test if lambda is set for lambda mode + Test if lambda is set for lambda mode (optimized with stateless lambda) """ # Given @@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then - assert "it_4->set_template([=]() -> esphome::optional {" in main_cpp + assert "it_4->set_template(+[]() -> esphome::optional {" in main_cpp assert 'return std::string{"Hello"};' in main_cpp