usawa

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

commit fe1638302dd048dd8be32f729ccbed9b5190ebd3
parent 429b73bec76553ec64e9053fe0c0b6fb768aef7e
Author: lash <dev@holbrook.no>
Date:   Sat, 21 Mar 2026 19:46:59 -0600

Enable account listing display and match filters

Diffstat:
Mdummy/tests/account.py | 40++++++++++++++++++++++++++++++++++++----
Mdummy/usawa/account.py | 65++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
2 files changed, 88 insertions(+), 17 deletions(-)

diff --git a/dummy/tests/account.py b/dummy/tests/account.py @@ -4,7 +4,7 @@ import unittest import os from usawa import UnitIndex -from usawa.account import AccountIndex +from usawa.account import AccountIndex, AccountType, AccountDisplay from usawa.error import AccountError logging.basicConfig(level=logging.DEBUG) @@ -29,8 +29,8 @@ class TestAccount(unittest.TestCase): idx.add('asset/bar/baz-', sym='FOO') with self.assertRaises(AccountError): idx.add('asset/foo/bar', sym='BAZ') - self.assertFalse(idx.check('BAR', 'liability/foo/baz')) - self.assertTrue(idx.check('FOO', 'liability/bar/baz')) + self.assertFalse(idx.check('BAR', AccountType.liability, 'foo/baz')) + self.assertTrue(idx.check('FOO', AccountType.liability, 'bar/baz')) def test_account_list(self): @@ -39,9 +39,41 @@ class TestAccount(unittest.TestCase): idx.add('liability/bar/baz', sym='FOO') idx.add('asset/foo/baz', sym='BAR') v = list(idx) - logg.debug('results {}'.format(v)) self.assertEqual(len(v), 3) + def test_account_filter(self): + idx = AccountIndex(self.uidx) + idx.add('asset/bar/bar', sym='FOO') + idx.add('liability/bar/baz', sym='FOO') + idx.add('asset/foo/baz', sym='BAR') + idx.add('asset/xyzzy', sym='BAR') + idx.set_filter(sym='FOO') + v = list(idx) + self.assertEqual(len(v), 2) + idx.set_filter(sym='BAR') + v = list(idx) + self.assertEqual(len(v), 2) + idx.set_filter(sym='FOO', typ=AccountType.liability) + v = list(idx) + self.assertEqual(len(v), 1) + idx.set_filter(sym='BAR', typ=AccountType.asset) + v = list(idx) + self.assertEqual(len(v), 2) + + + def test_account_display(self): + idx = AccountIndex(self.uidx) + idx.add('asset/bar/baz', sym='FOO') + v = list(idx) + self.assertEqual(v[0], 'FOO.asset/bar/baz') + idx.set_filter(display=AccountDisplay.typ) + v = list(idx) + self.assertEqual(v[0], 'asset/bar/baz') + idx.set_filter(display=AccountDisplay.path) + v = list(idx) + self.assertEqual(v[0], 'bar/baz') + + if __name__ == '__main__': unittest.main() diff --git a/dummy/usawa/account.py b/dummy/usawa/account.py @@ -13,19 +13,19 @@ def check_path_parts(path): raise AccountError('invalid part: ' + v) #return True typ = getattr(AccountType, parts[0].lower()) - return (typ, parts,) + return (typ, parts[1:],) def from_account_path(p, sym=None, typ=None): if sym != None: p = sym + '.' + p o = p.split('.') - logg.debug('have {} {} {}'.format(p, sym, typ)) if len(o) != 2: raise ValueError('account path should have zero or one symbol specifier') sym = o[0] - o = check_path_parts(o[1]) - typ = o[0] + if typ == None: + o = check_path_parts(o[1]) + typ = o[0] path = o[1] return (sym, typ, path,) @@ -40,6 +40,12 @@ class AccountType(enum.Enum): export = 'Export' +class AccountDisplay(enum.IntEnum): + full = 0 + typ = 1 + path = 2 + + class Account: path_parser = from_account_path @@ -59,8 +65,9 @@ class Account: def to_path(self): - path = self.segments.join('/') - path = '{}.{}/{}'.format(self.sym, self.typ, path) + path = '/'.join(self.segments) + path = '{}.{}/{}'.format(self.sym, self.typ.value.lower(), path) + return path class AccountIndex: @@ -71,6 +78,7 @@ class AccountIndex: self.locked = False #self.validate = pathvalidator self.iterval = None + self.iterfilter = None def add(self, path, sym=None, typ=None): @@ -85,6 +93,8 @@ class AccountIndex: self.accounts[sym] = [] elif path in self.accounts[sym]: logg.debug('Ignoring duplicate account: {}:{}'.format(sym, path)) + path = account.to_path() + logg.info('add account {}'.format(path)) self.accounts[sym].append(path) @@ -92,28 +102,57 @@ class AccountIndex: self.locked = True - def check(self, sym, path): + def check(self, sym, typ, path): + s = '{}.{}/{}'.format(sym, typ.value.lower(), path) try: - return path in self.accounts[sym] + return s in self.accounts[sym] except KeyError: return False - def __iter__(self): + def set_filter(self, sym=None, typ=None, display=AccountDisplay.full): + if typ != None: + typ = typ.value + self.iterfilter = (sym, typ, display,) + + + def __iter__(self, fltr=None): + self.iterval = None keys = list(self.uidx.syms()) keys.sort() + fltr = self.iterfilter for k in keys: - for v in self.accounts[k]: + if fltr != None: + if fltr[0] != None: + if fltr[0] != k: + continue + accounts = self.accounts.get(k) + if accounts == None: + continue + for v in accounts: + path = v + (sym, v) = v.split('.', maxsplit=1) + (typ, v) = v.split('/', maxsplit=1) + if fltr != None: + if fltr[1] != None: + if typ.casefold() != fltr[1].casefold(): + continue if self.iterval == None: self.iterval = [] - self.iterval.append(k + '/' + v) + if fltr != None: + if fltr[2] != AccountDisplay.full: + path = v + if fltr[2] == AccountDisplay.typ: + path = typ + '/' + path + self.iterval.append(path) return self def __next__(self): + if self.iterval == None: + raise StopIteration() if len(self.iterval) == 0: - self.iverval = None + self.iterval = None raise StopIteration() v = self.iterval.pop(0) return v -