books: Loader.from_all() accepts a start FY argument.
This commit is contained in:
parent
8dbe807efb
commit
2bd3e8b462
3 changed files with 61 additions and 26 deletions
|
@ -135,16 +135,38 @@ class Loader:
|
|||
errors.extend(new_errors)
|
||||
return entries, errors, options_map
|
||||
|
||||
def load_all(self) -> LoadResult:
|
||||
"""Load all of the books
|
||||
def _path_year(self, path: Path) -> int:
|
||||
return int(path.stem)
|
||||
|
||||
This method loads all of the books. It finds the books by simply
|
||||
globbing the filesystem. It still loads each fiscal year in sequence to
|
||||
provide the best cache utilization.
|
||||
def load_all(self, from_year: Optional[Year]=None) -> LoadResult:
|
||||
"""Load all of the books from a starting FY
|
||||
|
||||
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')
|
||||
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))
|
||||
|
||||
def load_fy_range(self,
|
||||
|
@ -154,9 +176,8 @@ class Loader:
|
|||
"""Load books for a range of fiscal years
|
||||
|
||||
This method generates a range of fiscal years by calling
|
||||
FiscalYear.range() with its first two arguments. It returns a string of
|
||||
Beancount directives to load the books from the first available fiscal
|
||||
year through the end of the range.
|
||||
FiscalYear.range() with its arguments. It loads all the books within
|
||||
that range.
|
||||
"""
|
||||
fy_range = self.fiscal_year.range(from_fy, to_fy)
|
||||
fy_paths = self._iter_fy_books(fy_range)
|
||||
|
|
|
@ -27,11 +27,13 @@ from . import testutil
|
|||
from beancount.core import data as bc_data
|
||||
from conservancy_beancount import books
|
||||
|
||||
FY_START_MONTH = 3
|
||||
|
||||
books_path = testutil.test_path('books')
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
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):
|
||||
openings = collections.defaultdict(int)
|
||||
|
@ -41,18 +43,13 @@ def check_openings(entries):
|
|||
for account, count in openings.items():
|
||||
assert count == 1, f"found {count} open directives for {account}"
|
||||
|
||||
def check_narrations(entries, expected):
|
||||
expected = iter(expected)
|
||||
expected_next = next(expected)
|
||||
def txn_dates(entries):
|
||||
for entry in entries:
|
||||
if (isinstance(entry, bc_data.Transaction)
|
||||
and entry.narration == expected_next):
|
||||
try:
|
||||
expected_next = next(expected)
|
||||
except StopIteration:
|
||||
break
|
||||
else:
|
||||
assert None, f"{expected_next} not found in entry narrations"
|
||||
if isinstance(entry, bc_data.Transaction):
|
||||
yield entry.date
|
||||
|
||||
def txn_years(entries):
|
||||
return frozenset(date.year for date in txn_dates(entries))
|
||||
|
||||
@pytest.mark.parametrize('from_fy,to_fy,expect_years', [
|
||||
(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):
|
||||
entries, errors, options_map = conservancy_loader.load_fy_range(from_fy, to_fy)
|
||||
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):
|
||||
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 options_map
|
||||
|
||||
def test_load_all(conservancy_loader):
|
||||
entries, errors, options_map = conservancy_loader.load_all()
|
||||
@pytest.mark.parametrize('from_year', [None, *range(2018, 2021)])
|
||||
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
|
||||
check_narrations(entries, [f'{year} donation' for year in range(2018, 2021)])
|
||||
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))
|
||||
|
|
|
@ -217,7 +217,7 @@ class TestBooksLoader(books.Loader):
|
|||
def __init__(self, source):
|
||||
self.source = source
|
||||
|
||||
def load_all(self):
|
||||
def load_all(self, from_year=None):
|
||||
return bc_loader.load_file(self.source)
|
||||
|
||||
def load_fy_range(self, from_fy, to_fy=None):
|
||||
|
|
Loading…
Reference in a new issue