Skip to content

by code

valhuber edited this page Sep 18, 2020 · 19 revisions

By Code

Logic Specifications (recap)

The logic requirements can be summarized in the following rule-based specification:

Rule.constraint(validate=Customer, as_condition=lambda row: row.Balance <= row.CreditLimit,
                error_msg="balance ({row.Balance}) exceeds credit ({row.CreditLimit})")
Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal,
         where=lambda row: row.ShippedDate is None)

Rule.sum(derive=Order.AmountTotal, as_sum_of=OrderDetail.Amount)

Rule.formula(derive=OrderDetail.Amount, as_expression=lambda row: row.UnitPrice * row.Quantity)
Rule.copy(derive=OrderDetail.UnitPrice, from_parent=Product.UnitPrice)
Rule.formula(derive=OrderDetail.ShippedDate, as_expression=lambda row: row.OrderHeader.ShippedDate)

Rule.sum(derive=Product.UnitsShipped, as_sum_of=OrderDetail.Quantity,
         where="row.ShippedDate is not None")
Rule.formula(derive=Product.UnitsInStock, calling=units_shipped)

The specification addresses around a dozen transactions. Here we look at:

  • Add Order (Check Credit) - enter an order/orderdetails, and rollup to AmountTotal / Balance to check CreditLimit
  • Ship / Unship an Order (Adjust Balance) - when an Order's DateShippped is changed, adjust the Customers balance

These required about 200 lines of code (not counting shareable logic in utils):

  • 20 in __init__.py to set up listeners code here
  • 90 in nw_logic/order_code.py for Order code (code here)
  • 90 in nw_logic/order_detail_code.py for OrderDetail code (code here)

The by-hand code does not address the cascade of ShippedDate into OrderDetails, nor its adjustment to Products.

Add Order: Check Credit

Here we focus on placing an order, and checking credit. The focus here is on multi-level roll-ups, to compute the balance and check it against the credit.

Execution begins in trans_tests/add_order.py.

The import statement from nw.nw_logic import session runs nw_logic/__init__, which opens the database and registers the listeners.

When add_order.py issues commit, sqlalchemy invokes the listeners. These forward before_flush events to nw_logic/order_details_code.py and nw_logic/order_code.py.

Adjustment Logic

Adjustments require we access/update rows that were not in the dirty list. Observations

  • for attached parents (like customer, arising from the upd_order_shipped test), updates are automatic. They do not, however, trigger flush logic.
  • for non-attached parents (like customer, arising from the add_order test)
    • you need to read them
    • add them to the session
    • and explicitly invoke their update logic (customer_update(customer, old_customer, a_session))

Ship / Unship an Order: Adjust Balance

Here we explore old values - we need to see what the ShippedDate was, and what it is changed to:

  • If it changed from empty to shipped, we need to decrease the balance.
  • If it changed from shipped to empty, we decrease the balance.

Observe the service to get an old_row, so you can code things like if a_row.ShippedDate != an_old_row.ShippedDate: Flow is as described above, reaching nw_logic/order_logic.py Note the call to logic_engine/util.get_old_row.

Clone this wiki locally