Changeset - 71d671e493ad
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-14 12:53:27
brettcsmith@brettcsmith.org
data: Add Metadata.human_name() classmethod.
3 files changed with 50 insertions and 12 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/data.py
Show inline comments
...
 
@@ -20,12 +20,13 @@ throughout Conservancy tools.
 
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

	
 
import collections
 
import datetime
 
import decimal
 
import functools
 
import re
 

	
 
from beancount.core import account as bc_account
 
from beancount.core import amount as bc_amount
 
from beancount.core import convert as bc_convert
 
from beancount.core import position as bc_position
 

	
...
 
@@ -210,12 +211,19 @@ class Metadata(MutableMapping[MetaKey, MetaValue]):
 
    """Transaction or posting metadata
 

	
 
    This class wraps a Beancount metadata dictionary with additional methods
 
    for common parsing and query tasks.
 
    """
 
    __slots__ = ('meta',)
 
    _HUMAN_NAMES: MutableMapping[MetaKey, str] = {
 
        # Initialize this dict with special cases.
 
        # We use it as a cache for other metadata names as they're queried.
 
        'check-id': 'Check Number',
 
        'paypal-id': 'PayPal ID',
 
        'rt-id': 'Ticket',
 
    }
 

	
 
    def __init__(self, source: MutableMapping[MetaKey, MetaValue]) -> None:
 
        self.meta = source
 

	
 
    def __iter__(self) -> Iterator[MetaKey]:
 
        return iter(self.meta)
...
 
@@ -253,12 +261,28 @@ class Metadata(MutableMapping[MetaKey, MetaValue]):
 
    def first_link(self, key: MetaKey, default: Optional[str]=None) -> Optional[str]:
 
        try:
 
            return self.get_links(key)[0]
 
        except (IndexError, TypeError):
 
            return default
 

	
 
    @classmethod
 
    def human_name(cls, key: MetaKey) -> str:
 
        """Return the "human" version of a metadata name
 

	
 
        This is usually the metadata key with punctuation replaced with spaces,
 
        and then titlecased, with a few special cases. The return value is
 
        suitable for using in reports.
 
        """
 
        try:
 
            retval = cls._HUMAN_NAMES[key]
 
        except KeyError:
 
            retval = key.replace('-', ' ').title()
 
            retval = re.sub(r'\bId$', 'ID', retval)
 
            cls._HUMAN_NAMES[key] = retval
 
        return retval
 

	
 

	
 
class PostingMeta(Metadata):
 
    """Combined access to posting metadata with its parent transaction metadata
 

	
 
    This lets you access posting metadata through a single dict-like object.
 
    If you try to look up metadata that doesn't exist on the posting, it will
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -270,23 +270,26 @@ class BaseReport:
 
        for index, invoice in enumerate(groups):
 
            for line in self._report(groups[invoice], index):
 
                print(line, file=self.out_file)
 

	
 

	
 
class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
 
    DOC_COLUMNS = [
 
        'rt-id',
 
        'invoice',
 
        'approval',
 
        'contract',
 
        'purchase-order',
 
    ]
 
    COLUMNS = [
 
        'Date',
 
        'Entity',
 
        data.Metadata.human_name('entity'),
 
        'Invoice Amount',
 
        'Booked Amount',
 
        'Project',
 
        'Ticket',
 
        'Invoice',
 
        'Approval',
 
        'Contract',
 
        'Purchase Order',
 
        data.Metadata.human_name('project'),
 
        *(data.Metadata.human_name(key) for key in DOC_COLUMNS),
 
    ]
 
    COL_COUNT = len(COLUMNS)
 

	
 
    def __init__(self,
 
                 rt_wrapper: rtutil.RT,
 
                 date: datetime.date,
...
 
@@ -407,17 +410,14 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
 
        self.add_row(
 
            self.date_cell(row[0].meta.date),
 
            self.multiline_cell(row.entities()),
 
            amount_cell,
 
            self.balance_cell(row.end_balance),
 
            self.multiline_cell(sorted(projects)),
 
            self.meta_links_cell(row.all_meta_links('rt-id')),
 
            self.meta_links_cell(row.all_meta_links('invoice')),
 
            self.meta_links_cell(row.all_meta_links('approval')),
 
            self.meta_links_cell(row.all_meta_links('contract')),
 
            self.meta_links_cell(row.all_meta_links('purchase-order')),
 
            *(self.meta_links_cell(row.all_meta_links(key))
 
              for key in self.DOC_COLUMNS),
 
        )
 

	
 

	
 
class AgingReport(BaseReport):
 
    def __init__(self,
 
                 rt_wrapper: rtutil.RT,
tests/test_data_metadata.py
Show inline comments
...
 
@@ -86,6 +86,20 @@ def test_first_link_bad_type_metadata(simple_txn, meta_value):
 

	
 
@pytest.mark.parametrize('meta_value', testutil.NON_STRING_METADATA_VALUES)
 
def test_first_link_bad_type_default(simple_txn, meta_value):
 
    simple_txn.meta['badmeta'] = meta_value
 
    meta = data.PostingMeta(simple_txn, 1)
 
    assert meta.first_link('badmeta', '_') == '_'
 

	
 
@pytest.mark.parametrize('meta_name,expected', [
 
    ('approval', 'Approval'),
 
    ('bank-id', 'Bank ID'),
 
    ('bank-statement', 'Bank Statement'),
 
    ('check-id', 'Check Number'),
 
    ('paypal-id', 'PayPal ID'),
 
    ('purchase-order', 'Purchase Order'),
 
    ('receipt', 'Receipt'),
 
    ('rt-id', 'Ticket'),
 
    ('tax-statement', 'Tax Statement'),
 
])
 
def test_human_name(meta_name, expected):
 
    assert data.Metadata.human_name(meta_name) == expected
0 comments (0 inline, 0 general)