Changeset - 9595d3334d5b
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-05-25 14:38:02
brettcsmith@brettcsmith.org
books: Add Loader.load_all method.
2 files changed with 57 insertions and 26 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/books.py
Show inline comments
...
 
@@ -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)
tests/test_books_loader.py
Show inline comments
...
 
@@ -14,6 +14,7 @@
 
# You should have received a copy of the GNU Affero General Public License
 
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

	
 
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)
0 comments (0 inline, 0 general)