rtutil: Add RT.txn_with_urls() method.

This commit is contained in:
Brett Smith 2020-04-28 16:20:25 -04:00
parent 701ccdc192
commit 999ca2c5e1
2 changed files with 92 additions and 0 deletions

View file

@ -25,8 +25,13 @@ import rt
from pathlib import Path
from . import data
from beancount.core import data as bc_data
from typing import (
overload,
Callable,
Iterable,
Iterator,
MutableMapping,
Optional,
@ -34,6 +39,9 @@ from typing import (
Tuple,
Union,
)
from .beancount_types import (
Transaction,
)
RTId = Union[int, str]
TicketAttachmentIds = Tuple[str, Optional[str]]
@ -263,6 +271,53 @@ class RT:
)
return self._extend_url(path_tail)
def _urls(self, links: Iterable[str]) -> Iterator[str]:
for link in links:
parsed = self.parse(link)
if parsed is None:
yield link
else:
ticket_id, attachment_id = parsed
url = self.url(ticket_id, attachment_id)
yield f'<{url}>'
@overload
def _meta_with_urls(self, meta: None) -> None: ...
@overload
def _meta_with_urls(self, meta: bc_data.Meta) -> bc_data.Meta: ...
def _meta_with_urls(self, meta: Optional[bc_data.Meta]) -> Optional[bc_data.Meta]:
if meta is None:
return None
link_meta = data.Metadata(meta)
retval = meta.copy()
for key in data.LINK_METADATA:
try:
links = link_meta.get_links(key)
except TypeError:
continue
if links:
retval[key] = ' '.join(self._urls(links))
return retval
def txn_with_urls(self, txn: Transaction) -> Transaction:
"""Copy a transaction with RT references replaced with web URLs
Given a Beancount Transaction, this method returns a Transaction
that's identical, except any references to RT in the metadata for
the Transaction and its Postings are replaced with web URLs.
This is useful for reporting tools that want to format the
transaction with URLs that are recognizable by other tools.
"""
# mypy doesn't recognize that postings is a valid argument, probably a
# bug in the NamedTuple→Directive→Transaction hierarchy.
return txn._replace( # type:ignore[call-arg]
meta=self._meta_with_urls(txn.meta),
postings=[post._replace(meta=self._meta_with_urls(post.meta))
for post in txn.postings],
)
def exists(self, ticket_id: RTId, attachment_id: Optional[RTId]=None) -> bool:
return self.url(ticket_id, attachment_id) is not None

View file

@ -39,6 +39,11 @@ EXPECTED_URLS = [
(9, None, None),
]
EXPECTED_URLS_MAP = {
(ticket_id, attachment_id): url
for ticket_id, attachment_id, url in EXPECTED_URLS
}
@pytest.fixture(scope='module')
def rt():
client = testutil.RTClient()
@ -203,3 +208,35 @@ def test_results_not_found_only_in_transient_cache(new_client):
new_client.TICKET_DATA['9'] = [('99', '(Unnamed)', 'text/plain', '0b')]
assert not rt1.exists(9)
assert rt2.exists(9)
def test_txn_with_urls(new_client):
txn_meta = {
'rt-id': 'rt:1',
'contract': 'RepoLink.pdf',
'statement': 'doc1.txt rt:1/4 doc2.txt',
}
txn = testutil.Transaction(**txn_meta, postings=[
('Income:Donations', -10, {'receipt': 'rt:2/13 donation.txt'}),
('Assets:Cash', 10, {'receipt': 'cash.png rt:2/14'}),
])
rt = rtutil.RT(new_client)
actual = rt.txn_with_urls(txn)
def check(source, key, ticket_id, attachment_id=None):
url_path = EXPECTED_URLS_MAP[(ticket_id, attachment_id)]
assert f'<{DEFAULT_RT_URL}{url_path}>' in source.meta[key]
expected_keys = set(txn_meta)
expected_keys.update(['filename', 'lineno'])
assert set(actual.meta) == expected_keys
check(actual, 'rt-id', 1)
assert actual.meta['contract'] == txn_meta['contract']
assert actual.meta['statement'].startswith('doc1.txt ')
check(actual, 'statement', 1, 4)
check(actual.postings[0], 'receipt', 2, 13)
assert actual.postings[0].meta['receipt'].endswith(' donation.txt')
check(actual.postings[1], 'receipt', 2, 14)
assert actual.postings[1].meta['receipt'].startswith('cash.png ')
# Check the original transaction is unchanged
for key, expected in txn_meta.items():
assert txn.meta[key] == expected
assert txn.postings[0].meta['receipt'] == 'rt:2/13 donation.txt'
assert txn.postings[1].meta['receipt'] == 'cash.png rt:2/14'