Changeset - 69f597a47c53
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-10-24 15:43:46
brettcsmith@brettcsmith.org
rewrite: Support ``metakey in value`` conditions.

For now, works exactly like ``.account in value``.
2 files changed with 49 insertions and 2 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/rewrite.py
Show inline comments
...
 
@@ -82,6 +82,13 @@ Conditions can always use Python's basic comparison operators:
 
                   account names. The condition matches when the posting's
 
                   account is any of those named accounts, or any of their
 
                   respective subaccounts.
 
  ---------------- -------------------------------------------------------
 
  ``metakey in``   Works analogously for metadata values that are
 
                   structured as a colon-separated hierarchy like account
 
                   names. The operand is parsed as a space-separated list
 
                   of parts of the hierarchy. The condition matches when
 
                   the posting's ``metakey`` metadata is under any of
 
                   those parts of the hierarchy.
 
  ================ =======================================================
 

	
 
Action Operators
...
 
@@ -141,9 +148,11 @@ accounting equation, Equity = Assets - Liabilities.
 
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

	
 
import abc
 
import collections
 
import datetime
 
import decimal
 
import enum
 
import functools
 
import logging
 
import operator as opmod
 
import re
...
 
@@ -290,9 +299,18 @@ class DateTest(Tester[datetime.date]):
 

	
 

	
 
class MetadataTest(Tester[Optional[MetaValue]]):
 
    _GETTERS: Mapping[MetaKey, Callable[[str], data.Account]] = \
 
        collections.defaultdict(lambda: functools.lru_cache()(data.Account))
 
    # We use this class variable to share one cache per metadata key being
 
    # tested.
 

	
 
    def __init__(self, key: MetaKey, operator: str, operand: str) -> None:
 
        super().__init__(operator, operand)
 
        self.key = key
 
        if operator == 'in':
 
            self._get_hierarchy = self._GETTERS[key]
 
            self.under_args = operand.split()
 
        else:
 
            super().__init__(operator, operand)
 

	
 
    @staticmethod
 
    def parse_operand(operand: str) -> str:
...
 
@@ -301,6 +319,18 @@ class MetadataTest(Tester[Optional[MetaValue]]):
 
    def post_get(self, post: data.Posting) -> Optional[MetaValue]:
 
        return post.meta.get(self.key)
 

	
 
    def __call__(self, post: data.Posting) -> bool:
 
        try:
 
            self.under_args
 
        except AttributeError:
 
            return super().__call__(post)
 
        else:
 
            meta_value = self.post_get(post)
 
            if isinstance(meta_value, str):
 
                return self._get_hierarchy(meta_value).is_under(*self.under_args) is not None
 
            else:
 
                return False
 

	
 

	
 
class NumberTest(Tester[Decimal]):
 
    @staticmethod
tests/test_reports_rewrite.py
Show inline comments
...
 
@@ -76,6 +76,24 @@ def test_metadata_condition(value, operator):
 
    tester = rewrite.MetadataTest(key, operator, operand)
 
    assert tester(post) == eval(f'value {operator} operand')
 

	
 
@pytest.mark.parametrize('value,expected', [
 
    ('Root', False),
 
    ('Root:Branch', True),
 
    ('Root:Branch:Leaf', True),
 
    ('Branch', False),
 
    ('RootBranch:Leaf', False),
 
    (None, False),
 
])
 
def test_metadata_in_condition(value, expected):
 
    key = 'testkey'
 
    meta = {} if value is None else {key: value}
 
    txn = testutil.Transaction(postings=[
 
        ('Income:Other', -5, meta),
 
    ])
 
    post, = data.Posting.from_txn(txn)
 
    tester = rewrite.MetadataTest(key, 'in', 'Root:Branch')
 
    assert tester(post) == expected
 

	
 
@pytest.mark.parametrize('value', ['4.5', '4.75', '5'])
 
@pytest.mark.parametrize('operator', CMP_OPS)
 
def test_number_condition(value, operator):
...
 
@@ -118,7 +136,6 @@ def test_parse_good_condition(subject, operator, operand):
 
    '.date in 1990-9-9',  # Bad operator
 
    '.number > 0xff',  # Bad operand
 
    '.number in 16',  # Bad operator
 
    'testkey in foo',  # Bad operator
 
    'units.number == 5',  # Bad subject (syntax)
 
    '.units == 5',  # Bad subject (unknown)
 
])
0 comments (0 inline, 0 general)