Changeset - 536b50b478d8
[Not reviewed]
0 10 0
Brett Smith - 4 years ago 2020-05-11 13:52:05
brettcsmith@brettcsmith.org
plugin: Don't validate transactions flagged with !. RT#10591.
10 files changed with 77 insertions and 17 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/plugin/core.py
Show inline comments
...
 
@@ -71,10 +71,6 @@ class Hook(Generic[Entry], metaclass=abc.ABCMeta):
 
    def run(self, entry: Entry) -> errormod.Iter: ...
 

	
 

	
 
class TransactionHook(Hook[Transaction]):
 
    DIRECTIVE = Transaction
 

	
 

	
 
### HELPER CLASSES
 

	
 
class LessComparable(metaclass=abc.ABCMeta):
...
 
@@ -178,18 +174,28 @@ class MetadataEnum:
 

	
 
### HOOK SUBCLASSES
 

	
 
class _PostingHook(TransactionHook, metaclass=abc.ABCMeta):
 
class TransactionHook(Hook[Transaction]):
 
    DIRECTIVE = Transaction
 
    TXN_DATE_RANGE: _GenericRange = _GenericRange(DEFAULT_START_DATE, DEFAULT_STOP_DATE)
 

	
 
    def __init_subclass__(cls) -> None:
 
        cls.HOOK_GROUPS = cls.HOOK_GROUPS.union(['posting'])
 

	
 
    def _run_on_txn(self, txn: Transaction) -> bool:
 
        """Check whether we should run on a given transaction
 

	
 
        This method implements our usual checks for whether or not a hook
 
        should run on a given transaction. It's here for subclasses to use in
 
        their own implementations. See _PostingHook below for an example.
 
        """
 
        return (
 
            txn.date in self.TXN_DATE_RANGE
 
            txn.flag != '!'
 
            and txn.date in self.TXN_DATE_RANGE
 
            and not data.is_opening_balance_txn(txn)
 
        )
 

	
 

	
 
class _PostingHook(TransactionHook, metaclass=abc.ABCMeta):
 
    def __init_subclass__(cls) -> None:
 
        cls.HOOK_GROUPS = cls.HOOK_GROUPS.union(['posting'])
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return True
 

	
conservancy_beancount/plugin/meta_repo_links.py
Show inline comments
...
 
@@ -63,7 +63,8 @@ class MetaRepoLinks(core.TransactionHook):
 
                        yield errormod.BrokenLinkError(txn, key, link)
 

	
 
    def run(self, txn: Transaction) -> errormod.Iter:
 
        yield from self._check_links(txn.meta, txn)
 
        for post in txn.postings:
 
            if post.meta is not None:
 
                yield from self._check_links(post.meta, txn, post)
 
        if self._run_on_txn(txn):
 
            yield from self._check_links(txn.meta, txn)
 
            for post in txn.postings:
 
                if post.meta is not None:
 
                    yield from self._check_links(post.meta, txn, post)
conservancy_beancount/plugin/meta_rt_links.py
Show inline comments
...
 
@@ -59,7 +59,8 @@ class MetaRTLinks(core.TransactionHook):
 
                        yield errormod.BrokenRTLinkError(txn, key, link, parsed)
 

	
 
    def run(self, txn: Transaction) -> errormod.Iter:
 
        yield from self._check_links(txn.meta, txn)
 
        for post in txn.postings:
 
            if post.meta is not None:
 
                yield from self._check_links(post.meta, txn, post)
 
        if self._run_on_txn(txn):
 
            yield from self._check_links(txn.meta, txn)
 
            for post in txn.postings:
 
                if post.meta is not None:
 
                    yield from self._check_links(post.meta, txn, post)
tests/test_meta_approval.py
Show inline comments
...
 
@@ -180,3 +180,10 @@ def test_approval_required_for_partial_transfer(hook):
 
    ])
 
    actual = {error.message for error in hook.run(txn)}
 
    assert actual == {"Assets:Checking missing {}".format(TEST_KEY)}
 

	
 
def test_not_required_on_flagged(hook):
 
    txn = testutil.Transaction(flag='!', postings=[
 
        ('Assets:Checking', -25),
 
        ('Liabilities:Payable:Accounts', 25),
 
    ])
 
    assert not list(hook.run(txn))
tests/test_meta_invoice.py
Show inline comments
...
 
@@ -144,3 +144,14 @@ def test_missing_invoice(hook, acct1, acct2):
 
def test_not_required_on_opening(hook):
 
    txn = testutil.OpeningBalance()
 
    assert not list(hook.run(txn))
 

	
 
@pytest.mark.parametrize('acct1,acct2', testutil.combine_values(
 
    REQUIRED_ACCOUNTS,
 
    NON_REQUIRED_ACCOUNTS,
 
))
 
def test_not_required_on_flagged(acct1, acct2, hook):
 
    txn = testutil.Transaction(flag='!', postings=[
 
        (acct1, 25),
 
        (acct2, -25),
 
    ])
 
    assert not list(hook.run(txn))
tests/test_meta_payable_documentation.py
Show inline comments
...
 
@@ -171,3 +171,6 @@ def test_not_required_on_opening(hook):
 
        (next(testutil.OPENING_EQUITY_ACCOUNTS), 40),
 
    ])
 
    assert not list(hook.run(txn))
 

	
 
def test_not_required_on_flagged(hook):
 
    check(hook, None, txn_meta={'flag': '!'})
tests/test_meta_receipt.py
Show inline comments
...
 
@@ -349,3 +349,10 @@ def test_fallback_on_zero_amount_postings(hook, test_acct, other_acct, value):
 
))
 
def test_not_required_on_opening(hook, test_acct, equity_acct):
 
    check(hook, test_acct, equity_acct, None)
 

	
 
@pytest.mark.parametrize('test_acct,other_acct', testutil.combine_values(
 
    ACCOUNTS,
 
    NOT_REQUIRED_ACCOUNTS,
 
))
 
def test_not_required_on_flagged(hook, test_acct, other_acct):
 
    check(hook, test_acct, other_acct, None, txn_meta={'flag': '!'})
tests/test_meta_receivable_documentation.py
Show inline comments
...
 
@@ -213,3 +213,7 @@ def test_not_required_on_opening(hook):
 
        (next(testutil.OPENING_EQUITY_ACCOUNTS), -300),
 
    ])
 
    assert not list(hook.run(txn))
 

	
 
def test_not_required_on_flagged(hook):
 
    post_meta = seed_meta()
 
    check(hook, None, txn_meta={'flag': '!'}, post_meta=post_meta)
tests/test_meta_repo_links.py
Show inline comments
...
 
@@ -100,6 +100,16 @@ def test_bad_post_links(hook):
 
    actual = {error.message for error in hook.run(txn)}
 
    assert expected == actual
 

	
 
def test_flagged_txn_not_checked(hook):
 
    keys = iter(METADATA_KEYS)
 
    txn_meta = build_meta(keys, BAD_LINKS)
 
    txn_meta['flag'] = '!'
 
    txn = testutil.Transaction(**txn_meta, postings=[
 
        ('Income:Donations', -5, build_meta(keys, BAD_LINKS)),
 
        ('Assets:Checking', 5, build_meta(keys, BAD_LINKS)),
 
    ])
 
    assert not list(hook.run(txn))
 

	
 
@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
 
def test_bad_metadata_type(hook, value):
 
    txn = testutil.Transaction(**{'check': value}, postings=[
tests/test_meta_rt_links.py
Show inline comments
...
 
@@ -146,6 +146,16 @@ def test_docs_outside_rt_not_checked(hook, ext_doc):
 
    actual = {error.message for error in hook.run(txn)}
 
    assert expected == actual
 

	
 
def test_flagged_txn_not_checked(hook):
 
    txn_meta = build_meta(None, MALFORMED_LINKS)
 
    txn_meta['flag'] = '!'
 
    keys = iter(METADATA_KEYS)
 
    txn = testutil.Transaction(**txn_meta, postings=[
 
        ('Income:Donations', -5, build_meta(keys, MALFORMED_LINKS)),
 
        ('Assets:Checking', 5, build_meta(keys, NOT_FOUND_LINKS)),
 
    ])
 
    assert not list(hook.run(txn))
 

	
 
def test_mixed_results(hook):
 
    txn = testutil.Transaction(
 
        approval='{} {}'.format(*GOOD_LINKS),
0 comments (0 inline, 0 general)