usawa

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

commit 9684a00072c9e3ae02056fa2d3ce5e1dbbba496d
parent ed233df02eeecba0de1643d841d2169f651ebdb1
Author: lash <dev@holbrook.no>
Date:   Sat, 16 May 2026 08:21:41 -0600

Use lookup hash for recalc parents

Diffstat:
Mdummy/setup.cfg | 2+-
Mdummy/usawa/asset.py | 2--
Mdummy/usawa/cli/entry.py | 27++++++++++++++++-----------
Mdummy/usawa/context.py | 15+++++++++------
Mdummy/usawa/data/schema.xsd | 2++
Mdummy/usawa/data/usawa.ini | 3+++
Mdummy/usawa/entry.py | 4++--
Mdummy/usawa/ledger.py | 4+++-
Mdummy/usawa/runnable/export.py | 86++++++++++---------------------------------------------------------------------
Mdummy/usawa/runnable/recalc.py | 14+++++++++++---
Mdummy/usawa/store.py | 1+
Mdummy/usawa/tag.py | 2+-
Mdummy/usawa/unit.py | 6++++--
13 files changed, 64 insertions(+), 104 deletions(-)

diff --git a/dummy/setup.cfg b/dummy/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = usawa -version = 0.3.0 +version = 0.3.1 description = Signed, immutable accounting. author = Louis Holbrook author_email = dev@holbrook.no diff --git a/dummy/usawa/asset.py b/dummy/usawa/asset.py @@ -329,7 +329,6 @@ class Asset(UsawaElement): for k in ['mime', 'ref', 'slug', 'ext', 'description', 'extref', 'enc']: #for k in ["mime", "slug", "ext", "description", "extref", "enc"]: v = getattr(self, k) - logg.debug('serialize asset part {} {}'.format(k, v)) d.append(v) return d @@ -364,7 +363,6 @@ class Asset(UsawaElement): vv = vv.decode("utf-8") setattr(o, k, vv) i += 1 - logg.debug('deserialized asset part {} {}'.format(k, vv)) if isinstance(digest, str): digest = bytes.fromhex(digest) o.digest = digest diff --git a/dummy/usawa/cli/entry.py b/dummy/usawa/cli/entry.py @@ -255,12 +255,14 @@ class EntrySession: self.amount = float(amount) self.attach_count = 0 + self.edit = False self.entry = try_entry(self.ctx, entry) if self.entry == None: self.entry = Entry.empty(unitindex=self.ctx.uidx, description=self.description, ref=self.ref, extref=self.extref, tx_date=self.dt, parent=self.ctx.ledger.cur) if self.entry.serial < 1: - #raise NotImplementedError('entry edit not yet implemented') self._do_prepare() + else: + self.edit = True def _cur_amount(self): @@ -446,19 +448,22 @@ class EntrySession: logg.error('abort write on check error: ' + str(e)) if self.final: - self.entry.description = self.get_description() - self.entry.parent = self.ctx.ledger.cur - self.entry.serial = self.ctx.ledger.next_serial() - self.entry.sign(self.ctx.wallet) - self.ctx.store.add_entry(self.entry, update_ledger=True) + if not self.edit: + self.entry.description = self.get_description() + self.entry.parent = self.ctx.ledger.cur + self.entry.serial = self.ctx.ledger.next_serial() + self.entry.sign(self.ctx.wallet) + update_ledger = not self.edit + self.ctx.store.add_entry(self.entry, update_ledger=update_ledger, overwrite=self.edit) self.ctx.store.put_draft(self.entry) self.ctx.ledger.truncate() self.ctx.ledger.sign() - f = open(self.ctx.ledger_path_out, 'w') - f.write(self.ctx.ledger.to_string()) - f.close() - if self.ctx.resolver != None: - self.ctx.resolver.put_entry(self.entry, lookup='sha512') + if not self.edit: + f = open(self.ctx.ledger_path_out, 'w') + f.write(self.ctx.ledger.to_string()) + f.close() + if self.ctx.resolver != None: + self.ctx.resolver.put_entry(self.entry, lookup='sha512') else: self.ctx.store.put_draft(self.entry) diff --git a/dummy/usawa/context.py b/dummy/usawa/context.py @@ -47,20 +47,23 @@ class UsawaContext: def init(self, args=None, store_scope=None): - v = None + #v = None if args != None: try: - v = args.i + self.ledger_path_in = args.i except AttributeError: pass - self.ledger_path_in = self.cfg.get('MAIN_LEDGER_FILE', v) - v = None + if self.ledger_path_in == None: + self.ledger_path_in = self.cfg.get('MAIN_LEDGER_FILE') + #v = None if args != None: try: - v = args.o + #v = args.o + self.ledger_path_out = args.o except AttributeError: pass - self.ledger_path_out = self.cfg.get('MAIN_LEDGER_FILE', v) + if self.ledger_path_out == None: + self.ledger_path_out = self.cfg.get('MAIN_LEDGER_FILE') if self.ledger_path_out == None: self.ledger_path_out = self.ledger_path_in if self.ledger_path_in != None: diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd @@ -39,6 +39,7 @@ <xs:element name="expense" type="xs:integer" minOccurs="0" /> <xs:element name="asset" type="xs:integer" /> <xs:element name="liability" type="xs:integer" /> + <xs:element name="equity" type="xs:integer" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="unit" type="xs:string" /> </xs:complexType> @@ -144,6 +145,7 @@ <xs:element name="serial" type="xs:positiveInteger" /> <xs:element name="date" type="xs:date" /> <xs:element name="dateTimeRegistered" type="xs:dateTime" /> + <xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="debit" type="EntryPart" minOccurs="1" maxOccurs="unbounded" /> <xs:element name="credit" type="EntryPart" minOccurs="1" maxOccurs="unbounded" /> <xs:element name="attachment" type="Attachment" minOccurs="0" maxOccurs="unbounded" /> diff --git a/dummy/usawa/data/usawa.ini b/dummy/usawa/data/usawa.ini @@ -4,6 +4,9 @@ ledger_file = timezone = UTC cafile = +[gui] +show_external_ref = 0 + [accounts] file = strict = 0 diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -718,7 +718,8 @@ class Entry(UsawaElement): def to_string(self, canon=False, lookup=None): tree = self.to_tree(canon=canon, lookup=lookup) #return lxml.etree.canonicalize(tree).decode('utf-8') - return lxml.etree.tostring(tree).decode('utf-8') + #return lxml.etree.tostring(tree).decode('utf-8') + return lxml.etree.tostring(tree, encoding='ascii', method="html") def get_lookup(self, lookup, tree=None): @@ -768,7 +769,6 @@ class Entry(UsawaElement): def has_tag(self, tag): - logg.debug("tagssss {}".format(self.tags)) return self.tags.has(tag) diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -479,7 +479,9 @@ class Ledger(UsawaElement): raise ValueError('Signature entry without signature value') wallet = DemoWallet(publickey=b) v = entry.sum() - return wallet.verify(v[0], sig) + r = wallet.verify(v[0], sig) + logg.debug('signature check {} {} {}'.format(r, k, entry)) + return r have = True if not have: raise VerifyError() diff --git a/dummy/usawa/runnable/export.py b/dummy/usawa/runnable/export.py @@ -11,6 +11,7 @@ from whee.fs import FsStore from xdg_base_dirs import xdg_data_home import usawa.config +from usawa.context import UsawaContext from usawa import Ledger, Entry, EntryPart, DemoWallet, UnitIndex, load, ACL from usawa.constant import CATEGORIES from usawa.store import LedgerStore @@ -21,92 +22,27 @@ logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() -class Context: - - def __init__(self): - self.unit = None - self.uidx = None - self.output = None - self.f = None - self.valkey_host = None - self.valkey_port = None - - - def close(self): - if self.f and self.f != sys.stdout: - self.f.close() - - - def open(self, output): - if output == '<stdout>': - self.f = sys.stdout.buffer - logg.debug('output is stdout') - else: - self.f = open(output, 'wb') - return self - - @staticmethod - def from_args(args): - ctx = Context() - if args.output == None: - raise ValueError('invalid output dir') - ctx.output = os.path.realpath(args.output) - - ctx.valkey_host = args.valkey_host - ctx.valkey_port = args.valkey_port - - return ctx - - def main(): argp = argparse.ArgumentParser() - argp.add_argument('-o', type=str, dest='output', help='output dir for resulting XML entry documents') argp.add_argument('-c', type=str, help='override config dir') argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') - argp.add_argument('--valkey-host', dest='valkey_host', type=str, default='localhost', help='Valkey host') - argp.add_argument('--valkey-port', dest='valkey_port', type=int, default=6379, help='Valkey port') - argp.add_argument('ledger_xml_file', type=str, help='load ledger metadata from XML file') + argp.add_argument('-i', type=str, help='input ledger state') + argp.add_argument('output', type=str, help='output dir for resulting XML entry documents') arg = argp.parse_args() if arg.v: logg.setLevel(getattr(logging, arg.v.upper())) - ctx = Context.from_args(arg) - - #ledger = None - #ledger_tree = load(arg.ledger_xml_file) - #uidx = UnitIndex.from_tree(ledger_tree) - #ledger = Ledger.from_tree(ledger_tree) - ledger = Ledger.from_file(arg.ledger_xml_file) + cfg = usawa.config.load_config(config_dir=arg.c) + ctx = UsawaContext(cfg, signing=False) + ctx.init(arg) - cfg = usawa.config.load_config(config_dir=arg.c) - store_type = cfg.get('STORE_TYPE') - db = None - if store_type == 'valkey': - db = ValkeyStore( - "", - host=cfg.get("VALKEY_HOST"), - port=cfg.get("VALKEY_PORT"), - ) - elif store_type == 'fs': - db = FsStore( - base=cfg.get('FSSTORE_BASE', xdg_data_home()), - dbname='usawa', - ) - else: - raise ValueError('invalid store type: ' + store_type) - store = LedgerStore(db, ledger) - wallet = store.get_default_key(DemoWallet) - acl = ACL.from_wallet(wallet) - store.load(acl=acl) - resolve_out = FSResolver(ctx.output) - resolve_in = None - resolve_in_path = cfg.get('FS_RESOLVER_STORE_PATH') - if resolve_in_path != None: - resolve_in = FSResolver(resolve_in_path) + ctx.store.load() + resolve_out = FSResolver(arg.output) + resolve_in = ctx.resolver - for k in ledger.entries: - o = ledger.entries[k] + for k in ctx.ledger.entries: + o = ctx.ledger.entries[k] r = resolve_out.put_entry(o, lookup='sha512') if resolve_in != None: for oo in o.attachment: diff --git a/dummy/usawa/runnable/recalc.py b/dummy/usawa/runnable/recalc.py @@ -18,10 +18,11 @@ from xdg_base_dirs import xdg_data_home logging.basicConfig(level=logging.WARNING) logg = logging.getLogger() +prev = None def main(): argp = argparse.ArgumentParser() - argp.add_argument('-o', type=str, dest='output', help='output file for resulting XML document') + argp.add_argument('-o', type=str, dest='output', help='output ledger state') #argp.add_argument('-p', action='store_true', help='prompt for password to open wallet') argp.add_argument('-c', type=str, help='override config dir') argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') @@ -37,11 +38,18 @@ def main(): ctx.init(arg) def override_parent(entry): - entry.parent = ctx.ledger.cur + global prev + digest = ctx.ledger.cur + if prev != None: + (k, v) = prev.get_lookup(ctx.ledger.lookup_algo) + ctx.ledger.cur = bytes.fromhex(k) + entry.parent = ctx.ledger.cur + logg.info('entry parent override {}'.format(entry.parent.hex())) ctx.store.add_entry(entry, overwrite=True) + prev = entry ctx.store.load(entry_callback_pre=override_parent) - + ctx.commit() if __name__ == '__main__': main() diff --git a/dummy/usawa/store.py b/dummy/usawa/store.py @@ -383,6 +383,7 @@ class LedgerStore(EntryStore, AssetStore, KeyStore): break if entry_callback_pre != None: entry_callback_pre(o) + logg.debug("load entry {}".format(o.to_string(canon=True))) self.ledger.add_entry(o) if entry_callback_post != None: entry_callback_post(o) diff --git a/dummy/usawa/tag.py b/dummy/usawa/tag.py @@ -17,7 +17,7 @@ class Tags: def has(self, tag): r = False try: - v = self.tags.get(tag) + v = self.tags[tag] r = True except KeyError: pass diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py @@ -13,7 +13,7 @@ logg = logging.getLogger('usawa.unit') class UnitIndex: - default_precision = 2 + default_precision = 9 default_unit = 'BTC' default_exchange = 1000000000 @@ -42,6 +42,8 @@ class UnitIndex: self.virt = {} if virt: self.virt[base] = True + logg.info('unitindex base {}'.format(base)) + def clone(self): @@ -76,7 +78,7 @@ class UnitIndex: v = int(r) m = int((r - v) * 1000000000) - logg.debug('val {} -> {},{} adj {}'.format(r, v, m, adj)) + logg.info('val {} -> {},{} adj {}'.format(r, v, m, adj)) return (v, m, )