diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 32e5743..3fb4e5b 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -553,7 +553,7 @@ class OutgoingReport(BaseReport): if raw_balance != posts.end_balance: balance_s = f'{raw_balance} ({balance_s})' - contract_links = posts.all_meta_links('contract') + contract_links = list(posts.all_meta_links('contract')) if contract_links: contract_s = ' , '.join(self.rt_wrapper.iter_urls( contract_links, missing_fmt='', diff --git a/conservancy_beancount/reports/core.py b/conservancy_beancount/reports/core.py index 6b759d8..c2b9a53 100644 --- a/conservancy_beancount/reports/core.py +++ b/conservancy_beancount/reports/core.py @@ -38,6 +38,7 @@ from pathlib import Path from beancount.core import amount as bc_amount from .. import data +from .. import filters from typing import ( cast, @@ -293,14 +294,15 @@ class RelatedPostings(Sequence[data.Posting]): def __len__(self) -> int: return len(self._postings) - def all_meta_links(self, key: MetaKey) -> Set[str]: - retval: Set[str] = set() + def _all_meta_links(self, key: MetaKey) -> Iterator[str]: for post in self: try: - retval.update(post.meta.get_links(key)) + yield from post.meta.get_links(key) except TypeError: pass - return retval + + def all_meta_links(self, key: MetaKey) -> Iterator[str]: + return filters.iter_unique(self._all_meta_links(key)) def iter_with_balance(self) -> Iterator[Tuple[data.Posting, Balance]]: balance = MutableBalance() diff --git a/tests/test_reports_related_postings.py b/tests/test_reports_related_postings.py index d014a94..e1b953c 100644 --- a/tests/test_reports_related_postings.py +++ b/tests/test_reports_related_postings.py @@ -256,7 +256,7 @@ def test_all_meta_links_zero(count): post._replace(meta=data.Metadata(post.meta)) for post in postings ) - assert related.all_meta_links('approval') == set() + assert next(related.all_meta_links('approval'), None) is None def test_all_meta_links_singletons(): postings = ( @@ -270,7 +270,7 @@ def test_all_meta_links_singletons(): post._replace(meta=data.Metadata(post.meta)) for post in postings ) - assert related.all_meta_links('statement') == testutil.LINK_METADATA_STRINGS + assert set(related.all_meta_links('statement')) == testutil.LINK_METADATA_STRINGS def test_all_meta_links_multiples(): postings = ( @@ -281,7 +281,18 @@ def test_all_meta_links_multiples(): post._replace(meta=data.Metadata(post.meta)) for post in postings ) - assert related.all_meta_links('approval') == testutil.LINK_METADATA_STRINGS + assert set(related.all_meta_links('approval')) == testutil.LINK_METADATA_STRINGS + +def test_all_meta_links_preserves_order(): + postings = ( + testutil.Posting('Income:Donations', -10, approval=c) + for c in '121323' + ) + related = core.RelatedPostings( + post._replace(meta=data.Metadata(post.meta)) + for post in postings + ) + assert list(related.all_meta_links('approval')) == list('123') def test_group_by_meta_zero(): assert not list(core.RelatedPostings.group_by_meta([], 'metacurrency'))