usawa

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

commit 4973edc147bf4be0040644ddaea9ecb4e4a8734b
parent 31736d748cecf7458a6d4091fa259293955fc4b5
Author: Carlosokumu <carlosokumu254@gmail.com>
Date:   Fri,  6 Mar 2026 08:08:45 +0300

catch exceptions

Diffstat:
Mdummy/usawa/core/entry_service.py | 54+++++++++++++++++++++++++++++++++++++-----------------
Mdummy/usawa/gui/controllers/entry_controller.py | 22+++++++++-------------
Mdummy/usawa/gui/views/create_entry_view.py | 33++++++++++++++++++++++++---------
Mdummy/usawa/storage/ledger_repository.py | 70+++++++++++++++++++++++++++++++++++++++++++++++++---------------------
4 files changed, 119 insertions(+), 60 deletions(-)

diff --git a/dummy/usawa/core/entry_service.py b/dummy/usawa/core/entry_service.py @@ -2,7 +2,6 @@ import logging from datetime import datetime import uuid -from usawa.service import UnixClient from .models import LedgerEntry from usawa.storage.ledger_repository import LedgerRepository @@ -14,28 +13,49 @@ class EntryService: def __init__(self, repository: LedgerRepository): self.repository = repository - - def save_entry(self, entry: LedgerEntry) -> bool: + + def save_entry(self, entry: LedgerEntry) -> tuple[bool, str]: """ Save entry with business logic :param entry: Entry to save :type entry: LedgerEntry - :return: True if saved successfully - :rtype: bool + :return: (success, error_message) tuple + :rtype: tuple[bool, str] """ - - entry.tx_date = datetime.now() - entry.date_registered = datetime.now() - entry.transaction_ref = self._generate_transaction_ref() - - is_valid, error_msg = entry.validate() - if not is_valid: - logg.error(f"Entry validation failed: {error_msg}") - return False - - return self.repository.save(entry) - + try: + entry.tx_date = datetime.now() + entry.date_registered = datetime.now() + entry.transaction_ref = self._generate_transaction_ref() + + is_valid, error_msg = entry.validate() + if not is_valid: + logg.error(f"Entry validation failed: {error_msg}") + return False, error_msg + + self.repository.save(entry) + + logg.info(f"Entry saved successfully") + return True, "" + + except FileExistsError as e: + error_msg = "Some file info for this entry is already recorded in the ledger" + return False, error_msg + + except ValueError as e: + error_msg = f"Invalid entry data: {str(e)}" + logg.error(f"Validation error: {e}") + return False, error_msg + + except IOError as e: + error_msg = f"File error: {str(e)}" + logg.error(f"File operation failed: {e}") + return False, error_msg + + except Exception as e: + error_msg = f"Failed to save entry: {str(e)}" + logg.error(f"Unexpected error: {e}", exc_info=True) + return False, error_msg def get_all_entries(self): diff --git a/dummy/usawa/gui/controllers/entry_controller.py b/dummy/usawa/gui/controllers/entry_controller.py @@ -39,19 +39,15 @@ class EntryController: logg.error(f"Failed to collect entry data: {e}") return None - def finalize_entry(self, entry: LedgerEntry) -> bool: - """Save the entry to the ledger""" - try: - logg.debug("Entry: %s", entry) - success = self.entry_service.save_entry(entry) - if success: - logg.info(f"Entry saved successfully") - else: - logg.error("Failed to save entry") - return True - except Exception as e: - logg.error(f"Failed to save entry: {e}") - return False + + def finalize_entry(self, entry: LedgerEntry) -> tuple[bool, str]: + logg.debug("Finalizing entry: %s", entry) + success, error_msg = self.entry_service.save_entry(entry) + + if success: + return True, "" + else: + return False, error_msg def get_all_entries(self): return self.entry_service.get_all_entries() diff --git a/dummy/usawa/gui/views/create_entry_view.py b/dummy/usawa/gui/views/create_entry_view.py @@ -440,7 +440,7 @@ class CreateEntryView(Gtk.Box): def _get_icon_for_file(self, filename: str, metadata: str): """Get appropriate icon for file type""" - icon_name = "text-x-generic-symbolic" # Default + icon_name = "text-x-generic-symbolic" if "pdf" in metadata.lower(): icon_name = "application-pdf-symbolic" @@ -486,23 +486,38 @@ class CreateEntryView(Gtk.Box): return f"{size_bytes:.1f} TB" def _on_finalize(self, button): - """Handle finalize button - delegates to controller""" entry = self.controller.collect_entry_data(self) - if self.attachment_paths: - entry.attachments.extend(self.attachment_paths) if entry is None: - self._show_error_dialog("Invalid Input", "Please check your entries and try again.") + self._show_error_dialog( + "Invalid Input", + "Please check your entries and try again." + ) return - success = self.controller.finalize_entry(entry) + if self.attachment_paths: + entry.add_attachment(self.attachment_paths) + + if entry.attachments: + for attachment_path in entry.attachments: + from pathlib import Path + if not Path(attachment_path).exists(): + self._show_error_dialog( + "Attachment Missing", + f"Attachment file not found: {Path(attachment_path).name}" + ) + return + + success, error_msg = self.controller.finalize_entry(entry) + if success: self.controller.notify_entry_created() - self.nav_view.pop() + logg.info("Entry saved successfully, returning to list") + self.nav_view.pop() else: - self._show_error_dialog("Save Failed", - "Could not save the entry. Please try again.") + self._show_error_dialog("Save Failed", error_msg) + def _show_error_dialog(self, title, message): """Show error dialog""" dialog = Adw.MessageDialog( diff --git a/dummy/usawa/storage/ledger_repository.py b/dummy/usawa/storage/ledger_repository.py @@ -86,37 +86,65 @@ class LedgerRepository: return self.store, ledger, self._wallet - def save(self, domain_entry: LedgerEntry) -> bool: - """Save a domain entry to storage""" + def save(self, domain_entry: LedgerEntry) -> None: + """ + Save a domain entry to storage + + :param domain_entry: Entry to save + :type domain_entry: LedgerEntry + :raises ValueError: If validation fails + :raises FileExistsError: If attachment already exists in the store + :raises IOError: If file operations fail + :raises Exception: For other storage errors + """ try: store, ledger, wallet = self._init_store(write=True) - + entry = EntryMapper.to_entry(domain_entry, ledger=ledger) entry.sign(wallet) - logg.debug(f"Mapped entry - Serial: {entry.serial}, Parent: {entry.parent.hex()}, Attachments: {entry.attachment}") - + + logg.debug("Mapped entry - Serial: %s, Parent: %s, Attachments: %s",entry.serial,entry.parent.hex(),entry.attachment) + for attachment in domain_entry.attachments: - info = self.get_file_info(attachment) - asset = Asset.from_file(attachment,slug=info["slug"], description= info["description"],mimetype= info["mimetype"]) try: + info = self.get_file_info(attachment) + asset = Asset.from_file( + attachment, + slug=info["slug"], + description=info["description"], + mimetype=info["mimetype"] + ) store.add_asset(asset) - except Exception as e: - logg.exception("Failed to add asset for attachment %s: %s", attachment, e) - return False - - entry.attach(asset) - - with open(attachment, "rb") as f: - data = f.read() - self.resolver.put(asset.get_digest(binary=True), data) - + entry.attach(asset) + + + with open(attachment, "rb") as f: + data = f.read() + self.resolver.put(asset.get_digest(binary=True), data) + + except FileNotFoundError as e: + raise IOError(f"Attachment file not found: {attachment}") from e + except PermissionError as e: + raise IOError(f"Cannot read attachment file: {attachment}") from e + store.add_entry(entry, update_ledger=True) + ledger.truncate() ledger.sign() - return True - except Exception: - logg.exception("Failed to save entry") - return False + logg.info(f"Successfully saved entry #{entry.serial}") + + except FileExistsError as e: + logg.debug(f"Entry fileinfo already exists: {e}") + raise + except ValueError as e: + logg.debug(f"Validation error: {e}") + raise + except IOError as e: + logg.debug(f"File operation failed: {e}") + raise + except Exception as e: + logg.debug(f"Failed to save entry: {e}", exc_info=True) + raise def get_all_entries(self) -> List[LedgerEntry]: """Get all entries"""