Changeset - f21ac740f24c
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-15 14:16:34
brettcsmith@brettcsmith.org
data: Add Posting.at_cost() method.
3 files changed with 32 insertions and 14 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/data.py
Show inline comments
...
 
@@ -395,32 +395,38 @@ class Posting(BasePosting):
 
        for index, post in enumerate(txn.postings):
 
            yield cls.from_beancount(txn, index, post)
 

	
 
    @classmethod
 
    def from_entries(cls, entries: Iterable[Directive]) -> Iterator['Posting']:
 
        """Yield an enhanced Posting object for every posting in these entries"""
 
        for entry in entries:
 
            # Because Beancount's own Transaction class isn't type-checkable,
 
            # we can't statically check this. Might as well rely on duck
 
            # typing while we're at it: just try to yield postings from
 
            # everything, and ignore entries that lack a postings attribute.
 
            try:
 
                yield from cls.from_txn(entry)  # type:ignore[arg-type]
 
            except AttributeError:
 
                pass
 

	
 
    def at_cost(self) -> Amount:
 
        if self.cost is None:
 
            return self.units
 
        else:
 
            return Amount(self.units.number * self.cost.number, self.cost.currency)
 

	
 

	
 
_KT = TypeVar('_KT', bound=Hashable)
 
_VT = TypeVar('_VT')
 
class _SizedDict(collections.OrderedDict, MutableMapping[_KT, _VT]):
 
    def __init__(self, maxsize: int=128) -> None:
 
        self.maxsize = maxsize
 
        super().__init__()
 

	
 
    def __setitem__(self, key: _KT, value: _VT) -> None:
 
        super().__setitem__(key, value)
 
        for _ in range(self.maxsize, len(self)):
 
            self.popitem(last=False)
 

	
 

	
 
def balance_of(txn: Transaction,
 
               *preds: Callable[[Account], Optional[bool]],
conservancy_beancount/reports/core.py
Show inline comments
...
 
@@ -343,48 +343,36 @@ class RelatedPostings(Sequence[data.Posting]):
 
                         default: Optional[str]='',
 
    ) -> Iterator[Optional[str]]:
 
        retval = filters.iter_unique(
 
            post.meta.first_link(key, default) for post in self
 
        )
 
        if default == '':
 
            retval = (s for s in retval if s)
 
        return retval
 

	
 
    def iter_with_balance(self) -> Iterator[Tuple[data.Posting, Balance]]:
 
        balance = MutableBalance()
 
        for post in self:
 
            balance += post.units
 
            yield post, balance
 

	
 
    def balance(self) -> Balance:
 
        for _, balance in self.iter_with_balance():
 
            pass
 
        try:
 
            return balance
 
        except NameError:
 
            return Balance()
 
        return Balance(post.units for post in self)
 

	
 
    def balance_at_cost(self) -> Balance:
 
        balance = MutableBalance()
 
        for post in self:
 
            if post.cost is None:
 
                balance += post.units
 
            else:
 
                number = post.units.number * post.cost.number
 
                balance += data.Amount(number, post.cost.currency)
 
        return balance
 
        return Balance(post.at_cost() for post in self)
 

	
 
    def meta_values(self,
 
                    key: MetaKey,
 
                    default: Optional[MetaValue]=None,
 
    ) -> Set[Optional[MetaValue]]:
 
        return {post.meta.get(key, default) for post in self}
 

	
 

	
 
class BaseSpreadsheet(Generic[RT, ST], metaclass=abc.ABCMeta):
 
    """Abstract base class to help write spreadsheets
 

	
 
    This class provides the very core logic to write an arbitrary set of data
 
    rows to arbitrary output. It calls hooks when it starts writing the
 
    spreadsheet, starts a new "section" of rows, ends a section, and ends the
 
    spreadsheet.
 

	
tests/test_data_posting.py
Show inline comments
...
 
@@ -69,16 +69,40 @@ def test_from_entries_two_txns(simple_txn):
 
        assert post.meta['note']  # Only works with PostingMeta
 

	
 
def test_from_entries_mix_txns_and_other_directives(simple_txn):
 
    meta = {
 
        'filename': __file__,
 
        'lineno': 75,
 
    }
 
    entries = [
 
        bc_data.Commodity(meta, testutil.FY_START_DATE, 'EUR'),
 
        bc_data.Commodity(meta, testutil.FY_START_DATE, 'USD'),
 
        simple_txn,
 
    ]
 
    for source, post in zip(simple_txn.postings, data.Posting.from_entries(entries)):
 
        assert all(source[x] == post[x] for x in range(len(source) - 1))
 
        assert isinstance(post.account, data.Account)
 
        assert post.meta['note']  # Only works with PostingMeta
 

	
 
@pytest.mark.parametrize('cost_num', [105, 110, 115])
 
def test_at_cost(cost_num):
 
    post = data.Posting(
 
        'Income:Donations',
 
        testutil.Amount(25, 'EUR'),
 
        testutil.Cost(cost_num, 'JPY'),
 
        None,
 
        '*',
 
        None,
 
    )
 
    assert post.at_cost() == testutil.Amount(25 * cost_num, 'JPY')
 

	
 
def test_at_cost_no_cost():
 
    amount = testutil.Amount(25, 'EUR')
 
    post = data.Posting(
 
        'Income:Donations',
 
        amount,
 
        None,
 
        None,
 
        '*',
 
        None,
 
    )
 
    assert post.at_cost() == amount
0 comments (0 inline, 0 general)