diff --git a/business_rules/__init__.py b/business_rules/__init__.py index 747404df..a27a6d55 100644 --- a/business_rules/__init__.py +++ b/business_rules/__init__.py @@ -1,8 +1,10 @@ __version__ = '1.0.1' from .engine import run_all +from .engine import run_all_with_results from .utils import export_rule_data # Appease pyflakes by "using" these exports assert run_all +assert run_all_with_results assert export_rule_data diff --git a/business_rules/engine.py b/business_rules/engine.py index eb3c00ad..9bcf1f42 100644 --- a/business_rules/engine.py +++ b/business_rules/engine.py @@ -1,10 +1,10 @@ from .fields import FIELD_NO_INPUT + def run_all(rule_list, defined_variables, defined_actions, stop_on_first_trigger=False): - rule_was_triggered = False for rule in rule_list: result = run(rule, defined_variables, defined_actions) @@ -14,6 +14,40 @@ def run_all(rule_list, return True return rule_was_triggered + +def run_all_with_results(rule_list, defined_variables, defined_actions, + stop_on_first_trigger=False): + '''Run all Rules and return the rules actions results + Returns: + rule_results(list): list of dictionaries. Each dictionary is a rule + actions' results + ''' + rule_results = [] + for rule in rule_list: + actionsResults = run_and_get_results(rule, defined_variables, + defined_actions) + if actionsResults: + rule_results.append(actionsResults) + if stop_on_first_trigger: + return rule_results + return rule_results + + +def run_and_get_results(rule, defined_variables, defined_actions): + '''Run the rule and get the action returned result + Attributes: + rule(dict): the rule dictionary + defined_variables(BaseVariables): the defined set of variables object + defined_actions(BaseActions): the actions object + ''' + actions_results = {} + conditions, actions = rule.get('conditions'), rule.get('actions') + if conditions and actions: + rule_triggered = check_conditions_recursively(conditions, defined_variables) + if rule_triggered: + actions_results = do_actions(actions, defined_actions) + return actions_results + def run(rule, defined_variables, defined_actions): conditions, actions = rule['conditions'], rule['actions'] rule_triggered = check_conditions_recursively(conditions, defined_variables) @@ -68,6 +102,7 @@ def fallback(*args, **kwargs): val = method() return method.field_type(val) + def _do_operator_comparison(operator_type, operator_name, comparison_value): """ Finds the method on the given operator_type and compares it to the given comparison_value. @@ -86,11 +121,28 @@ def fallback(*args, **kwargs): def do_actions(actions, defined_actions): + ''' Run the actions + Attributes: + actions(list): list of dictionaries of actions. e.g: [ + { "name": "put_on_sale", + "params": {"sale_percentage": 0.25}, + } + ] + Returns: + actionsResults(dict): Dictionary of actions results + e.g: {"put_on_sale: [product1, product2, ...]} + ''' + actions_results = {} for action in actions: method_name = action['name'] + def fallback(*args, **kwargs): - raise AssertionError("Action {0} is not defined in class {1}"\ - .format(method_name, defined_actions.__class__.__name__)) + raise AssertionError( + "Action {0} is not defined in class {1}".format(method_name, + defined_actions.__class__.__name__)) + params = action.get('params') or {} method = getattr(defined_actions, method_name, fallback) - method(**params) + actions_results[method_name] = method(**params) + return {method_name: result for method_name, result in + actions_results.iteritems() if result} diff --git a/tests/test_get_actions_results.py b/tests/test_get_actions_results.py new file mode 100644 index 00000000..a3f46481 --- /dev/null +++ b/tests/test_get_actions_results.py @@ -0,0 +1,61 @@ +from business_rules.actions import BaseActions, rule_action +from business_rules.fields import FIELD_TEXT +from business_rules.variables import BaseVariables, boolean_rule_variable +from business_rules.engine import run_all_with_results +from . import TestCase + + +class ActionsResultsClassTests(TestCase): + """ Test methods on getting fired rules actions results + """ + def test_get_actions_results(self): + class SomeVariables(BaseVariables): + @boolean_rule_variable + def this_is_rule_1(self): + return True + + @boolean_rule_variable + def this_is_rule_2(self): + return False + + class SomeActions(BaseActions): + + @rule_action(params={'foo':FIELD_TEXT}) + def some_action_1(self, foo): + return foo + + @rule_action(params={'foobar':FIELD_TEXT}) + def some_action_2(self, foobar): + return foobar + + @rule_action() + def some_action_3(self): + pass + + rule1 = {'conditions': {'all': [ + { + 'name': 'this_is_rule_1', + 'value': True, + 'operator': 'is_true' + }]}, + 'actions': [ + {'name': 'some_action_1', + 'params': {'foo': 'fooValue'} + }]} + rule2 = {'conditions': {'all': [ + { + 'name': 'this_is_rule_2', + 'value': True, + 'operator': 'is_false' + }]}, + 'actions': [ + {'name': 'some_action_2', + 'params': {'foobar': 'foobarValue'} + }, + {'name': 'some_action_3' + }]} + + variables = SomeVariables() + actions = SomeActions() + result = run_all_with_results([rule1, rule2], variables, actions) + self.assertEqual(result, [{'some_action_1': 'fooValue'}, {'some_action_2': 'foobarValue'}])