Contents
- pg_accumulatorSQLAlchemy
- Post Movement
- Unpost Document
- Repost (Correct)
- Create Order (ORM + Accumulator)
- Recent Orders
- Warehouses (SQLAlchemy ORM)
- Add Warehouse
- Products (SQLAlchemy ORM)
- Add Product
- Clients (SQLAlchemy ORM)
- Add Client
- Current Balances (from balance_cache — O(1) reads)
- Recent Movements (via handle.movements(limit=50))
- Balance Query
- Turnover Query
- Movements Query
- 1. Define a Register
- 2. Create Client & Register
- 3. Post & Query
- 4. ORM + Accumulator — Same Transaction
- Registered Accumulation Registers
- Register Details
- pg_accumulatorSQLAlchemy
- Post Movement
- Unpost Document
- Repost (Correct)
- Create Order (ORM + Accumulator)
- Recent Orders
- Warehouses (SQLAlchemy ORM)
- Add Warehouse
- Products (SQLAlchemy ORM)
- Add Product
- Clients (SQLAlchemy ORM)
- Add Client
- Current Balances (from balance_cache — O(1) reads)
- Recent Movements (via handle.movements(limit=50))
- Balance Query
- Turnover Query
- Movements Query
- 1. Define a Register
- 2. Create Client & Register
- 3. Post & Query
- 4. ORM + Accumulator — Same Transaction
- Registered Accumulation Registers
- Register Details
pg_accumulatorSQLAlchemy
Interactive demo of sqlalchemy-accumulator — the type-safe Python adapter for pg_accumulator
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %}{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
{% if error %}
{{ error }}
{% endif %}
Operations
Orders (ORM+Accum)
Catalog (ORM)
Live Data
Query API
Code Examples
Registers
Post Movement
Record inventory receipt or shipment via handle.post()
Unpost Document
Cancel all movements for a recorder via handle.unpost()
Repost (Correct)
Atomically replace movements via handle.repost()
Create Order (ORM + Accumulator)
Creates an ORM Order and posts inventory movement in a single transaction
# What happens behind the scenes:
with Session(engine) as session:
# 1) ORM: create order + order line
session.add(order)
session.flush()
# 2) Accumulator: post movement (same tx!)
accum = AccumulatorClient(session)
accum.use(inventory).post({...})
session.commit() # atomic!
Recent Orders
{% if orders %}| # | Client | Warehouse | Status | Action |
|---|---|---|---|---|
| {{ o.id }} | {{ o.client.name if o.client else '?' }} | {{ o.warehouse.name if o.warehouse else '?' }} | {% if o.status == 'posted' %} ● posted {% elif o.status == 'cancelled' %} ● cancelled {% else %} ● {{ o.status }} {% endif %} | {% if o.status == 'posted' %} {% endif %} |
No orders yet. Create one to see ORM + Accumulator in action.
{% endif %}
Warehouses (SQLAlchemy ORM)
{% if warehouses %}| ID | Name | Address |
|---|---|---|
| {{ w.id }} | {{ w.name }} | {{ w.address or '—' }} |
Add Warehouse
Products (SQLAlchemy ORM)
{% if products %}| ID | SKU | Name | Price | Category |
|---|---|---|---|---|
| {{ p.id }} | {{ p.sku }} |
{{ p.name }} | ${{ p.unit_price }} | {{ p.category or '—' }} |
Add Product
Clients (SQLAlchemy ORM)
{% if clients %}
{% endif %}
| ID | Name | Phone | |
|---|---|---|---|
| {{ c.id }} | {{ c.name }} | {{ c.email or '—' }} | {{ c.phone or '—' }} |
Add Client
Current Balances (from balance_cache — O(1) reads)
{% if balances %}
{% else %}
| Warehouse | Product | Quantity | Amount |
|---|---|---|---|
| {{ b.warehouse }} — {{ b.warehouse_name }} | {{ b.product }} — {{ b.product_name }} | {{ b.quantity }} | {{ b.amount }} |
No balance data yet. Post some movements first.
{% endif %}
Recent Movements (via handle.movements(limit=50))
{% if movements %}
{% else %}
| Recorder | Period | Warehouse | Product | Quantity | Amount |
|---|---|---|---|---|---|
| {{ m.get('recorder', '') }} | {{ m.get('period', '')[:10] if m.get('period') else '' }} | {{ m.get('warehouse', '') }} — {{ m.get('warehouse_name', '') }} | {{ m.get('product', '') }} — {{ m.get('product_name', '') }} | {{ m.get('quantity', '') }} | {{ m.get('amount', '') }} |
No movements recorded yet.
{% endif %}
Balance Query
Get instant balance via handle.balance()
Turnover Query
Aggregate turnover via handle.turnover()
Movements Query
Browse movement history via handle.movements()
1. Define a Register
from sqlalchemy_accumulator import define_register
inventory = define_register(
name="inventory",
kind="balance",
dimensions={"warehouse": "int", "product": "int"},
resources={"quantity": "numeric(18,4)", "amount": "numeric(18,2)"},
)
2. Create Client & Register
from sqlalchemy import create_engine
from sqlalchemy_accumulator import AccumulatorClient
engine = create_engine("postgresql://user:pass@localhost/mydb")
accum = AccumulatorClient(engine)
# Create the register in the database (one-time)
accum.create_register(inventory)
3. Post & Query
handle = accum.use(inventory)
# Post a warehouse receipt
handle.post({
"recorder": "receipt:1",
"period": "2026-04-01",
"warehouse": 1, "product": 101,
"quantity": 500, "amount": 125000,
})
# Instant balance — O(1) from materialized cache
bal = handle.balance(warehouse=1, product=101)
# {'quantity': Decimal('500'), 'amount': Decimal('125000')}
# Historical balance at a specific date
bal = handle.balance(warehouse=1, at_date="2026-04-01")
# Undo a document — all movements reversed atomically
handle.unpost("receipt:1")
# Correct a document — atomic repost
handle.repost("receipt:1", {
"recorder": "receipt:1",
"period": "2026-04-01",
"warehouse": 1, "product": 101,
"quantity": 600, "amount": 150000,
})
4. ORM + Accumulator — Same Transaction
from sqlalchemy.orm import Session
with Session(engine) as session:
# 1) Create ORM entities as usual
order = Order(client_id=1, warehouse_id=1, status="posted")
session.add(order)
session.flush() # get order.id
session.add(OrderLine(
order_id=order.id, product_id=101,
quantity=50, unit_price=250, amount=12500,
))
# 2) Post accumulator movement in the SAME transaction
accum = AccumulatorClient(session)
accum.use(inventory).post({
"recorder": f"order:{order.id}",
"period": "2026-04-19",
"warehouse": 1, "product": 101,
"quantity": -50, "amount": -12500,
})
# 3) Both ORM and accumulator commit atomically
session.commit()
# If anything fails — everything rolls back!
Registered Accumulation Registers
{% if registers %}
{% for r in registers %}
{{ r.name }}
{{ r.kind }}
{{ r.dimensions }} dims · {{ r.resources }} resources · {{ r.movements_count }} movements
{% endfor %}
{% else %}
No registers found. Check database connection.
{% endif %}