data: Add PostingMeta.detached() method.

This commit is contained in:
Brett Smith 2020-08-29 10:26:21 -04:00
parent dfdb9b65d5
commit 52adf1f0a5
2 changed files with 36 additions and 9 deletions

View file

@ -512,10 +512,9 @@ class PostingMeta(Metadata):
self.txn = txn
self.index = index
self.post = post
if post.meta is None:
self.meta = self.txn.meta
else:
self.meta = collections.ChainMap(post.meta, txn.meta)
self.meta: collections.ChainMap = collections.ChainMap(txn.meta)
if post.meta is not None:
self.meta = self.meta.new_child(post.meta)
def __getitem__(self, key: MetaKey) -> MetaValue:
try:
@ -527,17 +526,16 @@ class PostingMeta(Metadata):
raise
def __setitem__(self, key: MetaKey, value: MetaValue) -> None:
if self.post.meta is None:
if len(self.meta.maps) == 1:
self.post = self.post._replace(meta={key: value})
assert self.post.meta is not None
self.txn.postings[self.index] = self.post
# mypy complains that self.post.meta could be None, but we know
# from two lines up that it's not.
self.meta = collections.ChainMap(self.post.meta, self.txn.meta) # type:ignore[arg-type]
self.meta = self.meta.new_child(self.post.meta)
else:
super().__setitem__(key, value)
def __delitem__(self, key: MetaKey) -> None:
if self.post.meta is None:
if len(self.meta.maps) == 1:
raise KeyError(key)
else:
super().__delitem__(key)
@ -550,6 +548,17 @@ class PostingMeta(Metadata):
def date(self) -> datetime.date:
return self.txn.date
def detached(self) -> 'PostingMeta':
"""Create a copy of this PostingMeta detached from the original post
Changes you make to the detached copy will not propagate to the
underlying data structures. This is mostly useful for reporting code
that may want to "split" and manipulate the metadata multiple times.
"""
retval = type(self)(self.txn, self.index, self.post)
retval.meta = retval.meta.new_child()
return retval
class Posting(BasePosting):
"""Enhanced Posting objects

View file

@ -126,6 +126,24 @@ def test_date(date):
for index, post in enumerate(txn.postings):
assert data.PostingMeta(txn, index, post).date == date
def test_mutable_copy():
txn = testutil.Transaction(
filename='f', lineno=130, txnkey='one', postings=[
('Assets:Cash', 18),
('Income:Donations', -18),
])
meta = data.PostingMeta(txn, 1).detached()
meta['layerkey'] = 'two'
assert dict(meta) == {
'filename': 'f',
'lineno': 130,
'txnkey': 'one',
'layerkey': 'two',
}
assert 'layerkey' not in txn.meta
assert all(post.meta is None for post in txn.postings)
assert meta.date == txn.date
# The .get() tests are arguably testing the stdlib, but they're short and
# they confirm that we're using the stdlib as we intend.
def test_get_with_meta_value(simple_txn):