Skip to content

Commit

Permalink
refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
WolfgangFahl committed Oct 8, 2024
1 parent 07cf68c commit d117ba4
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 68 deletions.
3 changes: 2 additions & 1 deletion nomina/beancount_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@

from nomina.beancount import Beancount, Preamble
from nomina.date_utils import DateUtils
from nomina.file_formats import AccountingFileFormats
from nomina.ledger import Account as LedgerAccount
from nomina.ledger import Book as LedgerBook
from nomina.ledger import Split as LedgerSplit
from nomina.ledger import Transaction as LedgerTransaction
from nomina.nomina_converter import AccountingFileConverter
from nomina.file_formats import AccountingFileFormats


class BeancountToLedgerConverter(AccountingFileConverter):
"""
Expand Down
37 changes: 25 additions & 12 deletions nomina/bzv.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
"""

import json
import os
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List, Optional

from dacite import from_dict
from lodstorage.yamlable import lod_storable

from nomina.stats import Stats
from lodstorage.yamlable import lod_storable


@lod_storable
class Transaction:
Expand Down Expand Up @@ -47,23 +49,34 @@ class Account:
name: str
parent_account_id: Optional[str] = None


@lod_storable
class Book:
"""
a Banking ZV Book
"""
name: str
owner: Optional[str] = None,
url: Optional[str] = None,
since: Optional[str] = None,
accounts: Dict[str, Account] = field(default_factory=dict)
transactions: List[Transaction] = field(default_factory=list)

def __post_init__(
self,

):
self.load_accounts_from_accounts_dict(account_json_dict)
name: str
owner: Optional[str] = (None,)
url: Optional[str] = (None,)
since: Optional[str] = (None,)
account_json_exports: Dict[str, str] = field(default_factory=dict)

def __post_init__(self):
self.accounts: Dict[str, Account] = {}
self.transactions: List[Transaction] = []

@classmethod
def load_from_file(cls, filename: str) -> "Book":
book = Book.load_from_yaml_file(filename)
yaml_dir = os.path.dirname(filename)

# Adjust JSON file paths to be relative to the YAML file
for account, json_path in book.account_json_exports.items():
book.account_json_exports[account] = os.path.join(yaml_dir, json_path)

book.load_accounts_from_accounts_dict(book.account_json_exports)
return book

def get_stats(self) -> Stats:
"""
Expand Down
15 changes: 11 additions & 4 deletions nomina/bzv_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
from nomina.bzv import Account
from nomina.bzv import Book as BzvBook
from nomina.bzv import Transaction as BzvTransaction
from nomina.file_formats import AccountingFileFormats
from nomina.ledger import Account as LedgerAccount
from nomina.ledger import Book as LedgerBook
from nomina.ledger import Split as LedgerSplit
from nomina.ledger import Transaction as LedgerTransaction
from nomina.nomina_converter import AccountingFileConverter
from nomina.file_formats import AccountingFileFormats


class BankingZVToLedgerConverter(AccountingFileConverter):
"""
Expand All @@ -36,8 +37,10 @@ def __init__(self, debug: bool = False):
# Call the superclass constructor with the looked-up formats
super().__init__(from_format, to_format, debug)

def load(self, input_stream:TextIO):
self.bzv_book = bzv_book
def load(self, input_stream: TextIO):
self.bzv_book = BzvBook.load_from_file(input_stream)
self.source = self.bzv_book
return self.source

def create_ledger_account(self, bzv_account: Account) -> LedgerAccount:
"""
Expand Down Expand Up @@ -103,5 +106,9 @@ def convert_to_target(self) -> LedgerBook:
ledger_transaction = self.create_ledger_transaction(transaction)
transaction_id = f"{ledger_transaction.isodate}:{transaction.Id}"
ledger_book.transactions[transaction_id] = ledger_transaction
self.target=ledger_book
self.target = ledger_book
return ledger_book

def to_text(self) -> str:
yaml_str = self.target.to_yaml()
return yaml_str
2 changes: 1 addition & 1 deletion nomina/file_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(self):
fformat.acronym: fformat for fformat in self.formats
}

def get_by_acronym(self,acronym:str)->'AccountingFileFormat':
def get_by_acronym(self, acronym: str) -> "AccountingFileFormat":
"""
get format by acronym
"""
Expand Down
18 changes: 9 additions & 9 deletions nomina/gnc_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"""

import uuid
from typing import Dict, List
from typing import Dict, List, TextIO

