mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[emontx] Pass on_json/on_data data without copies and reject deferring actions (#20516)
This commit is contained in:
@@ -44,13 +44,29 @@ def _get_data() -> EmonTxData:
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def _synchronous_automation(value: ConfigType) -> ConfigType:
|
||||
"""Reject deferring actions: the received line and parsed JSON are only valid while the
|
||||
automation runs, and DelayAction and friends keep the trigger arguments for later."""
|
||||
if automation.has_non_synchronous_actions(value):
|
||||
raise cv.Invalid(
|
||||
"Deferring actions (delay, wait_until, script.wait, ...) are not allowed in on_json and "
|
||||
"on_data: the received data is only valid while the automation runs. Copy what you need "
|
||||
"into globals first, then defer in a separate script or automation."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _synchronous_automation_schema() -> cv.All:
|
||||
return cv.All(automation.validate_automation({}), _synchronous_automation)
|
||||
|
||||
|
||||
# Main configuration schema
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EmonTx),
|
||||
cv.Optional(CONF_ON_JSON): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_DATA): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_JSON): _synchronous_automation_schema(),
|
||||
cv.Optional(CONF_ON_DATA): _synchronous_automation_schema(),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -104,10 +120,10 @@ _CALLBACK_AUTOMATIONS = (
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_JSON,
|
||||
"add_on_json_callback",
|
||||
[(cg.JsonObject, "json"), (cg.std_string, "raw_json")],
|
||||
[(cg.JsonObject, "json"), (cg.StringRef, "raw_json")],
|
||||
),
|
||||
automation.CallbackAutomation(
|
||||
CONF_ON_DATA, "add_on_data_callback", [(cg.std_string, "data")]
|
||||
CONF_ON_DATA, "add_on_data_callback", [(cg.StringRef, "data")]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Tests that emontx on_json/on_data automations reject deferring actions.
|
||||
|
||||
The triggers pass views into the receive buffer and the parsed JSON document, which are only valid
|
||||
while the automation runs, so the deferring-actions guard is a safety property.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.emontx import CONF_ON_JSON, CONFIG_SCHEMA
|
||||
from esphome.const import CONF_ON_DATA
|
||||
from esphome.core import Lambda
|
||||
from esphome.types import ConfigType
|
||||
|
||||
TRIGGER_KEYS = [CONF_ON_JSON, CONF_ON_DATA]
|
||||
|
||||
DEFERRING_ACTION = {"delay": "1s"}
|
||||
SYNCHRONOUS_ACTION = {"lambda": Lambda('ESP_LOGD("test", "ran");')}
|
||||
TRUE_CONDITION = {"lambda": Lambda("return true;")}
|
||||
|
||||
DEFER_MESSAGE = "Deferring actions"
|
||||
|
||||
|
||||
def _trigger_schema(trigger_key: str) -> Callable[[list[ConfigType]], list[ConfigType]]:
|
||||
"""The validator CONFIG_SCHEMA applies to this trigger key."""
|
||||
return CONFIG_SCHEMA.schema[cv.Optional(trigger_key)]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trigger_key", TRIGGER_KEYS)
|
||||
def test_synchronous_automation_accepted(trigger_key: str) -> None:
|
||||
_trigger_schema(trigger_key)([{"then": [SYNCHRONOUS_ACTION]}])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trigger_key", TRIGGER_KEYS)
|
||||
def test_deferring_action_rejected(trigger_key: str) -> None:
|
||||
with pytest.raises(cv.Invalid, match=DEFER_MESSAGE):
|
||||
_trigger_schema(trigger_key)([{"then": [SYNCHRONOUS_ACTION, DEFERRING_ACTION]}])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trigger_key", TRIGGER_KEYS)
|
||||
def test_nested_deferring_action_rejected(trigger_key: str) -> None:
|
||||
actions = [{"if": {"condition": TRUE_CONDITION, "then": [DEFERRING_ACTION]}}]
|
||||
with pytest.raises(cv.Invalid, match=DEFER_MESSAGE):
|
||||
_trigger_schema(trigger_key)([{"then": actions}])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trigger_key", TRIGGER_KEYS)
|
||||
def test_deferring_action_in_second_automation_rejected(trigger_key: str) -> None:
|
||||
with pytest.raises(cv.Invalid, match=DEFER_MESSAGE):
|
||||
_trigger_schema(trigger_key)(
|
||||
[{"then": [SYNCHRONOUS_ACTION]}, {"then": [DEFERRING_ACTION]}]
|
||||
)
|
||||
Reference in New Issue
Block a user