books: Add docstrings throughout.
This commit is contained in:
parent
e2dda7ae0c
commit
323521344a
1 changed files with 59 additions and 1 deletions
|
@ -1,4 +1,8 @@
|
||||||
"""books - Tools for loading the books"""
|
"""books - Tools for loading the books
|
||||||
|
|
||||||
|
This module provides common functionality for loading books split by fiscal
|
||||||
|
year and doing common operations on the results.
|
||||||
|
"""
|
||||||
# Copyright © 2020 Brett Smith
|
# Copyright © 2020 Brett Smith
|
||||||
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
|
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
|
||||||
#
|
#
|
||||||
|
@ -39,10 +43,25 @@ PathLike = Union[str, Path]
|
||||||
Year = Union[int, datetime.date]
|
Year = Union[int, datetime.date]
|
||||||
|
|
||||||
class FiscalYear(NamedTuple):
|
class FiscalYear(NamedTuple):
|
||||||
|
"""Convert to and from fiscal years and calendar dates
|
||||||
|
|
||||||
|
Given a month and date that a fiscal year starts, this class provides
|
||||||
|
methods to calculate the fiscal year of a given calendar date; to return
|
||||||
|
important calendar dates associated with the fiscal year; and iterate
|
||||||
|
fiscal years.
|
||||||
|
|
||||||
|
Most methods can accept either an int, representing a fiscal year;
|
||||||
|
or a date. When you pass a date, the method will calculate that date's
|
||||||
|
corresponding fiscal year, and use it as the argument.
|
||||||
|
"""
|
||||||
month: int = 3
|
month: int = 3
|
||||||
day: int = 1
|
day: int = 1
|
||||||
|
|
||||||
def for_date(self, date: Optional[datetime.date]=None) -> int:
|
def for_date(self, date: Optional[datetime.date]=None) -> int:
|
||||||
|
"""Return the fiscal year of a given calendar date
|
||||||
|
|
||||||
|
The default date is today's date.
|
||||||
|
"""
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.date.today()
|
date = datetime.date.today()
|
||||||
if (date.month, date.day) < self:
|
if (date.month, date.day) < self:
|
||||||
|
@ -51,14 +70,17 @@ class FiscalYear(NamedTuple):
|
||||||
return date.year
|
return date.year
|
||||||
|
|
||||||
def first_date(self, year: Year) -> datetime.date:
|
def first_date(self, year: Year) -> datetime.date:
|
||||||
|
"""Return the first calendar date of a fiscal year"""
|
||||||
if isinstance(year, datetime.date):
|
if isinstance(year, datetime.date):
|
||||||
year = self.for_date(year)
|
year = self.for_date(year)
|
||||||
return datetime.date(year, self.month, self.day)
|
return datetime.date(year, self.month, self.day)
|
||||||
|
|
||||||
def last_date(self, year: Year) -> datetime.date:
|
def last_date(self, year: Year) -> datetime.date:
|
||||||
|
"""Return the last calendar date of a fiscal year"""
|
||||||
return self.next_fy_date(year) - datetime.timedelta(days=1)
|
return self.next_fy_date(year) - datetime.timedelta(days=1)
|
||||||
|
|
||||||
def next_fy_date(self, year: Year) -> datetime.date:
|
def next_fy_date(self, year: Year) -> datetime.date:
|
||||||
|
"""Return the last calendar date of a fiscal year"""
|
||||||
if isinstance(year, datetime.date):
|
if isinstance(year, datetime.date):
|
||||||
year = self.for_date(year)
|
year = self.for_date(year)
|
||||||
return datetime.date(year + 1, self.month, self.day)
|
return datetime.date(year + 1, self.month, self.day)
|
||||||
|
@ -100,12 +122,25 @@ class FiscalYear(NamedTuple):
|
||||||
|
|
||||||
|
|
||||||
class LoadResult(NamedTuple):
|
class LoadResult(NamedTuple):
|
||||||
|
"""Common functionality for loaded books
|
||||||
|
|
||||||
|
This class is type-compatible with the return value of the loader
|
||||||
|
functions in ``beancount.loader``. This provides named access to the
|
||||||
|
results, as well as common functionality methods.
|
||||||
|
"""
|
||||||
entries: Entries
|
entries: Entries
|
||||||
errors: Errors
|
errors: Errors
|
||||||
options_map: OptionsMap
|
options_map: OptionsMap
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def empty(cls, error: Optional[Error]=None) -> 'LoadResult':
|
def empty(cls, error: Optional[Error]=None) -> 'LoadResult':
|
||||||
|
"""Create a return result that represents nothing loaded
|
||||||
|
|
||||||
|
If an error is provided, it will be the sole error reported.
|
||||||
|
|
||||||
|
This method is useful to create a LoadResult when one can't be
|
||||||
|
created normally; e.g., because a books path is not properly configured.
|
||||||
|
"""
|
||||||
errors: Errors = []
|
errors: Errors = []
|
||||||
if error is not None:
|
if error is not None:
|
||||||
errors.append(error)
|
errors.append(error)
|
||||||
|
@ -116,6 +151,14 @@ class LoadResult(NamedTuple):
|
||||||
rewrites: Iterable[Union[Path, rewrite.RewriteRuleset]]=(),
|
rewrites: Iterable[Union[Path, rewrite.RewriteRuleset]]=(),
|
||||||
search_terms: Iterable[cliutil.SearchTerm]=(),
|
search_terms: Iterable[cliutil.SearchTerm]=(),
|
||||||
) -> Iterator[data.Posting]:
|
) -> Iterator[data.Posting]:
|
||||||
|
"""Iterate all the postings in this LoadResult
|
||||||
|
|
||||||
|
If ``rewrites`` are provided, postings will be passed through them all.
|
||||||
|
See the ``reports.rewrite`` pydoc for details.
|
||||||
|
|
||||||
|
If ``search_terms`` are provided, postings will be filtered through
|
||||||
|
them all. See the ``cliutil.SearchTerm`` pydoc for details.
|
||||||
|
"""
|
||||||
postings = data.Posting.from_entries(self.entries)
|
postings = data.Posting.from_entries(self.entries)
|
||||||
for ruleset in rewrites:
|
for ruleset in rewrites:
|
||||||
if isinstance(ruleset, Path):
|
if isinstance(ruleset, Path):
|
||||||
|
@ -126,9 +169,14 @@ class LoadResult(NamedTuple):
|
||||||
return postings
|
return postings
|
||||||
|
|
||||||
def load_account_metadata(self) -> None:
|
def load_account_metadata(self) -> None:
|
||||||
|
"""Load account metadata from this LoadResult"""
|
||||||
return data.Account.load_from_books(self.entries, self.options_map)
|
return data.Account.load_from_books(self.entries, self.options_map)
|
||||||
|
|
||||||
def print_errors(self, out_file: TextIO) -> bool:
|
def print_errors(self, out_file: TextIO) -> bool:
|
||||||
|
"""Report errors from this LoadResult to ``out_file``
|
||||||
|
|
||||||
|
Returns True if errors were reported, False otherwise.
|
||||||
|
"""
|
||||||
for error in self.errors:
|
for error in self.errors:
|
||||||
bc_printer.print_error(error, file=out_file)
|
bc_printer.print_error(error, file=out_file)
|
||||||
try:
|
try:
|
||||||
|
@ -139,6 +187,11 @@ class LoadResult(NamedTuple):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def returncode(self) -> int:
|
def returncode(self) -> int:
|
||||||
|
"""Return an appropriate Unix exit code for this LoadResult
|
||||||
|
|
||||||
|
If this LoadResult has errors, or no entries, return an exit code that
|
||||||
|
best represents that. Otherwise, return the standard OK exit code 0.
|
||||||
|
"""
|
||||||
if self.errors:
|
if self.errors:
|
||||||
if self.entries:
|
if self.entries:
|
||||||
return cliutil.ExitCode.BeancountErrors
|
return cliutil.ExitCode.BeancountErrors
|
||||||
|
@ -265,6 +318,11 @@ class Loader:
|
||||||
from_fy: Optional[Year]=None,
|
from_fy: Optional[Year]=None,
|
||||||
to_fy: Optional[Year]=None,
|
to_fy: Optional[Year]=None,
|
||||||
) -> LoadResult:
|
) -> LoadResult:
|
||||||
|
"""High-level, "do-what-I-mean"-ish books loader
|
||||||
|
|
||||||
|
Most tools can call this with a books loader from configuration, plus
|
||||||
|
one or two fiscal year arguments, to get the LoadResult they want.
|
||||||
|
"""
|
||||||
if loader is None:
|
if loader is None:
|
||||||
return cls.load_none()
|
return cls.load_none()
|
||||||
elif to_fy is None:
|
elif to_fy is None:
|
||||||
|
|
Loading…
Reference in a new issue