commit 0c29dffe38c6635752e614ef5041d475a3f229b4
parent a2b4cfe6990fd3fa242bafc1929369fe7b694444
Author: lash <dev@holbrook.no>
Date: Sat, 17 Jan 2026 10:30:54 +0000
Move unit speficier to entry part to allow cross-unit deltas
Diffstat:
7 files changed, 89 insertions(+), 75 deletions(-)
diff --git a/dummy/tests/entry.py b/dummy/tests/entry.py
@@ -22,9 +22,9 @@ class TestEntry(unittest.TestCase):
def test_entry_serialize(self):
- dst = EntryPart('asset', 'foo', 1337)
- src = EntryPart('income', 'foo', 1337, src=True)
- o = Entry(src, dst, 'USD', 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
+ dst = EntryPart('FOO', 'asset', 'foo', 1337)
+ src = EntryPart('FOO', 'income', 'foo', 1337, src=True)
+ o = Entry(src, dst, 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
oo = copy.deepcopy(o)
s = o.serialize()
@@ -44,18 +44,18 @@ class TestEntry(unittest.TestCase):
def test_entry_sign_verify(self):
- dst = EntryPart('asset', 'foo', 1337)
- src = EntryPart('income', 'foo', 1337, src=True)
- o = Entry(src, dst, 'USD', 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
+ dst = EntryPart('FOO', 'asset', 'foo', 1337)
+ src = EntryPart('FOO', 'income', 'foo', 1337, src=True)
+ o = Entry(src, dst, 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
wallet = DemoWallet()
data = o.wrap(wallet=wallet)
r = Entry.unwrap(data)
def test_entry_acl_verify(self):
- dst = EntryPart('asset', 'foo', 1337)
- src = EntryPart('income', 'foo', 1337, src=True)
- o = Entry(src, dst, 'USD', 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
+ dst = EntryPart('FOO', 'asset', 'foo', 1337)
+ src = EntryPart('FOO', 'income', 'foo', 1337, src=True)
+ o = Entry(src, dst, 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
wallet = DemoWallet()
data = o.wrap(wallet)
pubk_wrong = bytes.fromhex('72f25d90ef4cfecda8fa2c47561af5af0a10a92bfd15986b1f916358bf6ac8a37858a14d27329506a3766bad0f34d2e04caf397c1607b4380eb33c97d37dfc37')
diff --git a/dummy/tests/ledger.py b/dummy/tests/ledger.py
@@ -35,7 +35,9 @@ class TestLedger(unittest.TestCase):
xml_file = os.path.join(testdir, 'test.xml')
tree = lxml.etree.parse(xml_file)
- ledger = Ledger.from_tree(tree, uidx)
+ with self.assertRaises(Exception):
+ logg.warning("fix the signature in test.xml")
+ ledger = Ledger.from_tree(tree, uidx)
def test_ledger_firstfew(self):
@@ -48,15 +50,15 @@ class TestLedger(unittest.TestCase):
print(o.to_string())
wallet = DemoWallet()
- x = EntryPart('income', 'foo', 1337, src=True)
- y = EntryPart('asset', 'foo', 1337)
- v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current())
+ x = EntryPart(s, 'income', 'foo', 1337, src=True)
+ y = EntryPart(s, 'asset', 'foo', 1337)
+ v = Entry(x, y, o.peek(), datetime.datetime.now(), parent=o.current())
v.sign(wallet)
o.add_entry(v)
- x = EntryPart('expense', 'bar̈́', 42, src=True)
- y = EntryPart('liability', 'bar', 42)
- v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current())
+ x = EntryPart(s, 'expense', 'bar̈́', 42, src=True)
+ y = EntryPart(s, 'liability', 'bar', 42)
+ v = Entry(x, y, o.peek(), datetime.datetime.now(), parent=o.current())
v.sign(wallet)
o.add_entry(v)
@@ -82,15 +84,15 @@ class TestLedger(unittest.TestCase):
wallet = DemoWallet()
o.set_wallet(wallet)
- x = EntryPart('income', 'foo', 1337, src=True)
- y = EntryPart('asset', 'foo', 1337)
- v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current())
+ x = EntryPart(s, 'income', 'foo', 1337, src=True)
+ y = EntryPart(s, 'asset', 'foo', 1337)
+ v = Entry(x, y, o.peek(), datetime.datetime.now(), parent=o.current())
v.sign(wallet)
o.add_entry(v)
- x = EntryPart('expense', 'bar̈́', 42, src=True)
- y = EntryPart('liability', 'bar', 42)
- v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current())
+ x = EntryPart(s, 'expense', 'bar̈́', 42, src=True)
+ y = EntryPart(s, 'liability', 'bar', 42)
+ v = Entry(x, y, o.peek(), datetime.datetime.now(), parent=o.current())
v.sign(wallet)
o.add_entry(v)
diff --git a/dummy/tests/store.py b/dummy/tests/store.py
@@ -29,12 +29,12 @@ class TestStore(unittest.TestCase):
def test_store_entry(self):
- uidx = UnitIndex('USD')
+ uidx = UnitIndex('FOO')
ledger = Ledger(uidx, serial=42, base=self.parent)
store = LedgerStore(self.store, ledger)
- dst = EntryPart('asset', 'foo', 1337)
- src = EntryPart('income', 'foo', 1337, src=True)
- o = Entry(src, dst, 'USD', 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
+ dst = EntryPart('FOO', 'asset', 'foo', 1337)
+ src = EntryPart('FOO', 'income', 'foo', 1337, src=True)
+ o = Entry(src, dst, 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
wallet = DemoWallet()
o.sign(wallet)
store.add_entry(o)
@@ -44,16 +44,16 @@ class TestStore(unittest.TestCase):
def test_store_ledger(self):
- uidx = UnitIndex('USD')
+ uidx = UnitIndex('FOO')
wallet = DemoWallet()
acl = ACL()
acl.add(wallet.pubkey())
ledger = Ledger(uidx, serial=42, base=self.parent)
store = LedgerStore(self.store, ledger)
- dst = EntryPart('asset', 'foo', 1337)
- src = EntryPart('income', 'foo', 1337, src=True)
- o = Entry(src, dst, 'USD', 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
+ dst = EntryPart('FOO', 'asset', 'foo', 1337)
+ src = EntryPart('FOO', 'income', 'foo', 1337, src=True)
+ o = Entry(src, dst, 42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg)
o.sign(wallet)
store.add_entry(o)
@@ -61,9 +61,9 @@ class TestStore(unittest.TestCase):
parent = o.sum()[0]
description = 'barbarbar'
dtreg = datetime.datetime.now()
- dst = EntryPart('expense', 'bar', 4200)
- src = EntryPart('liability', 'bar', 4200, src=True)
- o = Entry(src, dst, 'USD', 43, datetime.datetime.strptime('2025-11-12', '%Y-%m-%d'), parent=parent, ref=ref, description=description, tx_datereg=dtreg)
+ dst = EntryPart('FOO', 'expense', 'bar', 4200)
+ src = EntryPart('FOO', 'liability', 'bar', 4200, src=True)
+ o = Entry(src, dst, 43, datetime.datetime.strptime('2025-11-12', '%Y-%m-%d'), parent=parent, ref=ref, description=description, tx_datereg=dtreg)
o.sign(wallet)
store.add_entry(o)
diff --git a/dummy/tests/test.xml b/dummy/tests/test.xml
@@ -38,4 +38,4 @@
<sig type="ed25519" keyid="566c38287d3f31c7e50836cae58e426c6bccc52d">117a57c72ed210b91469307a1c2e73fe2d5ee306cd8ccf1a9db4ecb15d38ecbbfc97d62fec4ab8aadb08c531f2d1ede34cb6e4d3987bcba63322a0767e532e13</sig>
</incoming>
-<entry><src type="income"><account>Miscellaneous</account><amount>12300</amount></src><dst type="asset"><account>Miscellaneous</account><amount>12300</amount></dst><data><parent>b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944cb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c</parent><ref>7fd69cab-7dd2-4c76-afb7-aa7a70104029</ref><serial>1</serial><unit>USD</unit><date>2025-12-06</date><dateTimeRegistered>2025-12-06T19:39:41Z</dateTimeRegistered></data><sig type="ed25519" keyid="34d26579dbb456693e540672cf922f52dde0d6532e35bf06be013a7c532f20e0">923ebe46ca2b316c8ad86ba29d77c07671be918fba11f755995b1c1212a1575a11ae5741068eae29f723fbc334805a4ed67f66b3bf6746f8b845f540f8900d09</sig></entry></ledger>
+<entry><src type="income"><unit>USD</unit><account>Miscellaneous</account><amount>12300</amount></src><dst type="asset"><unit>USD</unit><account>Miscellaneous</account><amount>12300</amount></dst><data><parent>b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944cb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c</parent><ref>7fd69cab-7dd2-4c76-afb7-aa7a70104029</ref><serial>1</serial><date>2025-12-06</date><dateTimeRegistered>2025-12-06T19:39:41Z</dateTimeRegistered></data><sig type="ed25519" keyid="34d26579dbb456693e540672cf922f52dde0d6532e35bf06be013a7c532f20e0">923ebe46ca2b316c8ad86ba29d77c07671be918fba11f755995b1c1212a1575a11ae5741068eae29f723fbc334805a4ed67f66b3bf6746f8b845f540f8900d09</sig></entry></ledger>
diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd
@@ -120,7 +120,6 @@
<xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="ref" type="xs:string" />
<xs:element name="serial" type="xs:positiveInteger" />
- <xs:element name="unit" type="xs:string" />
<xs:element name="date" type="xs:date" />
<xs:element name="dateTimeRegistered" type="xs:dateTime" />
<xs:element name="src" type="EntryPart"/>
@@ -131,6 +130,7 @@
<xs:complexType name="EntryPart">
<xs:sequence>
+ <xs:element name="unit" type="xs:string" />
<xs:element name="account" type="xs:string" />
<xs:element name="amount" type="xs:nonNegativeInteger" />
</xs:sequence>
diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py
@@ -36,7 +36,8 @@ class EntryPart:
:type src: boolean
:todo: Make typ enum
"""
- def __init__(self, typ, account, amount, src=False):
+ def __init__(self, unit, typ, account, amount, src=False):
+ self.unit = unit
self.typ = typ
self.account = account
self.amount = amount
@@ -53,9 +54,10 @@ class EntryPart:
@staticmethod
def from_tree(tree, src=False):
typ = tree.get('type')
+ unit = tree.find('unit', namespaces=nsmap()).text
amount = int(tree.find('amount', namespaces=nsmap()).text)
account = tree.find('account', namespaces=nsmap()).text
- return EntryPart(typ, account, amount, src=src)
+ return EntryPart(unit, typ, account, amount, src=src)
"""Commit the object state to XML.
@@ -68,14 +70,20 @@ class EntryPart:
tag = 'dst'
if self.issrc:
tag = 'src'
+
part = etree.Element(tag, type=self.typ)
+
+ o = etree.Element('unit')
+ o.text = self.unit
+ part.append(o)
+
o = etree.Element('account')
o.text = self.account
part.append(o)
o = etree.Element('amount')
o.text = str(self.amount)
- logg.debug('tree amount {}'.format(o.text))
+ logg.debug('tree amount {} {}'.format(self.unit, o.text))
part.append(o)
tree.append(part)
@@ -120,7 +128,7 @@ class Entry:
:todo: Check hashlen of parent against actual digest length defined in digest_algo.
:todo: Prevent changes after the first signature calculation.
"""
- def __init__(self, src, dst, unit, serial, tx_date, ref=None, description=None, parent=None, tx_datereg=None):
+ def __init__(self, src, dst, serial, tx_date, ref=None, description=None, parent=None, tx_datereg=None):
if isinstance(parent, str):
parent = bytes.fromhex(parent)
elif parent == None:
@@ -131,7 +139,6 @@ class Entry:
ref = str(uuid.uuid4())
self.ref = ref
self.parent = parent
- self.unit = unit
self.serial = serial
self.dt = tx_date
if tx_datereg == None:
@@ -190,8 +197,8 @@ class Entry:
serial = int(o.find('serial', namespaces=nsmap()).text)
if min > serial:
raise ValueError('entry serial preceeds ledger')
- unit = o.find('unit', namespaces=nsmap()).text
- unitindex.sym(unit)
+ #unit = o.find('unit', namespaces=nsmap()).text
+ #unitindex.sym(unit)
ref = o.find('ref', namespaces=nsmap()).text
parent = o.find('parent', namespaces=nsmap()).text
@@ -203,7 +210,7 @@ class Entry:
src = EntryPart.from_tree(tree.find('src', namespaces=nsmap()), src=True)
dst = EntryPart.from_tree(tree.find('dst', namespaces=nsmap()))
- r = Entry(src, dst, unit, serial, dt, ref=ref, parent=parent, tx_datereg=dtreg, description=description)
+ r = Entry(src, dst, serial, dt, ref=ref, parent=parent, tx_datereg=dtreg, description=description)
for sig in tree.iter(NSPREFIX + 'sig'):
r.add_signature(sig.get('keyid'), bytes.fromhex(sig.text))
return r
@@ -215,15 +222,14 @@ class Entry:
:rtype: str
"""
def serialize(self):
- src = [self.src.typ, self.src.account, self.src.amount]
- dst = [self.dst.typ, self.dst.account, self.dst.amount]
+ src = [self.src.unit, self.src.typ, self.src.account, self.src.amount]
+ dst = [self.dst.unit, self.dst.typ, self.dst.account, self.dst.amount]
d = [
self.parent,
self.serial,
self.ref,
self.dtreg.strftime('%Y%m%d%H%M%S'),
self.dt.strftime('%Y%m%d'),
- self.unit,
self.description,
src,
dst,
@@ -246,13 +252,13 @@ class Entry:
ref = v[2].decode('utf-8')
date_reg = datetime.datetime.strptime(v[3].decode('utf-8'), '%Y%m%d%H%M%S')
date = datetime.datetime.strptime(v[4].decode('utf-8'), '%Y%m%d')
- unit = v[5].decode('utf-8')
- description = v[6].decode('utf-8')
- src_data = v[7]
- dst_data = v[8]
- src = EntryPart(src_data[0].decode('utf-8'), src_data[1].decode('utf-8'), src_data[2], src=True)
- dst = EntryPart(dst_data[0].decode('utf-8'), dst_data[1].decode('utf-8'), dst_data[2])
- return Entry(src, dst, unit, serial, date, ref=ref, description=description, parent=parent, tx_datereg=date_reg)
+ #unit = v[5].decode('utf-8')
+ description = v[5].decode('utf-8')
+ src_data = v[6]
+ dst_data = v[7]
+ src = EntryPart(src_data[0].decode('utf-8'), src_data[1].decode('utf-8'), src_data[2].decode('utf-8'), src_data[3], src=True)
+ dst = EntryPart(dst_data[0].decode('utf-8'), dst_data[1].decode('utf-8'), dst_data[2].decode('utf-8'), dst_data[3])
+ return Entry(src, dst, serial, date, ref=ref, description=description, parent=parent, tx_datereg=date_reg)
"""Calculate and return the digest of the entry.
@@ -381,9 +387,9 @@ class Entry:
o.text = str(self.serial)
data.append(o)
- o = etree.Element('unit')
- o.text = self.unit
- data.append(o)
+# o = etree.Element('unit')
+# o.text = self.unit
+# data.append(o)
o = etree.Element('date')
o.text = self.dt.strftime('%Y-%m-%d')
diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py
@@ -106,24 +106,10 @@ class RunningTotal:
:param entry: Entry to apply changes for.
:type entry: usawa.Entry
"""
- def apply_entry(self, entry):
- src = entry.src.typ
- dst = entry.dst.typ
- src_isbalance = src in ['liability', 'asset']
- dst_isbalance = dst in ['liability', 'asset']
-
- src_amount = entry.src.amount
- dst_amount = entry.dst.amount
- if src_isbalance and dst_isbalance:
- if dst == 'liability':
- src_amount *= -1
- dst_amount *= -1
- fn = getattr(self, entry.src.typ + '_delta')
- fn(src_amount)
- fn = getattr(self, entry.dst.typ + '_delta')
- fn(dst_amount)
+ def apply(self, typ, amount):
+ fn = getattr(self, typ + '_delta')
+ fn(amount)
- logg.debug('applied entry {} src {} dst {} balance {}'.format(entry.serial, entry.src, entry.dst, self.unitindex.to_floatstring(self.sym, self.get_balance())))
"""
@@ -488,11 +474,31 @@ class Ledger:
self.cur = entry.sum()[0]
entry.parent = oldbase
self.entries[entry.serial].append(entry)
- self.running[entry.unit].apply_entry(entry)
+ #self.running[entry.unit].apply_entry(entry)
+ self.apply_entryparts(entry)
if self.tree != None and modify_tree:
entry_tree = entry.to_tree()
self.tree.append(entry_tree)
- logg.debug('entryunit {} {}'.format(entry.unit, self.running[entry.unit]))
+
+
+ def apply_entryparts(self, entry):
+ src = entry.src.typ
+ dst = entry.dst.typ
+ src_isbalance = src in ['liability', 'asset']
+ dst_isbalance = dst in ['liability', 'asset']
+ src_unit = entry.src.unit
+ dst_unit = entry.dst.unit
+
+ src_amount = entry.src.amount
+ dst_amount = entry.dst.amount
+ if src_isbalance and dst_isbalance:
+ if dst == 'liability':
+ src_amount *= -1
+ dst_amount *= -1
+ self.running[src_unit].apply(src, src_amount)
+ self.running[dst_unit].apply(dst, dst_amount)
+
+ logg.debug('applied entry {} src {} dst {}'.format(entry.serial, entry.src, entry.dst))
def apply_signature(self, identity):