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:
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, )