Skip to content

Commit

Permalink
feat: adds no match exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
nf1s committed Feb 1, 2024
1 parent 1e28a08 commit 3f73d6e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 19 deletions.
20 changes: 11 additions & 9 deletions src/rules_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
T = TypeVar('T')


class NoMatch(Exception):
message = "No conditions matched"


@dataclass
class Result:
value: Any
Expand Down Expand Up @@ -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__ != "<lambda>" 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]:
Expand Down
39 changes: 29 additions & 10 deletions tests/test_operators.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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"),
],
Expand All @@ -35,24 +33,31 @@ 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"),
],
)
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),
],
)
Expand All @@ -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)

0 comments on commit 3f73d6e

Please sign in to comment.