diff --git a/conservancy_beancount/books.py b/conservancy_beancount/books.py index 002f7ad4cdeb38e42e5b2757a695c0a91b479e04..e3fac9a5a54e58cb03c08ab8d0a353dc56bc85f6 100644 --- a/conservancy_beancount/books.py +++ b/conservancy_beancount/books.py @@ -118,24 +118,12 @@ class Loader: if path.exists(): yield path - def load_fy_range(self, - from_fy: Year, - to_fy: Optional[Year]=None, - ) -> LoadResult: - """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. - """ - fy_range = self.fiscal_year.range(from_fy, to_fy) - fy_paths = self._iter_fy_books(fy_range) + def _load_paths(self, paths: Iterator[Path]) -> LoadResult: try: - entries, errors, options_map = bc_loader.load_file(next(fy_paths)) + entries, errors, options_map = bc_loader.load_file(next(paths)) except StopIteration: entries, errors, options_map = [], [], {} - for load_path in fy_paths: + for load_path in paths: new_entries, new_errors, new_options = bc_loader.load_file(load_path) # We only want transactions from the new fiscal year. # We don't want the opening balance, duplicate definitions, etc. @@ -146,3 +134,30 @@ class Loader: ) errors.extend(new_errors) return entries, errors, options_map + + def load_all(self) -> LoadResult: + """Load all of the books + + 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. + """ + path = Path(self.books_root, 'books') + fy_paths = list(path.glob('[1-9][0-9][0-9][0-9].beancount')) + fy_paths.sort() + return self._load_paths(iter(fy_paths)) + + def load_fy_range(self, + from_fy: Year, + to_fy: Optional[Year]=None, + ) -> LoadResult: + """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. + """ + fy_range = self.fiscal_year.range(from_fy, to_fy) + fy_paths = self._iter_fy_books(fy_range) + return self._load_paths(fy_paths) diff --git a/tests/test_books_loader.py b/tests/test_books_loader.py index 86c764f0ba9d197cedf63e181a5ae1017696c477..ce6ae876757463c6bde1159791cfca7a730d692a 100644 --- a/tests/test_books_loader.py +++ b/tests/test_books_loader.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import collections import re from datetime import date @@ -23,6 +24,7 @@ import pytest from . import testutil +from beancount.core import data as bc_data from conservancy_beancount import books books_path = testutil.test_path('books') @@ -31,6 +33,20 @@ books_path = testutil.test_path('books') def conservancy_loader(): return books.Loader(books_path, books.FiscalYear(3)) +def check_openings(entries): + openings = collections.defaultdict(int) + for entry in entries: + if isinstance(entry, bc_data.Open): + openings[entry.account] += 1 + for account, count in openings.items(): + assert count == 1, f"found {count} open directives for {account}" + +def get_narrations(entries): + return { + entry.narration for entry in entries + if isinstance(entry, bc_data.Transaction) + } + @pytest.mark.parametrize('from_fy,to_fy,expect_years', [ (2019, 2019, range(2019, 2020)), (0, 2019, range(2019, 2020)), @@ -47,26 +63,26 @@ def conservancy_loader(): 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 - narrations = {getattr(entry, 'narration', None) for entry in entries} + narrations = get_narrations(entries) assert ('2018 donation' in narrations) == (2018 in expect_years) assert ('2019 donation' in narrations) == (2019 in expect_years) assert ('2020 donation' in narrations) == (2020 in expect_years) def test_load_fy_range_does_not_duplicate_openings(conservancy_loader): entries, errors, options_map = conservancy_loader.load_fy_range(2010, 2030) - openings = [] - open_accounts = set() - for entry in entries: - try: - open_accounts.add(entry.account) - except AttributeError: - pass - else: - openings.append(entry) - assert len(openings) == len(open_accounts) + check_openings(entries) def test_load_fy_range_empty(conservancy_loader): entries, errors, options_map = conservancy_loader.load_fy_range(2020, 2019) assert not errors assert not entries assert not options_map + +def test_load_all(conservancy_loader): + entries, errors, options_map = conservancy_loader.load_all() + assert not errors + narrations = get_narrations(entries) + assert '2018 donation' in narrations + assert '2019 donation' in narrations + assert '2020 donation' in narrations + check_openings(entries)