Skip to main content

Evaluate rules based on a subject

Project description

Python Simple Rules Engine

rogervila/python_simple_rules_engine

Coverage Quality Gate Status Maintainability Rating

Evaluate rules based on a subject.

Install

pip install python_simple_rules_engine

Usage

The package expects a subject and a list of rules.

Each rule must be a class that extends AbstractRule.

The subject parameter can be any type of object (Any)

Basic usage

Rules return a Evaluation object that should contain a result property defined by the user.

Also, the user can define the value of the stop property to determine if the evaluation process should stop or continue.

In this example, the stop property value does not affect the evaluation process since we are evaluating only one rule.

from python_simple_rules_engine import AbstractRule, Evaluation, run

class FooRule(AbstractRule):
    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        return Evaluation({
            'stop': False, # False by default. When set to True, the evaluation process is stopped.
            'result': (subject == 'foo') # Any. It should contain the evaluation result defined by the user.
        })

evaluation = run('foo', [FooRule()])

print(evaluation.result) # True
print(evaluation.rule) # FooRule instance

Advanced usage

When evaluating multiple rules you can retrieve the historic of rules evaluated for a specific evaluation process by passing the with_history parameter as True.

The final Evaluation object will contain a history list with evaluations returned by the rules evaluated during the evaluation process.

Check test_evaluation_with_history method on tests/test_python_simple_rules_engine.py for a more detailed implementation.

rules = [RuleA(), RuleB(), RuleC()]

# Let's pretend that the final evaluation comes from RuleC()
evaluation = run('C', rules, with_history=True)

print(len(evaluation.history)) # 2
print(evaluation.history[0].rule) # RuleA instance
print(evaluation.history[1].rule) # RuleB instance

Examples

The examples are very simple for demo purposes, but they show the basic features this package comes with.

There is another python rules engine called durable rules that comes with some examples. We will recreate them with this package.

Pattern matching

Find a credit card type based on its number.

Check test_match_example_with_cards method on tests/test_python_simple_rules_engine.py for a more detailed implementation.

class Card():
    def __init__(self, number):
        self.number = number

amex = Card('375678956789765')
visa = Card('4345634566789888')
mastercard = Card('2228345634567898')

class AmexRule(AbstractRule):
    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        card_type = None

        if re.match(r"3[47][0-9]{13}", subject.number):
            card_type = 'amex'

        return Evaluation({'stop': (card_type != None), 'result': card_type})

class VisaRule(AbstractRule):
    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        card_type = None

        if re.match(r"4[0-9]{12}([0-9]{3})?", subject.number):
            card_type = 'visa'

        return Evaluation({'stop': (card_type != None), 'result': card_type})

class MasterCardRule(AbstractRule):
    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        card_type = None

        if re.match(r"(5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|2720)[0-9]{12}", subject.number):
            card_type = 'mastercard'

        return Evaluation({'stop': (card_type != None), 'result': card_type})

# rules order does not affect the result.
rules = [AmexRule(), VisaRule(), MasterCardRule()]

evaluation = run(amex, rules)
print(evaluation.result) # 'amex'
print(evaluation.rule.__class__.__name__) # 'AmexRule'

evaluation = run(visa, rules)
print(evaluation.result) # 'visa'
print(evaluation.rule.__class__.__name__) # 'VisaRule'

evaluation = run(mastercard, rules)
print(evaluation.result) # 'mastercard'
print(evaluation.rule.__class__.__name__) # 'MasterCardRule'

Set of facts

Define the type of an animal based on facts.

In this case, we will compare the current rule result with the previous evaluation result. If they match, we stop the evaluation process.

Check test_facts_example method on tests/test_python_simple_rules_engine.py for a more detailed implementation.

class Animal():
    def __init__(self, eats, lives, color):
        self.eats = eats
        self.lives = lives
        self.color = color

frog = Animal('flies', 'water', 'green')
bird = Animal('worms', 'nest', 'black')

class EatsRule(AbstractRule):
    facts = {'flies': 'frog', 'worms': 'bird'}

    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        previous_result = previous_evaluation.result if previous_evaluation is not None else None
        current_result = self.facts[getattr(subject, 'eats')]

        return Evaluation({'stop': (previous_result == current_result), 'result': current_result})

class LivesRule(AbstractRule):
    facts = {'water': 'frog', 'nest': 'bird'}

    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        previous_result = previous_evaluation.result if previous_evaluation is not None else None
        current_result = self.facts[getattr(subject, 'lives')]

        return Evaluation({'stop': (previous_result == current_result), 'result': current_result})

class ColorRule(AbstractRule):
    facts = {'green': 'frog', 'black': 'bird'}

    def evaluate(self, subject, previous_evaluation: Evaluation = None) -> Evaluation:
        previous_result = previous_evaluation.result if previous_evaluation is not None else None
        current_result = self.facts[getattr(subject, 'color')]

        return Evaluation({'stop': (previous_result == current_result), 'result': current_result})

# rules order does not affect the result.
rules = [EatsRule(), ColorRule(), LivesRule()]

evaluation = run(frog, rules)
print(evaluation.result) # 'frog'

evaluation = run(bird, rules)
print(evaluation.result) # 'bird'

License

This project is open-sourced software licensed under the MIT license.

Icons made by Gregor Cresnar from www.flaticon.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

python_simple_rules_engine-1.0.0.tar.gz (5.8 kB view details)

Uploaded Source

Built Distribution

File details

Details for the file python_simple_rules_engine-1.0.0.tar.gz.

File metadata

File hashes

Hashes for python_simple_rules_engine-1.0.0.tar.gz
Algorithm Hash digest
SHA256 239f89f312c536e145a8c1149586a76a50b2ae235ef1a7a0d155b643b7492f1e
MD5 7afa192afced1c040a22ab399e88cf3a
BLAKE2b-256 4c82e17ee2aaca7593cac134f3fe64c2b4657a1e1f6fbe4d342220e0a0ae0406

See more details on using hashes here.

File details

Details for the file python_simple_rules_engine-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for python_simple_rules_engine-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7de3ce3d615d92424837c9867a10a5652bf829e23da7927ec03575a1842b8b3b
MD5 f6a6c6bbfa2dd1c95fd2d07bedc8aa97
BLAKE2b-256 69591e818cb3d25c7fd96e35374b4eb6139f5470011ce794ccb8d4c2f5ff0f14

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page