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 = ' '
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]:
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
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
@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
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
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
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
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]] = []