beancount.reports
Routines to produce various reports, either to HTML or to text.
beancount.reports.balance_reports
Report classes for all reports that display ending balances of accounts.
beancount.reports.balance_reports.BalanceSheetReport (HTMLReport)
Print out a balance sheet.
beancount.reports.balance_reports.BalanceSheetReport.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/balance_reports.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.balance_reports.BalancesReport (HTMLReport)
Print out the trial balance of accounts matching an expression.
beancount.reports.balance_reports.BalancesReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/balance_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-e', '--filter-expression', '--expression', '--regexp',
action='store', default=None,
help="Filter expression for which account balances to display.")
parser.add_argument('-c', '--at-cost', '--cost', action='store_true',
help="Render values at cost, convert the units to cost value")
beancount.reports.balance_reports.BalancesReport.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/balance_reports.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.balance_reports.IncomeStatementReport (HTMLReport)
Print out an income statement.
beancount.reports.balance_reports.IncomeStatementReport.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/balance_reports.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.base
Base class for all reports classes.
Each report class should be able to render a filtered list of entries to a variety of formats. Each report has a name, some command-line options, and supports some subset of formats.
beancount.reports.base.HTMLReport (Report)
A mixin for reports that support forwarding html to htmldiv implementation.
beancount.reports.base.RealizationMeta (type)
A metaclass for reports that render a realization.
The main use of this metaclass is to allow us to create report classes with render_real_*() methods that accept a RealAccount instance as the basis for producing a report.
RealAccount can be expensive to build, and may be pre-computed and kept around to generate the various reports related to a particular filter of a subset of transactions, and it would be inconvenient to have to recalculate it every time we need to produce a report. In particular, this is the case for the web interface: the user selects a particular subset of transactions to view, and can then click to the various reports related to this subset of transactions. This is why this is useful.
The classes generated with this metaclass respond to the same interface as the regular report classes, so that if invoked from the command-line, it will automatically build the realization from the given set of entries. This metaclass looks at the class' existing render_real_() methods and generate the corresponding render_() methods automatically.
beancount.reports.base.RealizationMeta.__new__(mcs, name, bases, namespace)
special
staticmethod
Create and return a new object. See help(type) for accurate signature.
Source code in beancount/reports/base.py
def __new__(mcs, name, bases, namespace):
new_type = super(RealizationMeta, mcs).__new__(mcs, name, bases, namespace)
# Go through the methods of the new type and look for render_real() methods.
new_methods = {}
for attr, value in new_type.__dict__.items():
match = re.match('render_real_(.*)', attr)
if not match:
continue
# Make sure that if an explicit version of render_*() has already
# been declared, that we don't override it.
render_function_name = 'render_{}'.format(match.group(1))
if render_function_name in new_type.__dict__:
continue
# Define a render_*() method on the class.
def forward_method(self, entries, errors, options_map, file, fwdfunc=value):
account_types = options.get_account_types(options_map)
real_root = realization.realize(entries, account_types)
price_map = prices.build_price_map(entries)
# Note: When we forward, use the latest date (None).
return fwdfunc(self, real_root, price_map, None, options_map, file)
forward_method.__name__ = render_function_name
new_methods[render_function_name] = forward_method
# Update the type with the newly defined methods..
for mname, mvalue in new_methods.items():
setattr(new_type, mname, mvalue)
# Auto-generate other methods if necessary.
if hasattr(new_type, 'render_real_htmldiv'):
setattr(new_type, 'render_real_html', mcs.render_real_html)
return new_type
beancount.reports.base.RealizationMeta.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/base.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.base.Report
Base class for all reports.
Attributes:
Name | Type | Description |
---|---|---|
names |
A list of strings, the various names of this report. The first name is taken to be the authoritative name of the report; the rest are considered aliases. |
|
parser |
The parser for the command's arguments. This is used to raise errors. |
|
args |
An object that contains the values of this command's parsed arguments. |
beancount.reports.base.Report.__call__(self, entries, errors, options_map, output_format=None, file=None)
special
Render a report of filtered entries to any format.
This function dispatches to a specific method.
Parameters: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
Source code in beancount/reports/base.py
def render(self, entries, errors, options_map, output_format=None, file=None):
"""Render a report of filtered entries to any format.
This function dispatches to a specific method.
Args:
entries: A list of directives to render.
errors: A list of errors that occurred during processing.
options_map: A dict of options, as produced by the parser.
output_format: A string, the name of the format. If not specified, use
the default format.
file: The file to write the output to.
Returns:
If no 'file' is provided, return the contents of the report as a
string.
Raises:
ReportError: If the requested format is not supported.
"""
try:
render_method = getattr(self, 'render_{}'.format(output_format or
self.default_format))
except AttributeError:
raise ReportError("Unsupported format: '{}'".format(output_format))
outfile = io.StringIO() if file is None else file
result = render_method(entries, errors, options_map, outfile)
assert result is None, "Render method must write to file."
if file is None:
return outfile.getvalue()
beancount.reports.base.Report.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/base.py
@classmethod
def add_args(cls, parser):
"""Add arguments to parse for this report.
Args:
parser: An instance of argparse.ArgumentParser.
"""
# No-op.
beancount.reports.base.Report.from_args(argv=None, **kwds)
classmethod
A convenience method used to create an instance from arguments.
This creates an instance of the report with default arguments. This is a convenience that may be used for tests. Our actual script uses subparsers and invokes add_args() and creates an appropriate instance directly.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/base.py
@classmethod
def from_args(cls, argv=None, **kwds):
"""A convenience method used to create an instance from arguments.
This creates an instance of the report with default arguments. This is a
convenience that may be used for tests. Our actual script uses subparsers
and invokes add_args() and creates an appropriate instance directly.
Args:
argv: A list of strings, command-line arguments to use to construct the report.
kwds: A dict of other keyword arguments to pass to the report's constructor.
Returns:
A new instance of the report.
"""
parser = version.ArgumentParser()
cls.add_args(parser)
return cls(parser.parse_args(argv or []), parser, **kwds)
beancount.reports.base.Report.get_supported_formats()
classmethod
Enumerates the list of supported formats, by inspecting methods of this object.
Returns: |
|
---|
Source code in beancount/reports/base.py
@classmethod
def get_supported_formats(cls):
"""Enumerates the list of supported formats, by inspecting methods of this object.
Returns:
A list of strings, such as ['html', 'text'].
"""
formats = []
for name in dir(cls):
match = re.match('render_([a-z0-9]+)$', name)
if match:
formats.append(match.group(1))
return sorted(formats)
beancount.reports.base.Report.render(self, entries, errors, options_map, output_format=None, file=None)
Render a report of filtered entries to any format.
This function dispatches to a specific method.
Parameters: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
Source code in beancount/reports/base.py
def render(self, entries, errors, options_map, output_format=None, file=None):
"""Render a report of filtered entries to any format.
This function dispatches to a specific method.
Args:
entries: A list of directives to render.
errors: A list of errors that occurred during processing.
options_map: A dict of options, as produced by the parser.
output_format: A string, the name of the format. If not specified, use
the default format.
file: The file to write the output to.
Returns:
If no 'file' is provided, return the contents of the report as a
string.
Raises:
ReportError: If the requested format is not supported.
"""
try:
render_method = getattr(self, 'render_{}'.format(output_format or
self.default_format))
except AttributeError:
raise ReportError("Unsupported format: '{}'".format(output_format))
outfile = io.StringIO() if file is None else file
result = render_method(entries, errors, options_map, outfile)
assert result is None, "Render method must write to file."
if file is None:
return outfile.getvalue()
beancount.reports.base.ReportError (Exception)
Error that occurred during report generation.
beancount.reports.base.TableReport (HTMLReport)
A base class for reports that supports automatic conversions from Table.
beancount.reports.base.TableReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/base.py
def generate_table(self, entries, errors, options_map):
"""Render the report to a Table instance.
Args:
entries: A list of directives to render.
errors: A list of errors that occurred during processing.
options_map: A dict of options, as produced by the parser.
Returns:
An instance of Table, that will get converted to another format.
"""
raise NotImplementedError
beancount.reports.base.get_html_template()
Returns our vanilla HTML template for embedding an HTML div.
Returns: |
|
---|
Source code in beancount/reports/base.py
def get_html_template():
"""Returns our vanilla HTML template for embedding an HTML div.
Returns:
A string, with a formatting style placeholders:
{title}: for the title of the page.
{body}: for the body, where the div goes.
"""
with open(path.join(path.dirname(__file__), 'template.html')) as infile:
return infile.read()
beancount.reports.context
Produce a rendering of the account balances just before and after a particular entry is applied.
beancount.reports.context.render_entry_context(entries, options_map, entry)
Render the context before and after a particular transaction is applied.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/context.py
def render_entry_context(entries, options_map, entry):
"""Render the context before and after a particular transaction is applied.
Args:
entries: A list of directives.
options_map: A dict of options, as produced by the parser.
entry: The entry instance which should be rendered. (Note that this object is
expected to be in the set of entries, not just structurally equal.)
Returns:
A multiline string of text, which consists of the context before the
transaction is applied, the transaction itself, and the context after it
is applied. You can just print that, it is in form that is intended to be
consumed by the user.
"""
oss = io.StringIO()
meta = entry.meta
print("Hash:{}".format(compare.hash_entry(entry)), file=oss)
print("Location: {}:{}".format(meta["filename"], meta["lineno"]), file=oss)
# Get the list of accounts sorted by the order in which they appear in the
# closest entry.
order = {}
if isinstance(entry, data.Transaction):
order = {posting.account: index
for index, posting in enumerate(entry.postings)}
accounts = sorted(getters.get_entry_accounts(entry),
key=lambda account: order.get(account, 10000))
# Accumulate the balances of these accounts up to the entry.
balance_before, balance_after = interpolate.compute_entry_context(entries,
entry)
# Create a format line for printing the contents of account balances.
max_account_width = max(map(len, accounts)) if accounts else 1
position_line = '{{:1}} {{:{width}}} {{:>49}}'.format(width=max_account_width)
# Print the context before.
print(file=oss)
print("------------ Balances before transaction", file=oss)
print(file=oss)
before_hashes = set()
for account in accounts:
positions = balance_before[account].get_positions()
for position in positions:
before_hashes.add((account, hash(position)))
print(position_line.format('', account, str(position)), file=oss)
if not positions:
print(position_line.format('', account, ''), file=oss)
print(file=oss)
# Print the entry itself.
print(file=oss)
print("------------ Transaction", file=oss)
print(file=oss)
dcontext = options_map['dcontext']
printer.print_entry(entry, dcontext, render_weights=True, file=oss)
if isinstance(entry, data.Transaction):
print(file=oss)
# Print residuals.
residual = interpolate.compute_residual(entry.postings)
if not residual.is_empty():
# Note: We render the residual at maximum precision, for debugging.
print('Residual: {}'.format(residual), file=oss)
# Dump the tolerances used.
tolerances = interpolate.infer_tolerances(entry.postings, options_map)
if tolerances:
print('Tolerances: {}'.format(
', '.join('{}={}'.format(key, value)
for key, value in sorted(tolerances.items()))), file=oss)
# Compute the total cost basis.
cost_basis = inventory.Inventory(
pos for pos in entry.postings if pos.cost is not None
).reduce(convert.get_cost)
if not cost_basis.is_empty():
print('Basis: {}'.format(cost_basis), file=oss)
# Print the context after.
print(file=oss)
print("------------ Balances after transaction", file=oss)
print(file=oss)
for account in accounts:
positions = balance_after[account].get_positions()
for position in positions:
changed = (account, hash(position)) not in before_hashes
print(position_line.format('*' if changed else '', account, str(position)),
file=oss)
if not positions:
print(position_line.format('', account, ''), file=oss)
print(file=oss)
return oss.getvalue()
beancount.reports.context.render_file_context(entries, options_map, filename, lineno)
Render the context before and after a particular transaction is applied.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/context.py
def render_file_context(entries, options_map, filename, lineno):
"""Render the context before and after a particular transaction is applied.
Args:
entries: A list of directives.
options_map: A dict of options, as produced by the parser.
filename: A string, the name of the file from which the transaction was parsed.
lineno: An integer, the line number in the file the transaction was parsed from.
Returns:
A multiline string of text, which consists of the context before the
transaction is applied, the transaction itself, and the context after it
is applied. You can just print that, it is in form that is intended to be
consumed by the user.
"""
# Find the closest entry.
closest_entry = data.find_closest(entries, filename, lineno)
if closest_entry is None:
raise SystemExit("No entry could be found before {}:{}".format(filename, lineno))
return render_entry_context(entries, options_map, closest_entry)
beancount.reports.convert_reports
Format converter reports.
This module contains reports that can convert an input file into other formats, such as Ledger.
beancount.reports.convert_reports.HLedgerPrinter (LedgerPrinter)
Multi-method for printing directives in HLedger format.
beancount.reports.convert_reports.HLedgerReport (Report)
Print out the entries in a format that can be parsed by HLedger.
beancount.reports.convert_reports.LedgerPrinter
Multi-method for printing directives in Ledger format.
beancount.reports.convert_reports.LedgerReport (Report)
Print out the entries in a format that can be parsed by Ledger.
beancount.reports.convert_reports.postings_by_type(entry)
Split up the postings by simple, at-cost, at-price.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/convert_reports.py
def postings_by_type(entry):
"""Split up the postings by simple, at-cost, at-price.
Args:
entry: An instance of Transaction.
Returns:
A tuple of simple postings, postings with price conversions, postings held at cost.
"""
postings_at_cost = []
postings_at_price = []
postings_simple = []
for posting in entry.postings:
if posting.cost:
accumlator = postings_at_cost
elif posting.price:
accumlator = postings_at_price
else:
accumlator = postings_simple
accumlator.append(posting)
return (postings_simple, postings_at_price, postings_at_cost)
beancount.reports.convert_reports.quote(match)
Add quotes around a re.MatchObject.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/convert_reports.py
def quote(match):
"""Add quotes around a re.MatchObject.
Args:
match: A MatchObject from the re module.
Returns:
A quoted string of the match contents.
"""
currency = match.group(1)
return '"{}"'.format(currency) if re.search(r'[0-9\.]', currency) else currency
beancount.reports.convert_reports.quote_currency(string)
Quote all the currencies with numbers from the given string.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/convert_reports.py
def quote_currency(string):
"""Quote all the currencies with numbers from the given string.
Args:
string: A string of text.
Returns:
A string of text, with the commodity expressions surrounded with quotes.
"""
return re.sub(r'\b({})\b'.format(amount.CURRENCY_RE), quote, string)
beancount.reports.convert_reports.split_currency_conversions(entry)
If the transaction has a mix of conversion at cost and a currency conversion, split the transaction into two transactions: one that applies the currency conversion in the same account, and one that uses the other currency without conversion.
This is required because Ledger does not appear to be able to grok a transaction like this one:
2014-11-02 * "Buy some stock with foreign currency funds" Assets:CA:Investment:HOOL 5 HOOL {520.0 USD} Expenses:Commissions 9.95 USD Assets:CA:Investment:Cash -2939.46 CAD @ 0.8879 USD
HISTORICAL NOTE: Adding a price directive on the first posting above makes Ledger accept the transaction. So we will not split the transaction here now. However, since Ledger's treatment of this type of conflict is subject to revision (See http://bugs.ledger-cli.org/show_bug.cgi?id=630), we will keep this code around, it might become useful eventually. See https://groups.google.com/d/msg/ledger-cli/35hA0Dvhom0/WX8gY_5kHy0J for details of the discussion.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/convert_reports.py
def split_currency_conversions(entry):
"""If the transaction has a mix of conversion at cost and a
currency conversion, split the transaction into two transactions: one
that applies the currency conversion in the same account, and one
that uses the other currency without conversion.
This is required because Ledger does not appear to be able to grok a
transaction like this one:
2014-11-02 * "Buy some stock with foreign currency funds"
Assets:CA:Investment:HOOL 5 HOOL {520.0 USD}
Expenses:Commissions 9.95 USD
Assets:CA:Investment:Cash -2939.46 CAD @ 0.8879 USD
HISTORICAL NOTE: Adding a price directive on the first posting above makes
Ledger accept the transaction. So we will not split the transaction here
now. However, since Ledger's treatment of this type of conflict is subject
to revision (See http://bugs.ledger-cli.org/show_bug.cgi?id=630), we will
keep this code around, it might become useful eventually. See
https://groups.google.com/d/msg/ledger-cli/35hA0Dvhom0/WX8gY_5kHy0J for
details of the discussion.
Args:
entry: An instance of Transaction.
Returns:
A pair of
converted: boolean, true if a conversion was made.
entries: A list of the original entry if converted was False,
or a list of the split converted entries if True.
"""
assert isinstance(entry, data.Transaction)
(postings_simple, postings_at_price, postings_at_cost) = postings_by_type(entry)
converted = postings_at_cost and postings_at_price
if converted:
# Generate a new entry for each currency conversion.
new_entries = []
replacement_postings = []
for posting_orig in postings_at_price:
weight = convert.get_weight(posting_orig)
posting_pos = data.Posting(posting_orig.account, weight, None,
None, None, None)
posting_neg = data.Posting(posting_orig.account, -weight, None,
None, None, None)
currency_entry = entry._replace(
postings=[posting_orig, posting_neg],
narration=entry.narration + ' (Currency conversion)')
new_entries.append(currency_entry)
replacement_postings.append(posting_pos)
converted_entry = entry._replace(postings=(
postings_at_cost + postings_simple + replacement_postings))
new_entries.append(converted_entry)
else:
new_entries = [entry]
return converted, new_entries
beancount.reports.export_reports
Reports to Export to third-party portfolio sites.
beancount.reports.export_reports.ExportEntry (tuple)
ExportEntry(symbol, cost_currency, number, cost_number, mutual_fund, memo, holdings)
beancount.reports.export_reports.ExportEntry.__getnewargs__(self)
special
Return self as a plain tuple. Used by copy and pickle.
Source code in beancount/reports/export_reports.py
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return _tuple(self)
beancount.reports.export_reports.ExportEntry.__new__(_cls, symbol, cost_currency, number, cost_number, mutual_fund, memo, holdings)
special
staticmethod
Create new instance of ExportEntry(symbol, cost_currency, number, cost_number, mutual_fund, memo, holdings)
beancount.reports.export_reports.ExportEntry.__repr__(self)
special
Return a nicely formatted representation string
Source code in beancount/reports/export_reports.py
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + repr_fmt % self
beancount.reports.export_reports.ExportPortfolioReport (TableReport)
Holdings lists that can be exported to external portfolio management software.
beancount.reports.export_reports.ExportPortfolioReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/export_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-d', '--debug', action='store_true',
help="Output position export debugging information on stderr.")
parser.add_argument('-p', '--promiscuous', action='store_true',
help=("Include title and account names in memos. "
"Use this if you trust wherever you upload."))
parser.add_argument('-a', '--aggregate-by-commodity', action='store_true',
help=("Group the holdings by account. This may help if your "
"portfolio fails to import and you have many holdings."))
beancount.reports.export_reports.classify_holdings_for_export(holdings_list, commodities_map)
Figure out what to do for example with each holding.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/export_reports.py
def classify_holdings_for_export(holdings_list, commodities_map):
"""Figure out what to do for example with each holding.
Args:
holdings_list: A list of Holding instances to be exported.
commodities_map: A dict of commodity to Commodity instances.
Returns:
A pair of:
action_holdings: A list of (symbol, holding) for each holding. 'Symbol'
is the ticker to use for export, and may be "CASH" or "IGNORE" for
holdings to be converted or ignored.
"""
# Get the map of commodities to tickers and export meta tags.
exports = getters.get_values_meta(commodities_map, FIELD)
# Classify the holdings based on their commodities' ticker metadata field.
action_holdings = []
for holding in holdings_list:
# Get export field and remove (MONEY:...) specifications.
export = re.sub(r'\(.*\)', '', exports.get(holding.currency, None) or '').strip()
if export:
if export.upper() == "CASH":
action_holdings.append(('CASH', holding))
elif export.upper() == "IGNORE":
action_holdings.append(('IGNORE', holding))
else:
action_holdings.append((export, holding))
else:
logging.warning(("Exporting holding using default commodity name '{}'; this "
"can potentially break the OFX import. Consider providing "
"'export' metadata for your commodities.").format(
holding.currency))
action_holdings.append((holding.currency, holding))
return action_holdings
beancount.reports.export_reports.export_holdings(entries, options_map, promiscuous, aggregate_by_commodity=False)
Compute a list of holdings to export.
Holdings that are converted to cash equivalents will receive a currency of "CASH:<currency>" where <currency> is the converted cash currency.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/export_reports.py
def export_holdings(entries, options_map, promiscuous, aggregate_by_commodity=False):
"""Compute a list of holdings to export.
Holdings that are converted to cash equivalents will receive a currency of
"CASH:<currency>" where <currency> is the converted cash currency.
Args:
entries: A list of directives.
options_map: A dict of options as provided by the parser.
promiscuous: A boolean, true if we should output a promiscuous memo.
aggregate_by_commodity: A boolean, true if we should group the holdings by account.
Returns:
A pair of
exported: A list of ExportEntry tuples, one for each exported position.
converted: A list of ExportEntry tuples, one for each converted position.
These will contain multiple holdings.
holdings_ignored: A list of Holding instances that were ignored, either
because they were explicitly marked to be ignored, or because we could
not convert them to a money vehicle matching the holding's cost-currency.
"""
# Get the desired list of holdings.
holdings_list, price_map = holdings_reports.get_assets_holdings(entries, options_map)
commodities_map = getters.get_commodity_map(entries)
dcontext = options_map['dcontext']
# Aggregate the holdings, if requested. Google Finance is notoriously
# finnicky and if you have many holdings this might help.
if aggregate_by_commodity:
holdings_list = holdings.aggregate_holdings_by(holdings_list,
lambda holding: holding.currency)
# Classify all the holdings for export.
action_holdings = classify_holdings_for_export(holdings_list, commodities_map)
# The lists of exported and converted export entries, and the list of
# ignored holdings.
exported = []
converted = []
holdings_ignored = []
# Export the holdings with tickers individually.
for symbol, holding in action_holdings:
if symbol in ("CASH", "IGNORE"):
continue
if holding.cost_number is None:
assert holding.cost_currency in (None, holding.currency)
cost_number = holding.number
cost_currency = holding.currency
else:
cost_number = holding.cost_number
cost_currency = holding.cost_currency
exported.append(
ExportEntry(symbol,
cost_currency,
holding.number,
cost_number,
is_mutual_fund(symbol),
holding.account if promiscuous else '',
[holding]))
# Convert all the cash entries to their book and market value by currency.
cash_holdings_map = collections.defaultdict(list)
for symbol, holding in action_holdings:
if symbol != "CASH":
continue
if holding.cost_currency:
# Accumulate market and book values.
cash_holdings_map[holding.cost_currency].append(holding)
else:
# We cannot price this... no cost currency.
holdings_ignored.append(holding)
# Get the money instruments.
money_instruments = get_money_instruments(commodities_map)
# Convert all the cash values to money instruments, if possible. If not
# possible, we'll just have to ignore those values.
# Go through all the holdings to convert, and for each of those which aren't
# in terms of one of the money instruments, which we can directly add to the
# exported portfolio, attempt to convert them into currencies to one of
# those in the money instruments.
money_values_book = collections.defaultdict(D)
money_values_market = collections.defaultdict(D)
money_values_holdings = collections.defaultdict(list)
for cost_currency, holdings_list in cash_holdings_map.items():
book_value = sum(holding.book_value for holding in holdings_list)
market_value = sum(holding.market_value for holding in holdings_list)
if cost_currency in money_instruments:
# The holding is already in terms of one of the money instruments.
money_values_book[cost_currency] += book_value
money_values_market[cost_currency] += market_value
money_values_holdings[cost_currency].extend(holdings_list)
else:
# The holding is not in terms of one of the money instruments.
# Find the first available price to convert it into one
for money_currency in money_instruments:
base_quote = (cost_currency, money_currency)
_, rate = prices.get_latest_price(price_map, base_quote)
if rate is not None:
money_values_book[money_currency] += book_value * rate
money_values_market[money_currency] += market_value * rate
money_values_holdings[money_currency].extend(holdings_list)
break
else:
# We could not convert into any of the money commodities. Ignore
# those holdings.
holdings_ignored.extend(holdings_list)
for money_currency in money_values_book.keys():
book_value = money_values_book[money_currency]
market_value = money_values_market[money_currency]
holdings_list = money_values_holdings[money_currency]
symbol = money_instruments[money_currency]
assert isinstance(book_value, Decimal)
assert isinstance(market_value, Decimal)
converted.append(
ExportEntry(symbol,
money_currency,
dcontext.quantize(market_value, money_currency),
dcontext.quantize(book_value / market_value, money_currency),
is_mutual_fund(symbol),
'',
holdings_list))
# Add all ignored holdings to a final list.
for symbol, holding in action_holdings:
if symbol == "IGNORE":
holdings_ignored.append(holding)
return exported, converted, holdings_ignored
beancount.reports.export_reports.get_money_instruments(commodities_map)
Get the money-market stand-ins for cash positions.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/export_reports.py
def get_money_instruments(commodities_map):
"""Get the money-market stand-ins for cash positions.
Args:
commodities_map: A map of currency to their corresponding Commodity directives.
Returns:
A dict of quote currency to the ticker symbol that stands for it,
e.g. {'USD': 'VMMXX'}.
"""
instruments = {}
for currency, entry in commodities_map.items():
export = entry.meta.get(FIELD, '')
paren_match = re.search(r'\((.*)\)', export)
if paren_match:
match = re.match('MONEY:({})'.format(amount.CURRENCY_RE), paren_match.group(1))
if match:
instruments[match.group(1)] = (
re.sub(r'\(.*\)', '', export).strip() or currency)
else:
logging.error("Invalid money specification: %s", export)
return instruments
beancount.reports.export_reports.get_symbol(sources, prefer='google')
Filter a source specification to some corresponding ticker.
Parameters: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
Source code in beancount/reports/export_reports.py
def get_symbol(sources, prefer='google'):
"""Filter a source specification to some corresponding ticker.
Args:
source: A comma-separated list of sources as a string, such as
"google/NASDAQ:AAPL,yahoo/AAPL".
Returns:
The symbol string.
Raises:
ValueError: If the sources does not contain a ticker for the
google source.
"""
# If the ticker is a list of <source>/<symbol>, extract the symbol
# from it.
symbol_items = []
for source in map(str.strip, sources.split(',')):
match = re.match('([a-zA-Z][a-zA-Z0-9._]+)/(.*)', source)
if match:
source, symbol = match.groups()
else:
source, symbol = None, source
symbol_items.append((source, symbol))
if not symbol_items:
raise ValueError(
'Invalid source "{}" does not contain a ticker'.format(sources))
symbol_map = dict(symbol_items)
# If not found, return the first symbol in the list of items.
return symbol_map.get(prefer, symbol_items[0][1])
beancount.reports.export_reports.is_mutual_fund(ticker)
Return true if the GFinance ticker is for a mutual fund.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/export_reports.py
def is_mutual_fund(ticker):
"""Return true if the GFinance ticker is for a mutual fund.
Args:
ticker: A string, the symbol for GFinance.
Returns:
A boolean, true for mutual funds.
"""
return bool(re.match('MUTF.*:', ticker))
beancount.reports.export_reports.render_ofx_date(dtime)
Render a datetime to the OFX format.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/export_reports.py
def render_ofx_date(dtime):
"""Render a datetime to the OFX format.
Args:
dtime: A datetime.datetime instance.
Returns:
A string, rendered to milliseconds.
"""
return '{}.{:03d}'.format(dtime.strftime('%Y%m%d%H%M%S'),
int(dtime.microsecond / 1000))
beancount.reports.gviz
Support for creating Google gviz timeline charts.
beancount.reports.gviz.gviz_timeline(time_array, data_array_map, css_id='chart')
Create a HTML rendering of the given arrays.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/gviz.py
def gviz_timeline(time_array, data_array_map, css_id='chart'):
"""Create a HTML rendering of the given arrays.
Args:
time_array: A sequence of datetime objects.
data_array_map: A dict or list of items of name to
sequence of data points.
css_id: A string, the CSS id attribute of the target node.
Returns:
Javascript code for rendering the chart. (It's up to you to
insert the a div with the correct CSS id in your accompanying
HTML page.)
"""
# Set the order of the data to be output.
if isinstance(data_array_map, dict):
data_array_map = list(data_array_map.items())
# Write preamble.
oss = io.StringIO()
oss.write('<script src="https://www.google.com/jsapi" type="text/javascript">'
'</script>\n')
oss.write('<script type="text/javascript">\n')
oss.write("""\
google.load('visualization', '1', {packages: ['annotatedtimeline']});
function draw() {
var data = new google.visualization.DataTable();
""")
# Declare columns.
oss.write("data.addColumn('{}', '{}');\n".format('datetime', 'Time'))
for name, _ in data_array_map:
oss.write("data.addColumn('{}', '{}');\n".format('number', name))
# Render the rows.
oss.write('data.addRows([\n')
datalists = [x[1] for x in data_array_map]
for dtime, datas in zip(time_array, zip(*datalists)):
js_datetime = ('Date({0.year}, {0.month}, {0.day})').format(dtime)
oss.write(' [new {}, {}],\n'.format(js_datetime, ', '.join(map(str, datas))))
oss.write(']);\n')
oss.write("""
var annotatedtimeline = new google.visualization.AnnotatedTimeLine(
document.getElementById('{css_id}')
);
var options = {{
'legendPosition' : 'newRow',
'displayAnnotations': true,
}};
annotatedtimeline.draw(data, options);
}}
google.setOnLoadCallback(draw);
""".format(css_id=css_id))
oss.write('</script>\n')
return oss.getvalue()
beancount.reports.holdings_reports
Generate reports no holdings.
beancount.reports.holdings_reports.CashReport (TableReport)
The list of cash holdings (defined as currency = cost-currency).
beancount.reports.holdings_reports.CashReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/holdings_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-c', '--currency',
action='store', default=None,
help="Which currency to convert all the holdings to")
parser.add_argument('-i', '--ignored',
action='store_true',
help="Report on ignored holdings instead of included ones")
parser.add_argument('-o', '--operating-only',
action='store_true',
help="Only report on operating currencies")
beancount.reports.holdings_reports.CashReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def generate_table(self, entries, errors, options_map):
holdings_list, price_map = get_assets_holdings(entries, options_map)
holdings_list_orig = holdings_list
# Keep only the holdings where currency is the same as the cost-currency.
holdings_list = [holding
for holding in holdings_list
if (holding.currency == holding.cost_currency or
holding.cost_currency is None)]
# Keep only those holdings held in one of the operating currencies.
if self.args.operating_only:
operating_currencies = set(options_map['operating_currency'])
holdings_list = [holding
for holding in holdings_list
if holding.currency in operating_currencies]
# Compute the list of ignored holdings and optionally report on them.
if self.args.ignored:
ignored_holdings = set(holdings_list_orig) - set(holdings_list)
holdings_list = ignored_holdings
# Convert holdings to a unified currency.
if self.args.currency:
holdings_list = holdings.convert_to_currency(price_map, self.args.currency,
holdings_list)
return table.create_table(holdings_list, FIELD_SPEC)
beancount.reports.holdings_reports.HoldingsReport (TableReport)
The full list of holdings for Asset and Liabilities accounts.
beancount.reports.holdings_reports.HoldingsReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/holdings_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-c', '--currency',
action='store', default=None,
help="Which currency to convert all the holdings to")
parser.add_argument('-r', '--relative',
action='store_true',
help="True if we should render as relative values only")
parser.add_argument('-g', '--groupby', '--by',
action='store', default=None,
choices=cls.aggregations.keys(),
help="How to group the holdings (default is: don't group)")
beancount.reports.holdings_reports.HoldingsReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def generate_table(self, entries, errors, options_map):
keywords = self.aggregations[self.args.groupby] if self.args.groupby else {}
return report_holdings(self.args.currency, self.args.relative,
entries, options_map,
**keywords)
beancount.reports.holdings_reports.NetWorthReport (TableReport)
Generate a table of total net worth for each operating currency.
beancount.reports.holdings_reports.NetWorthReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def generate_table(self, entries, errors, options_map):
holdings_list, price_map = get_assets_holdings(entries, options_map)
net_worths = []
for currency in options_map['operating_currency']:
# Convert holdings to a unified currency.
#
# Note: It's entirely possible that the price map does not have all
# the necessary rate conversions here. The resulting holdings will
# simply have no cost when that is the case. We must handle this
# gracefully below.
currency_holdings_list = holdings.convert_to_currency(price_map,
currency,
holdings_list)
if not currency_holdings_list:
continue
holdings_list = holdings.aggregate_holdings_by(
currency_holdings_list, lambda holding: holding.cost_currency)
holdings_list = [holding
for holding in holdings_list
if holding.currency and holding.cost_currency]
# If after conversion there are no valid holdings, skip the currency
# altogether.
if not holdings_list:
continue
net_worths.append((currency, holdings_list[0].market_value))
field_spec = [
(0, 'Currency'),
(1, 'Net Worth', '{:,.2f}'.format),
]
return table.create_table(net_worths, field_spec)
beancount.reports.holdings_reports.get_assets_holdings(entries, options_map, currency=None)
Return holdings for all assets and liabilities.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def get_assets_holdings(entries, options_map, currency=None):
"""Return holdings for all assets and liabilities.
Args:
entries: A list of directives.
options_map: A dict of parsed options.
currency: If specified, a string, the target currency to convert all
holding values to.
Returns:
A list of Holding instances and a price-map.
"""
# Compute a price map, to perform conversions.
price_map = prices.build_price_map(entries)
# Get the list of holdings.
account_types = options.get_account_types(options_map)
holdings_list = holdings.get_final_holdings(entries,
(account_types.assets,
account_types.liabilities),
price_map)
# Convert holdings to a unified currency.
if currency:
holdings_list = holdings.convert_to_currency(price_map, currency, holdings_list)
return holdings_list, price_map
beancount.reports.holdings_reports.get_holdings_entries(entries, options_map)
Summarizes the entries to list of entries representing the final holdings..
This list includes the latest prices entries as well. This can be used to load a full snapshot of holdings without including the entire history. This is a way of summarizing a balance sheet in a way that filters away history.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def get_holdings_entries(entries, options_map):
"""Summarizes the entries to list of entries representing the final holdings..
This list includes the latest prices entries as well. This can be used to
load a full snapshot of holdings without including the entire history. This
is a way of summarizing a balance sheet in a way that filters away history.
Args:
entries: A list of directives.
options_map: A dict of parsed options.
Returns:
A string, the entries to print out.
"""
# The entries will be created at the latest date, against an equity account.
latest_date = entries[-1].date
_, equity_account, _ = options.get_previous_accounts(options_map)
# Get all the assets.
holdings_list, _ = get_assets_holdings(entries, options_map)
# Create synthetic entries for them.
holdings_entries = []
for index, holding in enumerate(holdings_list):
meta = data.new_metadata('report_holdings_print', index)
entry = data.Transaction(meta, latest_date, flags.FLAG_SUMMARIZE,
None, "", None, None, [])
# Convert the holding to a position.
pos = holdings.holding_to_position(holding)
entry.postings.append(
data.Posting(holding.account, pos.units, pos.cost, None, None, None))
cost = -convert.get_cost(pos)
entry.postings.append(
data.Posting(equity_account, cost, None, None, None, None))
holdings_entries.append(entry)
# Get opening directives for all the accounts.
used_accounts = {holding.account for holding in holdings_list}
open_entries = summarize.get_open_entries(entries, latest_date)
used_open_entries = [open_entry
for open_entry in open_entries
if open_entry.account in used_accounts]
# Add an entry for the equity account we're using.
meta = data.new_metadata('report_holdings_print', -1)
used_open_entries.insert(0, data.Open(meta, latest_date, equity_account,
None, None))
# Get the latest price entries.
price_entries = prices.get_last_price_entries(entries, None)
return used_open_entries + holdings_entries + price_entries
beancount.reports.holdings_reports.load_from_csv(fileobj)
Load a list of holdings from a CSV file.
Parameters: |
|
---|
Yields: Instances of Holding, as read from the file.
Source code in beancount/reports/holdings_reports.py
def load_from_csv(fileobj):
"""Load a list of holdings from a CSV file.
Args:
fileobj: A file object.
Yields:
Instances of Holding, as read from the file.
"""
column_spec = [
('Account', 'account', None),
('Units', 'number', D),
('Currency', 'currency', None),
('Cost Currency', 'cost_currency', None),
('Average Cost', 'cost_number', D),
('Price', 'price_number', D),
('Book Value', 'book_value', D),
('Market Value', 'market_value', D),
('Price Date', 'price_date', None),
]
column_dict = {name: (attr, converter)
for name, attr, converter in column_spec}
klass = holdings.Holding
# Create a set of default values for the namedtuple.
defaults_dict = {attr: None for attr in klass._fields}
# Start reading the file.
reader = csv.reader(fileobj)
# Check that the header is readable.
header = next(reader)
attr_converters = []
for header_name in header:
try:
attr_converter = column_dict[header_name]
attr_converters.append(attr_converter)
except KeyError:
raise IOError("Invalid file contents for holdings")
for line in reader:
value_dict = defaults_dict.copy()
for (attr, converter), value in zip(attr_converters, line):
if converter:
value = converter(value)
value_dict[attr] = value
yield holdings.Holding(**value_dict)
beancount.reports.holdings_reports.report_holdings(currency, relative, entries, options_map, aggregation_key=None, sort_key=None)
Generate a detailed list of all holdings.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/holdings_reports.py
def report_holdings(currency, relative, entries, options_map,
aggregation_key=None,
sort_key=None):
"""Generate a detailed list of all holdings.
Args:
currency: A string, a currency to convert to. If left to None, no
conversion is carried out.
relative: A boolean, true if we should reduce this to a relative value.
entries: A list of directives.
options_map: A dict of parsed options.
aggregation_key: A callable use to generate aggregations.
sort_key: A function to use to sort the holdings, if specified.
Returns:
A Table instance.
"""
holdings_list, _ = get_assets_holdings(entries, options_map, currency)
if aggregation_key:
holdings_list = holdings.aggregate_holdings_by(holdings_list, aggregation_key)
if relative:
holdings_list = holdings.reduce_relative(holdings_list)
field_spec = RELATIVE_FIELD_SPEC
else:
field_spec = FIELD_SPEC
if sort_key:
holdings_list.sort(key=sort_key, reverse=True)
return table.create_table(holdings_list, field_spec)
beancount.reports.html_formatter
Base class for HTML formatters.
This object encapsulates the rendering of various objects to HTML. You may, and should, derive and override from this object in order to provide links within a web interface.
beancount.reports.html_formatter.HTMLFormatter
A trivial formatter object that can be used to format strings as themselves. This mainly defines an interface to implement.
beancount.reports.html_formatter.HTMLFormatter.__init__(self, dcontext)
special
Create an instance of HTMLFormatter.
Parameters: |
|
---|
Source code in beancount/reports/html_formatter.py
def __init__(self, dcontext):
"""Create an instance of HTMLFormatter.
Args:
dcontext: DisplayContext to use to render the numbers.
"""
self._dformat = dcontext.build(
precision=display_context.Precision.MOST_COMMON)
beancount.reports.html_formatter.HTMLFormatter.render_account(self, account_name)
Render an account name.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_account(self, account_name):
"""Render an account name.
Args:
account_name: A string, the name of the account to render.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return account_name
beancount.reports.html_formatter.HTMLFormatter.render_amount(self, amount)
Render an amount.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_amount(self, amount):
"""Render an amount.
Args:
amount: An Amount instance.
Returns:
A string of HTML to be spliced inside a table cell.
"""
return amount.to_string(self._dformat)
beancount.reports.html_formatter.HTMLFormatter.render_commodity(self, base_quote)
Render a commodity (base currency / quote currency).
This is only used when we want the commodity to link to its prices.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_commodity(self, base_quote):
"""Render a commodity (base currency / quote currency).
This is only used when we want the commodity to link to its prices.
Args:
commodity: A pair of strings, the base and quote currency names.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return '{} / {}'.format(*base_quote)
beancount.reports.html_formatter.HTMLFormatter.render_context(self, entry)
Render a reference to context around a transaction (maybe as an HTML link).
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_context(self, entry):
"""Render a reference to context around a transaction (maybe as an HTML link).
Args:
entry: A directive.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return ''
beancount.reports.html_formatter.HTMLFormatter.render_doc(self, filename)
Render a document path.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_doc(self, filename):
"""Render a document path.
Args:
filename: A string, the filename for the document.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return filename
beancount.reports.html_formatter.HTMLFormatter.render_event_type(self, event)
Render an event type.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_event_type(self, event):
"""Render an event type.
Args:
event: A string, the name of the even type.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return event
beancount.reports.html_formatter.HTMLFormatter.render_inventory(self, inv)
Render an inventory.
You can use this opportunity to convert the inventory to units or cost or whatever.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_inventory(self, inv):
"""Render an inventory.
You can use this opportunity to convert the inventory to units or cost
or whatever.
Args:
inv: An Inventory instance.
Returns:
A string of HTML to be spliced inside a table cell.
"""
return '<br/>'.join(position_.to_string(self._dformat)
for position_ in sorted(inv))
beancount.reports.html_formatter.HTMLFormatter.render_link(self, link)
Render a transaction link (maybe as an HTML link).
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_link(self, link):
"""Render a transaction link (maybe as an HTML link).
Args:
link: A string, the name of the link to render.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return link
beancount.reports.html_formatter.HTMLFormatter.render_number(self, number, currency)
Render a number for a currency using the formatter's display context.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_number(self, number, currency):
"""Render a number for a currency using the formatter's display context.
Args:
number: A Decimal instance, the number to be rendered.
currency: A string, the commodity the number represent.
Returns:
A string, the formatted number to render.
"""
return self._dformat.format(number, currency)
beancount.reports.html_formatter.HTMLFormatter.render_source(self, meta)
Render a reference to the source file.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/html_formatter.py
def render_source(self, meta):
"""Render a reference to the source file.
Args:
meta: A metadata dict object.
Returns:
A string of HTML to be spliced inside an HTML template.
"""
return printer.render_source(meta)
beancount.reports.journal_html
HTML rendering routines for serving a lists of postings/entries.
beancount.reports.journal_html.Row (tuple)
Row(entry, leg_postings, rowtype, extra_class, flag, description, links, amount_str, balance_str)
beancount.reports.journal_html.Row.__getnewargs__(self)
special
Return self as a plain tuple. Used by copy and pickle.
Source code in beancount/reports/journal_html.py
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return _tuple(self)
beancount.reports.journal_html.Row.__new__(_cls, entry, leg_postings, rowtype, extra_class, flag, description, links, amount_str, balance_str)
special
staticmethod
Create new instance of Row(entry, leg_postings, rowtype, extra_class, flag, description, links, amount_str, balance_str)
beancount.reports.journal_html.Row.__repr__(self)
special
Return a nicely formatted representation string
Source code in beancount/reports/journal_html.py
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + repr_fmt % self
beancount.reports.journal_html.html_entries_table(oss, txn_postings, formatter, render_postings=True)
Render a list of entries into an HTML table, with no running balance.
This is appropriate for rendering tables of entries for postings with multiple accounts, whereby computing the running balances makes little sense.
(This function returns nothing, it write to oss as a side-effect.)
Parameters: |
|
---|
Source code in beancount/reports/journal_html.py
def html_entries_table(oss, txn_postings, formatter, render_postings=True):
"""Render a list of entries into an HTML table, with no running balance.
This is appropriate for rendering tables of entries for postings with
multiple accounts, whereby computing the running balances makes little
sense.
(This function returns nothing, it write to oss as a side-effect.)
Args:
oss: A file object to write the output to.
txn_postings: A list of Posting or directive instances.
formatter: An instance of HTMLFormatter, to be render accounts,
inventories, links and docs.
render_postings: A boolean; if true, render the postings as rows under the
main transaction row.
"""
write = lambda data: (oss.write(data), oss.write('\n'))
write('''
<table class="entry-table">
<thead>
<tr>
<th class="datecell">Date</th>
<th class="flag">F</th>
<th class="description">Narration/Payee</th>
<th class="amount">Amount</th>
<th class="cost">Cost</th>
<th class="price">Price</th>
<th class="balance">Balance</th>
</tr>
</thead>
''')
for row in iterate_html_postings(txn_postings, formatter):
entry = row.entry
description = row.description
if row.links:
description += render_links(row.links)
# Render a row.
write('''
<tr class="{} {}" title="{}">
<td class="datecell"><a href="{}">{}</a></td>
<td class="flag">{}</td>
<td class="description" colspan="5">{}</td>
</tr>
'''.format(row.rowtype, row.extra_class,
'{}:{}'.format(entry.meta["filename"], entry.meta["lineno"]),
formatter.render_context(entry), entry.date,
row.flag, description))
if render_postings and isinstance(entry, data.Transaction):
for posting in entry.postings:
classes = ['Posting']
if posting.flag == flags.FLAG_WARNING:
classes.append('warning')
write('''
<tr class="{}">
<td class="datecell"></td>
<td class="flag">{}</td>
<td class="description">{}</td>
<td class="amount num">{}</td>
<td class="cost num">{}</td>
<td class="price num">{}</td>
<td class="balance num">{}</td>
</tr>
'''.format(' '.join(classes),
posting.flag or '',
formatter.render_account(posting.account),
posting.units or '',
posting.cost or '',
posting.price or '',
convert.get_weight(posting)))
write('</table>')
beancount.reports.journal_html.html_entries_table_with_balance(oss, txn_postings, formatter, render_postings=True)
Render a list of entries into an HTML table, with a running balance.
(This function returns nothing, it write to oss as a side-effect.)
Parameters: |
|
---|
Source code in beancount/reports/journal_html.py
def html_entries_table_with_balance(oss, txn_postings, formatter, render_postings=True):
"""Render a list of entries into an HTML table, with a running balance.
(This function returns nothing, it write to oss as a side-effect.)
Args:
oss: A file object to write the output to.
txn_postings: A list of Posting or directive instances.
formatter: An instance of HTMLFormatter, to be render accounts,
inventories, links and docs.
render_postings: A boolean; if true, render the postings as rows under the
main transaction row.
"""
write = lambda data: (oss.write(data), oss.write('\n'))
write('''
<table class="entry-table">
<thead>
<tr>
<th class="datecell">Date</th>
<th class="flag">F</th>
<th class="description">Narration/Payee</th>
<th class="position">Position</th>
<th class="price">Price</th>
<th class="cost">Cost</th>
<th class="change">Change</th>
<th class="balance">Balance</th>
</tr>
</thead>
''')
for row in iterate_html_postings(txn_postings, formatter):
entry = row.entry
description = row.description
if row.links:
description += render_links(row.links)
# Render a row.
write('''
<tr class="{} {}" title="{}">
<td class="datecell"><a href="{}">{}</a></td>
<td class="flag">{}</td>
<td class="description" colspan="4">{}</td>
<td class="change num">{}</td>
<td class="balance num">{}</td>
</tr>
'''.format(row.rowtype, row.extra_class,
'{}:{}'.format(entry.meta["filename"], entry.meta["lineno"]),
formatter.render_context(entry), entry.date,
row.flag, description,
row.amount_str, row.balance_str))
if render_postings and isinstance(entry, data.Transaction):
for posting in entry.postings:
classes = ['Posting']
if posting.flag == flags.FLAG_WARNING:
classes.append('warning')
if posting in row.leg_postings:
classes.append('leg')
write('''
<tr class="{}">
<td class="datecell"></td>
<td class="flag">{}</td>
<td class="description">{}</td>
<td class="position num">{}</td>
<td class="price num">{}</td>
<td class="cost num">{}</td>
<td class="change num"></td>
<td class="balance num"></td>
</tr>
'''.format(' '.join(classes),
posting.flag or '',
formatter.render_account(posting.account),
position.to_string(posting),
posting.price or '',
convert.get_weight(posting)))
write('</table>')
beancount.reports.journal_html.iterate_html_postings(txn_postings, formatter)
Iterate through the list of transactions with rendered HTML strings for each cell.
This pre-renders all the data for each row to HTML. This is reused by the entries table rendering routines.
Parameters: |
|
---|
Yields: Instances of Row tuples. See above.
Source code in beancount/reports/journal_html.py
def iterate_html_postings(txn_postings, formatter):
"""Iterate through the list of transactions with rendered HTML strings for each cell.
This pre-renders all the data for each row to HTML. This is reused by the entries
table rendering routines.
Args:
txn_postings: A list of TxnPosting or directive instances.
formatter: An instance of HTMLFormatter, to be render accounts,
inventories, links and docs.
Yields:
Instances of Row tuples. See above.
"""
for entry_line in realization.iterate_with_balance(txn_postings):
entry, leg_postings, change, entry_balance = entry_line
# Prepare the data to be rendered for this row.
balance_str = formatter.render_inventory(entry_balance)
rowtype = entry.__class__.__name__
flag = ''
extra_class = ''
links = None
if isinstance(entry, data.Transaction):
rowtype = FLAG_ROWTYPES.get(entry.flag, 'Transaction')
extra_class = 'warning' if entry.flag == flags.FLAG_WARNING else ''
flag = entry.flag
description = '<span class="narration">{}</span>'.format(entry.narration)
if entry.payee:
description = ('<span class="payee">{}</span>'
'<span class="pnsep">|</span>'
'{}').format(entry.payee, description)
amount_str = formatter.render_inventory(change)
if entry.links and formatter:
links = [formatter.render_link(link) for link in entry.links]
elif isinstance(entry, data.Balance):
# Check the balance here and possibly change the rowtype
if entry.diff_amount is None:
description = 'Balance {} has {}'.format(
formatter.render_account(entry.account),
entry.amount)
else:
description = ('Balance in {} fails; '
'expected = {}, balance = {}, difference = {}').format(
formatter.render_account(entry.account),
entry.amount,
entry_balance.get_currency_units(entry.amount.currency),
entry.diff_amount)
extra_class = 'fail'
amount_str = formatter.render_amount(entry.amount)
elif isinstance(entry, (data.Open, data.Close)):
description = '{} {}'.format(entry.__class__.__name__,
formatter.render_account(entry.account))
amount_str = ''
elif isinstance(entry, data.Note):
description = '{} {}'.format(entry.__class__.__name__, entry.comment)
amount_str = ''
balance_str = ''
elif isinstance(entry, data.Document):
assert path.isabs(entry.filename)
description = 'Document for {}: {}'.format(
formatter.render_account(entry.account),
formatter.render_doc(entry.filename))
amount_str = ''
balance_str = ''
else:
description = entry.__class__.__name__
amount_str = ''
balance_str = ''
yield Row(entry, leg_postings,
rowtype, extra_class,
flag, description, links, amount_str, balance_str)
beancount.reports.journal_html.render_links(links)
Render Transaction links to HTML.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_html.py
def render_links(links):
"""Render Transaction links to HTML.
Args:
links: A list of set of strings, transaction "links" to be rendered.
Returns:
A string, a snippet of HTML to be rendering somewhere.
"""
return '<span class="links">{}</span>'.format(
''.join('<a href="{}">^</a>'.format(link)
for link in links))
beancount.reports.journal_reports
Report classes for all reports that display ending journals of accounts.
beancount.reports.journal_reports.ConversionsReport (HTMLReport)
Print out a report of all conversions.
beancount.reports.journal_reports.DocumentsReport (HTMLReport)
Print out a report of documents.
beancount.reports.journal_reports.JournalReport (HTMLReport)
Print out an account register/journal.
beancount.reports.journal_reports.JournalReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/journal_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-a', '--account',
action='store', default=None,
help="Account to render")
parser.add_argument('-w', '--width', action='store', type=int, default=0,
help="The number of characters wide to render the report to")
parser.add_argument('-k', '--precision', action='store', type=int, default=2,
help="The number of digits to render after the period")
parser.add_argument('-b', '--render-balance', '--balance', action='store_true',
help="Render a running balance, not just changes")
parser.add_argument('-c', '--at-cost', '--cost', action='store_true',
help="Render values at cost, convert the units to cost value")
parser.add_argument('-x', '--compact', dest='verbosity', action='store_const',
const=journal_text.COMPACT, default=journal_text.NORMAL,
help="Rendering compactly")
parser.add_argument('-X', '--verbose', dest='verbosity', action='store_const',
const=journal_text.VERBOSE,
help="Rendering verbosely")
beancount.reports.journal_reports.JournalReport.get_postings(self, real_root)
Return the postings corresponding to the account filter option.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_reports.py
def get_postings(self, real_root):
"""Return the postings corresponding to the account filter option.
Args:
real_root: A RealAccount node for the root of all accounts.
Returns:
A list of posting or directive instances.
"""
if self.args.account:
real_account = realization.get(real_root, self.args.account)
if real_account is None:
# If the account isn't found, return an empty list of postings.
# Note that this used to return the following error.
# raise base.ReportError(
# "Invalid account name: {}".format(self.args.account))
return []
else:
real_account = real_root
return realization.get_postings(real_account)
beancount.reports.journal_reports.JournalReport.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/journal_reports.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.journal_text
Text rendering routines for serving a lists of postings/entries.
beancount.reports.journal_text.AmountColumnSizer
A class that computes minimal sizes for columns of numbers and their currencies.
beancount.reports.journal_text.AmountColumnSizer.get_format(self, precision)
Return a format string for the column of numbers.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_text.py
def get_format(self, precision):
"""Return a format string for the column of numbers.
Args:
precision: An integer, the number of digits to render after the period.
Returns:
A new-style Python format string, with PREFIX_number and PREFIX_currency named
fields.
"""
return ('{{0:>{width:d}.{precision:d}f}} {{1:<{currency_width}}}').format(
width=1 + self.get_number_width() + 1 + precision,
precision=precision,
currency_width=self.max_currency_width)
beancount.reports.journal_text.AmountColumnSizer.get_generic_format(self, precision)
Return a generic format string for rendering as wide as required. This can be used to render an empty string in-lieu of a number.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_text.py
def get_generic_format(self, precision):
"""Return a generic format string for rendering as wide as required.
This can be used to render an empty string in-lieu of a number.
Args:
precision: An integer, the number of digits to render after the period.
Returns:
A new-style Python format string, with PREFIX_number and PREFIX_currency named
fields.
"""
return '{{{prefix}:<{width}}}'.format(
prefix=self.prefix,
width=1 + self.get_number_width() + 1 + precision + 1 + self.max_currency_width)
beancount.reports.journal_text.AmountColumnSizer.get_number_width(self)
Return the width of the integer part of the max number.
Returns: |
|
---|
Source code in beancount/reports/journal_text.py
def get_number_width(self):
"""Return the width of the integer part of the max number.
Returns:
An integer, the number of digits required to render the integral part.
"""
return ((math.floor(math.log10(self.max_number)) + 1)
if self.max_number > 0
else 1)
beancount.reports.journal_text.AmountColumnSizer.update(self, number, currency)
Update the sizer with the given number and currency.
Parameters: |
|
---|
Source code in beancount/reports/journal_text.py
def update(self, number, currency):
"""Update the sizer with the given number and currency.
Args:
number: A Decimal instance.
currency: A string, the currency to render for it.
"""
abs_number = abs(number)
if abs_number > self.max_number:
self.max_number = abs_number
currency_width = len(currency)
if currency_width > self.max_currency_width:
self.max_currency_width = currency_width
beancount.reports.journal_text.get_entry_text_description(entry)
Return the text of a description.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_text.py
def get_entry_text_description(entry):
"""Return the text of a description.
Args:
entry: A directive, of any type.
Returns:
A string to use for the filling the description field in text reports.
"""
if isinstance(entry, data.Transaction):
description = ' | '.join([field
for field in [entry.payee, entry.narration]
if field is not None])
elif isinstance(entry, data.Balance):
if entry.diff_amount is None:
description = 'PASS - In {}'.format(entry.account)
else:
description = ('FAIL - In {}; '
'expected = {}, difference = {}').format(
entry.account,
entry.amount,
entry.diff_amount)
elif isinstance(entry, (data.Open, data.Close)):
description = entry.account
elif isinstance(entry, data.Note):
description = entry.comment
elif isinstance(entry, data.Document):
description = entry.filename
else:
description = '-'
return description
beancount.reports.journal_text.render_posting(posting, number_format)
Render a posting compactly, for text report rendering.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/journal_text.py
def render_posting(posting, number_format):
"""Render a posting compactly, for text report rendering.
Args:
posting: An instance of Posting.
Returns:
A string, the rendered posting.
"""
# Note: there's probably no need to redo the work of rendering here... see
# if you can't just simply replace this by Position.to_string().
units = posting.units
strings = [
posting.flag if posting.flag else ' ',
'{:32}'.format(posting.account),
number_format.format(units.number, units.currency)
]
cost = posting.cost
if cost:
strings.append('{{{}}}'.format(number_format.format(cost.number,
cost.currency).strip()))
price = posting.price
if price:
strings.append('@ {}'.format(number_format.format(price.number,
price.currency).strip()))
return ' '.join(strings)
beancount.reports.journal_text.size_and_render_amounts(postings, at_cost, render_balance)
Iterate through postings and compute sizers and render amounts.
Parameters: |
|
---|
Source code in beancount/reports/journal_text.py
def size_and_render_amounts(postings, at_cost, render_balance):
"""Iterate through postings and compute sizers and render amounts.
Args:
postings: A list of Posting or directive instances.
at_cost: A boolean, if true, render the cost value, not the actual.
render_balance: A boolean, if true, renders a running balance column.
"""
# Compute the maximum width required to render the change and balance
# columns. In order to carry this out, we will pre-compute all the data to
# render this and save it for later.
change_sizer = AmountColumnSizer('change')
balance_sizer = AmountColumnSizer('balance')
entry_data = []
for entry_line in realization.iterate_with_balance(postings):
entry, leg_postings, change, balance = entry_line
# Convert to cost if necessary. (Note that this agglutinates currencies,
# so we'd rather do make the conversion at this level (inventory) than
# convert the positions or amounts later.)
if at_cost:
change = change.reduce(convert.get_cost)
if render_balance:
balance = balance.reduce(convert.get_cost)
# Compute the amounts and maximum widths for the change column.
change_amounts = []
for position in change.get_positions():
units = position.units
change_amounts.append(units)
change_sizer.update(units.number, units.currency)
# Compute the amounts and maximum widths for the balance column.
balance_amounts = []
if render_balance:
for position in balance.get_positions():
units = position.units
balance_amounts.append(units)
balance_sizer.update(units.number, units.currency)
entry_data.append((entry, leg_postings, change_amounts, balance_amounts))
return (entry_data, change_sizer, balance_sizer)
beancount.reports.journal_text.text_entries_table(oss, postings, width, at_cost, render_balance, precision, verbosity, output_format)
Render a table of postings or directives with an accumulated balance.
This function has three verbosity modes for rendering: 1. COMPACT: no separating line, no postings 2. NORMAL: a separating line between entries, no postings 3. VERBOSE: renders all the postings in addition to normal.
The output is written to the 'oss' file object. Nothing is returned.
Parameters: |
|
---|
Exceptions: |
|
---|
Source code in beancount/reports/journal_text.py
def text_entries_table(oss, postings,
width, at_cost, render_balance, precision, verbosity,
output_format):
"""Render a table of postings or directives with an accumulated balance.
This function has three verbosity modes for rendering:
1. COMPACT: no separating line, no postings
2. NORMAL: a separating line between entries, no postings
3. VERBOSE: renders all the postings in addition to normal.
The output is written to the 'oss' file object. Nothing is returned.
Args:
oss: A file object to write the output to.
postings: A list of Posting or directive instances.
width: An integer, the width to render the table to.
at_cost: A boolean, if true, render the cost value, not the actual.
render_balance: A boolean, if true, renders a running balance column.
precision: An integer, the number of digits to render after the period.
verbosity: An integer, the verbosity level. See COMPACT, NORMAL, VERBOSE, etc.
output_format: A string, either 'text' or 'csv' for the chosen output format.
This routine's inner loop calculations are complex enough it gets reused by both
formats.
Raises:
ValueError: If the width is insufficient to render the description.
"""
assert output_format in (FORMAT_TEXT, FORMAT_CSV)
if output_format is FORMAT_CSV:
csv_writer = csv.writer(oss)
# Render the changes and balances to lists of amounts and precompute sizes.
entry_data, change_sizer, balance_sizer = size_and_render_amounts(postings,
at_cost,
render_balance)
# Render an empty line and compute the width the description should be (the
# description is the only elastic field).
empty_format = '{{date:10}} {{dirtype:5}} {{description}} {}'.format(
change_sizer.get_generic_format(precision))
if render_balance:
empty_format += ' {}'.format(balance_sizer.get_generic_format(precision))
empty_line = empty_format.format(date='', dirtype='', description='',
change='', balance='')
description_width = width - len(empty_line)
if description_width <= 0:
raise ValueError(
"Width not sufficient to render text report ({} chars)".format(width))
# Establish a format string for the final format of all lines.
# pylint: disable=duplicate-string-formatting-argument
line_format = '{{date:10}} {{dirtype:5}} {{description:{:d}.{:d}}} {}'.format(
description_width, description_width,
change_sizer.get_generic_format(precision))
change_format = change_sizer.get_format(precision)
if render_balance:
line_format += ' {}'.format(balance_sizer.get_generic_format(precision))
balance_format = balance_sizer.get_format(precision)
line_format += '\n'
# Iterate over all the pre-computed data.
for (entry, leg_postings, change_amounts, balance_amounts) in entry_data:
# Render the date.
date = entry.date.isoformat()
# Get the directive type name.
dirtype = TEXT_SHORT_NAME[type(entry)]
if isinstance(entry, data.Transaction) and entry.flag:
dirtype = entry.flag
# Get the description string and split the description line in multiple
# lines.
description = get_entry_text_description(entry)
description_lines = textwrap.wrap(description, width=description_width)
# Ensure at least one line is rendered (for zip_longest).
if not description_lines:
description_lines.append('')
# Render all the amounts in the line.
for (description,
change_amount,
balance_amount) in itertools.zip_longest(description_lines,
change_amounts,
balance_amounts,
fillvalue=''):
change = (change_format.format(change_amount.number,
change_amount.currency)
if change_amount
else '')
balance = (balance_format.format(balance_amount.number,
balance_amount.currency)
if balance_amount
else '')
if not description and verbosity >= VERBOSE and leg_postings:
description = '..'
if output_format is FORMAT_TEXT:
oss.write(line_format.format(date=date,
dirtype=dirtype,
description=description,
change=change,
balance=balance))
else:
change_number, change_currency = '', ''
if change:
change_number, change_currency = change.split()
if render_balance:
balance_number, balance_currency = '', ''
if balance:
balance_number, balance_currency = balance.split()
row = (date, dirtype, description,
change_number, change_currency,
balance_number, balance_currency)
else:
row = (date, dirtype, description,
change_number, change_currency)
csv_writer.writerow(row)
# Reset the date, directive type and description. Only the first
# line renders these; the other lines render only the amounts.
if date:
date = dirtype = ''
if verbosity >= VERBOSE:
for posting in leg_postings:
posting_str = render_posting(posting, change_format)
if len(posting_str) > description_width:
posting_str = posting_str[:description_width-3] + '...'
if output_format is FORMAT_TEXT:
oss.write(line_format.format(date='',
dirtype='',
description=posting_str,
change='',
balance=''))
else:
row = ('', '', posting_str)
csv_writer.writerow(row)
if verbosity >= NORMAL:
oss.write('\n')
beancount.reports.misc_reports
Miscellaneous report classes.
beancount.reports.misc_reports.ActivityReport (HTMLReport)
Render the last or recent update activity.
beancount.reports.misc_reports.ActivityReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/misc_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-d', '--cutoff',
action='store', default=None,
type=date_utils.parse_date_liberally,
help="Cutoff date where we ignore whatever comes after.")
beancount.reports.misc_reports.ActivityReport.render_real_html(cls, real_root, price_map, price_date, options_map, file)
Wrap an htmldiv into our standard HTML template.
Parameters: |
|
---|
Source code in beancount/reports/misc_reports.py
def render_real_html(cls, real_root, price_map, price_date, options_map, file):
"""Wrap an htmldiv into our standard HTML template.
Args:
real_root: An instance of RealAccount.
price_map: A price database.
price_date: A date for evaluating prices.
options_map: A dict, options as produced by the parser.
file: A file object to write the output to.
"""
template = get_html_template()
oss = io.StringIO()
cls.render_real_htmldiv(real_root, price_map, price_date, options_map, oss)
file.write(template.format(body=oss.getvalue(),
title=''))
beancount.reports.misc_reports.CurrentEventsReport (TableReport)
Produce a table of the current values of all event types.
beancount.reports.misc_reports.CurrentEventsReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/misc_reports.py
def generate_table(self, entries, errors, options_map):
events = {}
for entry in entries:
if isinstance(entry, data.Event):
events[entry.type] = entry.description
return table.create_table(list(sorted(events.items())),
[(0, "Type", self.formatter.render_event_type),
(1, "Description")])
beancount.reports.misc_reports.ErrorReport (HTMLReport)
Report the errors.
beancount.reports.misc_reports.EventsReport (TableReport)
Produce a table of all the values of a particular event.
beancount.reports.misc_reports.EventsReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/misc_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-e', '--expr',
action='store', default=None,
help="A regexp to filer on which events to display.")
beancount.reports.misc_reports.EventsReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/misc_reports.py
def generate_table(self, entries, errors, options_map):
event_entries = []
for entry in entries:
if not isinstance(entry, data.Event):
continue
if self.args.expr and not re.match(self.args.expr, entry.type):
continue
event_entries.append(entry)
return table.create_table([(entry.date, entry.type, entry.description)
for entry in event_entries],
[(0, "Date", datetime.date.isoformat),
(1, "Type"),
(2, "Description")])
beancount.reports.misc_reports.StatsDirectivesReport (TableReport)
Render statistics on each directive type, the number of entries by type.
beancount.reports.misc_reports.StatsDirectivesReport.generate_table(self, entries, _, __)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/misc_reports.py
def generate_table(self, entries, _, __):
entries_by_type = misc_utils.groupby(lambda entry: type(entry).__name__,
entries)
nb_entries_by_type = {name: len(entries)
for name, entries in entries_by_type.items()}
rows = sorted(nb_entries_by_type.items(),
key=lambda x: x[1], reverse=True)
rows = [(name, str(count)) for (name, count) in rows]
rows.append(('~Total~', str(len(entries))))
return table.create_table(rows, [(0, 'Type'),
(1, 'Num Entries', '{:>}'.format)])
beancount.reports.misc_reports.StatsPostingsReport (TableReport)
Render the number of postings for each account.
beancount.reports.misc_reports.StatsPostingsReport.generate_table(self, entries, _, __)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/misc_reports.py
def generate_table(self, entries, _, __):
all_postings = [posting
for entry in entries
if isinstance(entry, data.Transaction)
for posting in entry.postings]
postings_by_account = misc_utils.groupby(lambda posting: posting.account,
all_postings)
nb_postings_by_account = {key: len(postings)
for key, postings in postings_by_account.items()}
rows = sorted(nb_postings_by_account.items(), key=lambda x: x[1], reverse=True)
rows = [(name, str(count)) for (name, count) in rows]
rows.append(('~Total~', str(sum(nb_postings_by_account.values()))))
return table.create_table(rows, [(0, 'Account'),
(1, 'Num Postings', '{:>}'.format)])
beancount.reports.price_reports
Miscellaneous report classes.
beancount.reports.price_reports.CommoditiesReport (TableReport)
Print out a list of commodities.
beancount.reports.price_reports.CommoditiesReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/price_reports.py
def generate_table(self, entries, errors, options_map):
price_map = prices.build_price_map(entries)
return table.create_table([(base_quote,)
for base_quote in sorted(price_map.forward_pairs)],
[(0, "Base/Quote", self.formatter.render_commodity)])
beancount.reports.price_reports.CommodityLifetimes (TableReport)
Print out a list of lifetimes of each commodity.
beancount.reports.price_reports.CommodityLifetimes.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/price_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-c', '--compress-days', type=int,
action='store', default=None,
help="The number of unused days to allow for continuous usage.")
beancount.reports.price_reports.CommodityPricesReport (TableReport)
Print all the prices for a particular commodity.
beancount.reports.price_reports.CommodityPricesReport.add_args(parser)
classmethod
Add arguments to parse for this report.
Parameters: |
|
---|
Source code in beancount/reports/price_reports.py
@classmethod
def add_args(cls, parser):
parser.add_argument('-c', '--commodity', '--currency',
action='store', default=None,
help="The commodity pair to display.")
beancount.reports.price_reports.CommodityPricesReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/price_reports.py
def generate_table(self, entries, errors, options_map):
date_rates = self.get_date_rates(entries)
return table.create_table(date_rates,
[(0, "Date", datetime.date.isoformat),
(1, "Price", '{:.5f}'.format)])
beancount.reports.price_reports.PriceDBReport (Report)
Print out the normalized price entries from the price db. Normalized means that we print prices in the most common (base, quote) order. This can be used to rebuild a prices database without having to share the entire ledger file.
Only the forward prices are printed; which (base, quote) pair is selected is selected based on the most common occurrence between (base, quote) and (quote, base). This is done in the price map.
beancount.reports.price_reports.PricesReport (Report)
Print out the unnormalized price entries that we input. Unnormalized means that we may render both (base,quote) and (quote,base). This can be used to rebuild a prices database without having to share the entire ledger file.
Note: this type of report should be removed once we have filtering on directive type, this is simply the 'print' report with type:price. Maybe rename the 'pricedb' report to just 'prices' for simplicity's sake.
beancount.reports.price_reports.TickerReport (TableReport)
Print a parseable mapping of (base, quote, ticker, name) for all commodities.
beancount.reports.price_reports.TickerReport.generate_table(self, entries, errors, options_map)
Render the report to a Table instance.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/price_reports.py
def generate_table(self, entries, errors, options_map):
commodity_map = getters.get_commodity_map(entries)
ticker_info = getters.get_values_meta(commodity_map, 'name', 'ticker', 'quote')
price_rows = [
(currency, cost_currency, ticker, name)
for currency, (name, ticker, cost_currency) in sorted(ticker_info.items())
if ticker]
return table.create_table(price_rows,
[(0, "Currency"),
(1, "Cost-Currency"),
(2, "Symbol"),
(3, "Name")])
beancount.reports.report
Produce various custom implemented reports.
beancount.reports.report.ListFormatsAction (Action)
An argparse action that prints all supported formats (for each report).
beancount.reports.report.ListReportsAction (Action)
An argparse action that just prints the list of reports and exits.
beancount.reports.report.get_all_reports()
Return all report classes.
Returns: |
|
---|
Source code in beancount/reports/report.py
def get_all_reports():
"""Return all report classes.
Returns:
A list of all available report classes.
"""
return functools.reduce(operator.add,
map(lambda module: module.__reports__,
[balance_reports,
journal_reports,
holdings_reports,
export_reports,
price_reports,
misc_reports,
convert_reports]))
beancount.reports.report.get_list_report_string(only_report=None)
Return a formatted string for the list of supported reports.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/report.py
def get_list_report_string(only_report=None):
"""Return a formatted string for the list of supported reports.
Args:
only_report: A string, the name of a single report to produce the help
for. If not specified, list all the available reports.
Returns:
A help string, or None, if 'only_report' was provided and is not a valid
report name.
"""
oss = io.StringIO()
num_reports = 0
for report_class in get_all_reports():
# Filter the name
if only_report and only_report not in report_class.names:
continue
# Get the textual description.
description = textwrap.fill(
re.sub(' +', ' ', ' '.join(report_class.__doc__.splitlines())),
initial_indent=" ",
subsequent_indent=" ",
width=80)
# Get the report's arguments.
parser = version.ArgumentParser()
report_ = report_class
report_class.add_args(parser)
# Get the list of supported formats.
## formats = report_class.get_supported_formats()
oss.write('{}:\n{}\n'.format(','.join(report_.names),
description))
num_reports += 1
if not num_reports:
return None
return oss.getvalue()
beancount.reports.table
Table rendering.
beancount.reports.table.Table (tuple)
Table(columns, header, body)
beancount.reports.table.Table.__getnewargs__(self)
special
Return self as a plain tuple. Used by copy and pickle.
Source code in beancount/reports/table.py
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return _tuple(self)
beancount.reports.table.Table.__new__(_cls, columns, header, body)
special
staticmethod
Create new instance of Table(columns, header, body)
beancount.reports.table.Table.__repr__(self)
special
Return a nicely formatted representation string
Source code in beancount/reports/table.py
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + repr_fmt % self
beancount.reports.table.attribute_to_title(fieldname)
Convert programming id into readable field name.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/table.py
def attribute_to_title(fieldname):
"""Convert programming id into readable field name.
Args:
fieldname: A string, a programming ids, such as 'book_value'.
Returns:
A readable string, such as 'Book Value.'
"""
return fieldname.replace('_', ' ').title()
beancount.reports.table.compute_table_widths(rows)
Compute the max character widths of a list of rows.
Parameters: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
Source code in beancount/reports/table.py
def compute_table_widths(rows):
"""Compute the max character widths of a list of rows.
Args:
rows: A list of rows, which are sequences of strings.
Returns:
A list of integers, the maximum widths required to render the columns of
this table.
Raises:
IndexError: If the rows are of different lengths.
"""
row_iter = iter(rows)
first_row = next(row_iter)
num_columns = len(first_row)
column_widths = [len(cell) for cell in first_row]
for row in row_iter:
for i, cell in enumerate(row):
if not isinstance(cell, str):
cell = str(cell)
cell_len = len(cell)
if cell_len > column_widths[i]:
column_widths[i] = cell_len
if i+1 != num_columns:
raise IndexError("Invalid number of rows")
return column_widths
beancount.reports.table.create_table(rows, field_spec=None)
Convert a list of tuples to an table report object.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/table.py
def create_table(rows, field_spec=None):
"""Convert a list of tuples to an table report object.
Args:
rows: A list of tuples.
field_spec: A list of strings, or a list of
(FIELDNAME-OR-INDEX, HEADER, FORMATTER-FUNCTION)
triplets, that selects a subset of the fields is to be rendered as well
as their ordering. If this is a dict, the values are functions to call
on the fields to render them. If a function is set to None, we will just
call str() on the field.
Returns:
A Table instance.
"""
# Normalize field_spec to a dict.
if field_spec is None:
namedtuple_class = type(rows[0])
field_spec = [(field, None, None)
for field in namedtuple_class._fields]
elif isinstance(field_spec, (list, tuple)):
new_field_spec = []
for field in field_spec:
if isinstance(field, tuple):
assert len(field) <= 3, field
if len(field) == 1:
field = field[0]
new_field_spec.append((field, None, None))
elif len(field) == 2:
field, header = field
new_field_spec.append((field, header, None))
elif len(field) == 3:
new_field_spec.append(field)
else:
if isinstance(field, str):
title = attribute_to_title(field)
elif isinstance(field, int):
title = "Field {}".format(field)
else:
raise ValueError("Invalid type for column name")
new_field_spec.append((field, title, None))
field_spec = new_field_spec
# Ensure a nicely formatted header.
field_spec = [((name, attribute_to_title(name), formatter)
if header_ is None
else (name, header_, formatter))
for (name, header_, formatter) in field_spec]
assert isinstance(field_spec, list), field_spec
assert all(len(x) == 3 for x in field_spec), field_spec
# Compute the column names.
columns = [name for (name, _, __) in field_spec]
# Compute the table header.
header = [header_column for (_, header_column, __) in field_spec]
# Compute the table body.
body = []
for row in rows:
body_row = []
for name, _, formatter in field_spec:
if isinstance(name, str):
value = getattr(row, name)
elif isinstance(name, int):
value = row[name]
else:
raise ValueError("Invalid type for column name")
if value is not None:
if formatter is not None:
value = formatter(value)
else:
value = str(value)
else:
value = ''
body_row.append(value)
body.append(body_row)
return Table(columns, header, body)
beancount.reports.table.render_table(table_, output, output_format, css_id=None, css_class=None)
Render the given table to the output file object in the requested format.
The table gets written out to the 'output' file.
Parameters: |
|
---|
Source code in beancount/reports/table.py
def render_table(table_, output, output_format, css_id=None, css_class=None):
"""Render the given table to the output file object in the requested format.
The table gets written out to the 'output' file.
Args:
table_: An instance of Table.
output: A file object you can write to.
output_format: A string, the format to write the table to,
either 'csv', 'txt' or 'html'.
css_id: A string, an optional CSS id for the table object (only used for HTML).
css_class: A string, an optional CSS class for the table object (only used for HTML).
"""
if output_format in ('txt', 'text'):
text = table_to_text(table_, " ", formats={'*': '>', 'account': '<'})
output.write(text)
elif output_format in ('csv',):
table_to_csv(table_, file=output)
elif output_format in ('htmldiv', 'html'):
if output_format == 'html':
output.write('<html>\n')
output.write('<body>\n')
output.write('<div id="{}">\n'.format(css_id) if css_id else '<div>\n')
classes = [css_class] if css_class else None
table_to_html(table_, file=output, classes=classes)
output.write('</div>\n')
if output_format == 'html':
output.write('</body>\n')
output.write('</html>\n')
else:
raise NotImplementedError("Unsupported format: {}".format(output_format))
beancount.reports.table.table_to_csv(table, file=None, **kwargs)
Render a Table to a CSV file.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/table.py
def table_to_csv(table, file=None, **kwargs):
"""Render a Table to a CSV file.
Args:
table: An instance of a Table.
file: A file object to write to. If no object is provided, this
function returns a string.
**kwargs: Optional arguments forwarded to csv.writer().
Returns:
A string, the rendered table, or None, if a file object is provided
to write to.
"""
output_file = file or io.StringIO()
writer = csv.writer(output_file, **kwargs)
if table.header:
writer.writerow(table.header)
writer.writerows(table.body)
if not file:
return output_file.getvalue()
beancount.reports.table.table_to_html(table, classes=None, file=None)
Render a Table to HTML.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/table.py
def table_to_html(table, classes=None, file=None):
"""Render a Table to HTML.
Args:
table: An instance of a Table.
classes: A list of string, CSS classes to set on the table.
file: A file object to write to. If no object is provided, this
function returns a string.
Returns:
A string, the rendered table, or None, if a file object is provided
to write to.
"""
# Initialize file.
oss = io.StringIO() if file is None else file
oss.write('<table class="{}">\n'.format(' '.join(classes or [])))
# Render header.
if table.header:
oss.write(' <thead>\n')
oss.write(' <tr>\n')
for header in table.header:
oss.write(' <th>{}</th>\n'.format(header))
oss.write(' </tr>\n')
oss.write(' </thead>\n')
# Render body.
oss.write(' <tbody>\n')
for row in table.body:
oss.write(' <tr>\n')
for cell in row:
oss.write(' <td>{}</td>\n'.format(cell))
oss.write(' </tr>\n')
oss.write(' </tbody>\n')
# Render footer.
oss.write('</table>\n')
if file is None:
return oss.getvalue()
beancount.reports.table.table_to_text(table, column_interspace=' ', formats=None)
Render a Table to ASCII text.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/table.py
def table_to_text(table,
column_interspace=" ",
formats=None):
"""Render a Table to ASCII text.
Args:
table: An instance of a Table.
column_interspace: A string to render between the columns as spacer.
formats: An optional dict of column name to a format character that gets
inserted in a format string specified, like this (where '<char>' is):
{:<char><width>}. A key of '*' will provide a default value, like
this, for example: (... formats={'*': '>'}).
Returns:
A string, the rendered text table.
"""
column_widths = compute_table_widths(itertools.chain([table.header],
table.body))
# Insert column format chars and compute line formatting string.
column_formats = []
if formats:
default_format = formats.get('*', None)
for column, width in zip(table.columns, column_widths):
if column and formats:
format_ = formats.get(column, default_format)
if format_:
column_formats.append("{{:{}{:d}}}".format(format_, width))
else:
column_formats.append("{{:{:d}}}".format(width))
else:
column_formats.append("{{:{:d}}}".format(width))
line_format = column_interspace.join(column_formats) + "\n"
separator = line_format.format(*[('-' * width) for width in column_widths])
# Render the header.
oss = io.StringIO()
if table.header:
oss.write(line_format.format(*table.header))
# Render the body.
oss.write(separator)
for row in table.body:
oss.write(line_format.format(*row))
oss.write(separator)
return oss.getvalue()
beancount.reports.tree_table
Routines to render an HTML table with a tree of accounts.
beancount.reports.tree_table.is_account_active(real_account)
Return true if the account should be rendered. An active account has at least one directive that is not an Open directive.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/tree_table.py
def is_account_active(real_account):
"""Return true if the account should be rendered. An active account has
at least one directive that is not an Open directive.
Args:
real_account: An instance of RealAccount.
Returns:
A boolean, true if the account is active, according to the definition above.
"""
for entry in real_account.txn_postings:
if isinstance(entry, data.Open):
continue
return True
return False
beancount.reports.tree_table.table_of_balances(real_root, price_map, price_date, operating_currencies, formatter, classes=None)
Render a tree table with the balance of each accounts.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/tree_table.py
def table_of_balances(real_root, price_map, price_date,
operating_currencies, formatter,
classes=None):
"""Render a tree table with the balance of each accounts.
Args:
real_root: A RealAccount node, the root node to render.
price_map: A prices map, a built by build_price_map.
price_date: A datetime.date instance, the date at which to compute market value.
operating_currencies: A list of strings, the operating currencies to render
to their own dedicated columns.
formatter: A object used to render account names and other links.
classes: A list of strings, the CSS classes to attach to the rendered
top-level table object.
Returns:
A string with HTML contents, the rendered table.
"""
header = ['Account'] + operating_currencies + ['Other']
# Pre-calculate which accounts should be rendered.
real_active = realization.filter(real_root, is_account_active)
if real_active:
active_set = {real_account.account
for real_account in realization.iter_children(real_active)}
else:
active_set = set()
balance_totals = inventory.Inventory()
oss = io.StringIO()
classes = list(classes) if classes else []
classes.append('fullwidth')
for real_account, cells, row_classes in tree_table(oss, real_root, formatter,
header, classes):
if real_account is TOTALS_LINE:
line_balance = balance_totals
row_classes.append('totals')
else:
# Check if this account has had activity; if not, skip rendering it.
if (real_account.account not in active_set and
not account_types.is_root_account(real_account.account)):
continue
if real_account.account is None:
row_classes.append('parent-node')
# For each account line, get the final balance of the account at
# latest market value.
line_balance = real_account.balance.reduce(convert.get_value, price_map)
# Update the total balance for the totals line.
balance_totals.add_inventory(line_balance)
# Extract all the positions that the user has identified as operating
# currencies to their own subinventories.
ccy_dict = line_balance.segregate_units(operating_currencies)
# FIXME: This little algorithm is inefficient; rewrite it.
for currency in operating_currencies:
units = ccy_dict[currency].get_currency_units(currency)
cells.append(formatter.render_number(units.number, units.currency)
if units.number != ZERO
else '')
# Render all the rest of the inventory in the last cell.
if None in ccy_dict:
ccy_balance = ccy_dict[None]
last_cell = '<br/>'.join(formatter.render_amount(pos.units)
for pos in sorted(ccy_balance))
else:
last_cell = ''
cells.append(last_cell)
return oss.getvalue()
beancount.reports.tree_table.tree_table(oss, real_account, formatter, header=None, classes=None)
Generator to a tree of accounts as an HTML table.
This yields each real_account object in turn and a list object used to provide the values for the various columns to render.
Parameters: |
|
---|
Returns: |
|
---|
Source code in beancount/reports/tree_table.py
def tree_table(oss, real_account, formatter, header=None, classes=None):
"""Generator to a tree of accounts as an HTML table.
This yields each real_account object in turn and a list object used to
provide the values for the various columns to render.
Args:
oss: a io.StringIO instance, into which we will render the HTML.
real_account: an instance of a RealAccount node.
formatter: A object used to render account names and other links.
header: a list of header columns to render. The first column is special,
and is used for the account name.
classes: a list of CSS class strings to apply to the table element.
Returns:
A generator of tuples of
real_account: An instance of RealAccount to render as a row
cells: A mutable list object to accumulate values for each column you
want to render.
row_classes: A mutable list object to accumulate css classes that you
want to add for the row.
You need to append to the given 'cells' object; if you don't append
anything, this tells this routine to skip rendering the row. On the very
last line, the 'real_account' object will be a special sentinel value to
indicate that it is meant to render the totals line: TOTALS_LINE.
"""
write = lambda data: (oss.write(data), oss.write('\n'))
classes = list(classes) if classes else []
classes.append('tree-table')
write('<table class="{}">'.format(' '.join(classes) if classes else ''))
if header:
write('<thead>')
write('<tr>')
header_iter = iter(header)
write('<th class="first">{}</th>'.format(next(header_iter)))
for column in header_iter:
write('<th>{}</th>'.format(column))
write('</tr>')
write('</thead>')
# Note: This code eventually should be reworked to be agnostic regarding
# text or HTML output rendering.
lines = realization.dump(real_account)
# Yield with a None for the final line.
lines.append((None, None, TOTALS_LINE))
for first_line, unused_cont_line, real_acc in lines:
# Let the caller fill in the data to be rendered by adding it to a list
# objects. The caller may return multiple cell values; this will create
# multiple columns.
cells = []
row_classes = []
yield real_acc, cells, row_classes
# If no cells were added, skip the line. If you want to render empty
# cells, append empty strings.
if not cells:
continue
# Render the row
write('<tr class="{}">'.format(' '.join(row_classes)))
if real_acc is TOTALS_LINE:
label = '<span class="totals-label"></span>'
else:
label = (formatter.render_account(real_acc.account)
if formatter
else real_acc.account)
write('<td class="tree-node-name">{}</td>'.format(label))
# Add columns for each value rendered.
for cell in cells:
write('<td class="num">{}</td>'.format(cell))
write('</tr>')
write('</table>')