data: Add docstrings.
This commit is contained in:
parent
4fee91ad48
commit
a78db2ed36
1 changed files with 68 additions and 6 deletions
|
@ -1,4 +1,9 @@
|
|||
"""Enhanced Beancount data structures for Conservancy"""
|
||||
"""Enhanced Beancount data structures for Conservancy
|
||||
|
||||
The classes in this module are interface-compatible with Beancount's core data
|
||||
structures, and provide additional business logic that we want to use
|
||||
throughout Conservancy tools.
|
||||
"""
|
||||
# Copyright © 2020 Brett Smith
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
@ -33,6 +38,12 @@ from .beancount_types import (
|
|||
)
|
||||
|
||||
class Account(str):
|
||||
"""Account name string
|
||||
|
||||
This is a string that names an account, like Accrued:AccountsPayable
|
||||
or Income:Donations. This class provides additional methods for common
|
||||
account name parsing and queries.
|
||||
"""
|
||||
SEP = bc_account.sep
|
||||
|
||||
def is_income(self) -> bool:
|
||||
|
@ -45,6 +56,28 @@ class Account(str):
|
|||
)
|
||||
|
||||
def is_under(self, *acct_seq: str) -> Optional[str]:
|
||||
"""Return a match if this account is "under" a part of the hierarchy
|
||||
|
||||
Pass in any number of account name strings as arguments. If this
|
||||
account is under one of those strings in the account hierarchy, the
|
||||
first matching string will be returned. Otherwise, None is returned.
|
||||
|
||||
You can use the return value of this method as a boolean if you don't
|
||||
care which account string is matched.
|
||||
|
||||
An account is considered to be under itself:
|
||||
|
||||
Account('Expenses:Tax').is_under('Expenses:Tax') # returns 'Expenses:Tax'
|
||||
|
||||
To do a "strictly under" search, end your search strings with colons:
|
||||
|
||||
Account('Expenses:Tax').is_under('Expenses:Tax:') # returns None
|
||||
Account('Expenses:Tax').is_under('Expenses:') # returns 'Expenses:'
|
||||
|
||||
This method does check that all the account boundaries match:
|
||||
|
||||
Account('Expenses:Tax').is_under('Exp') # returns None
|
||||
"""
|
||||
for prefix in acct_seq:
|
||||
if self.startswith(prefix) and (
|
||||
prefix.endswith(self.SEP)
|
||||
|
@ -56,6 +89,24 @@ class Account(str):
|
|||
|
||||
|
||||
class PostingMeta(collections.abc.MutableMapping):
|
||||
"""Combined access to posting metadata with its parent transaction metadata
|
||||
|
||||
This lets you access posting metadata through a single dict-like object.
|
||||
If you try to look up metadata that doesn't exist on the posting, it will
|
||||
look for the value in the parent transaction metadata instead.
|
||||
|
||||
You can set and delete metadata as well. Changes only affect the metadata
|
||||
of the posting, never the transaction. Changes are propagated to the
|
||||
underlying Beancount data structures.
|
||||
|
||||
Functionally, you can think of this as identical to:
|
||||
|
||||
collections.ChainMap(post.meta, txn.meta)
|
||||
|
||||
Under the hood, this class does a little extra work to avoid creating
|
||||
posting metadata if it doesn't have to.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
txn: Transaction,
|
||||
index: int,
|
||||
|
@ -101,16 +152,27 @@ class PostingMeta(collections.abc.MutableMapping):
|
|||
|
||||
|
||||
class Posting(BasePosting):
|
||||
"""Enhanced Posting objects
|
||||
|
||||
This class is a subclass of Beancount's native Posting class where
|
||||
specific fields are replaced with enhanced versions:
|
||||
|
||||
* The `account` field is an Account object
|
||||
* The `meta` field is a PostingMeta object
|
||||
"""
|
||||
|
||||
account: Account
|
||||
# mypy correctly complains that our MutableMapping is not compatible with
|
||||
# Beancount's meta type declaration of Optional[Dict]. IMO this is a case
|
||||
# of Beancount's type declaration being a smidge too specific: I think it
|
||||
# would be very unusual for code to actually require a dict over a more
|
||||
# generic mapping. If it did, this would work fine.
|
||||
# mypy correctly complains that our MutableMapping is not compatible
|
||||
# with Beancount's meta type declaration of Optional[Dict]. IMO
|
||||
# Beancount's type declaration is a smidge too specific: I think its type
|
||||
# declaration should also use MutableMapping, because it would be very
|
||||
# unusual for code to specifically require a Dict over that.
|
||||
# If it did, this declaration would pass without issue.
|
||||
meta: MutableMapping[MetaKey, MetaValue] # type:ignore[assignment]
|
||||
|
||||
|
||||
def iter_postings(txn: Transaction) -> Iterator[Posting]:
|
||||
"""Yield an enhanced Posting object for every posting in the transaction"""
|
||||
for index, source in enumerate(txn.postings):
|
||||
yield Posting(
|
||||
Account(source.account),
|
||||
|
|
Loading…
Reference in a new issue