venmo / business-rules Goto Github PK
View Code? Open in Web Editor NEWPython DSL for setting up business intelligence rules that can be configured without code
License: MIT License
Python DSL for setting up business intelligence rules that can be configured without code
License: MIT License
The rules definition on the GitHub page, which is a JSON payload, is invalid, and I'm not sure what's wrong, please post some valid examples of how to use this package. The example provided on the page is not functional.
Please provide some working examples.
Hello, I am trying to implement this package and I have a error I can't understand
I have defined my variables like this
from business_rules.variables import *
class CommissionVariables(BaseVariables):
def __init__(self, commission):
self.commission = commission
@numeric_rule_variable
def month(self):
return self.commission.mes
@numeric_rule_variable
def commission_amount(self):
return self.commission.mto_comi_pdv
My actions like this
from business_rules.fields import *
from business_rules.actions import *
class CommissionActions(BaseActions):
def __init__(self, commission):
self.commission = commission
@rule_action(params={ "paid": FIELD_TEXT, "obs": FIELD_TEXT })
def mark_recharge_payment(self, paid, obs):
self.commission.pagado_recarga = paid
self.commission.observacion_recarga = obs
self.commission.save()
@rule_action(params={ "paid": FIELD_TEXT, 'obs': FIELD_TEXT })
def mark_first_charge_payment(self, paid, obs):
self.commission.pagado_primera_carga = paid
self.commission.observacion_primera_carga = obs
self.commission.save()
And my rules like this
rules = [
{
"conditions": { "all": [
{
"name": "month",
"operator": "greater_than",
"value": 4,
},
{
"name": "commission_amount",
"operator": "greater_than",
"value": 0,
},
]},
"actions": [
{
"name": "mark_recharge_payment",
"params": [{ "name": "paid", "value": 'NO' }, { 'name': 'obs', 'value': 'Not paid' }],
},
],
}
]
And when I try to run de rules:
commissions = Commission.query.limit(10).all()
for commission in commissions:
run_all(rule_list=rules,
defined_variables=CommissionVariables(commission),
defined_actions=CommissionActions(commission),
stop_on_first_trigger=True
)
I got this error
Traceback (most recent call last):
File "/home/<user>/commissions/main.py", line 51, in <module>
stop_on_first_trigger=True
File "build/bdist.linux-x86_64/egg/business_rules/engine.py", line 10, in run_all
File "build/bdist.linux-x86_64/egg/business_rules/engine.py", line 21, in run
File "build/bdist.linux-x86_64/egg/business_rules/engine.py", line 96, in do_actions
TypeError: mark_recharge_payment() argument after ** must be a mapping, not list
Could you tell me what I did wrong?.
Thanks
rules = [
# expiration_days < 5 AND current_inventory > 20
{ "conditions": { "all": [
{ "name": "expiration_days",
"operator": "less_than",
"value": 5,
},
{ "name": "current_inventory",
"operator": "greater_than",
"value": 20,
},
]},
"actions": [
{ "name": "put_on_sale",
"params": {"sale_percentage": 0.25},
},
],
},
# current_inventory < 5 OR (current_month = "December" AND current_inventory < 20)
{ "conditions": { "any": [
{ "name": "current_inventory",
"operator": "less_than",
"value": 5,
},
]},
{ "all": [
{ "name": "current_month",
"operator": "equal_to",
"value": "December",
},
{ "name": "current_inventory",
"operator": "less_than",
"value": 20,
}
]},
},
"actions": [
{ "name": "order_more",
"params":{"number_to_order": 40},
},
],
}]
some syntax alert in the second conditions
part. Should the all
part be put under the any
?
I am receiving this error when executing run_all:
Traceback (most recent call last):
File "ksl.py", line 225, in <module>
stop_on_first_trigger=False)
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 10, in run_all
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 19, in run
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 31, in check_conditions_recursively
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 46, in check_conditions_recursively
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 54, in check_condition
File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/business_rules-0.1.3-py3.4.egg/business_rules/engine.py", line 68, in _get_variable_value
TypeError: wrapper() missing 1 required positional argument: 'func'
Variables:
class CarVariables(BaseVariables):
""" Variables to look for in validating a car """
def __init__(self, car):
self.avg_miles_per_year = 20000
self.car = car
@numeric_rule_variable
def mileage(self):
return self.car.miles
@numeric_rule_variable
def price(self):
return self.car.price
@numeric_rule_variable
def year(self):
return self.car.year
@string_rule_variable
def make(self):
return self.car.make
@string_rule_variable
def model(self):
return self.car.model
@numeric_rule_variable(label='Mileage over standard yearly miles average.')
def miles_over_average(self):
year_diff = datetime.now().year - self.car.year
avg_miles = self.avg_miles_per_year * year_diff
avg_diff = self.car.miles - avg_miles
return avg_diff
Actions:
class CarActions(BaseActions):
""" Actions that can be taken when rules are met """
def __init__(self, car):
logging.getLogger()
self.car = car
@rule_action(params={})
def ready_for_review(self):
logging.debug('Marking car for review')
self.car.review = True
self.car.post = False
@rule_action(params={})
def not_for_review(self):
self.car.review = False
self.car.post = False
@rule_action(params={})
def ready_for_post(self):
self.car.post = True
I am new to Python, so would like to ask how I can call an async function in the actions specified?
Add a flag to run_all to return failed condition. Could be helpful if we want to display the failed condition in the UI, etc...
Would this be something of interest? If so, I'll take a stab at it.
Greetings!
It looks like with the newest release of setuptools this package is now failing to install. The issue is that there's a newline in the package description which is no longer permitted by setuptools.
https://github.com/venmo/business-rules/blob/master/setup.py#L15
python3.8 -m pip show setuptools
Name: setuptools
Version: 59.0.1
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: https://github.com/pypa/setuptools
Author: Python Packaging Authority
Author-email: [email protected]
License: UNKNOWN
Location: /usr/local/lib/python3.8/site-packages
Requires:
Required-by: check-manifest
python3.8 -m pip install business-rules==1.0.1
Collecting business-rules==1.0.1
Using cached business-rules-1.0.1.tar.gz (13 kB)
ERROR: Command errored out with exit status 1:
command: /usr/local/opt/[email protected]/bin/python3.8 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-install-05q8s9vi/business-rules_19d9ce7311f243dc8d21bd4d97ec02d0/setup.py'"'"'; __file__='"'"'/private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-install-05q8s9vi/business-rules_19d9ce7311f243dc8d21bd4d97ec02d0/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-pip-egg-info-68fyu19n
cwd: /private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-install-05q8s9vi/business-rules_19d9ce7311f243dc8d21bd4d97ec02d0/
Complete output (26 lines):
running egg_info
creating /private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-pip-egg-info-68fyu19n/business_rules.egg-info
writing /private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-pip-egg-info-68fyu19n/business_rules.egg-info/PKG-INFO
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/private/var/folders/qm/qnlxs5yj14vf2x5k94vrd10m0000gn/T/pip-install-05q8s9vi/business-rules_19d9ce7311f243dc8d21bd4d97ec02d0/setup.py", line 12, in <module>
setuptools.setup(
File "/usr/local/lib/python3.8/site-packages/setuptools/__init__.py", line 159, in setup
return distutils.core.setup(**attrs)
File "/usr/local/Cellar/[email protected]/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/core.py", line 148, in setup
dist.run_commands()
File "/usr/local/Cellar/[email protected]/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/dist.py", line 966, in run_commands
self.run_command(cmd)
File "/usr/local/Cellar/[email protected]/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/dist.py", line 985, in run_command
cmd_obj.run()
File "/usr/local/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 292, in run
writer(self, ep.name, os.path.join(self.egg_info, ep.name))
File "/usr/local/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 656, in write_pkg_info
metadata.write_pkg_info(cmd.egg_info)
File "/usr/local/Cellar/[email protected]/3.8.11/Frameworks/Python.framework/Versions/3.8/lib/python3.8/distutils/dist.py", line 1117, in write_pkg_info
self.write_pkg_file(pkg_info)
File "/usr/local/lib/python3.8/site-packages/setuptools/dist.py", line 167, in write_pkg_file
write_field('Summary', single_line(self.get_description()))
File "/usr/local/lib/python3.8/site-packages/setuptools/dist.py", line 151, in single_line
raise ValueError('Newlines are not allowed')
ValueError: Newlines are not allowed
----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/44/b1/5aa6d81111e701e83917ee4b16ac8882c2c4dfde57af4013f1f97a2175ac/business-rules-1.0.1.tar.gz#sha256=e0f58104df937378856e8199a49af4f5b4dbe5ac21b863a080810dedc3326d1d (from https://pypi.org/simple/business-rules/). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
ERROR: Could not find a version that satisfies the requirement business-rules==1.0.1 (from versions: 0.1, 0.1.1, 0.1.2, 0.1.3, 0.2.0, 1.0.1)
ERROR: No matching distribution found for business-rules==1.0.1
The issue can be worked around by downgrading to setuptools==58.5.3
.
Hi Team
Is there a way to capture conditions that eventually made the rule true , e.g. for below rule
{ "conditions": { "any": [
{ "name": "current_inventory",
"operator": "less_than",
"value": 5,
},
]},
{ "all": [
{ "name": "current_month",
"operator": "equal_to",
"value": "December",
},
{ "name": "current_inventory",
"operator": "less_than",
"value": 20,
}
]},
},
"actions": [
{ "name": "order_more",
"params":{"number_to_order": 40},
},
],
}]
that output reason column can have value (current_inventory|current_month;current_inventory) if both of conditions where true
or just (current_month;current_inventory) if just 1 of them was true.
This will help to identify which conditions within a rule made the rule to be true or false on very large dataset for further analysis.
Please let us know if that is possible as it is our 1 of main use case now
Data Eng
Variables represent values in the system , but what if i wanted to compute the data not direct coming from the database but the data that came from an Api end point? where in i will just call the endpoint and parse the values and then do calculations based on the response result .Is the business rule capable for that?
Thank You.
I need to provide logic for things like "not_equal_to" and "does_not_contain" to the @string_rule_variable
. Happy to open a PR for this if it's something you would be ok with adding to the project.
Hi, I have been trying to evaluate business rules for a project that I am working on, and I can't figure out how to parametrize a rule:
For example, I want to trigger the rule if it matches a specific configuration. A product has category
, and the category
is books
, and frequency
is 1.
{ "name": "attribute_frequency",
"operator": "less_than_or_equal_to",
"value": {"key": "category", "value": "books", "max_frequency": 1},
},
It is not possible to pass a more complex objects into the rule variables? Or pass parameters?
How do you address that?
Thank you!
Currently, a variable is compared to a value, then if the rule is triggered, the action is taken.
The variable
comes from the system. It's possible to make a complex logic to compute those variables.
But values
, the ones that are defined during the rule creation, are purely static and simple.
I believe allowing variables to be compared to variables would be very useful, allowing to created more generic rules.
One example: Suppose that we have Users
that can consume a Resource
, but each user have a consumption limitation. That limitation is specific to each user. Thus, in order to restrain the user consumption, we would need to create one business rule for each user, specifying the user capacity in each rule.
If it was possible to compare a variable to another variable, just one rule would be necessary by doing: user.resources_count_variable
< user.max_resource
. Then we could delegate the max_resource definition to another entity.
Right now we define rules like this. for this variable_1
should be there is in class variables or else at the time of evaluation it throws an error.
"rules": {
"conditions": {
"all": [
{
"name": "variable_1",
"operator": "is_true",
"value": true
}
]
},
"actions": [
{
"name": "action_1",
"params": {
}
}
]
}
My requirement is that is variable_1
is not there in class variables, either it should be ignored for evaluation or I should be able to pass a default value at the time of rules generation.
"rules": {
"conditions": {
"all": [
{
"name": "variable_1",
"operator": "is_true",
"value": true,
"default": true
}
]
},
"actions": [
{
"name": "action_1",
"params": {
}
}
]
}
Can some help me with this, how can I achieve this.
Thanks in advance
# current_inventory < 5 OR (current_month = "December" AND current_inventory < 20)
{ "conditions": { "any": [
{ "name": "current_inventory",
"operator": "less_than",
"value": 5,
},
]},
{ "all": [
{ "name": "current_month",
"operator": "equal_to",
"value": "December",
},
{ "name": "goes_well_with",
"operator": "shares_at_least_one_element_with",
"value": ["eggnog", "sugar cookies"],
}
]},
},
the last condition on this is incorrect, it should be current_inventory < 20. I'll issue a pull request later for this, just making a note.
get inspiration from fastapi use of type hinting
As of now in all tests we are using sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
to fetch the the core path and system path. Since it's not the exact method, do an R&D and implement a proper method to fix this.
It would be good to have date and time types (using datetime.date and datetime.datetime internally) that would allow for comparisons using the less_than etc operators.
hi, i got a problem in using busuness-rules.
i need to pass some parameters into variables so that the variables fun can query data from my database.
just like the actions can config some parm
@rule_action(params={"number_to_order": FIELD_NUMERIC})
def order_more(self, number_to_order):
ProductOrder.objects.create(product_id=self.product.id,quantity=number_to_order)
but it seems impossible to achieve.
what can i do?
It seems that you don't support the "select" fieldType for the parameters of an action.
There is no way to specify an option list, and the dictionnary returned by BaseActions.get_all_actions does not handle this case.
Also, the fork at https://github.com/venmo/business-rules-ui would fail with correct data, because of this line : https://github.com/venmo/business-rules-ui/blob/master/lib/business-rules/actions-builder.js#L115 as the "field" var does not exists.
The most challenging part of the implementation would be the definition of the option list. Maybe a rework of the param dictionnary would be needed.
I don't know why the value field on rule is mandatory for no input operator (is_true or is_false).
So I propose a patch to solve that.
variables.py
class ProductVariables(BaseVariables):
def __init__(self, product):
self.product = product
from business_rules import run_all
rules = _some_function_to_receive_from_client()
for product in Products.objects.all():
run_all(rule_list=rules,
defined_variables=ProductVariables(product),
defined_actions=ProductActions(product),
stop_on_first_trigger=True
)
Instead of passing a single product object to this above class from run_all(), is there any way that we can pass whole Query-set of Product model from run_all() method in a way that a bulk support can be added instead of iterating through each product object in run_all() ?
I've an API endpoint, /api/create/
. This creates a new entry in the DB depending upon the payload received from the client.
Payload - {
'field1': 'value1`,
'field2': 'value2`,
'field3': 'value3`,
'field4': 'value4`,
'type': 'A'
}
Now depending on the type
, I've a specific ruleset/workflow. For eg:- Type A
needs to perform certain tasks before saving the data, Type B
has its own set of operations.
I don't have any UI, just the requests coming over the POST request. How can I use business-rules for such a use-case?
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!
Can we get a PyPi release. The action params format expected by the business-rules-ui is out of sync with the params format used by 0.1.3
This function will always return True
, except when other_value
is empty and the for loop is skipped entirely.
@type_operator(FIELD_SELECT_MULTIPLE)
def shares_exactly_one_element_with(self, other_value):
found_one = False
select = SelectType(self.value)
for other_val in other_value:
if select.contains(other_val):
if found_one:
return False
found_one = True
return found_one
The current logic is equivalent to:
return len(other_value) > 0
I have records of 100 students on which I apply the rule.
One of the variable is
@numeric_rule_variable
def age(self):
if self.student.age is not None:
return self.contact.age
else:
return ????
If age is not present in some of the students what should I return in "else: part as this variable expects to return something numerical,
Error:
lib/python3.5/site-packages/business_rules/engine.py", line 81, in fallback
operator_name, operator_type.class.name))
AssertionError: Operator not_equal_to does not exist for type StringType
Based on the rules example in ReadMe, we are able to have both ANY and ALL conditions in one rule and pass it to Venmo/business-rules.
# current_inventory < 5 OR (current_month = "December" AND current_inventory < 20)
{ "conditions": { "any": [
{ "name": "current_inventory",
"operator": "less_than",
"value": 5,
},
]},
{ "all": [
{ "name": "current_month",
"operator": "equal_to",
"value": "December",
},
{ "name": "current_inventory",
"operator": "less_than",
"value": 20,
}
]},
},
"actions": [
{ "name": "order_more",
"params":{"number_to_order": 40},
},
],
}]
However, in the actual code, it doesn't look like working as it described in the example. "==" seems to limit that only when one kind of condition is in the condition list, it will work. For mixed situation it doesn't seem work.
Current Code in business_rules/engine.py
:
def check_conditions_recursively(conditions, defined_variables):
keys = list(conditions.keys())
if keys == ['all']:
assert len(conditions['all']) >= 1
for condition in conditions['all']:
if not check_conditions_recursively(condition, defined_variables):
return False
return True
elif keys == ['any']:
assert len(conditions['any']) >= 1
for condition in conditions['any']:
if check_conditions_recursively(condition, defined_variables):
return True
return False
else:
# help prevent errors - any and all can only be in the condition dict
# if they're the only item
assert not ('any' in keys or 'all' in keys)
return check_condition(conditions, defined_variables)
Here, me and my team want to have a mixed rule with both ANY and ALL conditions, and also wish it won't make any final decision when just validating one of it. So first to check if ANY or ALL exists, then start checking one of it, if doesn't apply, then go to validate the other one if exists.
Proposal code (mainly update "keys == ..." to "... in keys" and update the return condition):
def check_conditions_recursively(conditions, defined_variables):
keys = list(conditions.keys())
if 'any' in keys:
assert len(conditions['any']) >= 1
for condition in conditions['any']:
if check_conditions_recursively(condition, defined_variables):
return True
if 'all' not in keys:
return False
elif 'all' in keys:
assert len(conditions['all']) >= 1
for condition in conditions['all']:
if not check_conditions_recursively(condition, defined_variables):
return False
return True
else:
# help prevent errors - any and all can only be in the condition dict
# if they're the only item
assert not ('any' in keys or 'all' in keys)
return check_condition(conditions, defined_variables)
Please feel free to leave any comments to help me have a better understanding of this part of code. Thank you!
I exported the rules data using export_rule_data(), but the output doesn't seem to be directly compatible with the chrisjpower/business-rules javascript library linked to in the README.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.