From 3f73d6ef2ac9b01b100f7fa15cadcc8cfa986a37 Mon Sep 17 00:00:00 2001 From: Ahmed Nafies Date: Fri, 2 Feb 2024 00:42:26 +0100 Subject: [PATCH] feat: adds no match exceptions --- src/rules_engine/__init__.py | 20 +++++++++--------- tests/test_operators.py | 39 +++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/rules_engine/__init__.py b/src/rules_engine/__init__.py index 7ac154e..c1802de 100644 --- a/src/rules_engine/__init__.py +++ b/src/rules_engine/__init__.py @@ -4,6 +4,10 @@ T = TypeVar('T') +class NoMatch(Exception): + message = "No conditions matched" + + @dataclass class Result: value: Any @@ -36,24 +40,22 @@ class RulesEngine: def __init__(self, *rules: Rule) -> None: self.rules = rules - def _get_message(self, rule) -> Optional[str]: - if rule.message: - return rule.message - return rule.condition.__name__ if rule.condition.__name__ != "" else None - def run(self, *args: Any, **kwargs: Any) -> Any: for rule in self.rules: if rule.condition(*args, **kwargs): - return Result(value=rule.action(*args, **kwargs), message=self._get_message(rule)) + return Result(value=rule.action(*args, **kwargs), message=rule.message) - return Result(value=None, message="No conditions matched") + raise NoMatch def run_all(self, *args: Any, **kwargs: Any) -> list: - return [ - Result(value=rule.action(*args, **kwargs), message=self._get_message(rule)) + results = [ + Result(value=rule.action(*args, **kwargs), message=rule.message) for rule in self.rules if rule.condition(*args, **kwargs) ] + if not results: + raise NoMatch + return results def when(state: bool) -> Callable[..., bool]: diff --git a/tests/test_operators.py b/tests/test_operators.py index 58d3d0c..a92d45b 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -1,6 +1,6 @@ import pytest -from src.rules_engine import Rule, RulesEngine, all_, any_, not_, then, when +from src.rules_engine import NoMatch, Rule, RulesEngine, all_, any_, not_, then, when def raise_cannot_be_none_error(obj): @@ -13,15 +13,13 @@ def test_when_then_operator(): with pytest.raises(ValueError): RulesEngine(Rule(when(obj is None), raise_cannot_be_none_error)).run(obj) - result = RulesEngine(Rule(when(obj is not None), then(True), "obj is None")).run(obj) - assert result.value is None - assert result.message == "No conditions matched" + with pytest.raises(NoMatch): + RulesEngine(Rule(when(obj is not None), raise_cannot_be_none_error)).run(obj) @pytest.mark.parametrize( "condition,action,result", [ - (True, True, None), (False, "A", "A"), (False, "B", "B"), ], @@ -35,7 +33,6 @@ def test_not_operator(condition, action, result): @pytest.mark.parametrize( "conditions,action,result", [ - ([when(False), when(False), when(False)], "A", None), ([when(True), when(False), when(False)], "A", "A"), ([when(True), when(True), when(False)], "A", "A"), ([when(True), when(True), when(True)], "A", "A"), @@ -43,16 +40,24 @@ def test_not_operator(condition, action, result): ) def test_any_operator(conditions, action, result): obj = None - assert RulesEngine(Rule(any_(*conditions), then(action))).run(obj).value is result +@pytest.mark.parametrize( + "conditions,action", + [ + ([when(False), when(False), when(False)], "A"), + ], +) +def test_any_operator_no_match(conditions, action): + obj = None + with pytest.raises(NoMatch): + RulesEngine(Rule(any_(*conditions), then(action))).run(obj) + + @pytest.mark.parametrize( "conditions,action,value,message", [ - ([when(False), when(False), when(False)], "A", None, "No conditions matched"), - ([when(True), when(False), when(False)], "A", None, "No conditions matched"), - ([when(True), when(True), when(False)], "A", None, "No conditions matched"), ([when(True), when(True), when(True)], "A", "A", None), ], ) @@ -62,3 +67,17 @@ def test_all_operator(conditions, action, value, message): result = RulesEngine(Rule(all_(*conditions), then(action))).run(obj) assert result.value == value assert result.message == message + + +@pytest.mark.parametrize( + "conditions,action,value,message", + [ + ([when(False), when(False), when(False)], "A", None, "No conditions matched"), + ([when(True), when(False), when(False)], "A", None, "No conditions matched"), + ([when(True), when(True), when(False)], "A", None, "No conditions matched"), + ], +) +def test_all_operator_no_match(conditions, action, value, message): + obj = None + with pytest.raises(NoMatch): + RulesEngine(Rule(all_(*conditions), then(action))).run(obj)