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():
|
if path.exists():
|
||||||
yield path
|
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,
|
def load_fy_range(self,
|
||||||
from_fy: Year,
|
from_fy: Year,
|
||||||
to_fy: Optional[Year]=None,
|
to_fy: Optional[Year]=None,
|
||||||
|
@ -131,18 +160,4 @@ class Loader:
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
try:
|
return self._load_paths(fy_paths)
|
||||||
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
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import collections
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -23,6 +24,7 @@ import pytest
|
||||||
|
|
||||||
from . import testutil
|
from . import testutil
|
||||||
|
|
||||||
|
from beancount.core import data as bc_data
|
||||||
from conservancy_beancount import books
|
from conservancy_beancount import books
|
||||||
|
|
||||||
books_path = testutil.test_path('books')
|
books_path = testutil.test_path('books')
|
||||||
|
@ -31,6 +33,20 @@ books_path = testutil.test_path('books')
|
||||||
def conservancy_loader():
|
def conservancy_loader():
|
||||||
return books.Loader(books_path, books.FiscalYear(3))
|
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', [
|
@pytest.mark.parametrize('from_fy,to_fy,expect_years', [
|
||||||
(2019, 2019, range(2019, 2020)),
|
(2019, 2019, range(2019, 2020)),
|
||||||
(0, 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):
|
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
|
||||||
narrations = {getattr(entry, 'narration', None) for entry in entries}
|
narrations = get_narrations(entries)
|
||||||
assert ('2018 donation' in narrations) == (2018 in expect_years)
|
assert ('2018 donation' in narrations) == (2018 in expect_years)
|
||||||
assert ('2019 donation' in narrations) == (2019 in expect_years)
|
assert ('2019 donation' in narrations) == (2019 in expect_years)
|
||||||
assert ('2020 donation' in narrations) == (2020 in expect_years)
|
assert ('2020 donation' in narrations) == (2020 in 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)
|
||||||
openings = []
|
check_openings(entries)
|
||||||
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)
|
|
||||||
|
|
||||||
def test_load_fy_range_empty(conservancy_loader):
|
def test_load_fy_range_empty(conservancy_loader):
|
||||||
entries, errors, options_map = conservancy_loader.load_fy_range(2020, 2019)
|
entries, errors, options_map = conservancy_loader.load_fy_range(2020, 2019)
|
||||||
assert not errors
|
assert not errors
|
||||||
assert not entries
|
assert not entries
|
||||||
assert not options_map
|
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