venmo/business-rules

Variable memoization seems broken on Python 3.4.1

Closed this issue · 3 comments

The function memoization seems to be broken on Python 3.4.1. I've got the
following variables and rules for a Django model called Entry (just assume
it's got some text and a submitted_on field that is a datetime object).

The variables are configured like so:

class EntryVariables(BaseVariables):

    def __init__(self, entry):
        self.entry = entry

    @string_rule_variable
    def submitted_day(self):
        # Weekday as locale’s full name; e.g. 'Saturday'
        return self.entry.submitted_on.strftime("%A")

While the Actions are configured as:

class EntryActions(BaseActions):

    def __init__(self, entry):
        self.entry = entry

    @rule_action(params={})  # Seems I need params or this doesn't work?
    def send_notification(self):
        print("Have a great Weekend")

I've got the following rules:

entry_rules = [

    {
        "conditions": {
            "all": [
                {
                    "name": "submitted_day",
                    "operator": "equal_to",
                    "value": "Saturday",
                },
            ]
        },
        "actions": [
            {
                "name": "send_notification",
                "fields": [],
            },
        ],
    },
]

And I'm running the rules like so:

# Running all the rules
def run_rules():
    for entry in Entry.objects.all():  # Gives me all Entry instances.
        run_all(
            rule_list=entry_rules,
            defined_variables=EntryVariables(entry),
            defined_actions=EntryActions(entry),
            stop_on_first_trigger=False
        )

As configured above, doing this results in:

.../business_rules/engine.py in _get_variable_value(defined_variables, name)
     67     method = getattr(defined_variables, name, fallback)
     68     val = method()
---> 69     return method.field_type(val)
     70
     71 def _do_operator_comparison(operator_type, operator_name, comparison_value):

AttributeError: 'function' object has no attribute 'field_type'

However, if I skip the memoization of the variables, by using the rule_variable
decorator directly, things work as expected.

class EntryVariables(BaseVariables):

    # ...

    @rule_variable(StringType, cache_result=False)  # Don't cache.
    def submitted_day(self):
        # ...

Meanwhile, at the point in the stack trace above, method appears to be:

<bound method EntryVariables.wrapper of <myapp.rules.EntryVariables object at 0x104834208>>

which does not have a field_type attribute, while val is:

<function _memoize_return_values.<locals>.memf at 0x10486bd08>

and does seem to have the field_type attribute, and also seems to be an
instance of a rule variable (val.is_rule_variable is True).

I'm a little at a loss as to what the intention was here, but I wanted to report
what I've found so far.

Any ideas? Am I doing something obviously wrong?

Thanks in advance!

@bradmontgomery try defining your submitted_day variable like this:

@string_rule_variable()
    def submitted_day(self):
        # Weekday as locale’s full name; e.g. 'Saturday'
        return self.entry.submitted_on.strftime("%A")

Notice the open/close parens on the decorator. This should fix the issue.

@bradmontgomery this PR should fix that requirement: #17

doh! Thanks for the clarification 😅

And 👍 for #17