This Tutorial has been moved to examples in LogicBank.
This project explores the shortest possible example of multi-table logic, to illustrate the use of LogicBank:
- Add an Order to a Customer
- Rules declared to
- Adjust the Customer Balance, and
- Verify the Balance <= 2000
Use Logic Bank to govern SQLAlchemy update transaction logic - multi-table derivations, constraints, and actions such as sending mail or messages. Logic consists of both:
-
Rules - 40X more concise using a spreadsheet-like paradigm, and
-
Python - control and extensibility, using standard tools and techniques
Logic Bank is based on SQLAlchemy - it handles before_flush
events
to enforce your logic.
Begin by cloning this repository:
git clone https://github.com/valhuber/LogicBankTutorial.git
It is good idea (but not required) to create a virtual environment for this project. We'll do this using virtualenv to keep things simple:
cd LogicBankTutorial # Create a virtualenv in which we can install the dependencies
virtualenv venv # windows: python -m venv venv
source venv/bin/activate # windows: .\venv\Scripts\activate
Now we can install our dependencies:
pip install -r requirements.txt
Verify that the following runs, but fails:
cd tests
python add_order.py
It failed our assert that the balance increased. This is expected, because there are no rules.
Let's fix that: add the following to logic/rules_bank.py
:
Rule.constraint(validate=Customer,
error_msg="balance ({row.Balance}) exceeds 2000)",
as_condition=lambda row: row.Balance <= 2000)
Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal)
You can paste these rules, or build them with an IDE such as PyCharm using code completion:
The logic/__init__
file contains the code
that opens the database and registers the rules,
near the end:
conn_string = "sqlite:///" + db_loc
engine = sqlalchemy.create_engine(conn_string, echo=False) # sqlalchemy sqls...
session_maker = sqlalchemy.orm.sessionmaker()
session_maker.configure(bind=engine)
session = session_maker()
LogicBank.activate(session=session, activator=declare_logic)
print("\n" + prt("END - connected, session created, listeners registered\n"))
Note rules are based on your data model. Important considerations apply, as described in the Logic Bank Wiki.
You don't invoke the rules directly; the Logic Base
Rule Engine handles before_flush
events
to
- watch for changes to referenced attributes
- react by running the referencing rules
- changes can chain to other rules
You can re-run the test, which should now succeed.
Here, the insertion of an Order with
AmountTotal
triggers theCustomer.Balance
rule.
Note the log, which shows all the rules that fire:
Scalability depends on SQL tuning. Logic Base addresses this with techniques such as:
-
Adjustments - in the example above, observe the system added 500 to the existing Balance with a 1 row (adjustment) update - not an expensive
Select sum
query -
Pruning - parent (Customer) updates are eliminated altogether if there is no change to the summed field, the qualification, or the foreign key.
You can explore scalability, here.
Find this line in tests/add_order.py
, and change the 500 to 1000:
amount_total = 500 # 500 should work; change to 1000 to see constraint fire
Re-run the test; it should now fail with a constraint exception.
Note rules are not tied to a specific verb, but rather to the data. That means they apply to (are reused over) all transactions.
So, our sum
rule will also react if you delete an order, or
update an order with the AmountTotal
changed.
This is because rules are the declarative, as described here.
This completes our simple example. You will typically have more rules that fire on a transaction. You can explore a more typical set of rules in the Logic Bank examples.
In this example, our logic was strictly spreadsheet-like rules. You will typically need to extend rules with Python code, as illustrated in extensibility, here.
Just as with regular Python, you can set breakpoints, perform logging, etc.