books: Add Loader.load_all method.
This commit is contained in:
parent
6801d12359
commit
9595d3334d
2 changed files with 57 additions and 26 deletions
|
@ -118,6 +118,35 @@ class Loader:
|
|||
if path.exists():
|
||||
yield path
|
||||
|
||||
def _load_paths(self, paths: Iterator[Path]) -> LoadResult:
|
||||
try:
|
||||
entries, errors, options_map = bc_loader.load_file(next(paths))
|
||||
except StopIteration:
|
||||
entries, errors, options_map = [], [], {}
|
||||
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.
|
||||
fy_filename = str(load_path.parent.parent / load_path.name)
|
||||
entries.extend(
|
||||
entry for entry in new_entries
|
||||
if entry.meta.get('filename') == fy_filename
|
||||
)
|
||||
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,
|
||||
|
@ -131,18 +160,4 @@ class Loader:
|
|||
"""
|
||||
fy_range = self.fiscal_year.range(from_fy, to_fy)
|
||||
fy_paths = self._iter_fy_books(fy_range)
|
||||
try:
|
||||
entries, errors, options_map = bc_loader.load_file(next(fy_paths))
|
||||
except StopIteration:
|
||||
entries, errors, options_map = [], [], {}
|
||||
for load_path in fy_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.
|
||||
fy_filename = str(load_path.parent.parent / load_path.name)
|
||||
entries.extend(
|
||||
entry for entry in new_entries
|
||||
if entry.meta.get('filename') == fy_filename
|
||||
)
|
||||
errors.extend(new_errors)
|
||||
return entries, errors, options_map
|
||||
return self._load_paths(fy_paths)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue