diff --git a/conservancy_beancount/filters.py b/conservancy_beancount/filters.py new file mode 100644 index 0000000..7168eb9 --- /dev/null +++ b/conservancy_beancount/filters.py @@ -0,0 +1,35 @@ +"""filters.py - Common filters for postings""" +# Copyright © 2020 Brett Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from . import data + +from typing import ( + Iterable, +) +from .beancount_types import ( + MetaKey, + MetaValue, +) + +Postings = Iterable[data.Posting] + +def filter_meta_equal(postings: Postings, key: MetaKey, value: MetaValue) -> Postings: + for post in postings: + try: + if post.meta[key] == value: + yield post + except KeyError: + pass diff --git a/tests/test_filters.py b/tests/test_filters.py new file mode 100644 index 0000000..8e475cb --- /dev/null +++ b/tests/test_filters.py @@ -0,0 +1,77 @@ +"""test_filters - Unit tests for filter functions""" +# Copyright © 2020 Brett Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import itertools + +import pytest + +from . import testutil + +from conservancy_beancount import data +from conservancy_beancount import filters + +MISSING_POSTING = testutil.Posting('', 0) + +@pytest.fixture +def cc_txn_pair(): + dates = testutil.date_seq() + txn_meta = { + 'payee': 'Smith-Dakota', + 'rt-id': 'rt:550', + } + return [ + testutil.Transaction( + **txn_meta, + date=next(dates), + receipt='CCReceipt.pdf', + postings=[ + ('Liabilities:CreditCard', -36), + ('Expenses:Other', 35), + ('Expenses:Tax:Sales', 1), + ], + ), + testutil.Transaction( + **txn_meta, + date=next(dates), + receipt='CCPayment.pdf', + postings=[ + ('Liabilities:CreditCard', 36), + ('Assets:Checking', -36, {'statement': 'CheckingStatement.pdf'}), + ], + ), + ] + +def check_filter(actual, entries, expected_indexes): + postings = [post for txn in entries for post in txn.postings] + expected = (postings[ii] for ii in expected_indexes) + for actual_post, expected_post in itertools.zip_longest( + actual, expected, fillvalue=MISSING_POSTING, + ): + assert actual_post[:-1] == expected_post[:-1] + +@pytest.mark.parametrize('key,value,expected_indexes', [ + ('entity', 'Smith-Dakota', range(5)), + ('receipt', 'CCReceipt.pdf', range(3)), + ('receipt', 'CCPayment.pdf', range(3, 5)), + ('receipt', 'CC', ()), + ('statement', 'CheckingStatement.pdf', [4]), + ('BadKey', '', ()), + ('emptykey', '', ()), +]) +def test_filter_meta_equal(cc_txn_pair, key, value, expected_indexes): + postings = data.Posting.from_entries(cc_txn_pair) + actual = filters.filter_meta_equal(postings, key, value) + check_filter(actual, cc_txn_pair, expected_indexes)