usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

commit 8e64f048687ef5affed079788f285e3c0ad9b4ef
parent c1319b7331c50f433684b266a4912cc93198ef10
Author: lash <dev@holbrook.no>
Date:   Sun, 24 May 2026 11:21:20 -0600

Attach entry index to ledger object

Diffstat:
Adummy/tests/accountsindex.py | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adummy/tests/entryindex.py | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ddummy/tests/index.py | 78------------------------------------------------------------------------------
Adummy/usawa/index/entry.py | 38++++++++++++++++++++++++++++++++++++++
Mdummy/usawa/ledger.py | 6+++++-
5 files changed, 178 insertions(+), 79 deletions(-)

diff --git a/dummy/tests/accountsindex.py b/dummy/tests/accountsindex.py @@ -0,0 +1,76 @@ +import logging +import datetime +import unittest +import os + +from whee.mem import MemStore + +from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL, schema_path +from usawa.store import LedgerStore +from usawa.account import AccountIndex, AccountType, AccountDisplay +from usawa.error import AccountError +from usawa.index.account import EntryAccountIndex + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +testdir = os.path.realpath(os.path.dirname(__file__)) + +class TestAccountsIndex(unittest.TestCase): + + + def setUp(self): + self.store = MemStore() + self.eidx = EntryAccountIndex() + + + def test_accountsindex(self): + s = 'FOO' + uidx = UnitIndex(s) + wallet = DemoWallet() + o = Ledger(uidx, wallet=wallet) + o.register_callback(self.eidx.entry_callback) + store = LedgerStore(self.store, ledger=o) + store.start() + + + x = EntryPart(s + '.Income/foo', 1337, debit=True) + y = EntryPart(s + '.Asset/foo', 1337) + v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) + v.add_part(x) + v.add_part(y) + v.sign(wallet) + store.add_entry(v, update_ledger=True) + + x = EntryPart(s + '.Expense/bar', 42, debit=True) + y = EntryPart(s + '.Liability/bar', 42) + v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) + v.add_part(x) + v.add_part(y) + v.sign(wallet) + store.add_entry(v, update_ledger=True) + + x = EntryPart(s + '.Expense/bar', 42, debit=True) + y = EntryPart(s + '.Liability/bar', 42) + v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) + v.add_part(x) + v.add_part(y) + v.sign(wallet) + store.add_entry(v, update_ledger=True) + + self.eidx.start('FOO.income/foo') + r = list(self.eidx) + self.assertEqual(len(r), 1) + entry = Entry.empty(serial=r[0]) + entry = store.get_entry(entry) + + self.eidx.start('FOO.expense/bar') + r = list(self.eidx) + self.assertEqual(len(r), 2) + for v in r: + entry = Entry.empty(serial=v) + entry = store.get_entry(entry) + + +if __name__ == '__main__': + unittest.main() diff --git a/dummy/tests/entryindex.py b/dummy/tests/entryindex.py @@ -0,0 +1,59 @@ +import logging +import datetime +import unittest +import os +import uuid + +from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet +from usawa.store import LedgerStore +from usawa.error import AccountError +from usawa.index import EntryIndex + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +testdir = os.path.realpath(os.path.dirname(__file__)) + +class TestEntryIndex(unittest.TestCase): + + def setUp(self): + self.eidx = EntryIndex(digest=True) + self.uidx = UnitIndex('FOO') + self.ledger = Ledger(self.uidx, topic=b'bar', entry_index=self.eidx) + + + def test_entry_index(self): + wallet = DemoWallet() + + dst = EntryPart('FOO.Asset/foo', 1337) + src = EntryPart('FOO.Income/foo', 1337, debit=True) + entry_a = Entry(1, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), ref=str(uuid.uuid4()), description='foo', unitindex=self.uidx) + o = entry_a + o.add_part(src) + o.add_part(dst) + o.sign(wallet) + self.ledger.add_entry(o) + + dst = EntryPart('FOO.Expense/bar', 42) + src = EntryPart('FOO.Asset/bar', -42, debit=True) + now = datetime.datetime.now(datetime.UTC) + entry_b = Entry(2, now, parent=self.ledger.cur, ref=str(uuid.uuid4()), description='bar', unitindex=self.uidx) + o = entry_b + o.add_part(src) + o.add_part(dst) + o.sign(wallet) + self.ledger.add_entry(o) + + r = self.eidx.get_ref(entry_a) + self.assertEqual(r, entry_a.ref) + r = self.eidx.get_digest(entry_a) + self.assertEqual(r, entry_a.sum()) + + r = self.eidx.get_ref(entry_b) + self.assertEqual(r, entry_b.ref) + r = self.eidx.get_digest(entry_b) + self.assertEqual(r, entry_b.sum()) + + +if __name__ == '__main__': + unittest.main() diff --git a/dummy/tests/index.py b/dummy/tests/index.py @@ -1,78 +0,0 @@ -import logging -import datetime -import unittest -import os - -from whee.mem import MemStore - -from usawa import UnitIndex -from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL, schema_path -from usawa.store import LedgerStore -from usawa.account import AccountIndex, AccountType, AccountDisplay -from usawa.error import AccountError -from usawa.index.account import EntryAccountIndex - -logging.basicConfig(level=logging.DEBUG) -logg = logging.getLogger() - -testdir = os.path.realpath(os.path.dirname(__file__)) - -class TestIndex(unittest.TestCase): - - - def setUp(self): - self.store = MemStore() - self.eidx = EntryAccountIndex() - - - def test_accountsindex(self): - s = 'FOO' - uidx = UnitIndex(s) - wallet = DemoWallet() - o = Ledger(uidx, wallet=wallet) - o.register_callback(self.eidx.entry_callback) - store = LedgerStore(self.store, ledger=o) - store.start() - - - x = EntryPart(s + '.Income/foo', 1337, debit=True) - y = EntryPart(s + '.Asset/foo', 1337) - v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) - v.add_part(x) - v.add_part(y) - v.sign(wallet) - store.add_entry(v, update_ledger=True) - - x = EntryPart(s + '.Expense/bar', 42, debit=True) - y = EntryPart(s + '.Liability/bar', 42) - v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) - v.add_part(x) - v.add_part(y) - v.sign(wallet) - store.add_entry(v, update_ledger=True) - - x = EntryPart(s + '.Expense/bar', 42, debit=True) - y = EntryPart(s + '.Liability/bar', 42) - v = Entry(o.peek(), datetime.datetime.now(), parent=o.current()) - v.add_part(x) - v.add_part(y) - v.sign(wallet) - store.add_entry(v, update_ledger=True) - - self.eidx.start('FOO.income/foo') - r = list(self.eidx) - self.assertEqual(len(r), 1) - entry = Entry.empty(serial=r[0]) - entry = store.get_entry(entry) - - self.eidx.start('FOO.expense/bar') - r = list(self.eidx) - self.assertEqual(len(r), 2) - for v in r: - entry = Entry.empty(serial=v) - entry = store.get_entry(entry) - - - -if __name__ == '__main__': - unittest.main() diff --git a/dummy/usawa/index/entry.py b/dummy/usawa/index/entry.py @@ -0,0 +1,38 @@ +class EntryIndex: + + def __init__(self, ref=True, digest=False): + self.idx = {} + self.idx_r = {} + self.idx_z = {} + self.idx_zr = {} + self.m = 0 + if ref: + self.m |= 1 + if digest: + self.m |= 2 + if self.m == 0: + raise AttributeError('nonsense index without anything to index') + + + def register(self, entry): + if self.m & 1 > 0: + if entry.ref == None: + raise AttrubuteError('ref missing') + self.idx[entry.serial] = entry.ref + self.idx_r[entry.ref] = entry.serial + if self.m & 2 > 0: + z = entry.sum() + self.idx_z[entry.serial] = z + self.idx_zr[z] = entry.serial + + + def get_ref(self, entry): + if self.m & 1 == 0: + raise AttributeError('not a ref index') + return self.idx[entry.serial] + + + def get_digest(self, entry): + if self.m & 1 == 0: + raise AttributeError('not a ref index') + return self.idx_z[entry.serial] diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -248,7 +248,7 @@ class Ledger(UsawaElement): :todo: Remove enclosing array for entries :todo: Implement real and virt unit indices for running totals """ - def __init__(self, unitindex, acl=None, serial=0, base=DEFAULTPARENT, topic=None, src=None, wallet=None): + def __init__(self, unitindex, acl=None, serial=0, base=DEFAULTPARENT, topic=None, src=None, wallet=None, entry_index=None): self.uidx = unitindex self.sigs = {} self.entries = {} @@ -258,6 +258,7 @@ class Ledger(UsawaElement): self.lookup_algo = 'sha512' self.pre_cb = [] self.post_cb = [] + self.entry_index = entry_index for k in self.uidx.syms(): if self.running.get(k) != None: @@ -528,6 +529,9 @@ class Ledger(UsawaElement): if not fn(entry): raise VerifyError('entry post callback not passed') + if self.entry_index: + self.entry_index.register(entry) + """Update running total according to the entry.