usawa

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

commit e88fede8394ece82a404c8940ce3422b9389f458
parent 429b73bec76553ec64e9053fe0c0b6fb768aef7e
Author: Carlosokumu <carlosokumu254@gmail.com>
Date:   Thu, 19 Mar 2026 20:33:45 +0300

deprecate all xml build functions already implemented by usawa

Diffstat:
Mdummy/usawa/storage/ledger_repository.py | 23+++++++----------------
Mdummy/usawa/storage/xml_utils.py | 193++-----------------------------------------------------------------------------
2 files changed, 11 insertions(+), 205 deletions(-)

diff --git a/dummy/usawa/storage/ledger_repository.py b/dummy/usawa/storage/ledger_repository.py @@ -2,11 +2,7 @@ import logging from typing import List from usawa.storage.file_utils import path_from_uri from usawa.storage.xml_utils import ( - _build_export_root, - _build_incoming_element, - _find_entry_by_serial, _write_xml_to_file, - resolve_namespace, ) from usawa.asset import Asset from usawa.crypto import ACL, DemoWallet, Wallet @@ -163,14 +159,12 @@ class LedgerRepository: """Get all entries""" try: store, _, _ = self._init_store() - return [ EntryMapper.to_domain_entry(storage_entry) for _, storage_entry in store.ledger.entries.items() ] except Exception as e: - # logg.error(f"Failed to retrieve entries: {e}") - logg.error("failed to map entries: %s", e, exc_info=True) + logg.error(f"Failed to retrieve entries: {e}") return [] def get_asset_bytes(self, digest: str): @@ -242,16 +236,13 @@ class LedgerRepository: return False, f"Entry #{serial} not found" _, ledger, _ = self._init_store() + try: + _write_xml_to_file(storage_entry.to_string(), output_path) + except Exception as e: + logg.debug( + "Failed to write entry #%d to file %s: %s", serial, output_path, e + ) - xml_tree = self.store.ledger.to_tree() - ns_uri = resolve_namespace(xml_tree) - target_entry = _find_entry_by_serial(xml_tree, ns_uri, serial) - if target_entry is None: - return False, f"Entry #{serial} not found in XML" - - incoming = _build_incoming_element(ns_uri, target_entry, xml_tree) - root = _build_export_root(xml_tree, ns_uri, target_entry, incoming) - _write_xml_to_file(root, output_path) ledger.truncate() logg.debug( "Ledger entries after truncate: %d", len(self.store.ledger.entries) diff --git a/dummy/usawa/storage/xml_utils.py b/dummy/usawa/storage/xml_utils.py @@ -1,204 +1,19 @@ import logging -from copy import deepcopy from pathlib import Path from lxml import etree as ET -logg = logging.getLogger(__name__) +logg = logging.getLogger("usawa.xml_utils") -FALLBACK_NS = "http://usawa.defalsify.org/" +def _write_xml_to_file(xml_string, output_path: str) -> None: -def _get_local_name(element): - """Extract local name from element tag (without namespace)""" - tag = element.tag - return tag.split("}")[-1] if "}" in tag else tag - - -def _find_child(parent, local_name): - """Find child element by local name""" - for child in parent: - if _get_local_name(child) == local_name: - return child - return None - - -def _get_local_name(element): - """Extract local name from element tag (without namespace)""" - tag = element.tag - return tag.split("}")[-1] if "}" in tag else tag - - -def _find_child(parent, local_name): - """Find child element by local name, ignoring namespace.""" - for child in parent: - if _get_local_name(child) == local_name: - return child - return None - - -def resolve_namespace(xml_tree) -> str: - """Resolve the namespace URI from an XML tree. - - Tries nsmap first, then falls back to parsing the root tag, - then falls back to the well-known usawa namespace. - - :param xml_tree: Root XML element. - :type xml_tree: lxml.etree.Element - :return: Namespace URI string. - :rtype: str - """ - ns_uri = xml_tree.nsmap.get(None) - if ns_uri is not None: - return ns_uri - if "}" in xml_tree.tag: - return xml_tree.tag.split("}")[0].strip("{") - logg.warning("Could not resolve namespace, using fallback: %s", FALLBACK_NS) - return FALLBACK_NS - - -def _find_entry_by_serial(xml_tree, ns_uri: str, serial: int): - """Find an entry element by its serial number. - - :param xml_tree: Root XML element to search within. - :type xml_tree: lxml.etree.Element - :param ns_uri: Namespace URI. - :type ns_uri: str - :param serial: Entry serial number to find. - :type serial: int - :return: Matching entry element, or None if not found. - :rtype: lxml.etree.Element or None - """ - all_entries = xml_tree.findall(".//{%s}entry" % ns_uri) - logg.debug("Searching %d entries for serial %d", len(all_entries), serial) - - for entry_elem in all_entries: - data_elem = _find_child(entry_elem, "data") - if data_elem is None: - logg.warning("Entry without <data> element, skipping") - continue - - serial_elem = _find_child(data_elem, "serial") - if serial_elem is None: - logg.warning("Entry without <serial> element, skipping") - continue - - if int(serial_elem.text) == serial: - logg.debug("Found target entry serial %d", serial) - return entry_elem - - return None - - -def _build_incoming_element(ns_uri: str, target_entry, orig_incoming=None): - """Build the <incoming> XML element from entry debit/credit values. - - :param ns_uri: Namespace URI. - :type ns_uri: str - :param target_entry: Entry element to read debit/credit amounts from. - :type target_entry: lxml.etree.Element - :param orig_incoming: Existing <incoming> element to copy digest/sig from. - :type orig_incoming: lxml.etree.Element or None - :return: Constructed <incoming> element. - :rtype: lxml.etree.Element - """ - data_elem = _find_child(target_entry, "data") - - debit_val = 0 - credit_val = 0 - - if data_elem is not None: - debit_elem = _find_child(data_elem, "debit") - credit_elem = _find_child(data_elem, "credit") - - if debit_elem is not None: - amount_elem = _find_child(debit_elem, "amount") - if amount_elem is not None: - debit_val = int(amount_elem.text) - - if credit_elem is not None: - amount_elem = _find_child(credit_elem, "amount") - if amount_elem is not None: - credit_val = int(amount_elem.text) - - expense = -abs(debit_val) - asset = credit_val - - incoming = ET.Element("{%s}incoming" % ns_uri) - incoming.set("serial", "0") - - real = ET.SubElement(incoming, "{%s}real" % ns_uri) - real.set("unit", "BTC") - - ET.SubElement(real, "{%s}income" % ns_uri).text = "0" - ET.SubElement(real, "{%s}expense" % ns_uri).text = str(expense) - ET.SubElement(real, "{%s}asset" % ns_uri).text = str(asset) - ET.SubElement(real, "{%s}liability" % ns_uri).text = "0" - - if orig_incoming is not None: - for tag in ["digest", "sig"]: - elem = _find_child(orig_incoming, tag) - if elem is not None: - incoming.append(deepcopy(elem)) - - return incoming - - -def _build_export_root(xml_tree, ns_uri: str, target_entry, incoming): - """Assemble the export root element with header metadata, incoming, and target entry. - - :param xml_tree: Source ledger XML tree to copy header elements from. - :type xml_tree: lxml.etree.Element - :param ns_uri: Namespace URI. - :type ns_uri: str - :param target_entry: The entry element to include in the export. - :type target_entry: lxml.etree.Element - :param incoming: The <incoming> element to include. - :type incoming: lxml.etree.Element - :return: Assembled root element. - :rtype: lxml.etree.Element - """ - root = ET.Element("{%s}ledger" % ns_uri, nsmap={None: ns_uri}) - root.set("version", xml_tree.get("version", "")) - - for tag in ["topic", "generated", "src", "units", "identity"]: - elem = _find_child(xml_tree, tag) - if elem is not None: - logg.debug("Copying header element: %s", tag) - root.append(deepcopy(elem)) - - root.append(incoming) - root.append(deepcopy(target_entry)) - - final_entries = root.findall(".//{%s}entry" % ns_uri) - logg.debug("Export root contains %d entry(ies)", len(final_entries)) - - return root - - -def _write_xml_to_file(root, output_path: str) -> None: - """Serialize an XML element tree and write it to a file. - - Creates parent directories if they do not exist. - - :param root: Root XML element to serialize. - :type root: lxml.etree.Element - :param output_path: Destination file path. - :type output_path: str - :raises PermissionError: If the file cannot be written. - :raises IOError: If a file system error occurs. - """ output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) - xml_string = ET.tostring( - root, - encoding="utf-8", - xml_declaration=True, - pretty_print=True, - ) - with open(output_file, "wb") as f: + if isinstance(xml_string, str): + xml_string = xml_string.encode("utf-8") f.write(xml_string) logg.debug("Wrote XML to %s (%d bytes)", output_path, len(xml_string))