diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index 02ef628..598109f 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -1196,11 +1196,7 @@ async def trigger_watch(self): continue if self.state_trig_eval: - try: - trig_ok = await self.state_trig_eval.eval(new_vars) - except Exception as e: - self.state_trig_eval.log_exception(e) - trig_ok = False + trig_ok = await self._call_expression(self.state_trig_eval, new_vars) if self.state_hold_false is not None: if "var_name" not in func_args: @@ -1269,17 +1265,17 @@ async def trigger_watch(self): func_args = notify_info user_kwargs = self.event_trigger_kwargs.get("kwargs", {}) if self.event_trig_expr: - trig_ok = await self.event_trig_expr.eval(notify_info) + trig_ok = await self._call_expression(self.event_trig_expr, notify_info) elif notify_type == "mqtt": func_args = notify_info user_kwargs = self.mqtt_trigger_kwargs.get("kwargs", {}) if self.mqtt_trig_expr: - trig_ok = await self.mqtt_trig_expr.eval(notify_info) + trig_ok = await self._call_expression(self.mqtt_trig_expr, notify_info) elif notify_type == "webhook": func_args = notify_info user_kwargs = self.webhook_trigger_kwargs.get("kwargs", {}) if self.webhook_trig_expr: - trig_ok = await self.webhook_trig_expr.eval(notify_info) + trig_ok = await self._call_expression(self.webhook_trig_expr, notify_info) else: user_kwargs = self.time_trigger_kwargs.get("kwargs", {}) @@ -1339,6 +1335,13 @@ async def trigger_watch(self): Webhook.notify_del(self.webhook_trigger[0], self.notify_q) return + async def _call_expression(self, ast_expr, notify_info): + try: + return await ast_expr.eval(notify_info) + except Exception as exc: + ast_expr.log_exception(exc) + return False + def call_action(self, notify_type, func_args, run_task=True): """Call the trigger action function.""" action_ast_ctx = AstEval(f"{self.action.global_ctx_name}.{self.action.name}", self.action.global_ctx) diff --git a/tests/test_decorator_errors.py b/tests/test_decorator_errors.py index 7fe5c6c..b03d8cc 100644 --- a/tests/test_decorator_errors.py +++ b/tests/test_decorator_errors.py @@ -213,6 +213,74 @@ def func4(): ) +@pytest.mark.asyncio +async def test_trigger_expression_errors(hass, caplog, monkeypatch): + """Legacy trigger expression errors should not stop trigger loops.""" + notify_q = asyncio.Queue(0) + await setup_script( + hass, + notify_q, + [dt(2020, 7, 1, 10, 59, 59, 999999), dt(2020, 7, 1, 11, 59, 59, 999999)], + """ +seq_num = 0 + +@time_trigger("startup") +def func_startup_sync(trigger_type=None, trigger_time=None): + global seq_num + + seq_num += 1 + pyscript.done = seq_num + +@event_trigger("test_event", "1 / int(arg1)") +def func1(arg1=None): + global seq_num + + seq_num += 1 + pyscript.done = ["event", seq_num, arg1] + +@state_trigger("1 / int(pyscript.var1)") +def func2(var_name=None, value=None): + global seq_num + + seq_num += 1 + pyscript.done = ["state", seq_num, var_name, int(value)] +""", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + assert literal_eval(await wait_until_done(notify_q)) == 1 + + hass.bus.async_fire("test_event", {"arg1": 0}) + await hass.async_block_till_done() + assert notify_q.empty() + + hass.states.async_set("pyscript.var1", 0) + await hass.async_block_till_done() + assert notify_q.empty() + + hass.bus.async_fire("test_event", {"arg1": 1}) + assert literal_eval(await wait_until_done(notify_q)) == ["event", 2, 1] + + hass.states.async_set("pyscript.var1", 1) + assert literal_eval(await wait_until_done(notify_q)) == ["state", 3, "pyscript.var1", 1] + + assert ( + """File "/hello.py", line 1, in file.hello.func1 @event_trigger() + 1 / int(arg1) + ~~^~~~~~~~~~~ +ZeroDivisionError: division by zero""" + in caplog.text + ) + + assert ( + """File "/hello.py", line 1, in file.hello.func2 @state_trigger() + 1 / int(pyscript.var1) + ~~^~~~~~~~~~~~~~~~~~~~ +ZeroDivisionError: division by zero""" + in caplog.text + ) + + @pytest.mark.asyncio async def test_decorator_errors_missing_arg(hass, caplog): """Test decorator syntax and run-time errors."""