From 547ae657808f05fcd1160c568ed4f5f22d492b6c 2020-03-08 15:32:03 From: Brett Smith Date: 2020-03-08 15:32:03 Subject: [PATCH] plugin.core: _meta_set properly handles when post.meta is None. post is a NamedTuple, so attribute assignment is not allowed. Instead we have to construct a whole new Posting. --- diff --git a/conservancy_beancount/plugin/__init__.py b/conservancy_beancount/plugin/__init__.py index 3c8842332ca052bb9db5a8ed3be048a13ab55556..cbcb8809baf55950de55b46b422a37523c96628d 100644 --- a/conservancy_beancount/plugin/__init__.py +++ b/conservancy_beancount/plugin/__init__.py @@ -86,8 +86,8 @@ def run(entries, options_map, config='', hook_registry=HOOK_REGISTRY): for hook in hooks[entry_type]: errors.extend(hook.run(entry)) if entry_type == 'Transaction': - for post in entry.postings: + for index, post in enumerate(entry.postings): for hook in hooks['Posting']: - errors.extend(hook.run(entry, post)) + errors.extend(hook.run(entry, post, index)) return entries, errors diff --git a/conservancy_beancount/plugin/core.py b/conservancy_beancount/plugin/core.py index fc8a1308fa84a1b496e2d5fe97f973ad27ec04a3..ddf2437f42a89fbbf60352af43b3f9766345c0b8 100644 --- a/conservancy_beancount/plugin/core.py +++ b/conservancy_beancount/plugin/core.py @@ -132,10 +132,11 @@ class PostingChecker: except (KeyError, TypeError): return txn.meta.get(key, default) - def _meta_set(self, post, key, value): + def _meta_set(self, txn, post, post_index, key, value): if post.meta is None: - post.meta = {} - post.meta[key] = value + txn.postings[post_index] = Posting(*post[:5], {key: value}) + else: + post.meta[key] = value # If the posting does not specify METADATA_KEY, the hook calls # _default_value to get a default. This method should either return diff --git a/tests/test_meta_expenseAllocation.py b/tests/test_meta_expenseAllocation.py index 33240dca61f4a2d879be45d4aa03e601e29ebfb9..2c01e41e2d7976c83d809596d83110cb9db00666 100644 --- a/tests/test_meta_expenseAllocation.py +++ b/tests/test_meta_expenseAllocation.py @@ -42,7 +42,7 @@ def test_valid_values_on_postings(src_value, set_value): ('Expenses:General', 25, {'expenseAllocation': src_value}), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert txn.postings[-1].meta.get('expenseAllocation') == set_value @@ -53,7 +53,7 @@ def test_invalid_values_on_postings(src_value): ('Expenses:General', 25, {'expenseAllocation': src_value}), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert errors @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) @@ -63,7 +63,7 @@ def test_valid_values_on_transactions(src_value, set_value): ('Expenses:General', 25), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert txn.postings[-1].meta.get('expenseAllocation') == set_value @@ -74,7 +74,7 @@ def test_invalid_values_on_transactions(src_value): ('Expenses:General', 25), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert errors @pytest.mark.parametrize('account', [ @@ -90,7 +90,7 @@ def test_non_expense_accounts_skipped(account): ('Expenses:General', 25, {'expenseAllocation': 'program'}), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[0]) + errors = checker.run(txn, txn.postings[0], 0) assert not errors @pytest.mark.parametrize('account,set_value', [ @@ -106,7 +106,7 @@ def test_default_values(account, set_value): (account, 25), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert txn.postings[-1].meta['expenseAllocation'] == set_value @@ -123,7 +123,7 @@ def test_default_value_set_in_date_range(date, set_value): ('Expenses:General', 25), ]) checker = meta_expense_allocation.MetaExpenseAllocation() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors got_value = (txn.postings[-1].meta or {}).get('expenseAllocation') assert bool(got_value) == bool(set_value) diff --git a/tests/test_meta_taxImplication.py b/tests/test_meta_taxImplication.py index fe2a794fd92d63b334179bbe13b96be4ac763e4c..d745a164387f5db18966ebd39406d64a127bf6a4 100644 --- a/tests/test_meta_taxImplication.py +++ b/tests/test_meta_taxImplication.py @@ -54,7 +54,7 @@ def test_valid_values_on_postings(src_value, set_value): ('Assets:Cash', -25, {'taxImplication': src_value}), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert txn.postings[-1].meta.get('taxImplication') == set_value @@ -65,7 +65,7 @@ def test_invalid_values_on_postings(src_value): ('Assets:Cash', -25, {'taxImplication': src_value}), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert errors @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) @@ -75,7 +75,7 @@ def test_valid_values_on_transactions(src_value, set_value): ('Assets:Cash', -25), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert txn.postings[-1].meta.get('taxImplication') == set_value @@ -86,7 +86,7 @@ def test_invalid_values_on_transactions(src_value): ('Assets:Cash', -25), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert errors @pytest.mark.parametrize('account', [ @@ -100,7 +100,7 @@ def test_non_asset_accounts_skipped(account): ('Assets:Cash', -25, {'taxImplication': 'USA-Corporation'}), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[0]) + errors = checker.run(txn, txn.postings[0], 0) assert not errors def test_asset_credits_skipped(): @@ -109,7 +109,7 @@ def test_asset_credits_skipped(): ('Assets:Cash', 25), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert not errors assert not txn.postings[-1].meta @@ -126,5 +126,5 @@ def test_default_value_set_in_date_range(date, need_value): ('Assets:Cash', -25), ]) checker = meta_tax_implication.MetaTaxImplication() - errors = checker.run(txn, txn.postings[-1]) + errors = checker.run(txn, txn.postings[-1], -1) assert bool(errors) == bool(need_value) diff --git a/tests/test_plugin_run.py b/tests/test_plugin_run.py index 521193490bd94db6528af9c7f8bfdf1b8bee6d08..42838298fcb2ad28e7b71f86b10ebefa59e62cf3 100644 --- a/tests/test_plugin_run.py +++ b/tests/test_plugin_run.py @@ -35,7 +35,7 @@ class TransactionCounter: class PostingCounter(TransactionCounter): HOOK_GROUPS = frozenset(['Posting', 'counter']) - def run(self, txn, post): + def run(self, txn, post, post_index): return ['post:{}'.format(id(post))] diff --git a/tests/testutil.py b/tests/testutil.py index c113e1bbd11b25798e3b0b9f718c139c98cab9fc..7fc38156f5e148911f003ddabf181d25ebb74bc2 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -33,6 +33,8 @@ def parse_date(s, fmt='%Y-%m-%d'): def Posting(account, number, currency='USD', cost=None, price=None, flag=None, **meta): + if not meta: + meta = None return bc_data.Posting( account, bc_amount.Amount(Decimal(number), currency),