from nomina.file_formats import AccountingFileFormats
from nomina.gnucash import (
Account,
Book,
Expand All @@ -27,8 +28,7 @@
from nomina.ledger import Split as LedgerSplit
from nomina.ledger import Transaction as LedgerTransaction
from nomina.nomina_converter import AccountingFileConverter
from nomina.file_formats import AccountingFileFormats
from typing import TextIO


class GnuCashToLedgerConverter(AccountingFileConverter):
"""
Expand Down Expand Up @@ -132,8 +132,8 @@ def convert_to_target(self) -> LedgerBook:

return ledger_book

def to_text(self)->str:
yaml_str=self.target.to_yaml()
def to_text(self) -> str:
yaml_str = self.target.to_yaml()
return yaml_str


Expand All @@ -160,7 +160,7 @@ def __init__(self, debug: bool = False):
# Initialize instance variables
self.lbook = None
self.account_map = {}
self.gcxml=GnuCashXml()
self.gcxml = GnuCashXml()

def load(self, input_file: str) -> LedgerBook:
"""
Expand Down Expand Up @@ -278,9 +278,9 @@ def convert_to_target(self) -> GncV2:

# Create GncV2
gnc_v2 = GncV2(count_data=CountData(type_value="book", value=1), book=book)
self.target=gnc_v2
self.target = gnc_v2
return gnc_v2

def to_text(self)->str:
xml_string=self.gcxml.to_text(self.target)
def to_text(self) -> str:
xml_string = self.gcxml.to_text(self.target)
return xml_string
1 change: 0 additions & 1 deletion nomina/gnucash.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,6 @@ def xml_format(self, xml_string: str) -> str:

return formatted_xml


def to_text(self, gnucash_data: GncV2) -> str:
"""
Serialize the GnuCash data object to an XML string.
Expand Down
23 changes: 14 additions & 9 deletions nomina/nomina_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@
from typing import TextIO

from lodstorage.persistent_log import Log

from nomina.file_formats import AccountingFileFormat


class AccountingFileConverter:
"""
Abstract base class for accounting file converters.
This class provides a structure for converting accounting data from one format to another.
"""

def __init__(self, from_format:AccountingFileFormat, to_format:AccountingFileFormat,debug: bool = False):
def __init__(
self,
from_format: AccountingFileFormat,
to_format: AccountingFileFormat,
debug: bool = False,
):
"""
Constructor that initializes the log instance.
"""
self.from_format=from_format
self.to_format=to_format
self.from_format = from_format
self.to_format = to_format
self.debug = debug
self.source=None
self.target=None
self.source = None
self.target = None
self.log = Log()

def convert(self, input_stream: TextIO, output_stream: TextIO) -> str:
Expand Down Expand Up @@ -51,14 +58,13 @@ def convert(self, input_stream: TextIO, output_stream: TextIO) -> str:
return self.text

def show_stats(self):
source_stats=self.source.get_stats()
source_stats = self.source.get_stats()
if self.debug:
source_stats.show()
target_stats=self.target.get_stats()
target_stats = self.target.get_stats()
if self.debug:
target_stats.show()


def load(self, input_stream: TextIO):
"""
Load the content from the input stream.
Expand Down Expand Up @@ -91,4 +97,3 @@ def to_text(self) -> str:
ValueError: If not implemented by a subclass.
"""
raise ValueError("to_text must be implemented in the subclass")

9 changes: 9 additions & 0 deletions nomina_examples/expenses2024_bzv.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# Banking ZV Subsembly json
# WF 2024-10-08
#
name: expenses2024
owner: John Doe
url: https://raw.githubusercontent.com/WolfgangFahl/pynomina/refs/heads/main/nomina_examples/expenses2024_bzv.yaml
account_json_exports:
expenses: expenses2024_bzv.json
9 changes: 0 additions & 9 deletions nomina_examples/expenses_2024_bzv.yaml

This file was deleted.

44 changes: 31 additions & 13 deletions tests/example_testcases.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# expense_example.py
from pathlib import Path
from typing import Dict
from typing import Dict, Iterable

import requests
from lodstorage.persistent_log import Log
Expand Down Expand Up @@ -163,8 +163,28 @@ def check_file(self, file: Path):

def check_stats(self, stats: Stats, expected_stats: Stats = None) -> int:
"""
check the statistics
Check the statistics using a simplified, recursive approach.
"""

def compare(expected, actual, path=""):
if isinstance(expected, dict) and isinstance(actual, dict):
for k in set(expected) | set(actual):
yield from compare(
expected.get(k), actual.get(k), f"{path}.{k}" if path else k
)
elif (
isinstance(expected, Iterable)
and isinstance(actual, Iterable)
and not isinstance(expected, (str, bytes))
):
if len(expected) != len(actual):
yield False, f"Length mismatch for {path}. Expected: {len(expected)}, Got: {len(actual)}"
else:
for i, (e, a) in enumerate(zip(expected, actual)):
yield from compare(e, a, f"{path}[{i}]")
elif expected != actual:
yield False, f"Mismatch for {path}. Expected: {expected}, Got: {actual}"

wrong = 0
if expected_stats is None:
expected_stats = self.expected_stats
Expand All @@ -173,18 +193,16 @@ def check_stats(self, stats: Stats, expected_stats: Stats = None) -> int:
expected_value = getattr(expected_stats, attr)
actual_value = getattr(stats, attr)

if actual_value != expected_value:
self.log(
"⚠️",
f"{attr}_mismatch",
f"Stat mismatch for {attr}. Expected: {expected_value}, Got: {actual_value}",
)
wrong += 1
mismatches = list(compare(expected_value, actual_value, attr))

if mismatches:
for _, message in mismatches:
self.log("⚠️", f"{attr}_mismatch", message)
wrong += 1
else:
self.log(
"✅", f"{attr}_match", f"Stat match for {attr}: {actual_value}"
)
return wrong
self.log("✅", f"{attr}_match", f"Match for {attr}")

return wrong

def expenses_qif(self) -> str:
qif = """!Account
Expand Down
7 changes: 3 additions & 4 deletions tests/test_bzv.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ def test_read_bzv(self):
"""
test reading Banking ZV files
"""
bzv_example = self.examples_path + "/expenses2024.json"
account_json_dict = {"expenses": bzv_example}
book = Book(account_json_dict, name="expenses2024", owner="John doe")
bzv_example = self.examples_path + "/expenses2024_bzv.yaml"
book = Book.load_from_file(bzv_example)
stats = book.get_stats()
if self.debug:
stats.show()
Expand All @@ -35,6 +34,6 @@ def test_read_bzv(self):
owner="John Doe",
url=None,
example_path=Path(self.examples_path),
expected_stats=Stats(accounts=2, transactions=2),
expected_stats=Stats(accounts=2, transactions=2, currencies={"EUR": 2}),
)
example.check_stats(stats)
2 changes: 1 addition & 1 deletion tests/test_file_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ def test_detect_formats(self):
)

# Assert that we detected at least 13 files
self.assertGreaterEqual(13,len(detected_formats))
self.assertGreaterEqual(13, len(detected_formats))
2 changes: 1 addition & 1 deletion tests/test_ledger_beancount.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def test_ledger2beancount(self):
with open(output_path, "w") as bean_file:
beancount_output = l2b.convert(example.ledger_file, bean_file)
if self.debug:
stats=l2b.target.get_stats()
stats = l2b.target.get_stats()
stats.show()
# Verify the conversion
self.assertIsNotNone(beancount_output)
Expand Down
8 changes: 5 additions & 3 deletions tests/test_ledger_gnucash.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
@author: wf
"""

import os

from nomina.gnc_ledger import GnuCashToLedgerConverter, LedgerToGnuCashConverter
from tests.basetest import Basetest
from tests.example_testcases import NominaExample
import os


class Test_LedgerGnuCash(Basetest):
"""
Expand All @@ -27,7 +29,7 @@ def test_ledger2gnucash(self):
l2g = LedgerToGnuCashConverter(debug=self.debug)
output_path = os.path.join("/tmp", f"{name}_l2g_xml.gnucash")
with open(output_path, "w") as gc_file:
l2g.convert(example.ledger_file,gc_file)
l2g.convert(example.ledger_file, gc_file)
if self.debug:
l2g.show_stats()

Expand All @@ -41,6 +43,6 @@ def test_gnucash2ledger(self):
output_path = os.path.join("/tmp", f"{name}_g2l.yaml")
# owner=example.owner, url=example.url
with open(output_path, "w") as ledger_file:
g2l.convert(example.gnu_cash_xml_file,ledger_file)
g2l.convert(example.gnu_cash_xml_file, ledger_file)
if self.debug:
g2l.show_stats()

0 comments on commit d117ba4

Please sign in to comment.