jruizgit / rules

Durable Rules Engine
MIT License
1.15k stars 206 forks source link

Get name of the rule triggered in the consequent #356

Open rdevivo74 opened 3 years ago

rdevivo74 commented 3 years ago

First of all, many thanks to Jesus Ruiz and everyone contributing to this project. I am using this package with Python in an IoT scenario, where it fits perfectly. I started using the basic syntax to define rules, the one where the antecedent, which is defined thorugh a decorator, and the consequent are together.

Then, as I am moving to a more dynamic scenario, I managed to separate the antecedents, which are stored in JSON format in a config file, from the consequents (actions), which are methods in a python module. Before, with the basic syntax, I had an action for each consequent.
In the current scenario that I have in mind, multiple antecedents can lead to the same consequent.

How do I get the name of the rule (antecedent) triggered inside the scope of the consequent? I need to know which rule has been triggered when the predefined action is run. Example:

config.yml

ruleset: {
  "ruleset_name": {
    "rule1": {
      "pri": 1,
      "all": [
        {
          ....
              }
            }
          }
        }
      ],
      "run": "turn_on"
    },
    "rule2": {
      "pri": 1,
      "all": [
        {
          ....
              }
            }
          }
        }
      ],
      "run": "turn_off"
    },
....

  }
 }
}

durable_rule.py


# Action 1
def turn_on(c):
    commands = {"ON_OFF": 1}
    rule_triggered = get_rule_name(c)
    c.s.result = commands, rule_triggered

# Action 2
def turn_off(c):
    commands = {"ON_OFF": 0}
    rule_triggered = get_rule_name(c)
    c.s.result = commands, rule_triggered

Thank you.

dcrespol commented 3 years ago

@alaxtair I also need to get the rule(s) that triggered, which, according to @jruizgit, is not currently supported. By any chance do you have a workaround?

rdevivo74 commented 3 years ago

Hello dcrespol thank you for bringing up my topic as it is still something I have to solve in a short time frame. Unfortunately I did not find a workaround yet but it seems another guy managed to do something interesting for my case: https://github.com/jruizgit/rules/issues/296 This is what I was trying to do at last. If I can manage to pass some kind of argument, hardcoded inside the callback name, I could get that in the callback and know which rule has been triggered. It seems a bit hacky to me but no problem if it works fine.

dcrespol commented 3 years ago

Thanks @alaxtair I was thinking about embedding the parameter value in the action name itself, sort of like what you pointed out: “actionName|myparamvalue”... So, before I tried parsing this, I attempted to create an action with an extra parameter: changed def actionName(c) to def actionName(c, param=‘some value’). In the body, I tried printing param, but for some reason it is populated by the engine with an object of type “Promise”. How are you defining your action?

rdevivo74 commented 3 years ago

I did not manage to make it work unfortunately. I was trying to do the same thing of https://github.com/jruizgit/rules/issues/296 but did not succeed. I tried to do the same thing you described but I got an error so I did not try anything more. If I cannot manage to fix this issue, I cannot define generic actions as I would like to do.

dcrespol commented 3 years ago

@alaxtair here's what I have managed to do, but I'm still not happy.

First, I inherited from engine.Host as follows:

from durable import engine
import durable_rules_engine
import json

class MyDurableRulesEngine(engine.Host):

    _actions = {}

    def post(self, ruleset_name, message):
        try:
            super().post(ruleset_name, message)
        except:
            return

    def add_action(self, action_name, func):
        self._actions[action_name] = func

    def get_action(self, action_name):
        if action_name in self._actions:
            param = action_name.split('|')[1]
            return self._actions[action_name](param)
        else:
            super().get_action(action_name)

    def set_rules(self, rules):
        print('Setting new rules set')
        print(json.dumps(rules))
        self.set_rulesets(rules)

Then, assuming the following ruleset:

{
    "sensor_status_rules": {
        "r_0": {
            "all": [
                {
                    "m": {
                        "$and": [
                            {
                                "$eq": {
                                    "event_name": "status_updated"
                                }
                            },
                            {
                                "$eq": {
                                    "sensor_name": "temperature_reader"
                                }
                            },
                            {
                                "$gte": {
                                    "event_detail.temperature.value": 75
                                }
                            }
                        ]
                    }
                }
            ],
            "run": "emit_rule_triggered|Temperature too high"
        }
    }
}

...and the following actions:

def preprocess_rule_triggered(message):
    print('Preprocessing trigger...')
    print(message)
    print('Calling emit_rule_triggered...')
    return emit_rule_triggered

def emit_rule_triggered(c):
    """the body for the actual call from durable_dules""""

...I did:

for rule_definition in durable_rules["sensor_status_rules"].values():
    action_name = rule_definition["run"]
    self._my_durable_rules_engine.add_action(action_name, preprocess_rule_triggered)

The problem with the above is that I don't have access to the state or anything durable rules engine is providing as part of its c parameter.

rdevivo74 commented 3 years ago

I will start working on this part again because I will need it in the near future. I already looked into the c parameter using the debugging tool. I will let you know as soon as I have results. Please do the same in case you can solve it.

dcrespol commented 3 years ago

@alaxtair FYI, I ended up implementing a version of the suggestion made by @ruslanolkhovsky in issue https://github.com/jruizgit/rules/issues/296. Take a look at the recent activity in that issue.

rdevivo74 commented 3 years ago

Hey @dcrespol, I also managed to make it work yesterday. Thank you very much for pinging me!