From 3074c069b5afa8c9380779f632977466afe7cea4 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Mon, 2 Feb 2026 20:44:11 +0100 Subject: [PATCH] Optimize performance by caching some values. --- src/ledgplom/ledger.py | 51 ++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/ledgplom/ledger.py b/src/ledgplom/ledger.py index 2a1ee59..2762498 100644 --- a/src/ledgplom/ledger.py +++ b/src/ledgplom/ledger.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from datetime import date as dt_date from decimal import Decimal, InvalidOperation as DecimalInvalidOperation from pathlib import Path -from typing import Any, Iterator, Optional, Self +from typing import Any, Callable, Iterator, Optional, Self SPACE = ' ' @@ -284,27 +284,46 @@ class _TransferLine(_BookingLine): class _LinesBlock: - _lines: list[_DatLine] + _lines: tuple[_DatLine, ...] = tuple() + _cached_id = -1 + _cached: dict[str, Any] def __init__(self) -> None: - self._lines = [] + self._cached = {} + + @staticmethod + def _cache(key_cached: str) -> Callable: + def decorator(f: Callable) -> Callable: + def wrapper(self): + # pylint: disable=protected-access + if key_cached not in self._cached: + self._cached[key_cached] = f(self) + return self._cached[key_cached] + return wrapper + return decorator @property def lines(self) -> tuple[_DatLine, ...]: 'Return collected lines.' - return tuple(self._lines) + return self._lines + + @lines.setter + def lines(self, new_lines: tuple[_DatLine, ...]) -> None: + self._lines = new_lines + self._cached.clear() def add(self, lines: tuple[_DatLine, ...], at_end=True) -> None: 'Grow block downwards by this one DatLine.' if at_end: - self._lines += lines + self.lines += lines else: - self._lines[0:0] = list(lines) + self.lines = lines + self.lines class _Booking(_LinesBlock): @property + @_LinesBlock._cache('_sink_account') def _sink_account(self) -> str: for tf_line in [tl for tl in self.transfer_lines if tl.amount is None and not tl.errors]: @@ -312,8 +331,8 @@ class _Booking(_LinesBlock): return '' @property - def lines(self) -> tuple[_BookingLine, ...]: - 'Return collected lines.' + def booking_lines(self) -> tuple[_BookingLine, ...]: + 'Sequence of .intro_line and .transfer_lines.' return (self.intro_line, ) + self.transfer_lines @property @@ -336,6 +355,7 @@ class _Booking(_LinesBlock): return {acc: diff for acc, diff in self._diffs_targeted.items() if acc} @property + @_LinesBlock._cache('sink_error') def sink_error(self) -> str: 'Message on error, if any, regarding sink calculation/placement.' for _ in [tl for tl in self.transfer_lines @@ -362,12 +382,13 @@ class _Booking(_LinesBlock): @property def intro_line(self) -> _IntroLine: 'Return collected _IntroLine.' - return _IntroLine(self._lines[0].raw) + return _IntroLine(self.lines[0].raw) @property + @_LinesBlock._cache('transfer_lines') def transfer_lines(self) -> tuple[_TransferLine, ...]: 'Any lines past the first with .code.' - return tuple(_TransferLine(line.raw) for line in self._lines[1:] + return tuple(_TransferLine(line.raw) for line in self.lines[1:] if line.code) @property @@ -405,6 +426,7 @@ class _DatBlock(_LinesBlock): return bool([line for line in self.lines if line.len_indent]) @property + @_LinesBlock._cache('gap_lines') def gap_lines(self) -> tuple[_DatLine, ...]: 'Sequence of all included DatLines without code and indent.' return tuple(line for line in self.lines @@ -426,10 +448,11 @@ class _DatBlock(_LinesBlock): def remove_redundant_empty_lines(self): 'From self remove .redundant_empty_lines.' - for line in self.redundant_empty_lines: - self._lines.remove(line) + self.lines = tuple(line for line in self.lines + if line not in self.redundant_empty_lines) @property + @_LinesBlock._cache('booking') def booking(self) -> Optional[_Booking]: 'Booking made from lines indented or with code.' booking_lines = tuple(_DatLine(line.raw) for line in self.lines @@ -617,7 +640,7 @@ class Ledger: if block.booking: if block.booking.sink_error: return False - if [line for line in block.booking.lines if line.errors]: + if [bl for bl in block.booking.booking_lines if bl.errors]: return False if block.date_error: return False @@ -693,7 +716,7 @@ class Ledger: if lines: return {'raw_gap_lines': [dl.raw for dl in block.gap_lines], 'booking_lines': ([line.as_dict - for line in block.booking.lines] + for line in block.booking.booking_lines] if block.booking else tuple())} accounts = self._calc_accounts() roots: list[dict[str, Any]] = [] -- 2.30.2