usawa

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

commit cd9355a09d69b47a8ee57d7d0a3650a54aaab50c
parent 97e179e76e9ad89a926a800945b96b846f88e600
Author: lash <dev@holbrook.no>
Date:   Sun, 19 Apr 2026 22:53:30 -0600

Entry tag deserialize, ensure extref store

Diffstat:
Mdummy/usawa/cli/entry.py | 57++++++++++++++++++++++++++++++++++++---------------------
Mdummy/usawa/entry.py | 36+++++++++++++++++++++++++-----------
Mdummy/usawa/runnable/entry.py | 2+-
Mdummy/usawa/store.py | 4++--
4 files changed, 64 insertions(+), 35 deletions(-)

diff --git a/dummy/usawa/cli/entry.py b/dummy/usawa/cli/entry.py @@ -16,7 +16,7 @@ class AbortMenu(Exception): pass -def writehelp(w=sys.stdout): +def writehelp(w=sys.stdout, short=False): s = """h: this help i: input account o: output account @@ -178,7 +178,7 @@ def try_entry_uuid(ctx, v): def try_entry_serial(ctx, v): v = int(v) entry = Entry(v, None) - return ctx.store.get_entry(entry) + return ctx.store.get_entry(entry, unitindex=ctx.uidx) def try_entry_digest(ctx, k): @@ -220,7 +220,7 @@ def try_entry(ctx, entry_spec): class EntrySession: - def __init__(self, ctx, entry=None, heading=None, description=None, extref=None, dt=None, ref=None, amount=None, lines=[], base=None): + def __init__(self, ctx, entry=None, heading=None, description=None, extref=None, dt=None, ref=None, amount=None, lines=[], base=None, dynamic=False): # context props self.ctx = ctx self.unitbase = base @@ -239,6 +239,7 @@ class EntrySession: self.part_side = 'src' self.have_src = False self.have_dst = False + self.dynamic = dynamic # supplementary props self.heading = heading @@ -251,9 +252,9 @@ class EntrySession: 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 > 0: - raise NotImplementedError('entry edit not yet implemented') - self._do_prepare() + if self.entry.serial < 1: + #raise NotImplementedError('entry edit not yet implemented') + self._do_prepare() def _cur_amount(self): @@ -345,31 +346,40 @@ class EntrySession: def handle_input(self, v): - if v == 'i' or v == 'o': - k = 'src' - if v == 'o': - k = 'dst' - try: - self.enter_part(side=k) - except ValueError as e: - logg.error('enter part fail: ' + str(e)) + if v == 'x': + handle_ref(self.ctx, self.entry, v) return True - if v == 'q': - raise StopIteration() if v == '+' or v == '-': r = handle_tag(self.ctx, self.entry, v) return True + if v == 'q': + raise StopIteration() if v == 'w': self.commit = True self.final = True return False + if v == 'v': + handle_view(self.ctx, self.entry, v) + return True + + if self.entry.serial > 0: + logg.error('illegal choice for committed entry') + return True + if v == 't': self.commit = True return False - if v == 'v': - handle_view(self.ctx, self.entry, v) + + if v == 'i' or v == 'o': + k = 'src' + if v == 'o': + k = 'dst' + try: + self.enter_part(side=k) + except ValueError as e: + logg.error('enter part fail: ' + str(e)) return True - if v == 'x' or v == 'y': + if v == 'y': handle_ref(self.ctx, self.entry, v) return True if v == 'd': @@ -448,8 +458,9 @@ class EntrySession: def start(self, skip_first=False): - if not skip_first: - self.do_interactive_one() + if self.entry.serial < 1: + if not skip_first: + self.do_interactive_one() r = True while r: try: @@ -477,6 +488,10 @@ Extref: {} self.entry.extref, ) + tags = [] + if self.entry.tags: + tags = self.entry.tags + s += "Tags: " + ', '.join(tags) + "\n" s += "Description: " + self.get_description() + "\n" for v in self.lines: diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -363,9 +363,9 @@ class Entry(UsawaElement): for v in self.attachment: attach.append(v.get_digest(binary=True)) - tags = None + tags = [] if self.tags: - tags = self.tags.serialize() + tags = self.tags base = super(Entry, self).serialize() logg.debug('serializing with parent {}'.format(self.parent.hex())) @@ -381,6 +381,7 @@ class Entry(UsawaElement): debit, attach, tags, + self.extref, ] return d @@ -408,19 +409,28 @@ class Entry(UsawaElement): parent = v[1] serial = v[2] ref = v[3].decode('utf-8') + extref = None + try: + extref = v[11] + except IndexError: + pass date_reg = datetime.datetime.strptime(v[4].decode('utf-8'), '%Y%m%d%H%M%S') date = datetime.datetime.strptime(v[5].decode('utf-8'), '%Y%m%d%H%M%S') description = v[6].decode('utf-8') dst_data = v[7] src_data = v[8] attach_data = v[9] - tags = None - try: - tags = Tags.deserialize(v[10]) - except TypeError: - pass + #tags = None + #try: + # tags = Tags.deserialize(v[10]) + #except TypeError: + # pass + tags = [] + for s in v[10]: + tags.append(s.decode('utf-8')) o = Entry(serial, date, ref=ref, description=description, parent=parent, tx_datereg=date_reg, tags=tags, unitindex=unitindex) super(Entry, o).deserialize(v[0]) + o.balancer = Balancer(unitindex) for v in src_data: src = EntryPart.deserialize(v, debit=True) o.add_part(src) @@ -522,9 +532,9 @@ class Entry(UsawaElement): :todo: Current version only takes into account single signature """ @staticmethod - def unwrap(data, acl=None): + def unwrap(data, acl=None, unitindex=None): v = rencode.loads(data) - entry = Entry.deserialize(v[2]) + entry = Entry.deserialize(v[2], unitindex=unitindex) pubkey_bytes = v[0][0][1] sig = v[1][0] entry.add_signature(pubkey_bytes, sig) @@ -599,6 +609,10 @@ class Entry(UsawaElement): o.text = self.ref data.append(o) + o = lxml.etree.Element('extref') + o.text = self.ref + data.append(o) + o = lxml.etree.Element('serial') o.text = str(self.serial) data.append(o) @@ -680,7 +694,7 @@ class Entry(UsawaElement): """ def canon(self): tree = self.to_tree(canon=True) - b = lxml.etree.canonicalize(tree, strip_text=True, exclude_tags=['sig', 'lookup']) + b = lxml.etree.canonicalize(tree, strip_text=True, exclude_tags=['sig', 'lookup', 'extref']) return b.encode('utf-8') @@ -688,7 +702,7 @@ class Entry(UsawaElement): def tag(self, tag): if self.tags == None: self.tags = [] - elif not tag in self.tags: + if not tag in self.tags: self.tags.append(tag) diff --git a/dummy/usawa/runnable/entry.py b/dummy/usawa/runnable/entry.py @@ -54,7 +54,7 @@ def main(): ctx.set('unitbase', ctx.uidx.base) ctx.set('commit', False) - o = EntrySession(ctx, args.entry) + o = EntrySession(ctx, entry=args.entry) for v in args.x: o.attach_ref(v) diff --git a/dummy/usawa/store.py b/dummy/usawa/store.py @@ -317,10 +317,10 @@ class EntryStore(KeyStore): :raises: FileExistsError if entry is already in store. :todo: optimize replacing asset stub with deserialized asset """ - def get_entry(self, entry, acl=None): + def get_entry(self, entry, acl=None, unitindex=None): k = pfx_entry(self.ledger, entry) v = self.db.get(k) - entry = Entry.unwrap(v) + entry = Entry.unwrap(v, unitindex=unitindex) # TODO: hacky! i = 0 for o in entry.attachment: