books: Loader.from_all() accepts a start FY argument.

This commit is contained in:
Brett Smith 2020-06-04 09:03:10 -04:00
parent 8dbe807efb
commit 2bd3e8b462
3 changed files with 61 additions and 26 deletions

View file

@ -135,16 +135,38 @@ class Loader:
errors.extend(new_errors) errors.extend(new_errors)
return entries, errors, options_map return entries, errors, options_map
def load_all(self) -> LoadResult: def _path_year(self, path: Path) -> int:
"""Load all of the books return int(path.stem)
This method loads all of the books. It finds the books by simply def load_all(self, from_year: Optional[Year]=None) -> LoadResult:
globbing the filesystem. It still loads each fiscal year in sequence to """Load all of the books from a starting FY
provide the best cache utilization.
This method loads all of the books, starting from the fiscal year you
specify.
* Pass in a date to start from the FY for that date.
* Pass in an integer >= 1000 to start from that year.
* Pass in a smaller integer to start from an FY relative to today
(e.g., -2 starts two FYs before today).
* Pass is no argument to load all books from the first available FY.
This method finds books by globbing the filesystem. It still loads
each fiscal year in sequence to provide the best cache utilization.
""" """
path = Path(self.books_root, 'books') path = Path(self.books_root, 'books')
fy_paths = list(path.glob('[1-9][0-9][0-9][0-9].beancount')) fy_paths = list(path.glob('[1-9][0-9][0-9][0-9].beancount'))
fy_paths.sort(key=lambda path: int(path.stem)) fy_paths.sort(key=self._path_year)
if from_year is not None:
if not isinstance(from_year, int):
from_year = self.fiscal_year.for_date(from_year)
elif from_year < 1000:
from_year = self.fiscal_year.for_date() + from_year
for index, path in enumerate(fy_paths):
if self._path_year(path) >= from_year:
fy_paths = fy_paths[index:]
break
else:
fy_paths = []
return self._load_paths(iter(fy_paths)) return self._load_paths(iter(fy_paths))
def load_fy_range(self, def load_fy_range(self,
@ -154,9 +176,8 @@ class Loader:
"""Load books for a range of fiscal years """Load books for a range of fiscal years
This method generates a range of fiscal years by calling This method generates a range of fiscal years by calling
FiscalYear.range() with its first two arguments. It returns a string of FiscalYear.range() with its arguments. It loads all the books within
Beancount directives to load the books from the first available fiscal that range.
year through the end of the range.
""" """
fy_range = self.fiscal_year.range(from_fy, to_fy) fy_range = self.fiscal_year.range(from_fy, to_fy)
fy_paths = self._iter_fy_books(fy_range) fy_paths = self._iter_fy_books(fy_range)

View file

@ -27,11 +27,13 @@ from . import testutil
from beancount.core import data as bc_data from beancount.core import data as bc_data
from conservancy_beancount import books from conservancy_beancount import books
FY_START_MONTH = 3
books_path = testutil.test_path('books') books_path = testutil.test_path('books')
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def conservancy_loader(): def conservancy_loader():
return books.Loader(books_path, books.FiscalYear(3)) return books.Loader(books_path, books.FiscalYear(FY_START_MONTH))
def check_openings(entries): def check_openings(entries):
openings = collections.defaultdict(int) openings = collections.defaultdict(int)
@ -41,18 +43,13 @@ def check_openings(entries):
for account, count in openings.items(): for account, count in openings.items():
assert count == 1, f"found {count} open directives for {account}" assert count == 1, f"found {count} open directives for {account}"
def check_narrations(entries, expected): def txn_dates(entries):
expected = iter(expected)
expected_next = next(expected)
for entry in entries: for entry in entries:
if (isinstance(entry, bc_data.Transaction) if isinstance(entry, bc_data.Transaction):
and entry.narration == expected_next): yield entry.date
try:
expected_next = next(expected) def txn_years(entries):
except StopIteration: return frozenset(date.year for date in txn_dates(entries))
break
else:
assert None, f"{expected_next} not found in entry narrations"
@pytest.mark.parametrize('from_fy,to_fy,expect_years', [ @pytest.mark.parametrize('from_fy,to_fy,expect_years', [
(2019, 2019, range(2019, 2020)), (2019, 2019, range(2019, 2020)),
@ -70,7 +67,7 @@ def check_narrations(entries, expected):
def test_load_fy_range(conservancy_loader, from_fy, to_fy, expect_years): def test_load_fy_range(conservancy_loader, from_fy, to_fy, expect_years):
entries, errors, options_map = conservancy_loader.load_fy_range(from_fy, to_fy) entries, errors, options_map = conservancy_loader.load_fy_range(from_fy, to_fy)
assert not errors assert not errors
check_narrations(entries, [f'{year} donation' for year in expect_years]) assert txn_years(entries).issuperset(expect_years)
def test_load_fy_range_does_not_duplicate_openings(conservancy_loader): def test_load_fy_range_does_not_duplicate_openings(conservancy_loader):
entries, errors, options_map = conservancy_loader.load_fy_range(2010, 2030) entries, errors, options_map = conservancy_loader.load_fy_range(2010, 2030)
@ -82,8 +79,25 @@ def test_load_fy_range_empty(conservancy_loader):
assert not entries assert not entries
assert not options_map assert not options_map
def test_load_all(conservancy_loader): @pytest.mark.parametrize('from_year', [None, *range(2018, 2021)])
entries, errors, options_map = conservancy_loader.load_all() def test_load_all(conservancy_loader, from_year):
entries, errors, options_map = conservancy_loader.load_all(from_year)
from_year = from_year or 2018
assert not errors assert not errors
check_narrations(entries, [f'{year} donation' for year in range(2018, 2021)])
check_openings(entries) check_openings(entries)
assert txn_years(entries).issuperset(range(from_year or 2018, 2021))
@pytest.mark.parametrize('from_date', [
date(2019, 2, 1),
date(2019, 9, 15),
date(2020, 1, 20),
date(2020, 5, 31),
])
def test_load_all_from_date(conservancy_loader, from_date):
from_year = from_date.year
if from_date.month < FY_START_MONTH:
from_year -= 1
entries, errors, options_map = conservancy_loader.load_all(from_date)
assert not errors
check_openings(entries)
assert txn_years(entries).issuperset(range(from_year, 2021))

View file

@ -217,7 +217,7 @@ class TestBooksLoader(books.Loader):
def __init__(self, source): def __init__(self, source):
self.source = source self.source = source
def load_all(self): def load_all(self, from_year=None):
return bc_loader.load_file(self.source) return bc_loader.load_file(self.source)
def load_fy_range(self, from_fy, to_fy=None): def load_fy_range(self, from_fy, to_fy=None):