usawa

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

commit 83e5c191a0e0324109d8e10cd54cc9a367eadc98
parent 56f718d1f95230bbc2af0080e94039048b512606
Author: lash <dev@holbrook.no>
Date:   Mon,  9 Mar 2026 07:00:03 -0600

Introduce pre and post callback for ledger

Diffstat:
Mdummy/requirements.txt | 1+
Mdummy/tests/ledger.py | 42+++++++++++++++++++++++++++++++++++++++++-
Mdummy/usawa/ledger.py | 30+++++++++++++++++++++++-------
3 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/dummy/requirements.txt b/dummy/requirements.txt @@ -6,3 +6,4 @@ PyNaCl==1.6.0 python-gnupg==0.4.9 rencode==1.0.8 wheepy[valkey]~=0.0.3 +filemagic~=1.6 diff --git a/dummy/tests/ledger.py b/dummy/tests/ledger.py @@ -8,7 +8,9 @@ import lxml.etree from whee.mem import MemStore from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL, schema_path +from usawa.ledger import CallbackType from usawa.store import LedgerStore +from usawa.error import VerifyError logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -16,6 +18,14 @@ logg = logging.getLogger() testdir = os.path.realpath(os.path.dirname(__file__)) +def cb_no(self): + return False + + +def cb_yes(self): + return True + + class TestLedger(unittest.TestCase): def setUp(self): @@ -148,7 +158,37 @@ class TestLedger(unittest.TestCase): ledger.truncate() self.assertEqual(ledger.serial, 2) - + + + def test_ledger_callback(self): + s = 'FOO' + uidx = UnitIndex(s) + uidx.add('USD') + ledger = Ledger(uidx) + ledger.register_callback(cb_yes) + ledger.register_callback(cb_yes, CallbackType.PRE) + store = LedgerStore(self.store, ledger=ledger) + store.start() + + wallet = DemoWallet() + ledger.set_wallet(wallet) + x = EntryPart(s, 'income', 'foo', 1337, debit=True) + y = EntryPart(s, 'asset', 'foo', 1337) + v = Entry(ledger.peek(), datetime.datetime.now(), parent=ledger.current()) + v.add_part(x, debit=True) + v.add_part(y) + v.sign(wallet) + ledger.add_entry(v) + + ledger.register_callback(cb_no, CallbackType.POST) + x = EntryPart(s, 'income', 'foo', 1337, debit=True) + y = EntryPart(s, 'asset', 'foo', 1337) + v = Entry(ledger.peek(), datetime.datetime.now(), parent=ledger.current()) + v.add_part(x, debit=True) + v.add_part(y) + v.sign(wallet) + with self.assertRaises(VerifyError): + ledger.add_entry(v) if __name__ == '__main__': unittest.main() diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -1,3 +1,4 @@ +import enum import os import datetime @@ -19,6 +20,10 @@ from .error import VerifyError logg = logging.getLogger('usawa.ledger') +class CallbackType(enum.Enum): + PRE = 'PRE' + POST = 'POST' + class RunningTotal: """RunningTotal is used by the Ledger object to keep track of running totals in all asset and transaction categories of a given unit symbol. @@ -245,7 +250,8 @@ class Ledger: self.wallet = None self.lookup = None self.lookup_algo = 'sha512' - self.entry_cb = [] + self.pre_cb = [] + self.post_cb = [] for k in self.uidx.syms(): if self.running.get(k) != None: @@ -306,8 +312,13 @@ class Ledger: :param fn: Callback function :type fn: function """ - def register_callback(self, fn): - self.entry_cb.append(fn) + def register_callback(self, fn, typ=CallbackType.POST): + if typ == CallbackType.PRE: + self.pre_cb.append(fn) + elif typ == CallbackType.POST: + self.post_cb.append(fn) + else: + raise ValueError('invalid callback type') """Retrieve the serial that will be assigned to the next entry, without incrementing it in the object state. @@ -481,7 +492,11 @@ class Ledger: if check_parent and self.cur != entry.parent: raise ValueError('entry parent {} does not match ledger state {}'.format(entry.parent.hex(), self.cur.hex())) self.check_sigs(entry) - + + for fn in self.pre_cb: + if not fn(entry): + raise VerifyError('entry pre callback not passed') + # update the internal state self.serial = entry.serial #oldsum = self.cur @@ -497,10 +512,11 @@ class Ledger: # Add entry to the ledger object. self.entries[entry.serial] = entry - for fn in self.entry_cb: - fn(entry) + for fn in self.post_cb: + if not fn(entry): + raise VerifyError('entry post callback not passed') + - """Update running total according to the entry. Object does not keep track of which entries have been applied to the running total. Caller must take care not to call this more than once for each entry.