https://github.com/fyndiq/rules-engine
Rules engine used for Fyndiqs business rules.
https://github.com/fyndiq/rules-engine
Last synced: about 2 months ago
JSON representation
Rules engine used for Fyndiqs business rules.
- Host: GitHub
- URL: https://github.com/fyndiq/rules-engine
- Owner: fyndiq
- License: mit
- Created: 2021-12-10T17:45:57.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-10-09T14:20:38.000Z (8 months ago)
- Last Synced: 2025-03-28T18:54:10.433Z (2 months ago)
- Language: Python
- Homepage:
- Size: 574 KB
- Stars: 8
- Watchers: 6
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Getting Started

[](https://pepy.tech/project/rules-engine) ## Description
Simple rules engine inspired by [Martin Fowler's blog post in
2009](https://www.martinfowler.com/bliki/RulesEngine.html) and
[funnel-rules-engine](https://github.com/funnel-io/funnel-rules-engine).Full Documentation can be found [here](https://fyndiq.github.io/rules-engine/)
## Requirements
python >= 3.6
## How to install
pip install rules-engine
or
poetry add rules-engine## How to use
```python
from rules_engine import Rule, RulesEngine, when, thenname = "fyndiq"
RulesEngine(Rule(when(name == "fyndiq"),then(True), 'it is fyndiq')).run(name)
>>> Result(value=True, message='it is fyndiq')
```## When
Evaluates a condition.
let's check if a value is `None` and raise an exception.
```python
from rules_engine import Rule, RulesEngine, when
obj = Nonedef no_a_string(obj):
return "not a string error"RulesEngine(Rule(when(obj is None), cannot_be_none_error)).run(obj)
>>> Result(value='not a string error', message=None)
```## Then
Evaluates an action.
```python
from rules_engine import Rule, RulesEngine, when
obj = NoneRulesEngine(Rule(when(obj is None), then('not a string error'))).run(obj)
>>> Result(value='not a string error', message=None)
```## Not
The `not_` keyword is a logical operator.
The return value will be `True` if the statement(s) are not `True`, otherwise it will return `False`.
```python
from rules_engine import Rule, RulesEngine, not_
def is_missing(obj):
return not objobj="Hello"
RulesEngine(Rule(not_(is_missing), then(True)), 'object is missing').run(obj)
>>> Result(value=True, message='object is missing')
```## All
Evaluates multiple conditions and if all conditions are `True` the action is executed
- Example:
- We need to check if an object `obj` is not missing and is of type `list````python
from rules_engine import Rule, RulesEngine, all_
def is_missing(obj):
return not objdef is_a_list(obj):
return isinstance(obj, list)obj = [1,2]
RulesEngine(Rule(all_(not_(is_missing), is_a_list), then(True))).run(obj)
>>> Result(value=True, message=None)
```## Any
Evaluates multiple conditions and if any of the conditions is `True` the action is executed
- Example:
- We need to check if an object `obj` is a `str` or a `list````python
from rules_engine import Rule, RulesEngine, any_
def is_a_str(obj):
return isinstance(obj, str)def is_a_list(obj):
return isinstance(obj, list)obj = "Hello"
RulesEngine(Rule(any_(is_a_str, is_a_list), then(True), "it is a string or a list")).run(obj)
>>> Result(value=True, message="it is a string or a list")
```## Run/RunAll
### Run
Runs rules sequentially and exists executes the action for the first passing condition.
```python
from rules_engine import Rule, RulesEngine, then
obj = Nonedef is_integer(value):
return isinstance(value, int)def is_string(value):
return isinstance(value, str)value=1234
RulesEngine(
Rule(is_integer, then("integer")),
Rule(is_string, then("string")),
).run(value)>>> Result(value='integer', message=None)
```Since the first rule satisfies the conditions the rules engine will go no further
### RunAll
Evaluates all conditions and adds them to a list
```python
from rules_engine import Rule, RulesEngine, thendef is_integer(value):
return isinstance(value, int)def is_string(value):
return isinstance(value, str)def is_gr_3_chars(value):
return len(value) > 3value="Hello"
RulesEngine(
Rule(is_integer, then("integer")),
Rule(is_string, then("string")),
Rule(is_gr_3_chars, then("greater than 3 charcters")),
).run_all(value)>>>[Result(value='string', message=None),Result(value='greater than 3 charcters', message=None)]
```
# Full Example
In order for an article to be completed it must have the following rules
1. stock is > 0.
2. image url is present.
3. price exists.```python
from collections import namedtuple
from typing import Unionfrom rules_engine import Otherwise, Rule, RulesEngine, then
Article = namedtuple("Article", "title price image_url stock")
article = Article(title="Iphone Case", price=1000, image_url="http://localhost/image", stock=None)def produce_article_completed_event():
return Nonedef article_controller(article: Article):
if not article.stock:
return False
if not article.price:
raise ValueError("Article price missing")
if not article.image_url:
raise ValueError("Article image_url missing")
return produce_article_completed_event()
```To be able to change to rules engine, we need to split the conditions and actions.
Rules engine is pretty simple if the condition is `True`, its corresponding action will be executed.
```python
### Conditions
def no_article_stock(article):
return not article.stockdef no_article_price(article):
return not article.pricedef no_article_image_url(article):
return not article.image_url### Actions
def article_price_missing_error(article):
raise ValueError("Article price missing")def article_image_missing_error(article):
raise ValueError("Article image_url missing")### Rules Engine
def article_complete_rules(article):
RulesEngine(
Rule(no_article_stock, then(False)),
Rule(no_article_price, article_price_missing_error),
Rule(no_article_image_url, article_image_missing_error),
Otherwise(produce_article_completed_event()),
).run(article)
```