Skip to content

Commit f1fa60d

Browse files
authored
Filter Event Handler Expression (#197)
* lib/filtereventhandler: Add support for general expression * lib/filtereventhandler: Add test cases * lib/filtereventhandler: Handle eval exceptions
1 parent 4ee7382 commit f1fa60d

3 files changed

Lines changed: 483 additions & 1 deletion

File tree

qtoggleserver/lib/filtereventhandler.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from qtoggleserver.core import expressions as core_expressions
77
from qtoggleserver.core import ports as core_ports
88
from qtoggleserver.core.expressions import exceptions as expression_exceptions
9+
from qtoggleserver.core.expressions.exceptions import ExpressionEvalException
910
from qtoggleserver.core.typing import Attribute, Attributes, NullablePortValue
1011
from qtoggleserver.slaves import devices as slaves_devices
1112
from qtoggleserver.slaves import events as slaves_events
@@ -41,6 +42,8 @@ def __init__(self, *, filter: dict | None = None, name: str | None = None) -> No
4142
self._filter_slave_attr_transitions: dict[str, tuple[Attribute, Attribute]] = {}
4243
self._filter_slave_attr_names: set[str] = set()
4344

45+
self._filter_expression: core_expressions.Expression | None = None
46+
4447
# Maintain an internal "last" state for all objects, so we can detect changes in attributes and values
4548
self._device_attrs: Attributes | dict[str, list[Attribute]] = {}
4649
self._port_values: dict[str, NullablePortValue] = {}
@@ -113,6 +116,18 @@ def _prepare_filter(self) -> None:
113116
self._filter_slave_attr_names.update(self._filter_slave_attrs.keys())
114117
self._filter_slave_attr_names.update(self._filter_slave_attr_transitions.keys())
115118

119+
filter_sexpression = self._filter.get("expression")
120+
if isinstance(filter_sexpression, str):
121+
try:
122+
self.debug('using filter expression "%s"', filter_sexpression)
123+
self._filter_expression = core_expressions.parse(
124+
self_port_id=None, sexpression=filter_sexpression, role=core_expressions.Role.FILTER
125+
)
126+
except expression_exceptions.ExpressionParseError as e:
127+
self.error('failed to parse filter expression "%s": %s', filter_sexpression, e)
128+
129+
raise
130+
116131
self._filter_prepared = True
117132
self.debug("filter prepared")
118133

@@ -261,7 +276,11 @@ async def accepts_port_value(
261276
elif isinstance(self._filter_port_value, core_expressions.Expression): # an expression
262277
port_values = {p.get_id(): p.get_last_read_value() for p in core_ports.get_all() if p.is_enabled()}
263278
eval_context = core_expressions.EvalContext(port_values=port_values, now_ms=int(time.time() * 1000))
264-
if new_value != await self._filter_port_value.eval(context=eval_context):
279+
try:
280+
if new_value != await self._filter_port_value.eval(context=eval_context):
281+
return False
282+
except ExpressionEvalException as e:
283+
self.warning('Expression evaluation failed for "%s": %s', self._filter_port_value, e)
265284
return False
266285
elif new_value != self._filter_port_value:
267286
return False
@@ -322,6 +341,16 @@ async def accepts(
322341
):
323342
return False
324343

344+
if self._filter_expression:
345+
port_values = {p.get_id(): p.get_last_read_value() for p in core_ports.get_all() if p.is_enabled()}
346+
eval_context = core_expressions.EvalContext(port_values=port_values, now_ms=int(time.time() * 1000))
347+
try:
348+
if not await self._filter_expression.eval(context=eval_context):
349+
return False
350+
except ExpressionEvalException as e:
351+
self.warning('Expression evaluation failed for "%s": %s', self._filter_expression, e)
352+
return False
353+
325354
return True
326355

327356
def get_device_attrs(self) -> Attributes:

tests/unit/qtoggleserver/lib/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)