diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 32e57438ef58a7d9611a4192a93101a54c3f9925..3fb4e5bad134d52df1b54ad00909340a3a88a8d9 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 6b759d816cf956a6c8158923048875640c1c439d..c2b9a533335836019fc8bedf9e02e7639fc42db3 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 d014a941a10e6cc0f3773582ed4d18bea299ca7c..e1b953cc4728112d9c65a993e3383e58098fdc77 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'))