Package: rulesengine
rulesengineIs there any developer liking to work with hundreds of chained if/else? If you're one of those this package isn't for you.A rules engine is a way to structure your code as pipelines. The objective is to create chained modular and reusable rules with a simple structure structure :Afilter: can I do some stuff?Anaction: I know I can do some stuff so I do itWith a rules engine you have to think your pipeline has stackable modules to generate easily readable pipelines.Example with some potatoesHere is an example on how to (try to) eat smashed potatoes:# How to eat smashed potatoesrules=RulesEngine()rules.add(GetPotatoes)rules.add(CookPotatoes)rules.add(EatPotatoes)rules.add(SmashPotatoes)rules.add(EatSmashedPotatoes)rules_output=rules.run()rules_ouput.get("Eaten smashed potatoes portions")# Sorry there is nothingWith a pipeline like this one, you won't eat any smashed potatoes but you'll
still have eaten some potatoes (ifGetPotatoesgot you at least one potato).
Let's see theSmashPotatoesrule to understand:classSmashPotatoes(Rule):RULE_NAME="Smash potatoes"deffilter(self,input_data):"""Can I really smash potatoes?"""# Get my smashable potatoessmashable_potatoes=input_data.get("cooked potatoes")# Have I any smashable potatoes ?ifsmashable_potatoesisNoneorsmashable_potatoes<1:# There isn't any potato to smash. We won't do any thing...returnFalse# YES! We can smash potatoes!!!returnTruedefaction(self,input_data):"""Let's smash potatoes because we can"""# Get you potatoessmashable_potatoes=input_data.get("cooked potatoes")# Do your job (an other RulesEngine?)smashed_potatoes_portions=...input_data.set("smashed potatoes portions",smashed_potatoes_portions)input_data.set("cooked potatoes",0)In this rule if the filter isn't applied, we won't apply the action :Nocooked potatoesno action and finally nosmashed potatoes portionsAt least 1cooked potatoes, the action is performed and we have at least 1smashed potatoes portionsInstallationpipinstallrulesengineDocumentation & HelpCreate a rulefromrulesengineimportRuleclassMyRule(Rule):RULE_NAME="A minimal rule"# An execution order (positive or -1). Rules of same level are executed in their# declaration order. -1 are executed lastly and minimal values are executed firstlyRULE_ORDER=-1deffilter(self,input_data):"""Test if you can apply your rule, are all variables initialized?"""returnTruedefaction(self,input_data):"""Do some stuff"""Access dataAn object is passed between rules and returned by theRulesEngine.run()method :RulesData# Get an element (return None if not initialized)my_data.get('my element key')# Set or update an elementmy_data.set('my element key',something)# Delete an element (let's free some memory!)my_data.delete('my element key')# Get previously executed rulemy_data.get_previous()# Set expected next rulemy_data.set_expected_next(MyNextRule.RULE_NAME)Create pipelinefromrulesengineimportRulesEngine# Initialize the pipelinerules=RulesEngine()rules.add(MyRule,forced_order=0)# Will be executed firstrules.add(MyRule)# The same rule againrules.add(AnOtherRule,previous_rule=MyNextRule.RULE_NAME)# Won't match the previous rule conditionrules.add(AnOtherRule,converter=a_funtion)# Sometime, to reuse rules we need to do extra stuff before the rulerules_output=rules.run()Pipelines doesn't have to be run to the endfromrulesengineimportRulesCourse# Run every runnable rules (default)rules=RulesEngine(rules_course=RulesCourse.non_blocking)# Stop on the first rules's filter returning Falserules=RulesEngine(rules_course=RulesCourse.block_on_false)# Stop on the first rules's filter returning Truerules=RulesEngine(rules_course=RulesCourse.block_on_true)# Raise an error on the first rules's filter returning Falserules=RulesEngine(rules_course=RulesCourse.error_on_false)Populate the pipeline before execution# At creationrules=RulesEngine({"data_1":1,"data_2":list(),})# afterwardrules.set(key,value)Manage errorsThere is only one rulesengine error :rulesengine.RulesEngineExceptionContributingAs an open source project, rulesengine welcomes contributions of all forms (especially culinary but please don't send any potato, the smashing potato workflow will fail).