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:
  • 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.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • 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.

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:
  • 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.

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:
  • 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.

Exceptions:
  • ReportError – If the requested format is not supported.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • A list of strings, such as ['html', 'text'].

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:
  • 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.

Exceptions:
  • ReportError – If the requested format is not supported.

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:
  • 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.

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:
  • A string, with a formatting style placeholders – {title}: for the title of the page. {body}: for the body, where the div goes.

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:
  • 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.

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:
  • 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.

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:
  • entry – An instance of Transaction.

Returns:
  • A tuple of simple postings, postings with price conversions, postings held at cost.

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:
  • match – A MatchObject from the re module.

Returns:
  • A quoted string of the match contents.

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:
  • string – A string of text.

Returns:
  • A string of text, with the commodity expressions surrounded with quotes.

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:
  • 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.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • 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.

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:
  • 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'}.

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:
  • source – A comma-separated list of sources as a string, such as "google/NASDAQ:AAPL,yahoo/AAPL".

Returns:
  • The symbol string.

Exceptions:
  • ValueError – If the sources does not contain a ticker for the google source.

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:
  • ticker – A string, the symbol for GFinance.

Returns:
  • A boolean, true for mutual funds.

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:
  • dtime – A datetime.datetime instance.

Returns:
  • A string, rendered to milliseconds.

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:
  • 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.)

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • 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.

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:
  • 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.

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:
  • entries – A list of directives.

  • options_map – A dict of parsed options.

Returns:
  • A string, the entries to print out.

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:
  • fileobj – A file object.

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:
  • 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.

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:
  • dcontext – DisplayContext to use to render the numbers.

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:
  • account_name – A string, the name of the account to render.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • amount – An Amount instance.

Returns:
  • A string of HTML to be spliced inside a table cell.

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:
  • commodity – A pair of strings, the base and quote currency names.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • entry – A directive.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • filename – A string, the filename for the document.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • event – A string, the name of the even type.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • inv – An Inventory instance.

Returns:
  • A string of HTML to be spliced inside a table cell.

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))

Render a transaction link (maybe as an HTML link).

Parameters:
  • link – A string, the name of the link to render.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • 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.

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:
  • meta – A metadata dict object.

Returns:
  • A string of HTML to be spliced inside an HTML template.

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:
  • 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.

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:
  • 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.

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:
  • 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.

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)

Render Transaction links to HTML.

Parameters:
  • links – A list of set of strings, transaction "links" to be rendered.

Returns:
  • A string, a snippet of HTML to be rendering somewhere.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • real_root – A RealAccount node for the root of all accounts.

Returns:
  • A list of posting or directive instances.

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:
  • 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.

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:
  • 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.

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:
  • 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.

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:
  • An integer, the number of digits required to render the integral part.

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:
  • number – A Decimal instance.

  • currency – A string, the currency to render for it.

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:
  • entry – A directive, of any type.

Returns:
  • A string to use for the filling the description field in text reports.

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:
  • posting – An instance of Posting.

Returns:
  • A string, the rendered posting.

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:
  • 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.

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:
  • 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.

Exceptions:
  • ValueError – If the width is insufficient to render the description.

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.AccountsReport (Report)

Print out the list of all accounts.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • 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.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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.NoopReport (Report)

Report nothing.

beancount.reports.misc_reports.PrintReport (Report)

Print out the entries.

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:
  • 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.

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:
  • 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.

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:
  • 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.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • parser – An instance of argparse.ArgumentParser.

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:
  • 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.

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:
  • 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.

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:
  • A list of all available report classes.

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:
  • 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.

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:
  • fieldname – A string, a programming ids, such as 'book_value'.

Returns:
  • A readable string, such as 'Book Value.'

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:
  • 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.

Exceptions:
  • IndexError – If the rows are of different lengths.

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:
  • 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.

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:
  • 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).

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:
  • 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.

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:
  • 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.

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:
  • 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.

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:
  • real_account – An instance of RealAccount.

Returns:
  • A boolean, true if the account is active, according to the definition above.

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:
  • 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.

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:
  • 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.

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>')