beancount.query

Transaction and postings filtering syntax parser.

beancount.query.numberify

Code to split table columns containing amounts and inventories into number columns.

For example, given a column with this content:

----- amount ------
101.23 USD
   200 JPY
 99.23 USD
 38.34 USD, 100 JPY

We can convert this into two columns and remove the currencies:

-amount (USD)- -amount (JPY)-
        101.23
                          200
         99.23
         38.34            100

The point is that the columns should be typed as numbers to make this importable into a spreadsheet and able to be processed.

Notes:

  • This handles the Amount, Position and Inventory datatypes. There is code to automatically recognize columns containing such types from a table of strings and convert such columns to their corresponding guessed data types.

  • The per-currency columns are ordered in decreasing order of the number of instances of numbers seen for each currency. So if the most numbers you have in a column are USD, then the USD column renders first.

  • Cost basis specifications should be unmodified and reported to a dedicated extra column, like this:

    ----- amount ------ 1 AAPL {21.23 USD}

We can convert this into two columns and remove the currencies:

-amount (AAPL)- -Cost basis-
              1 {21.23 USD}

(Eventually we might support the conversion of cost amounts as well, but they may contain other information, such as a label or a date, so for now we don't convert them. I'm not sure there's a good practical use case in doing that yet.)

  • We may provide some options to break out only some of the currencies into columns, in order to handle the case where an inventory contains a large number of currencies and we want to only operate on a restricted set of operating currencies.

  • If you provide a DisplayFormatter object to the numberification routine, they quantize each column according to their currency's precision. It is recommended that you do that.

beancount.query.numberify.AmountConverter

A converter that extracts the number of an amount for a specific currency.

beancount.query.numberify.AmountConverter.dtype

Construct a new Decimal object. 'value' can be an integer, string, tuple, or another Decimal object. If no value is given, return Decimal('0'). The context does not affect the conversion and is only passed to determine if the InvalidOperation trap is active.

beancount.query.numberify.IdentityConverter

A converter that simply copies its column.

beancount.query.numberify.InventoryConverter

A converter that extracts the number of a inventory for a specific currency. If there are multiple lots we aggregate by currency.

beancount.query.numberify.InventoryConverter.dtype

Construct a new Decimal object. 'value' can be an integer, string, tuple, or another Decimal object. If no value is given, return Decimal('0'). The context does not affect the conversion and is only passed to determine if the InvalidOperation trap is active.

beancount.query.numberify.PositionConverter

A converter that extracts the number of a position for a specific currency.

beancount.query.numberify.PositionConverter.dtype

Construct a new Decimal object. 'value' can be an integer, string, tuple, or another Decimal object. If no value is given, return Decimal('0'). The context does not affect the conversion and is only passed to determine if the InvalidOperation trap is active.

beancount.query.numberify.convert_col_Amount(name, drows, index)

Create converters for a column of type Amount.

Parameters:
  • name – A string, the column name.

  • drows – The table of objects.

  • index – The column number.

Returns:
  • A list of Converter instances, one for each of the currency types found.

Source code in beancount/query/numberify.py
def convert_col_Amount(name, drows, index):
    """Create converters for a column of type Amount.

    Args:
      name: A string, the column name.
      drows: The table of objects.
      index: The column number.
    Returns:
      A list of Converter instances, one for each of the currency types found.
    """
    currency_map = collections.defaultdict(int)
    for drow in drows:
        vamount = drow[index]
        if vamount and vamount.currency:
            currency_map[vamount.currency] += 1
    return [AmountConverter('{} ({})'.format(name, currency), index, currency)
            for currency, _ in sorted(currency_map.items(),
                                      key=lambda item: (item[1], item[0]),
                                      reverse=True)]

beancount.query.numberify.convert_col_Inventory(name, drows, index)

Create converters for a column of type Inventory.

Parameters:
  • name – A string, the column name.

  • drows – The table of objects.

  • index – The column number.

Returns:
  • A list of Converter instances, one for each of the currency types found.

Source code in beancount/query/numberify.py
def convert_col_Inventory(name, drows, index):
    """Create converters for a column of type Inventory.

    Args:
      name: A string, the column name.
      drows: The table of objects.
      index: The column number.
    Returns:
      A list of Converter instances, one for each of the currency types found.
    """
    currency_map = collections.defaultdict(int)
    for drow in drows:
        inv = drow[index]
        for currency in inv.currencies():
            currency_map[currency] += 1
    return [InventoryConverter('{} ({})'.format(name, currency), index, currency)
            for currency, _ in sorted(currency_map.items(),
                                      key=lambda item: (item[1], item[0]),
                                      reverse=True)]

beancount.query.numberify.convert_col_Position(name, drows, index)

Create converters for a column of type Position.

Parameters:
  • name – A string, the column name.

  • drows – The table of objects.

  • index – The column number.

Returns:
  • A list of Converter instances, one for each of the currency types found.

Source code in beancount/query/numberify.py
def convert_col_Position(name, drows, index):
    """Create converters for a column of type Position.

    Args:
      name: A string, the column name.
      drows: The table of objects.
      index: The column number.
    Returns:
      A list of Converter instances, one for each of the currency types found.
    """
    currency_map = collections.defaultdict(int)
    for drow in drows:
        pos = drow[index]
        if pos and pos.units.currency:
            currency_map[pos.units.currency] += 1
    return [PositionConverter('{} ({})'.format(name, currency), index, currency)
            for currency, _ in sorted(currency_map.items(),
                                      key=lambda item: (item[1], item[0]),
                                      reverse=True)]

beancount.query.numberify.numberify_results(dtypes, drows, dformat=None)

Number rows containing Amount, Position or Inventory types.

Parameters:
  • result_types – A list of items describing the names and data types of the items in each column.

  • result_rows – A list of ResultRow instances.

  • dformat – An optional DisplayFormatter. If set, quantize the numbers by their currency-specific precision when converting the Amount's, Position's or Inventory'es..

Returns:
  • A pair of modified (result_types, result_rows) with converted datatypes.

Source code in beancount/query/numberify.py
def numberify_results(dtypes, drows, dformat=None):
    """Number rows containing Amount, Position or Inventory types.

    Args:
      result_types: A list of items describing the names and data types of the items in
        each column.
      result_rows: A list of ResultRow instances.
      dformat: An optional DisplayFormatter. If set, quantize the numbers by
        their currency-specific precision when converting the Amount's,
        Position's or Inventory'es..
    Returns:
      A pair of modified (result_types, result_rows) with converted datatypes.
    """
    # Build an array of converters.
    converters = []
    for index, col_desc in enumerate(dtypes):
        name, dtype = col_desc
        convert_col_fun = CONVERTING_TYPES.get(dtype, None)
        if convert_col_fun is None:
            converters.append(IdentityConverter(name, dtype, index))
        else:
            col_converters = convert_col_fun(name, drows, index)
            converters.extend(col_converters)

    # Derive the output types from the expected outputs from the converters
    # themselves.
    otypes = [(c.name, c.dtype) for c in converters]

    # Convert the input rows by processing them through the converters.
    orows = []
    for drow in drows:
        orow = []
        for converter in converters:
            orow.append(converter(drow, dformat))
        orows.append(orow)

    return otypes, orows

beancount.query.query

A library to run queries. This glues together all the parts of the query engine.

beancount.query.query.run_query(entries, options_map, query, *format_args, *, numberify=False)

Compile and execute a query, return the result types and rows.

Parameters:
  • entries – A list of entries, as produced by the loader.

  • options_map – A dict of options, as produced by the loader.

  • query – A string, a single BQL query, optionally containing some new-style (e.g., {}) formatting specifications.

  • format_args – A tuple of arguments to be formatted in the query. This is just provided as a convenience.

  • numberify – If true, numberify the results before returning them.

Returns:
  • A pair of result types and result rows.

Exceptions:
  • ParseError – If the statement cannot be parsed.

  • CompilationError – If the statement cannot be compiled.

Source code in beancount/query/query.py
def run_query(entries, options_map, query, *format_args, numberify=False):
    """Compile and execute a query, return the result types and rows.

    Args:
      entries: A list of entries, as produced by the loader.
      options_map: A dict of options, as produced by the loader.
      query: A string, a single BQL query, optionally containing some new-style
        (e.g., {}) formatting specifications.
      format_args: A tuple of arguments to be formatted in the query. This is
        just provided as a convenience.
      numberify: If true, numberify the results before returning them.
    Returns:
      A pair of result types and result rows.
    Raises:
      ParseError: If the statement cannot be parsed.
      CompilationError: If the statement cannot be compiled.
    """
    env_targets = query_env.TargetsEnvironment()
    env_entries = query_env.FilterEntriesEnvironment()
    env_postings = query_env.FilterPostingsEnvironment()

    # Apply formatting to the query.
    formatted_query = query.format(*format_args)

    # Parse the statement.
    parser = query_parser.Parser()
    statement = parser.parse(formatted_query)

    # Compile the SELECT statement.
    c_query = query_compile.compile(statement,
                                    env_targets,
                                    env_postings,
                                    env_entries)

    # Execute it to obtain the result rows.
    rtypes, rrows = query_execute.execute_query(c_query, entries, options_map)

    # Numberify the results, if requested.
    if numberify:
        dformat = options_map['dcontext'].build()
        rtypes, rrows = numberify_lib.numberify_results(rtypes, rrows, dformat)

    return rtypes, rrows

beancount.query.query_compile

Interpreter for the query language's AST.

This code accepts the abstract syntax tree produced by the query parser, resolves the column and function names, compiles and interpreter and prepares a query to be run against a list of entries.

beancount.query.query_compile.CompilationEnvironment

Base class for all compilation contexts. A compilation context provides column accessors specific to the particular row objects that we will access.

beancount.query.query_compile.CompilationEnvironment.get_column(self, name)

Return a column accessor for the given named column.

Parameters:
  • name – A string, the name of the column to access.

Source code in beancount/query/query_compile.py
def get_column(self, name):
    """Return a column accessor for the given named column.
    Args:
      name: A string, the name of the column to access.
    """
    try:
        return self.columns[name]()
    except KeyError:
        raise CompilationError("Invalid column name '{}' in {} context.".format(
            name, self.context_name))

beancount.query.query_compile.CompilationEnvironment.get_function(self, name, operands)

Return a function accessor for the given named function.

Parameters:
  • name – A string, the name of the function to access.

Source code in beancount/query/query_compile.py
def get_function(self, name, operands):
    """Return a function accessor for the given named function.
    Args:
      name: A string, the name of the function to access.
    """
    try:
        key = tuple([name] + [operand.dtype for operand in operands])
        return self.functions[key](operands)
    except KeyError:
        # If not found with the operands, try just looking it up by name.
        try:
            return self.functions[name](operands)
        except KeyError:
            signature = '{}({})'.format(name,
                                        ', '.join(operand.dtype.__name__
                                                  for operand in operands))
            raise CompilationError("Invalid function '{}' in {} context".format(
                signature, self.context_name))

beancount.query.query_compile.CompilationError (Exception)

A compiler/interpreter error.

beancount.query.query_compile.EvalAggregator (EvalFunction)

Base class for all aggregator evaluator types.

beancount.query.query_compile.EvalAggregator.__call__(self, context) special

Return the value on evaluation.

Parameters:
  • context – The evaluation object to which the evaluation need to apply. This is either an entry, a Posting instance, or a particular result set row from a sub-select. This is the provider for the underlying data.

Returns:
  • The final aggregated value.

Source code in beancount/query/query_compile.py
def __call__(self, context):
    """Return the value on evaluation.

    Args:
      context: The evaluation object to which the evaluation need to apply.
        This is either an entry, a Posting instance, or a particular result
        set row from a sub-select. This is the provider for the underlying
        data.
    Returns:
      The final aggregated value.
    """
    # Return None by default.

beancount.query.query_compile.EvalAggregator.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_compile.py
def allocate(self, allocator):
    """Allocate handles to store data for a node's aggregate storage.

    This is called once before beginning aggregations. If you need any
    kind of per-aggregate storage during the computation phase, get it
    in this method.

    Args:
      allocator: An instance of Allocator, on which you can call allocate() to
        obtain a handle for a slot to store data on store objects later on.
    """
    # Do nothing by default.

beancount.query.query_compile.EvalAggregator.finalize(self, store)

Finalize this node's aggregate data and return it.

For aggregate methods, this finalizes the node and returns the final value. The context node will be the alloc instead of the context object.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_compile.py
def finalize(self, store):
    """Finalize this node's aggregate data and return it.

    For aggregate methods, this finalizes the node and returns the final
    value. The context node will be the alloc instead of the context object.

    Args:
      store: An object indexable by handles appropriated during allocate().
    """
    # Do nothing by default.

beancount.query.query_compile.EvalAggregator.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_compile.py
def initialize(self, store):
    """Initialize this node's aggregate data. If the node is not an aggregate,
    simply initialize the subnodes. Override this method in the aggregator
    if you need data for storage.

    Args:
      store: An object indexable by handles appropriated during allocate().
    """
    # Do nothing by default.

beancount.query.query_compile.EvalAggregator.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_compile.py
def update(self, store, context):
    """Evaluate this node. This is designed to recurse on its children.

    Args:
      store: An object indexable by handles appropriated during allocate().
      context: The object to which the evaluation need to apply (see __call__).
    """
    # Do nothing by default.

beancount.query.query_compile.EvalColumn (EvalNode)

Base class for all column accessors.

beancount.query.query_compile.EvalFrom (tuple)

EvalFrom(c_expr, open, close, clear)

beancount.query.query_compile.EvalFrom.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_compile.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_compile.EvalFrom.__new__(_cls, c_expr, open, close, clear) special staticmethod

Create new instance of EvalFrom(c_expr, open, close, clear)

beancount.query.query_compile.EvalFrom.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_compile.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_compile.EvalFrom._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_compile.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_compile.EvalFrom._make(iterable) classmethod private

Make a new EvalFrom object from a sequence or iterable

Source code in beancount/query/query_compile.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_compile.EvalFrom._replace(/, self, **kwds) private

Return a new EvalFrom object replacing specified fields with new values

Source code in beancount/query/query_compile.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_compile.EvalFunction (EvalNode)

Base class for all function objects.

beancount.query.query_compile.EvalNode

beancount.query.query_compile.EvalNode.__call__(self, context) special

Evaluate this node. This is designed to recurse on its children. All subclasses must override and implement this method.

Parameters:
  • context – The evaluation object to which the evaluation need to apply. This is either an entry, a Posting instance, or a particular result set row from a sub-select. This is the provider for the underlying data.

Returns:
  • The evaluated value for this sub-expression tree.

Source code in beancount/query/query_compile.py
def __call__(self, context):
    """Evaluate this node. This is designed to recurse on its children.
    All subclasses must override and implement this method.

    Args:
      context: The evaluation object to which the evaluation need to apply.
        This is either an entry, a Posting instance, or a particular result
        set row from a sub-select. This is the provider for the underlying
        data.
    Returns:
      The evaluated value for this sub-expression tree.
    """
    raise NotImplementedError

beancount.query.query_compile.EvalNode.__eq__(self, other) special

Override the equality operator to compare the data type and a all attributes of this node. This is used by tests for comparing nodes.

Source code in beancount/query/query_compile.py
def __eq__(self, other):
    """Override the equality operator to compare the data type and a all attributes
    of this node. This is used by tests for comparing nodes.
    """
    return (isinstance(other, type(self))
            and all(
                getattr(self, attribute) == getattr(other, attribute)
                for attribute in self.__slots__))

beancount.query.query_compile.EvalNode.__repr__(self) special

Return str(self).

Source code in beancount/query/query_compile.py
def __str__(self):
    return "{}({})".format(type(self).__name__,
                           ', '.join(repr(getattr(self, child))
                                     for child in self.__slots__))

beancount.query.query_compile.EvalNode.childnodes(self)

Returns the child nodes of this node. Yields: A list of EvalNode instances.

Source code in beancount/query/query_compile.py
def childnodes(self):
    """Returns the child nodes of this node.
    Yields:
      A list of EvalNode instances.
    """
    for attr in self.__slots__:
        child = getattr(self, attr)
        if isinstance(child, EvalNode):
            yield child
        elif isinstance(child, list):
            for element in child:
                if isinstance(element, EvalNode):
                    yield element

beancount.query.query_compile.EvalPrint (tuple)

EvalPrint(c_from,)

beancount.query.query_compile.EvalPrint.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_compile.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_compile.EvalPrint.__new__(_cls, c_from) special staticmethod

Create new instance of EvalPrint(c_from,)

beancount.query.query_compile.EvalPrint.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_compile.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_compile.EvalPrint._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_compile.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_compile.EvalPrint._make(iterable) classmethod private

Make a new EvalPrint object from a sequence or iterable

Source code in beancount/query/query_compile.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_compile.EvalPrint._replace(/, self, **kwds) private

Return a new EvalPrint object replacing specified fields with new values

Source code in beancount/query/query_compile.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_compile.EvalQuery (tuple)

EvalQuery(c_targets, c_from, c_where, group_indexes, order_indexes, ordering, limit, distinct, flatten)

beancount.query.query_compile.EvalQuery.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_compile.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_compile.EvalQuery.__new__(_cls, c_targets, c_from, c_where, group_indexes, order_indexes, ordering, limit, distinct, flatten) special staticmethod

Create new instance of EvalQuery(c_targets, c_from, c_where, group_indexes, order_indexes, ordering, limit, distinct, flatten)

beancount.query.query_compile.EvalQuery.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_compile.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_compile.EvalQuery._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_compile.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_compile.EvalQuery._make(iterable) classmethod private

Make a new EvalQuery object from a sequence or iterable

Source code in beancount/query/query_compile.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_compile.EvalQuery._replace(/, self, **kwds) private

Return a new EvalQuery object replacing specified fields with new values

Source code in beancount/query/query_compile.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_compile.EvalTarget (tuple)

EvalTarget(c_expr, name, is_aggregate)

beancount.query.query_compile.EvalTarget.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_compile.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_compile.EvalTarget.__new__(_cls, c_expr, name, is_aggregate) special staticmethod

Create new instance of EvalTarget(c_expr, name, is_aggregate)

beancount.query.query_compile.EvalTarget.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_compile.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_compile.EvalTarget._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_compile.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_compile.EvalTarget._make(iterable) classmethod private

Make a new EvalTarget object from a sequence or iterable

Source code in beancount/query/query_compile.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_compile.EvalTarget._replace(/, self, **kwds) private

Return a new EvalTarget object replacing specified fields with new values

Source code in beancount/query/query_compile.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_compile.ResultSetEnvironment (CompilationEnvironment)

An execution context that provides access to attributes from a result set.

beancount.query.query_compile.ResultSetEnvironment.get_column(self, name)

Override the column getter to provide a single attribute getter.

Source code in beancount/query/query_compile.py
def get_column(self, name):
    """Override the column getter to provide a single attribute getter.
    """
    # FIXME: How do we figure out the data type here? We need the context.
    return AttributeColumn(name)

beancount.query.query_compile._get_columns_and_aggregates(node, columns, aggregates) private

Walk down a tree of nodes and fetch the column accessors and aggregates.

This function ignores all nodes under aggregate nodes.

Parameters:
  • node – An instance of EvalNode.

  • columns – An accumulator for columns found so far.

  • aggregate – An accumulator for aggregate notes found so far.

Source code in beancount/query/query_compile.py
def _get_columns_and_aggregates(node, columns, aggregates):
    """Walk down a tree of nodes and fetch the column accessors and aggregates.

    This function ignores all nodes under aggregate nodes.

    Args:
      node: An instance of EvalNode.
      columns: An accumulator for columns found so far.
      aggregate: An accumulator for aggregate notes found so far.
    """
    if isinstance(node, EvalAggregator):
        aggregates.append(node)
    elif isinstance(node, EvalColumn):
        columns.append(node)
    else:
        for child in node.childnodes():
            _get_columns_and_aggregates(child, columns, aggregates)

beancount.query.query_compile.compile(statement, targets_environ, postings_environ, entries_environ)

Prepare an AST any of the statement into an executable statement.

Parameters:
  • statement – An instance of the parser's Select, Balances, Journal or Print.

  • targets_environ – A compilation environment for evaluating targets.

  • postings_environ – : A compilation environment for evaluating postings filters.

  • entries_environ – : A compilation environment for evaluating entry filters.

Returns:
  • An instance of EvalQuery or EvalPrint, ready to be executed.

Exceptions:
  • CompilationError – If the statement cannot be compiled, or is not one of the supported statements.

Source code in beancount/query/query_compile.py
def compile(statement, targets_environ, postings_environ, entries_environ):
    """Prepare an AST any of the statement into an executable statement.

    Args:
      statement: An instance of the parser's Select, Balances, Journal or Print.
      targets_environ: A compilation environment for evaluating targets.
      postings_environ: : A compilation environment for evaluating postings filters.
      entries_environ: : A compilation environment for evaluating entry filters.
    Returns:
      An instance of EvalQuery or EvalPrint, ready to be executed.
    Raises:
      CompilationError: If the statement cannot be compiled, or is not one of the
        supported statements.
    """
    if isinstance(statement, query_parser.Balances):
        statement = transform_balances(statement)
    elif isinstance(statement, query_parser.Journal):
        statement = transform_journal(statement)

    if isinstance(statement, query_parser.Select):
        c_query = compile_select(statement,
                                 targets_environ, postings_environ, entries_environ)
    elif isinstance(statement, query_parser.Print):
        c_query = compile_print(statement, entries_environ)
    else:
        raise CompilationError(
            "Cannot compile a statement of type '{}'".format(type(statement)))

    return c_query

beancount.query.query_compile.compile_expression(expr, environ)

Bind an expression to its execution context.

Parameters:
  • expr – The root node of an expression.

  • environ – An CompilationEnvironment instance.

Returns:
  • The root node of a bound expression.

Source code in beancount/query/query_compile.py
def compile_expression(expr, environ):
    """Bind an expression to its execution context.

    Args:
      expr: The root node of an expression.
      environ: An CompilationEnvironment instance.
    Returns:
      The root node of a bound expression.
    """
    # Convert column references to the context.
    if isinstance(expr, query_parser.Column):
        c_expr = environ.get_column(expr.name)

    elif isinstance(expr, query_parser.Function):
        c_operands = [compile_expression(operand, environ)
                      for operand in expr.operands]
        c_expr = environ.get_function(expr.fname, c_operands)

    elif isinstance(expr, query_parser.UnaryOp):
        node_type = OPERATORS[type(expr)]
        c_expr = node_type(compile_expression(expr.operand, environ))

    elif isinstance(expr, query_parser.BinaryOp):
        node_type = OPERATORS[type(expr)]
        c_expr = node_type(compile_expression(expr.left, environ),
                           compile_expression(expr.right, environ))

    elif isinstance(expr, query_parser.Constant):
        c_expr = EvalConstant(expr.value)

    else:
        assert False, "Invalid expression to compile: {}".format(expr)

    return c_expr

beancount.query.query_compile.compile_from(from_clause, environ)

Compiled a From clause as provided by the parser, in the given environment.

Parameters:
  • select – An instance of query_parser.Select.

  • environ – : A compilation context for evaluating entry filters.

Returns:
  • An instance of Query, ready to be executed.

Source code in beancount/query/query_compile.py
def compile_from(from_clause, environ):
    """Compiled a From clause as provided by the parser, in the given environment.

    Args:
      select: An instance of query_parser.Select.
      environ: : A compilation context for evaluating entry filters.
    Returns:
      An instance of Query, ready to be executed.
    """
    if from_clause is not None:
        c_expression = (compile_expression(from_clause.expression, environ)
                        if from_clause.expression is not None
                        else None)

        # Check that the from clause does not contain aggregates.
        if c_expression is not None and is_aggregate(c_expression):
            raise CompilationError("Aggregates are not allowed in from clause")

        if (isinstance(from_clause.open, datetime.date) and
            isinstance(from_clause.close, datetime.date) and
            from_clause.open > from_clause.close):
            raise CompilationError("Invalid dates: CLOSE date must follow OPEN date")

        c_from = EvalFrom(c_expression,
                          from_clause.open,
                          from_clause.close,
                          from_clause.clear)
    else:
        c_from = None

    return c_from

beancount.query.query_compile.compile_group_by(group_by, c_targets, environ)

Process a group-by clause.

Parameters:
  • group_by – A GroupBy instance as provided by the parser.

  • c_targets – A list of compiled target expressions.

  • environ – A compilation context to be used to evaluate GROUP BY expressions.

Returns:
  • A tuple of new_targets – A list of new compiled target nodes. group_indexes: If the query is an aggregate query, a list of integer indexes to be used for processing grouping. Note that this list may be empty (in the case of targets with only aggregates). On the other hand, if this is not an aggregated query, this is set to None. So do distinguish the empty list vs. None.

Source code in beancount/query/query_compile.py
def compile_group_by(group_by, c_targets, environ):
    """Process a group-by clause.

    Args:
      group_by: A GroupBy instance as provided by the parser.
      c_targets: A list of compiled target expressions.
      environ: A compilation context to be used to evaluate GROUP BY expressions.
    Returns:
      A tuple of
       new_targets: A list of new compiled target nodes.
       group_indexes: If the query is an aggregate query, a list of integer
         indexes to be used for processing grouping. Note that this list may be
         empty (in the case of targets with only aggregates). On the other hand,
         if this is not an aggregated query, this is set to None. So do
         distinguish the empty list vs. None.
    """
    new_targets = copy.copy(c_targets)
    c_target_expressions = [c_target.c_expr for c_target in c_targets]

    group_indexes = []
    if group_by:
        # Check that HAVING is not supported yet.
        if group_by and group_by.having is not None:
            raise CompilationError("The HAVING clause is not supported yet")

        assert group_by.columns, "Internal error with GROUP-BY parsing"

        # Compile group-by expressions and resolve them to their targets if
        # possible. A GROUP-BY column may be one of the following:
        #
        # * A reference to a target by name.
        # * A reference to a target by index (starting at one).
        # * A new, non-aggregate expression.
        #
        # References by name are converted to indexes. New expressions are
        # inserted into the list of targets as invisible targets.
        targets_name_map = {target.name: index
                            for index, target in enumerate(c_targets)}
        for column in group_by.columns:
            index = None

            # Process target references by index.
            if isinstance(column, int):
                index = column - 1
                if not (0 <= index < len(c_targets)):
                    raise CompilationError(
                        "Invalid GROUP-BY column index {}".format(column))

            else:
                # Process target references by name. These will be parsed as
                # simple Column expressions. If they refer to a target name, we
                # resolve them.
                if isinstance(column, query_parser.Column):
                    name = column.name
                    index = targets_name_map.get(name, None)

                # Otherwise we compile the expression and add it to the list of
                # targets to evaluate and index into that new target.
                if index is None:
                    c_expr = compile_expression(column, environ)

                    # Check if the new expression is an aggregate.
                    aggregate = is_aggregate(c_expr)
                    if aggregate:
                        raise CompilationError(
                            "GROUP-BY expressions may not be aggregates: '{}'".format(
                                column))

                    # Attempt to reconcile the expression with one of the existing
                    # target expressions.
                    try:
                        index = c_target_expressions.index(c_expr)
                    except ValueError:
                        # Add the new target. 'None' for the target name implies it
                        # should be invisible, not to be rendered.
                        index = len(new_targets)
                        new_targets.append(EvalTarget(c_expr, None, aggregate))
                        c_target_expressions.append(c_expr)

            assert index is not None, "Internal error, could not index group-by reference."
            group_indexes.append(index)

            # Check that the group-by column references a non-aggregate.
            c_expr = new_targets[index].c_expr
            if is_aggregate(c_expr):
                raise CompilationError(
                    "GROUP-BY expressions may not reference aggregates: '{}'".format(
                        column))

            # Check that the group-by column has a supported hashable type.
            if not is_hashable_type(c_expr):
                raise CompilationError(
                    "GROUP-BY a non-hashable type is not supported: '{}'".format(
                        column))


    else:
        # If it does not have a GROUP-BY clause...
        aggregate_bools = [c_target.is_aggregate for c_target in c_targets]
        if any(aggregate_bools):
            # If the query is an aggregate query, check that all the targets are
            # aggregates.
            if all(aggregate_bools):
                assert group_indexes == []
            else:
                # If some of the targets aren't aggregates, automatically infer
                # that they are to be implicit group by targets. This makes for
                # a much more convenient syntax for our lightweight SQL, where
                # grouping is optional.
                if SUPPORT_IMPLICIT_GROUPBY:
                    group_indexes = [index
                                     for index, c_target in enumerate(c_targets)
                                     if not c_target.is_aggregate]
                else:
                    raise CompilationError(
                        "Aggregate query without a GROUP-BY should have only aggregates")
        else:
            # This is not an aggregate query; don't set group_indexes to
            # anything useful, we won't need it.
            group_indexes = None

    return new_targets[len(c_targets):], group_indexes

beancount.query.query_compile.compile_order_by(order_by, c_targets, environ)

Process an order-by clause.

Parameters:
  • order_by – A OrderBy instance as provided by the parser.

  • c_targets – A list of compiled target expressions.

  • environ – A compilation context to be used to evaluate ORDER BY expressions.

Returns:
  • A tuple of new_targets – A list of new compiled target nodes. order_indexes: A list of integer indexes to be used for processing ordering.

Source code in beancount/query/query_compile.py
def compile_order_by(order_by, c_targets, environ):
    """Process an order-by clause.

    Args:
      order_by: A OrderBy instance as provided by the parser.
      c_targets: A list of compiled target expressions.
      environ: A compilation context to be used to evaluate ORDER BY expressions.
    Returns:
      A tuple of
       new_targets: A list of new compiled target nodes.
       order_indexes: A list of integer indexes to be used for processing ordering.
    """
    new_targets = copy.copy(c_targets)
    c_target_expressions = [c_target.c_expr for c_target in c_targets]
    order_indexes = []

    # Compile order-by expressions and resolve them to their targets if
    # possible. A ORDER-BY column may be one of the following:
    #
    # * A reference to a target by name.
    # * A reference to a target by index (starting at one).
    # * A new expression, aggregate or not.
    #
    # References by name are converted to indexes. New expressions are
    # inserted into the list of targets as invisible targets.
    targets_name_map = {target.name: index
                        for index, target in enumerate(c_targets)}
    for column in order_by.columns:
        index = None

        # Process target references by index.
        if isinstance(column, int):
            index = column - 1
            if not (0 <= index < len(c_targets)):
                raise CompilationError(
                    "Invalid ORDER-BY column index {}".format(column))

        else:
            # Process target references by name. These will be parsed as
            # simple Column expressions. If they refer to a target name, we
            # resolve them.
            if isinstance(column, query_parser.Column):
                name = column.name
                index = targets_name_map.get(name, None)

            # Otherwise we compile the expression and add it to the list of
            # targets to evaluate and index into that new target.
            if index is None:
                c_expr = compile_expression(column, environ)

                # Attempt to reconcile the expression with one of the existing
                # target expressions.
                try:
                    index = c_target_expressions.index(c_expr)
                except ValueError:
                    # Add the new target. 'None' for the target name implies it
                    # should be invisible, not to be rendered.
                    index = len(new_targets)
                    new_targets.append(EvalTarget(c_expr, None, is_aggregate(c_expr)))
                    c_target_expressions.append(c_expr)

        assert index is not None, "Internal error, could not index order-by reference."
        order_indexes.append(index)

    return (new_targets[len(c_targets):], order_indexes)

beancount.query.query_compile.compile_print(print_stmt, env_entries)

Compile a Print statement.

Parameters:
  • statement – An instance of query_parser.Print.

  • entries_environ – : A compilation environment for evaluating entry filters.

Returns:
  • An instance of EvalPrint, ready to be executed.

Source code in beancount/query/query_compile.py
def compile_print(print_stmt, env_entries):
    """Compile a Print statement.

    Args:
      statement: An instance of query_parser.Print.
      entries_environ: : A compilation environment for evaluating entry filters.
    Returns:
      An instance of EvalPrint, ready to be executed.
    """
    c_from = compile_from(print_stmt.from_clause, env_entries)
    return EvalPrint(c_from)

beancount.query.query_compile.compile_select(select, targets_environ, postings_environ, entries_environ)

Prepare an AST for a Select statement into a very rudimentary execution tree. The execution tree mostly looks much like an AST, but with some nodes replaced with knowledge specific to an execution context and eventually some basic optimizations.

Parameters:
  • select – An instance of query_parser.Select.

  • targets_environ – A compilation environment for evaluating targets.

  • postings_environ – A compilation environment for evaluating postings filters.

  • entries_environ – A compilation environment for evaluating entry filters.

Returns:
  • An instance of EvalQuery, ready to be executed.

Source code in beancount/query/query_compile.py
def compile_select(select, targets_environ, postings_environ, entries_environ):
    """Prepare an AST for a Select statement into a very rudimentary execution tree.
    The execution tree mostly looks much like an AST, but with some nodes
    replaced with knowledge specific to an execution context and eventually some
    basic optimizations.

    Args:
      select: An instance of query_parser.Select.
      targets_environ: A compilation environment for evaluating targets.
      postings_environ: A compilation environment for evaluating postings filters.
      entries_environ: A compilation environment for evaluating entry filters.
    Returns:
      An instance of EvalQuery, ready to be executed.
    """

    # Process the FROM clause and figure out the execution environment for the
    # targets and the where clause.
    from_clause = select.from_clause
    if isinstance(from_clause, query_parser.Select):
        c_from = None
        environ_target = ResultSetEnvironment()
        environ_where = ResultSetEnvironment()

        # Remove this when we add support for nested queries.
        raise CompilationError("Queries from nested SELECT are not supported yet")

    if from_clause is None or isinstance(from_clause, query_parser.From):
        # Bind the from clause contents.
        c_from = compile_from(from_clause, entries_environ)
        environ_target = targets_environ
        environ_where = postings_environ

    else:
        raise CompilationError("Unexpected from clause in AST: {}".format(from_clause))

    # Compile the targets.
    c_targets = compile_targets(select.targets, environ_target)

    # Bind the WHERE expression to the execution environment.
    if select.where_clause is not None:
        c_where = compile_expression(select.where_clause, environ_where)

        # Aggregates are disallowed in this clause. Check for this.
        # NOTE: This should never trigger if the compilation environment does not
        # contain any aggregate. Just being manic and safe here.
        if is_aggregate(c_where):
            raise CompilationError("Aggregates are disallowed in WHERE clause")
    else:
        c_where = None

    # Process the GROUP-BY clause.
    new_targets, group_indexes = compile_group_by(select.group_by,
                                                  c_targets,
                                                  environ_target)
    if new_targets:
        c_targets.extend(new_targets)

    # Process the ORDER-BY clause.
    if select.order_by is not None:
        (new_targets, order_indexes) = compile_order_by(select.order_by,
                                                        c_targets,
                                                        environ_target)
        if new_targets:
            c_targets.extend(new_targets)
        ordering = select.order_by.ordering
    else:
        order_indexes = None
        ordering = None

    # If this is an aggregate query (it groups, see list of indexes), check that
    # the set of non-aggregates match exactly the group indexes. This should
    # always be the case at this point, because we have added all the necessary
    # targets to the list of group-by expressions and should have resolved all
    # the indexes.
    if group_indexes is not None:
        non_aggregate_indexes = set(index
                                    for index, c_target in enumerate(c_targets)
                                    if not c_target.is_aggregate)
        if non_aggregate_indexes != set(group_indexes):
            missing_names = ['"{}"'.format(c_targets[index].name)
                             for index in non_aggregate_indexes - set(group_indexes)]
            raise CompilationError(
                "All non-aggregates must be covered by GROUP-BY clause in aggregate query; "
                "the following targets are missing: {}".format(",".join(missing_names)))

    # Check that PIVOT-BY is not supported yet.
    if select.pivot_by is not None:
        raise CompilationError("The PIVOT BY clause is not supported yet")

    return EvalQuery(c_targets,
                     c_from,
                     c_where,
                     group_indexes,
                     order_indexes,
                     ordering,
                     select.limit,
                     select.distinct,
                     select.flatten)

beancount.query.query_compile.compile_targets(targets, environ)

Compile the targets and check for their validity. Process wildcard.

Parameters:
  • targets – A list of target expressions from the parser.

  • environ – A compilation context for the targets.

Returns:
  • A list of compiled target expressions with resolved names.

Source code in beancount/query/query_compile.py
def compile_targets(targets, environ):
    """Compile the targets and check for their validity. Process wildcard.

    Args:
      targets: A list of target expressions from the parser.
      environ: A compilation context for the targets.
    Returns:
      A list of compiled target expressions with resolved names.
    """
    # Bind the targets expressions to the execution context.
    if isinstance(targets, query_parser.Wildcard):
        # Insert the full list of available columns.
        targets = [query_parser.Target(query_parser.Column(name), None)
                   for name in environ.wildcard_columns]

    # Compile targets.
    c_targets = []
    target_names = set()
    for target in targets:
        c_expr = compile_expression(target.expression, environ)
        target_name = find_unique_name(
            target.name or query_parser.get_expression_name(target.expression),
            target_names)
        target_names.add(target_name)
        c_targets.append(EvalTarget(c_expr, target_name, is_aggregate(c_expr)))

    # Figure out if this query is an aggregate query and check validity of each
    # target's aggregation type.
    for index, c_target in enumerate(c_targets):
        columns, aggregates = get_columns_and_aggregates(c_target.c_expr)

        # Check for mixed aggregates and non-aggregates.
        if columns and aggregates:
            raise CompilationError(
                "Mixed aggregates and non-aggregates are not allowed")

        if aggregates:
            # Check for aggregates of aggregates.
            for aggregate in aggregates:
                for child in aggregate.childnodes():
                    if is_aggregate(child):
                        raise CompilationError(
                            "Aggregates of aggregates are not allowed")

    return c_targets

beancount.query.query_compile.find_unique_name(name, allocated_set)

Come up with a unique name for 'name' amongst 'allocated_set'.

Parameters:
  • name – A string, the prefix of the name to find a unique for.

  • allocated_set – A set of string, the set of already allocated names.

Returns:
  • A unique name. 'allocated_set' is unmodified.

Source code in beancount/query/query_compile.py
def find_unique_name(name, allocated_set):
    """Come up with a unique name for 'name' amongst 'allocated_set'.

    Args:
      name: A string, the prefix of the name to find a unique for.
      allocated_set: A set of string, the set of already allocated names.
    Returns:
      A unique name. 'allocated_set' is unmodified.
    """
    # Make sure the name is unique.
    prefix = name
    i = 1
    while name in allocated_set:
        name = '{}_{}'.format(prefix, i)
        i += 1
    return name

beancount.query.query_compile.get_columns_and_aggregates(node)

Find the columns and aggregate nodes below this tree.

All nodes under aggregate nodes are ignored.

Parameters:
  • node – An instance of EvalNode.

Returns:
  • A pair of (columns, aggregates), both of which are lists of EvalNode instances. columns – The list of all columns accessed not under an aggregate node. aggregates: The list of all aggregate nodes.

Source code in beancount/query/query_compile.py
def get_columns_and_aggregates(node):
    """Find the columns and aggregate nodes below this tree.

    All nodes under aggregate nodes are ignored.

    Args:
      node: An instance of EvalNode.
    Returns:
      A pair of (columns, aggregates), both of which are lists of EvalNode instances.
        columns: The list of all columns accessed not under an aggregate node.
        aggregates: The list of all aggregate nodes.
    """
    columns = []
    aggregates = []
    _get_columns_and_aggregates(node, columns, aggregates)
    return columns, aggregates

beancount.query.query_compile.is_aggregate(node)

Return true if the node is an aggregate.

Parameters:
  • node – An instance of EvalNode.

Returns:
  • A boolean.

Source code in beancount/query/query_compile.py
def is_aggregate(node):
    """Return true if the node is an aggregate.

    Args:
      node: An instance of EvalNode.
    Returns:
      A boolean.
    """
    # Note: We could be a tiny bit more efficient here, but it doesn't matter
    # much. Performance of the query compilation matters very little overall.
    _, aggregates = get_columns_and_aggregates(node)
    return bool(aggregates)

beancount.query.query_compile.is_hashable_type(node)

Return true if the node is of a hashable type.

Parameters:
  • node – An instance of EvalNode.

Returns:
  • A boolean.

Source code in beancount/query/query_compile.py
def is_hashable_type(node):
    """Return true if the node is of a hashable type.

    Args:
      node: An instance of EvalNode.
    Returns:
      A boolean.
    """
    return not issubclass(node.dtype, inventory.Inventory)

beancount.query.query_compile.transform_balances(balances)

Translate a Balances entry into an uncompiled Select statement.

Parameters:
  • balances – An instance of a Balance object.

Returns:
  • An instance of an uncompiled Select object.

Source code in beancount/query/query_compile.py
def transform_balances(balances):
    """Translate a Balances entry into an uncompiled Select statement.

    Args:
      balances: An instance of a Balance object.
    Returns:
      An instance of an uncompiled Select object.
    """
    ## FIXME: Change the aggregation rules to allow GROUP-BY not to include the
    ## non-aggregate ORDER-BY columns, so we could just GROUP-BY accounts here
    ## instead of having to include the sort-key. I think it should be fine if
    ## the first or last sort-order value gets used, because it would simplify
    ## the input statement.

    cooked_select = query_parser.Parser().parse("""

      SELECT account, SUM({}(position))
      GROUP BY account, ACCOUNT_SORTKEY(account)
      ORDER BY ACCOUNT_SORTKEY(account)

    """.format(balances.summary_func or ""))

    return query_parser.Select(cooked_select.targets,
                               balances.from_clause,
                               balances.where_clause,
                               cooked_select.group_by,
                               cooked_select.order_by,
                               None, None, None, None)

beancount.query.query_compile.transform_journal(journal)

Translate a Journal entry into an uncompiled Select statement.

Parameters:
  • journal – An instance of a Journal object.

Returns:
  • An instance of an uncompiled Select object.

Source code in beancount/query/query_compile.py
def transform_journal(journal):
    """Translate a Journal entry into an uncompiled Select statement.

    Args:
      journal: An instance of a Journal object.
    Returns:
      An instance of an uncompiled Select object.
    """
    cooked_select = query_parser.Parser().parse("""

        SELECT
           date,
           flag,
           MAXWIDTH(payee, 48),
           MAXWIDTH(narration, 80),
           account,
           {summary_func}(position),
           {summary_func}(balance)
        {where}

    """.format(where=('WHERE account ~ "{}"'.format(journal.account)
                      if journal.account
                      else ''),
               summary_func=journal.summary_func or ''))

    return query_parser.Select(cooked_select.targets,
                               journal.from_clause,
                               cooked_select.where_clause,
                               None, None, None, None, None, None)

beancount.query.query_compile_test

beancount.query.query_compile_test.CompileSelectBase (TestCase)

beancount.query.query_compile_test.CompileSelectBase.assertCompile(self, expected, query, debug=False)

Assert parsed and compiled contents from 'query' is 'expected'.

Parameters:
  • expected – An expected AST to compare against the parsed value.

  • query – An SQL query to be parsed.

  • debug – A boolean, if true, print extra debugging information on the console.

Exceptions:
  • AssertionError – If the actual AST does not match the expected one.

Source code in beancount/query/query_compile_test.py
def assertCompile(self, expected, query, debug=False):
    """Assert parsed and compiled contents from 'query' is 'expected'.

    Args:
      expected: An expected AST to compare against the parsed value.
      query: An SQL query to be parsed.
      debug: A boolean, if true, print extra debugging information on the console.
    Raises:
      AssertionError: If the actual AST does not match the expected one.
    """
    actual = self.compile(query)
    if debug:
        print()
        print()
        print(actual)
        print()
    try:
        self.assertEqual(expected, actual)
        return actual
    except AssertionError:
        print()
        print("Expected: {}".format(expected))
        print("Actual  : {}".format(actual))
        raise

beancount.query.query_compile_test.CompileSelectBase.assertIndexes(self, query, expected_simple_indexes, expected_aggregate_indexes, expected_group_indexes, expected_order_indexes)

Check the four lists of indexes for comparison.

Parameters:
  • query – An instance of EvalQuery, a compiled query statement.

  • expected_simple_indexes – The expected visible non-aggregate indexes.

  • expected_aggregate_indexes – The expected visible aggregate indexes.

  • expected_group_indexes – The expected group_indexes.

  • expected_order_indexes – The expected order_indexes.

Exceptions:
  • AssertionError – if the check fails.

Source code in beancount/query/query_compile_test.py
def assertIndexes(self,
                  query,
                  expected_simple_indexes,
                  expected_aggregate_indexes,
                  expected_group_indexes,
                  expected_order_indexes):
    """Check the four lists of indexes for comparison.

    Args:
      query: An instance of EvalQuery, a compiled query statement.
      expected_simple_indexes: The expected visible non-aggregate indexes.
      expected_aggregate_indexes: The expected visible aggregate indexes.
      expected_group_indexes: The expected group_indexes.
      expected_order_indexes: The expected order_indexes.
    Raises:
      AssertionError: if the check fails.
    """
    # Compute the list of _visible_ aggregates and non-aggregates.
    simple_indexes = [index
                      for index, c_target in enumerate(query.c_targets)
                      if c_target.name and not qc.is_aggregate(c_target.expression)]
    aggregate_indexes = [index
                         for index, c_target in enumerate(query.c_targets)
                         if c_target.name and qc.is_aggregate(c_target.expression)]

    self.assertEqual(set(expected_simple_indexes), set(simple_indexes))

    self.assertEqual(set(expected_aggregate_indexes), set(aggregate_indexes))

    self.assertEqual(
        set(expected_group_indexes) if expected_group_indexes is not None else None,
        set(query.group_indexes) if query.group_indexes is not None else None)

    self.assertEqual(
        set(expected_order_indexes) if expected_order_indexes is not None else None,
        set(query.order_indexes) if query.order_indexes is not None else None)

beancount.query.query_compile_test.CompileSelectBase.assertSelectInvariants(self, query)

Assert the invariants on the query.

Parameters:
  • query – An instance of EvalQuery, a compiled query statement.

Exceptions:
  • AssertionError – if the check fails.

Source code in beancount/query/query_compile_test.py
def assertSelectInvariants(self, query):
    """Assert the invariants on the query.

    Args:
      query: An instance of EvalQuery, a compiled query statement.
    Raises:
      AssertionError: if the check fails.
    """
    # Check that the group references cover all the simple indexes.
    if query.group_indexes is not None:
        non_aggregate_indexes = [index
                                 for index, c_target in enumerate(query.c_targets)
                                 if not qc.is_aggregate(c_target.c_expr)]

        self.assertEqual(set(non_aggregate_indexes), set(query.group_indexes),
                         "Invalid indexes: {}".format(query))

beancount.query.query_compile_test.CompileSelectBase.compile(self, query)

Parse one query and compile it.

Parameters:
  • query – An SQL query to be parsed.

Returns:
  • The AST.

Source code in beancount/query/query_compile_test.py
def compile(self, query):
    """Parse one query and compile it.

    Args:
      query: An SQL query to be parsed.
    Returns:
      The AST.
    """
    statement = self.parse(query)
    c_query = qc.compile(statement,
                         self.xcontext_targets,
                         self.xcontext_postings,
                         self.xcontext_entries)
    if isinstance(c_query, qp.Select):
        self.assertSelectInvariants(c_query)
    return c_query

beancount.query.query_compile_test.CompileSelectBase.setUp(self)

Hook method for setting up the test fixture before exercising it.

Source code in beancount/query/query_compile_test.py
def setUp(self):
    self.parser = qp.Parser()

beancount.query.query_env

Environment object for compiler.

This module contains the various column accessors and function evaluators that are made available by the query compiler via their compilation context objects. Define new columns and functions here.

beancount.query.query_env.AbsDecimal (EvalFunction)

Compute the length of the argument. This works on sequences.

beancount.query.query_env.AbsInventory (EvalFunction)

Compute the length of the argument. This works on sequences.

beancount.query.query_env.AbsPosition (EvalFunction)

Compute the length of the argument. This works on sequences.

beancount.query.query_env.AccountColumn (EvalColumn)

The account of the posting.

beancount.query.query_env.AccountSortKey (EvalFunction)

Get a string to sort accounts in order taking into account the types.

beancount.query.query_env.AnyMeta (EvalFunction)

Get metadata from the posting or its parent transaction's metadata if not present.

beancount.query.query_env.BalanceColumn (EvalColumn)

The balance for the posting. These can be summed into inventories.

beancount.query.query_env.CloseDate (EvalFunction)

Get the date of the close directive of the account.

beancount.query.query_env.Coalesce (EvalFunction)

Return the first non-null argument

beancount.query.query_env.ConvertAmount (EvalFunction)

Coerce an amount to a particular currency.

beancount.query.query_env.ConvertAmountWithDate (EvalFunction)

Coerce an amount to a particular currency.

beancount.query.query_env.ConvertInventory (EvalFunction)

Coerce an inventory to a particular currency.

beancount.query.query_env.ConvertInventoryWithDate (EvalFunction)

Coerce an inventory to a particular currency.

beancount.query.query_env.ConvertPosition (EvalFunction)

Coerce an amount to a particular currency.

beancount.query.query_env.ConvertPositionWithDate (EvalFunction)

Coerce an amount to a particular currency.

beancount.query.query_env.CostCurrencyColumn (EvalColumn)

The cost currency of the posting.

beancount.query.query_env.CostDateColumn (EvalColumn)

The cost currency of the posting.

beancount.query.query_env.CostInventory (EvalFunction)

Get the cost of an inventory.

beancount.query.query_env.CostLabelColumn (EvalColumn)

The cost currency of the posting.

beancount.query.query_env.CostNumberColumn (EvalColumn)

The number of cost units of the posting.

beancount.query.query_env.CostPosition (EvalFunction)

Get the cost of a position.

beancount.query.query_env.Count (EvalAggregator)

Count the number of occurrences of the argument.

beancount.query.query_env.Count.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.Count.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = 0

beancount.query.query_env.Count.update(self, store, unused_ontext)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, unused_ontext):
    store[self.handle] += 1

beancount.query.query_env.Currency (EvalFunction)

Extract the currency from an Amount.

beancount.query.query_env.CurrencyColumn (EvalColumn)

The currency of the posting.

beancount.query.query_env.CurrencyMeta (EvalFunction)

Get the metadata dict of the commodity directive of the currency.

beancount.query.query_env.Date (EvalFunction)

Construct a date with year, month, day arguments

beancount.query.query_env.DateAdd (EvalFunction)

Adds/subtracts number of days from the given date

beancount.query.query_env.DateColumn (EvalColumn)

The date of the parent transaction for this posting.

beancount.query.query_env.DateDiff (EvalFunction)

Calculates the difference (in days) between two dates

beancount.query.query_env.DateEntryColumn (EvalColumn)

The date of the directive.

beancount.query.query_env.Day (EvalFunction)

Extract the day from a date.

beancount.query.query_env.DayColumn (EvalColumn)

The day of the date of the parent transaction for this posting.

beancount.query.query_env.DayEntryColumn (EvalColumn)

The day of the date of the directive.

beancount.query.query_env.DescriptionColumn (EvalColumn)

A combination of the payee + narration for the transaction of this posting.

beancount.query.query_env.DescriptionEntryColumn (EvalColumn)

A combination of the payee + narration of the transaction, if present.

beancount.query.query_env.EntryMeta (EvalFunction)

Get some metadata key of the parent directive (Transaction).

beancount.query.query_env.FileLocationColumn (EvalColumn)

The filename:lineno where the posting was parsed from or created.

If you select this column as the first column, because it renders like errors, Emacs is able to pick those up and you can navigate between an arbitrary list of transactions with next-error and previous-error.

beancount.query.query_env.FilenameColumn (EvalColumn)

The filename where the posting was parsed from or created.

beancount.query.query_env.FilenameEntryColumn (EvalColumn)

The filename where the directive was parsed from or created.

beancount.query.query_env.FilterCurrencyInventory (EvalFunction)

Filter an inventory to just the specified currency.

beancount.query.query_env.FilterCurrencyPosition (EvalFunction)

Filter an inventory to just the specified currency.

beancount.query.query_env.FilterEntriesEnvironment (CompilationEnvironment)

An execution context that provides access to attributes on Transactions and other entry types.

beancount.query.query_env.FilterPostingsEnvironment (CompilationEnvironment)

An execution context that provides access to attributes on Postings.

beancount.query.query_env.FindFirst (EvalFunction)

Filter a string sequence by regular expression and return the first match.

beancount.query.query_env.First (EvalAggregator)

Keep the first of the values seen.

beancount.query.query_env.First.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.First.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = None

beancount.query.query_env.First.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    if store[self.handle] is None:
        value = self.eval_args(context)[0]
        store[self.handle] = value

beancount.query.query_env.FlagColumn (EvalColumn)

The flag of the parent transaction for this posting.

beancount.query.query_env.FlagEntryColumn (EvalColumn)

The flag the transaction.

beancount.query.query_env.GetItemStr (EvalFunction)

Get the string value of a dict. The value is always converted to a string.

beancount.query.query_env.Grep (EvalFunction)

Match a group against a string and return only the matched portion.

beancount.query.query_env.GrepN (EvalFunction)

Match a pattern with subgroups against a string and return the subgroup at the index

beancount.query.query_env.IdColumn (EvalColumn)

The unique id of the parent transaction for this posting.

beancount.query.query_env.IdEntryColumn (EvalColumn)

Unique id of a directive.

beancount.query.query_env.JoinStr (EvalFunction)

Join a sequence of strings to a single comma-separated string.

beancount.query.query_env.Last (EvalAggregator)

Keep the last of the values seen.

beancount.query.query_env.Last.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.Last.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = None

beancount.query.query_env.Last.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    store[self.handle] = value

beancount.query.query_env.Leaf (EvalFunction)

Get the name of the leaf subaccount.

beancount.query.query_env.Length (EvalFunction)

Compute the length of the argument. This works on sequences.

beancount.query.query_env.LineNoColumn (EvalColumn)

The line number from the file the posting was parsed from.

beancount.query.query_env.LineNoEntryColumn (EvalColumn)

The line number from the file the directive was parsed from.

beancount.query.query_env.LinksColumn (EvalColumn)

The set of links of the parent transaction for this posting.

beancount.query.query_env.LinksEntryColumn (EvalColumn)

The set of links of the transaction.

beancount.query.query_env.MatchAccount (EvalFunction)

A predicate, true if the transaction has at least one posting matching the regular expression argument.

beancount.query.query_env.Max (EvalAggregator)

Compute the maximum of the values.

beancount.query.query_env.Max.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.Max.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = self.dtype()

beancount.query.query_env.Max.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    if value > store[self.handle]:
        store[self.handle] = value

beancount.query.query_env.MaxWidth (EvalFunction)

Convert the argument to a substring. This can be used to ensure maximum width

beancount.query.query_env.Meta (EvalFunction)

Get some metadata key of the Posting.

beancount.query.query_env.Min (EvalAggregator)

Compute the minimum of the values.

beancount.query.query_env.Min.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.Min.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = self.dtype()

beancount.query.query_env.Min.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    if value < store[self.handle]:
        store[self.handle] = value

beancount.query.query_env.Month (EvalFunction)

Extract the month from a date.

beancount.query.query_env.MonthColumn (EvalColumn)

The month of the date of the parent transaction for this posting.

beancount.query.query_env.MonthEntryColumn (EvalColumn)

The month of the date of the directive.

beancount.query.query_env.NarrationColumn (EvalColumn)

The narration of the parent transaction for this posting.

beancount.query.query_env.NarrationEntryColumn (EvalColumn)

The narration of the transaction.

beancount.query.query_env.Number (EvalFunction)

Extract the number from an Amount.

beancount.query.query_env.NumberColumn (EvalColumn)

The number of units of the posting.

beancount.query.query_env.OnlyInventory (EvalFunction)

Get one currency's amount from the inventory.

beancount.query.query_env.OpenDate (EvalFunction)

Get the date of the open directive of the account.

beancount.query.query_env.OpenMeta (EvalFunction)

Get the metadata dict of the open directive of the account.

beancount.query.query_env.OtherAccountsColumn (EvalColumn)

The list of other accounts in the transaction, excluding that of this posting.

beancount.query.query_env.Parent (EvalFunction)

Get the parent name of the account.

beancount.query.query_env.ParseDate (EvalFunction)

Construct a date with year, month, day arguments

beancount.query.query_env.PayeeColumn (EvalColumn)

The payee of the parent transaction for this posting.

beancount.query.query_env.PayeeEntryColumn (EvalColumn)

The payee of the transaction.

beancount.query.query_env.PosSignAmount (EvalFunction)

Correct sign of an Amount based on the usual balance of associated account.

beancount.query.query_env.PosSignDecimal (EvalFunction)

Correct sign of an Amount based on the usual balance of associated account.

beancount.query.query_env.PosSignInventory (EvalFunction)

Correct sign of an Amount based on the usual balance of associated account.

beancount.query.query_env.PosSignPosition (EvalFunction)

Correct sign of an Amount based on the usual balance of associated account.

beancount.query.query_env.PositionColumn (EvalColumn)

The position for the posting. These can be summed into inventories.

beancount.query.query_env.PostingFlagColumn (EvalColumn)

The flag of the posting itself.

beancount.query.query_env.Price (EvalFunction)

Fetch a price for something at a particular date

beancount.query.query_env.PriceColumn (EvalColumn)

The price attached to the posting.

beancount.query.query_env.PriceWithDate (EvalFunction)

Fetch a price for something at a particular date

beancount.query.query_env.Quarter (EvalFunction)

Extract the quarter from a date.

beancount.query.query_env.Root (EvalFunction)

Get the root name(s) of the account.

beancount.query.query_env.SafeDiv (EvalFunction)

A division operation that swallows dbz exceptions and outputs 0 instead.

beancount.query.query_env.Str (EvalFunction)

Convert the argument to a string.

beancount.query.query_env.Subst (EvalFunction)

Substitute leftmost non-overlapping occurrences of pattern by replacement.

beancount.query.query_env.Sum (EvalAggregator)

Calculate the sum of the numerical argument.

beancount.query.query_env.Sum.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.Sum.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = self.dtype()

beancount.query.query_env.Sum.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    if value is not None:
        store[self.handle] += value

beancount.query.query_env.SumAmount (SumBase)

Calculate the sum of the amount. The result is an Inventory.

beancount.query.query_env.SumAmount.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    store[self.handle].add_amount(value)

beancount.query.query_env.SumBase (EvalAggregator)

beancount.query.query_env.SumBase.allocate(self, allocator)

Allocate handles to store data for a node's aggregate storage.

This is called once before beginning aggregations. If you need any kind of per-aggregate storage during the computation phase, get it in this method.

Parameters:
  • allocator – An instance of Allocator, on which you can call allocate() to obtain a handle for a slot to store data on store objects later on.

Source code in beancount/query/query_env.py
def allocate(self, allocator):
    self.handle = allocator.allocate()

beancount.query.query_env.SumBase.initialize(self, store)

Initialize this node's aggregate data. If the node is not an aggregate, simply initialize the subnodes. Override this method in the aggregator if you need data for storage.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

Source code in beancount/query/query_env.py
def initialize(self, store):
    store[self.handle] = inventory.Inventory()

beancount.query.query_env.SumInventory (SumBase)

Calculate the sum of the inventories. The result is an Inventory.

beancount.query.query_env.SumInventory.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    store[self.handle].add_inventory(value)

beancount.query.query_env.SumPosition (SumBase)

Calculate the sum of the position. The result is an Inventory.

beancount.query.query_env.SumPosition.update(self, store, context)

Evaluate this node. This is designed to recurse on its children.

Parameters:
  • store – An object indexable by handles appropriated during allocate().

  • context – The object to which the evaluation need to apply (see call).

Source code in beancount/query/query_env.py
def update(self, store, context):
    value = self.eval_args(context)[0]
    store[self.handle].add_position(value)

beancount.query.query_env.TagsColumn (EvalColumn)

The set of tags of the parent transaction for this posting.

beancount.query.query_env.TagsEntryColumn (EvalColumn)

The set of tags of the transaction.

beancount.query.query_env.TargetsEnvironment (FilterPostingsEnvironment)

An execution context that provides access to attributes on Postings.

beancount.query.query_env.Today (EvalFunction)

Today's date

beancount.query.query_env.TypeColumn (EvalColumn)

The data type of the parent transaction for this posting.

beancount.query.query_env.TypeEntryColumn (EvalColumn)

The data type of the directive.

beancount.query.query_env.UnitsInventory (EvalFunction)

Get the number of units of an inventory (stripping cost).

beancount.query.query_env.UnitsPosition (EvalFunction)

Get the number of units of a position (stripping cost).

beancount.query.query_env.ValueInventory (EvalFunction)

Coerce an inventory to its market value at the current date.

beancount.query.query_env.ValueInventoryWithDate (EvalFunction)

Coerce an inventory to its market value at a particular date.

beancount.query.query_env.ValuePosition (EvalFunction)

Convert a position to its cost currency at the market value.

beancount.query.query_env.ValuePositionWithDate (EvalFunction)

Convert a position to its cost currency at the market value of a particular date.

beancount.query.query_env.Weekday (EvalFunction)

Extract a 3-letter weekday from a date.

beancount.query.query_env.WeightColumn (EvalColumn)

The computed weight used for this posting.

beancount.query.query_env.Year (EvalFunction)

Extract the year from a date.

beancount.query.query_env.YearColumn (EvalColumn)

The year of the date of the parent transaction for this posting.

beancount.query.query_env.YearEntryColumn (EvalColumn)

The year of the date of the directive.

beancount.query.query_env.YearMonth (EvalFunction)

Extract the year and month from a date.

beancount.query.query_env._Neg (EvalFunction) private

Compute the negative value of the argument. This works on various types.

beancount.query.query_env_test

beancount.query.query_env_test.TestEnv (TestCase)

beancount.query.query_env_test.TestEnv.test_AnyMeta(self, entries, _, options_map)

2016-11-20 * name: "TheName" address: "1 Wrong Way" empty: "NotEmpty" Assets:Banking 1 USD color: "Green" address: "1 Right Way" empty:

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_AnyMeta(self, entries, _, options_map):
    """
    2016-11-20 *
      name: "TheName"
      address: "1 Wrong Way"
      empty: "NotEmpty"
      Assets:Banking          1 USD
        color: "Green"
        address: "1 Right Way"
        empty:
    """
    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT ANY_META("name") as m')
    self.assertEqual([('TheName',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT ANY_META("color") as m')
    self.assertEqual([('Green',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT ANY_META("address") as m')
    self.assertEqual([('1 Right Way',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT ANY_META("empty") as m')
    self.assertEqual([(None,)], rrows)

beancount.query.query_env_test.TestEnv.test_Coalesce(self, entries, _, options_map)

2016-11-20 * Assets:Banking 1 USD

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_Coalesce(self, entries, _, options_map):
    """
    2016-11-20 *
      Assets:Banking          1 USD
    """
    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT COALESCE(account, price) as m')
    self.assertEqual([('Assets:Banking',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT COALESCE(price, account) as m')
    self.assertEqual([('Assets:Banking',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT COALESCE(price, cost_number) as m')
    self.assertEqual([(None,)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT COALESCE(narration, account) as m')
    self.assertEqual([('',)], rrows)

beancount.query.query_env_test.TestEnv.test_Date(self, entries, _, options_map)

2016-11-20 * "ok" Assets:Banking 1 USD

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_Date(self, entries, _, options_map):
    """
    2016-11-20 * "ok"
      Assets:Banking          1 USD
    """
    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date(2020, 1, 2) as m')
    self.assertEqual([(datetime.date(2020, 1, 2),)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date(year, month, 1) as m')
    self.assertEqual([(datetime.date(2016, 11, 1),)], rrows)

    with self.assertRaisesRegex(ValueError, "day is out of range for month"):
        rtypes, rrows = query.run_query(entries, options_map,
                                        'SELECT date(2020, 2, 32) as m')

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date("2020-01-02") as m')
    self.assertEqual([(datetime.date(2020, 1, 2),)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date("2016/11/1") as m')
    self.assertEqual([(datetime.date(2016, 11, 1),)], rrows)

beancount.query.query_env_test.TestEnv.test_DateDiffAdjust(self, entries, _, options_map)

2016-11-20 * "ok" Assets:Banking -1 STOCK { 5 USD, 2016-10-30 }

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_DateDiffAdjust(self, entries, _, options_map):
    """
    2016-11-20 * "ok"
      Assets:Banking          -1 STOCK { 5 USD, 2016-10-30 }
    """
    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date_diff(date, cost_date) as m')
    self.assertEqual([(21,)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date_diff(cost_date, date) as m')
    self.assertEqual([(-21,)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date_add(date, 1) as m')
    self.assertEqual([(datetime.date(2016, 11, 21),)], rrows)

    rtypes, rrows = query.run_query(entries, options_map,
                                    'SELECT date_add(date, -1) as m')
    self.assertEqual([(datetime.date(2016, 11, 19),)], rrows)

beancount.query.query_env_test.TestEnv.test_GrepN(self, entries, _, options_map)

2016-11-20 * "prev match in context next" Assets:Banking 1 USD

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_GrepN(self, entries, _, options_map):
    """
    2016-11-20 * "prev match in context next"
      Assets:Banking          1 USD
    """
    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT GREPN("in", narration, 0) as m
    ''')
    self.assertEqual([('in',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT GREPN("match (.*) context", narration, 1) as m
    ''')
    self.assertEqual([('in',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT GREPN("(.*) in (.*)", narration, 2) as m
    ''')
    self.assertEqual([('context next',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT GREPN("ab(at)hing", "abathing", 1) as m
    ''')
    self.assertEqual([('at',)], rrows)

beancount.query.query_env_test.TestEnv.test_Subst(self, entries, _, options_map)

2016-11-20 * "I love candy" Assets:Banking -1 USD

2016-11-21 * "Buy thing thing" Assets:Cash -1 USD

Source code in beancount/query/query_env_test.py
@parser.parse_doc()
def test_Subst(self, entries, _, options_map):
    """
    2016-11-20 * "I love candy"
      Assets:Banking       -1 USD

    2016-11-21 * "Buy thing thing"
      Assets:Cash          -1 USD
    """
    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT SUBST("[Cc]andy", "carrots", narration) as m where date = 2016-11-20
    ''')
    self.assertEqual([('I love carrots',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT SUBST("thing", "t", narration) as m where date = 2016-11-21
    ''')
    self.assertEqual([('Buy t t',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT SUBST("random", "t", narration) as m where date = 2016-11-21
    ''')
    self.assertEqual([('Buy thing thing',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT SUBST("(love)", "\\1 \\1", narration) as m where date = 2016-11-20
    ''')
    self.assertEqual([('I love love candy',)], rrows)

    rtypes, rrows = query.run_query(entries, options_map, '''
      SELECT SUBST("Assets:.*", "Savings", account) as a, str(sum(position)) as p
    ''')
    self.assertEqual([('Savings', '(-2 USD)')], rrows)

beancount.query.query_execute

Execution of interpreter on data rows.

beancount.query.query_execute.Allocator

A helper class to count slot allocations and return unique handles to them.

beancount.query.query_execute.Allocator.allocate(self)

Allocate a new slot to store row aggregation information.

Returns:
  • A unique handle used to index into an row-aggregation store (an integer).

Source code in beancount/query/query_execute.py
def allocate(self):
    """Allocate a new slot to store row aggregation information.

    Returns:
      A unique handle used to index into an row-aggregation store (an integer).
    """
    handle = self.size
    self.size += 1
    return handle

beancount.query.query_execute.Allocator.create_store(self)

Create a new row-aggregation store suitable to contain all the node allocations.

Returns:
  • A store that can accommodate and be indexed by all the allocated slot handles.

Source code in beancount/query/query_execute.py
def create_store(self):
    """Create a new row-aggregation store suitable to contain all the node allocations.

    Returns:
      A store that can accommodate and be indexed by all the allocated slot handles.
    """
    return [None] * self.size

beancount.query.query_execute.RowContext

A dumb container for information used by a row expression.

beancount.query.query_execute.create_row_context(entries, options_map)

Create the context container which we will use to evaluate rows.

Source code in beancount/query/query_execute.py
def create_row_context(entries, options_map):
    """Create the context container which we will use to evaluate rows."""
    context = RowContext()
    context.balance = inventory.Inventory()

    # Initialize some global properties for use by some of the accessors.
    context.options_map = options_map
    context.account_types = options.get_account_types(options_map)
    context.open_close_map = getters.get_account_open_close(entries)
    context.commodity_map = getters.get_commodity_map(entries)
    context.price_map = prices.build_price_map(entries)

    return context

beancount.query.query_execute.execute_print(c_print, entries, options_map, file)

Print entries from a print statement specification.

Parameters:
  • c_print – An instance of a compiled EvalPrint statement.

  • entries – A list of directives.

  • options_map – A parser's option_map.

  • file – The output file to print to.

Source code in beancount/query/query_execute.py
def execute_print(c_print, entries, options_map, file):
    """Print entries from a print statement specification.

    Args:
      c_print: An instance of a compiled EvalPrint statement.
      entries: A list of directives.
      options_map: A parser's option_map.
      file: The output file to print to.
    """
    if c_print and c_print.c_from is not None:
        context = create_row_context(entries, options_map)
        entries = filter_entries(c_print.c_from, entries, options_map, context)

    # Create a context that renders all numbers with their natural
    # precision, but honors the commas option. This is kept in sync with
    # {2c694afe3140} to avoid a dependency.
    dcontext = display_context.DisplayContext()
    dcontext.set_commas(options_map['dcontext'].commas)
    printer.print_entries(entries, dcontext, file=file)

beancount.query.query_execute.execute_query(query, entries, options_map)

Given a compiled select statement, execute the query.

Parameters:
  • query – An instance of a query_compile.Query

  • entries – A list of directives.

  • options_map – A parser's option_map.

Returns:
  • A pair of – result_types: A list of (name, data-type) item pairs. result_rows: A list of ResultRow tuples of length and types described by 'result_types'.

Source code in beancount/query/query_execute.py
def execute_query(query, entries, options_map):
    """Given a compiled select statement, execute the query.

    Args:
      query: An instance of a query_compile.Query
      entries: A list of directives.
      options_map: A parser's option_map.
    Returns:
      A pair of:
        result_types: A list of (name, data-type) item pairs.
        result_rows: A list of ResultRow tuples of length and types described by
          'result_types'.
    """
    # Figure out the result types that describe what we return.
    result_types = [(target.name, target.c_expr.dtype)
                    for target in query.c_targets
                    if target.name is not None]

    # Create a class for each final result.
    # pylint: disable=invalid-name
    ResultRow = collections.namedtuple('ResultRow',
                                       [target.name
                                        for target in query.c_targets
                                        if target.name is not None])

    # Pre-compute lists of the expressions to evaluate.
    group_indexes = (set(query.group_indexes)
                     if query.group_indexes is not None
                     else query.group_indexes)

    # Indexes of the columns for result rows and order rows.
    result_indexes = [index
                      for index, c_target in enumerate(query.c_targets)
                      if c_target.name]
    order_indexes = query.order_indexes

    # Figure out if we need to compute balance.
    uses_balance = any(uses_balance_column(c_expr)
                       for c_expr in itertools.chain(
                               [c_target.c_expr for c_target in query.c_targets],
                               [query.c_where] if query.c_where else []))

    context = create_row_context(entries, options_map)

    # Filter the entries using the FROM clause.
    filt_entries = (filter_entries(query.c_from, entries, options_map, context)
                    if query.c_from is not None else
                    entries)

    # Dispatch between the non-aggregated queries and aggregated queries.
    c_where = query.c_where
    schwartz_rows = []

    # Precompute a list of expressions to be evaluated.
    c_target_exprs = [c_target.c_expr for c_target in query.c_targets]

    if query.group_indexes is None:
        # This is a non-aggregated query.

        # Iterate over all the postings once and produce schwartzian rows.
        for entry in misc_utils.filter_type(filt_entries, data.Transaction):
            context.entry = entry
            for posting in entry.postings:
                context.posting = posting
                if c_where is None or c_where(context):
                    # Compute the balance.
                    if uses_balance:
                        context.balance.add_position(posting)

                    # Evaluate all the values.
                    values = [c_expr(context) for c_expr in c_target_exprs]

                    # Compute result and sort-key objects.
                    result = ResultRow._make(values[index]
                                             for index in result_indexes)
                    sortkey = row_sortkey(order_indexes, values, c_target_exprs)
                    schwartz_rows.append((sortkey, result))
    else:
        # This is an aggregated query.

        # Precompute lists of non-aggregate and aggregate expressions to
        # evaluate. For aggregate targets, we hunt down the aggregate
        # sub-expressions to evaluate, to avoid recursion during iteration.
        c_nonaggregate_exprs = []
        c_aggregate_exprs = []
        for index, c_expr in enumerate(c_target_exprs):
            if index in group_indexes:
                c_nonaggregate_exprs.append(c_expr)
            else:
                _, aggregate_exprs = query_compile.get_columns_and_aggregates(c_expr)
                c_aggregate_exprs.extend(aggregate_exprs)
        # Note: it is possible that there are no aggregates to compute here. You could
        # have all columns be non-aggregates and group-by the entire list of columns.

        # Pre-allocate handles in aggregation nodes.
        allocator = Allocator()
        for c_expr in c_aggregate_exprs:
            c_expr.allocate(allocator)

        # Iterate over all the postings to evaluate the aggregates.
        agg_store = {}
        for entry in misc_utils.filter_type(filt_entries, data.Transaction):
            context.entry = entry
            for posting in entry.postings:
                context.posting = posting
                if c_where is None or c_where(context):
                    # Compute the balance.
                    if uses_balance:
                        context.balance.add_position(posting)

                    # Compute the non-aggregate expressions.
                    row_key = tuple(c_expr(context)
                                    for c_expr in c_nonaggregate_exprs)

                    # Get an appropriate store for the unique key of this row.
                    try:
                        store = agg_store[row_key]
                    except KeyError:
                        # This is a row; create a new store.
                        store = allocator.create_store()
                        for c_expr in c_aggregate_exprs:
                            c_expr.initialize(store)
                        agg_store[row_key] = store

                    # Update the aggregate expressions.
                    for c_expr in c_aggregate_exprs:
                        c_expr.update(store, context)

        # Iterate over all the aggregations to produce the schwartzian rows.
        for key, store in agg_store.items():
            key_iter = iter(key)
            values = []

            # Finalize the store.
            for c_expr in c_aggregate_exprs:
                c_expr.finalize(store)
            context.store = store

            for index, c_expr in enumerate(c_target_exprs):
                if index in group_indexes:
                    value = next(key_iter)
                else:
                    value = c_expr(context)
                values.append(value)

            # Compute result and sort-key objects.
            result = ResultRow._make(values[index]
                                     for index in result_indexes)
            sortkey = row_sortkey(order_indexes, values, c_target_exprs)
            schwartz_rows.append((sortkey, result))

    # Order results if requested.
    if order_indexes is not None:
        schwartz_rows.sort(key=operator.itemgetter(0),
                           reverse=(query.ordering == 'DESC'))

    # Extract final results, in sorted order at this point.
    result_rows = [x[1] for x in schwartz_rows]

    # Apply distinct.
    if query.distinct:
        result_rows = list(misc_utils.uniquify(result_rows))

    # Apply limit.
    if query.limit is not None:
        result_rows = result_rows[:query.limit]

    # Flatten inventories if requested.
    if query.flatten:
        result_types, result_rows = flatten_results(result_types, result_rows)

    return (result_types, result_rows)

beancount.query.query_execute.filter_entries(c_from, entries, options_map, context)

Filter the entries by the given compiled FROM clause.

Parameters:
  • c_from – A compiled From clause instance.

  • entries – A list of directives.

  • options_map – A parser's option_map.

  • context – A prototype of RowContext to use for evaluation.

Returns:
  • A list of filtered entries.

Source code in beancount/query/query_execute.py
def filter_entries(c_from, entries, options_map, context):
    """Filter the entries by the given compiled FROM clause.

    Args:
      c_from: A compiled From clause instance.
      entries: A list of directives.
      options_map: A parser's option_map.
      context: A prototype of RowContext to use for evaluation.
    Returns:
      A list of filtered entries.
    """
    assert c_from is None or isinstance(c_from, query_compile.EvalFrom)
    assert isinstance(entries, list)

    context = copy.copy(context)

    if c_from is None:
        return entries

    # Process the OPEN clause.
    if c_from.open is not None:
        assert isinstance(c_from.open, datetime.date)
        open_date = c_from.open
        entries, index = summarize.open_opt(entries, open_date, options_map)

    # Process the CLOSE clause.
    if c_from.close is not None:
        if isinstance(c_from.close, datetime.date):
            close_date = c_from.close
            entries, index = summarize.close_opt(entries, close_date, options_map)
        elif c_from.close is True:
            entries, index = summarize.close_opt(entries, None, options_map)

    # Process the CLEAR clause.
    if c_from.clear is not None:
        entries, index = summarize.clear_opt(entries, None, options_map)

    # Filter the entries with the FROM clause's expression.
    c_expr = c_from.c_expr
    if c_expr is not None:
        # A simple function receives a context; how come close_date() is
        # accepted in the context of a FROM clause? It shouldn't be.
        new_entries = []
        for entry in entries:
            context.entry = entry
            if c_expr(context):
                new_entries.append(entry)
        entries = new_entries

    return entries

beancount.query.query_execute.flatten_results(result_types, result_rows)

Convert inventories in result types to have a row for each.

This routine will expand all result lines with an inventory into a new line for each position.

Parameters:
  • result_types – A list of (name, data-type) item pairs.

  • result_rows – A list of ResultRow tuples of length and types described by 'result_types'.

Returns:
  • result_types – A list of (name, data-type) item pairs. There should be no Inventory types anymore. result_rows: A list of ResultRow tuples of length and types described by 'result_types'. All inventories from the input should have been converted to Position types.

Source code in beancount/query/query_execute.py
def flatten_results(result_types, result_rows):
    """Convert inventories in result types to have a row for each.

    This routine will expand all result lines with an inventory into a new line
    for each position.

    Args:
        result_types: A list of (name, data-type) item pairs.
        result_rows: A list of ResultRow tuples of length and types described by
          'result_types'.
    Returns:
        result_types: A list of (name, data-type) item pairs. There should be no
          Inventory types anymore.
        result_rows: A list of ResultRow tuples of length and types described by
          'result_types'. All inventories from the input should have been converted
          to Position types.
    """
    indexes = set(index
                  for index, (name, result_type) in enumerate(result_types)
                  if result_type is inventory.Inventory)
    if not indexes:
        return (result_types, result_rows)

    # pylint: disable=invalid-name
    ResultRow = type(result_rows[0])

    # We have to make at least some conversions.
    num_columns = len(result_types)
    output_rows = []
    for result_row in result_rows:
        max_rows = max(len(result_row[icol]) for icol in indexes)
        for irow in range(max_rows):
            output_row = []
            for icol in range(num_columns):
                value = result_row[icol]
                if icol in indexes:
                    value = value[irow] if irow < len(value) else None
                output_row.append(value)
            output_rows.append(ResultRow._make(output_row))

    # Convert the types.
    output_types = [(name, (position.Position
                            if result_type is inventory.Inventory
                            else result_type))
                    for name, result_type in result_types]

    return output_types, output_rows

beancount.query.query_execute.row_sortkey(order_indexes, values, c_exprs)

Generate a sortkey for the given values.

Parameters:
  • order_indexes – The indexes by which the rows should be sorted.

  • values – The computed values in the row.

  • c_exprs – The matching c_expr's.

Returns:
  • A tuple, the sortkey.

Source code in beancount/query/query_execute.py
def row_sortkey(order_indexes, values, c_exprs):
    """Generate a sortkey for the given values.

    Args:
      order_indexes: The indexes by which the rows should be sorted.
      values: The computed values in the row.
      c_exprs: The matching c_expr's.
    Returns:
      A tuple, the sortkey.
    """
    if order_indexes is None:
        return None
    key = []
    for index in order_indexes:
        value = values[index]
        key.append(_MIN_VALUES.get(c_exprs[index].dtype, None)
                   if value is None
                   else value)
    return tuple(key)

beancount.query.query_execute.uses_balance_column(c_expr)

Return true if the expression accesses the special 'balance' column.

Parameters:
  • c_expr – A compiled expression tree (an EvalNode node).

Returns:
  • A boolean, true if the expression contains a BalanceColumn node.

Source code in beancount/query/query_execute.py
def uses_balance_column(c_expr):
    """Return true if the expression accesses the special 'balance' column.

    Args:
      c_expr: A compiled expression tree (an EvalNode node).
    Returns:
      A boolean, true if the expression contains a BalanceColumn node.
    """
    return (isinstance(c_expr, query_env.BalanceColumn) or
            any(uses_balance_column(c_node) for c_node in c_expr.childnodes()))

beancount.query.query_execute_test

beancount.query.query_execute_test.QueryBase (TestCase)

beancount.query.query_execute_test.QueryBase.compile(self, bql_string)

Parse a query and compile it.

Parameters:
  • bql_string – An SQL query to be parsed.

Returns:
  • A compiled EvalQuery node.

Source code in beancount/query/query_execute_test.py
def compile(self, bql_string):
    """Parse a query and compile it.

    Args:
      bql_string: An SQL query to be parsed.
    Returns:
      A compiled EvalQuery node.
    """
    return qc.compile_select(self.parse(bql_string),
                             self.xcontext_targets,
                             self.xcontext_postings,
                             self.xcontext_entries)

beancount.query.query_execute_test.QueryBase.parse(self, bql_string)

Parse a query.

Parameters:
  • bql_string – An SQL query to be parsed.

Returns:
  • A parsed statement (Select() node).

Source code in beancount/query/query_execute_test.py
def parse(self, bql_string):
    """Parse a query.

    Args:
      bql_string: An SQL query to be parsed.
    Returns:
      A parsed statement (Select() node).
    """
    return self.parser.parse(bql_string.strip())

beancount.query.query_execute_test.QueryBase.setUp(self)

Hook method for setting up the test fixture before exercising it.

Source code in beancount/query/query_execute_test.py
def setUp(self):
    super().setUp()
    self.parser = query_parser.Parser()

beancount.query.query_parser

Parser for Beancount Query Language.

beancount.query.query_parser.Balances (tuple)

Balances(summary_func, from_clause, where_clause)

beancount.query.query_parser.Balances.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Balances.__new__(_cls, summary_func, from_clause, where_clause) special staticmethod

Create new instance of Balances(summary_func, from_clause, where_clause)

beancount.query.query_parser.Balances.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Balances._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Balances._make(iterable) classmethod private

Make a new Balances object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Balances._replace(/, self, **kwds) private

Return a new Balances object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Errors (tuple)

Errors()

beancount.query.query_parser.Errors.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Errors.__new__(_cls) special staticmethod

Create new instance of Errors()

beancount.query.query_parser.Errors.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Errors._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Errors._make(iterable) classmethod private

Make a new Errors object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Errors._replace(/, self, **kwds) private

Return a new Errors object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Explain (tuple)

Explain(statement,)

beancount.query.query_parser.Explain.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Explain.__new__(_cls, statement) special staticmethod

Create new instance of Explain(statement,)

beancount.query.query_parser.Explain.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Explain._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Explain._make(iterable) classmethod private

Make a new Explain object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Explain._replace(/, self, **kwds) private

Return a new Explain object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Journal (tuple)

Journal(account, summary_func, from_clause)

beancount.query.query_parser.Journal.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Journal.__new__(_cls, account, summary_func, from_clause) special staticmethod

Create new instance of Journal(account, summary_func, from_clause)

beancount.query.query_parser.Journal.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Journal._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Journal._make(iterable) classmethod private

Make a new Journal object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Journal._replace(/, self, **kwds) private

Return a new Journal object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Lexer

PLY lexer for the Beancount Query Language.

beancount.query.query_parser.Lexer.t_DATE(self, token)

(#(\"[^\"]\"|\'[^\']\')|\d\d\d\d-\d\d-\d\d)

Source code in beancount/query/query_parser.py
def t_DATE(self, token):
    r"(\#(\"[^\"]*\"|\'[^\']*\')|\d\d\d\d-\d\d-\d\d)"
    if token.value[0] == '#':
        token.value = dateutil.parser.parse(token.value[2:-1]).date()
    else:
        token.value = datetime.datetime.strptime(token.value, '%Y-%m-%d').date()
    return token

beancount.query.query_parser.Lexer.t_DECIMAL(self, token)

[-+]?([0-9]+.[0-9]|[0-9].[0-9]+)

Source code in beancount/query/query_parser.py
def t_DECIMAL(self, token):
    r"[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)"
    token.value = D(token.value)
    return token

beancount.query.query_parser.Lexer.t_ID(self, token)

[a-zA-Z][a-zA-Z_]*

Source code in beancount/query/query_parser.py
def t_ID(self, token):
    "[a-zA-Z][a-zA-Z_]*"
    utoken = token.value.upper()
    if utoken in self.keywords:
        token.type = utoken
        token.value = token.value.upper()
    else:
        token.value = token.value.lower()
    return token

beancount.query.query_parser.Lexer.t_INTEGER(self, token)

[-+]?[0-9]+

Source code in beancount/query/query_parser.py
def t_INTEGER(self, token):
    r"[-+]?[0-9]+"
    token.value = int(token.value)
    return token

beancount.query.query_parser.Lexer.t_STRING(self, token)

("[^"]"|'[^']')

Source code in beancount/query/query_parser.py
def t_STRING(self, token):
    "(\"[^\"]*\"|\'[^\']*\')"
    token.value = token.value[1:-1]
    return token

beancount.query.query_parser.ParseError (Exception)

A parser error.

beancount.query.query_parser.Parser (SelectParser)

PLY parser for the Beancount Query Language's full command syntax.

beancount.query.query_parser.Parser.p_balances_statement(self, p)

balances_statement : BALANCES summary_func from where

Source code in beancount/query/query_parser.py
def p_balances_statement(self, p):
    """
    balances_statement : BALANCES summary_func from where
    """
    p[0] = Balances(p[2], p[3], p[4])

beancount.query.query_parser.Parser.p_delimiter(self, p)

delimiter : SEMI | empty

Source code in beancount/query/query_parser.py
def p_delimiter(self, p):
    """
    delimiter : SEMI
              | empty
    """

beancount.query.query_parser.Parser.p_errors_statement(self, p)

errors_statement : ERRORS

Source code in beancount/query/query_parser.py
def p_errors_statement(self, p):
    """
    errors_statement : ERRORS
    """
    p[0] = Errors()

beancount.query.query_parser.Parser.p_explain_statement(self, p)

top_statement : EXPLAIN statement delimiter

Source code in beancount/query/query_parser.py
def p_explain_statement(self, p):
    "top_statement : EXPLAIN statement delimiter"
    p[0] = Explain(p[2])

beancount.query.query_parser.Parser.p_journal_statement(self, p)

journal_statement : JOURNAL summary_func from | JOURNAL account summary_func from

Source code in beancount/query/query_parser.py
def p_journal_statement(self, p):
    """
    journal_statement : JOURNAL summary_func from
                      | JOURNAL account summary_func from
    """
    p[0] = Journal(None, p[2], p[3]) if len(p) == 4 else Journal(p[2], p[3], p[4])

beancount.query.query_parser.Parser.p_print_statement(self, p)

print_statement : PRINT from

Source code in beancount/query/query_parser.py
def p_print_statement(self, p):
    """
    print_statement : PRINT from
    """
    p[0] = Print(p[2])

beancount.query.query_parser.Parser.p_regular_statement(self, p)

top_statement : statement delimiter

Source code in beancount/query/query_parser.py
def p_regular_statement(self, p):
    "top_statement : statement delimiter"
    p[0] = p[1]

beancount.query.query_parser.Parser.p_reload_statement(self, p)

reload_statement : RELOAD

Source code in beancount/query/query_parser.py
def p_reload_statement(self, p):
    """
    reload_statement : RELOAD
    """
    p[0] = Reload()

beancount.query.query_parser.Parser.p_run_statement(self, p)

run_statement : RUN ID | RUN STRING | RUN ASTERISK | RUN empty

Source code in beancount/query/query_parser.py
def p_run_statement(self, p):
    """
    run_statement : RUN ID
                  | RUN STRING
                  | RUN ASTERISK
                  | RUN empty
    """
    p[0] = RunCustom(p[2])

beancount.query.query_parser.Parser.p_statement(self, p)

statement : select_statement | balances_statement | journal_statement | print_statement | run_statement | errors_statement | reload_statement

Source code in beancount/query/query_parser.py
def p_statement(self, p):
    """
    statement : select_statement
              | balances_statement
              | journal_statement
              | print_statement
              | run_statement
              | errors_statement
              | reload_statement
    """
    p[0] = p[1]

beancount.query.query_parser.Parser.p_summary_func(self, p)

summary_func : empty | AT ID

Source code in beancount/query/query_parser.py
def p_summary_func(self, p):
    """
    summary_func : empty
                 | AT ID
    """
    p[0] = p[2] if len(p) == 3 else None

beancount.query.query_parser.Print (tuple)

Print(from_clause,)

beancount.query.query_parser.Print.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Print.__new__(_cls, from_clause) special staticmethod

Create new instance of Print(from_clause,)

beancount.query.query_parser.Print.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Print._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Print._make(iterable) classmethod private

Make a new Print object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Print._replace(/, self, **kwds) private

Return a new Print object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Reload (tuple)

Reload()

beancount.query.query_parser.Reload.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Reload.__new__(_cls) special staticmethod

Create new instance of Reload()

beancount.query.query_parser.Reload.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Reload._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Reload._make(iterable) classmethod private

Make a new Reload object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Reload._replace(/, self, **kwds) private

Return a new Reload object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.RunCustom (tuple)

RunCustom(query_name,)

beancount.query.query_parser.RunCustom.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.RunCustom.__new__(_cls, query_name) special staticmethod

Create new instance of RunCustom(query_name,)

beancount.query.query_parser.RunCustom.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.RunCustom._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.RunCustom._make(iterable) classmethod private

Make a new RunCustom object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.RunCustom._replace(/, self, **kwds) private

Return a new RunCustom object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.Select (tuple)

Select(targets, from_clause, where_clause, group_by, order_by, pivot_by, limit, distinct, flatten)

beancount.query.query_parser.Select.__getnewargs__(self) special

Return self as a plain tuple. Used by copy and pickle.

Source code in beancount/query/query_parser.py
def __getnewargs__(self):
    'Return self as a plain tuple.  Used by copy and pickle.'
    return _tuple(self)

beancount.query.query_parser.Select.__new__(_cls, targets, from_clause, where_clause, group_by, order_by, pivot_by, limit, distinct, flatten) special staticmethod

Create new instance of Select(targets, from_clause, where_clause, group_by, order_by, pivot_by, limit, distinct, flatten)

beancount.query.query_parser.Select.__repr__(self) special

Return a nicely formatted representation string

Source code in beancount/query/query_parser.py
def __repr__(self):
    'Return a nicely formatted representation string'
    return self.__class__.__name__ + repr_fmt % self

beancount.query.query_parser.Select._asdict(self) private

Return a new dict which maps field names to their values.

Source code in beancount/query/query_parser.py
def _asdict(self):
    'Return a new dict which maps field names to their values.'
    return _dict(_zip(self._fields, self))

beancount.query.query_parser.Select._make(iterable) classmethod private

Make a new Select object from a sequence or iterable

Source code in beancount/query/query_parser.py
@classmethod
def _make(cls, iterable):
    result = tuple_new(cls, iterable)
    if _len(result) != num_fields:
        raise TypeError(f'Expected {num_fields} arguments, got {len(result)}')
    return result

beancount.query.query_parser.Select._replace(/, self, **kwds) private

Return a new Select object replacing specified fields with new values

Source code in beancount/query/query_parser.py
def _replace(self, /, **kwds):
    result = self._make(_map(kwds.pop, field_names, self))
    if kwds:
        raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
    return result

beancount.query.query_parser.SelectParser (Lexer)

PLY parser for the Beancount Query Language's SELECT statement.

beancount.query.query_parser.SelectParser.handle_comma_separated_list(self, p)

Handle a list of 0, 1 or more comma-separated values.

Parameters:
  • p – A grammar object.

Source code in beancount/query/query_parser.py
def handle_comma_separated_list(self, p):
    """Handle a list of 0, 1 or more comma-separated values.
    Args:
      p: A grammar object.
    """
    if len(p) == 2:
        return [] if p[1] is None else [p[1]]
    else:
        return p[1] + [p[3]]

beancount.query.query_parser.SelectParser.p_account(self, p)

account : STRING

Source code in beancount/query/query_parser.py
def p_account(self, p):
    """
    account : STRING
    """
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_boolean(self, p)

boolean : TRUE | FALSE

Source code in beancount/query/query_parser.py
def p_boolean(self, p):
    """
    boolean : TRUE
            | FALSE
    """
    p[0] = (p[1] == 'TRUE')

beancount.query.query_parser.SelectParser.p_column(self, p)

column : ID

Source code in beancount/query/query_parser.py
def p_column(self, p):
    """
    column : ID
    """
    p[0] = Column(p[1])

beancount.query.query_parser.SelectParser.p_column_list(self, p)

column_list : column | column_list COMMA column

Source code in beancount/query/query_parser.py
def p_column_list(self, p):
    """
    column_list : column
                | column_list COMMA column
    """
    p[0] = self.handle_comma_separated_list(p)

beancount.query.query_parser.SelectParser.p_constant(self, p)

constant : NULL | boolean | INTEGER | DECIMAL | STRING | DATE

Source code in beancount/query/query_parser.py
def p_constant(self, p):
    """
    constant : NULL
             | boolean
             | INTEGER
             | DECIMAL
             | STRING
             | DATE
    """
    p[0] = Constant(p[1] if p[1] != 'NULL' else None)

beancount.query.query_parser.SelectParser.p_distinct(self, p)

distinct : empty | DISTINCT

Source code in beancount/query/query_parser.py
def p_distinct(self, p):
    """
    distinct : empty
             | DISTINCT
    """
    p[0] = True if p[1] == 'DISTINCT' else None

beancount.query.query_parser.SelectParser.p_empty(self, _)

empty :

Source code in beancount/query/query_parser.py
def p_empty(self, _):
    """
    empty :
    """

beancount.query.query_parser.SelectParser.p_expr_index(self, p)

expr_index : expression | INTEGER

Source code in beancount/query/query_parser.py
def p_expr_index(self, p):
    """
    expr_index : expression
               | INTEGER
    """
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_expr_index_list(self, p)

expr_index_list : expr_index | expr_index_list COMMA expr_index

Source code in beancount/query/query_parser.py
def p_expr_index_list(self, p):
    """
    expr_index_list : expr_index
                    | expr_index_list COMMA expr_index
    """
    p[0] = self.handle_comma_separated_list(p)

beancount.query.query_parser.SelectParser.p_expression_add(self, p)

expression : expression PLUS expression

Source code in beancount/query/query_parser.py
def p_expression_add(self, p):
    "expression : expression PLUS expression"
    p[0] = Add(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_and(self, p)

expression : expression AND expression

Source code in beancount/query/query_parser.py
def p_expression_and(self, p):
    "expression : expression AND expression"
    p[0] = And(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_column(self, p)

expression : column

Source code in beancount/query/query_parser.py
def p_expression_column(self, p):
    "expression : column"
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_expression_constant(self, p)

expression : constant

Source code in beancount/query/query_parser.py
def p_expression_constant(self, p):
    "expression : constant"
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_expression_contains(self, p)

expression : expression IN expression

Source code in beancount/query/query_parser.py
def p_expression_contains(self, p):
    "expression : expression IN expression"
    p[0] = Contains(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_div(self, p)

expression : expression SLASH expression

Source code in beancount/query/query_parser.py
def p_expression_div(self, p):
    "expression : expression SLASH expression"
    p[0] = Div(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_eq(self, p)

expression : expression EQ expression

Source code in beancount/query/query_parser.py
def p_expression_eq(self, p):
    "expression : expression EQ expression"
    p[0] = Equal(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_function(self, p)

expression : ID LPAREN expression_list_opt RPAREN

Source code in beancount/query/query_parser.py
def p_expression_function(self, p):
    "expression : ID LPAREN expression_list_opt RPAREN"
    p[0] = Function(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_gt(self, p)

expression : expression GT expression

Source code in beancount/query/query_parser.py
def p_expression_gt(self, p):
    "expression : expression GT expression"
    p[0] = Greater(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_gte(self, p)

expression : expression GTE expression

Source code in beancount/query/query_parser.py
def p_expression_gte(self, p):
    "expression : expression GTE expression"
    p[0] = GreaterEq(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_list(self, p)

expression_list : expression | expression_list COMMA expression

Source code in beancount/query/query_parser.py
def p_expression_list(self, p):
    """
    expression_list : expression
                    | expression_list COMMA expression
    """
    p[0] = self.handle_comma_separated_list(p)

beancount.query.query_parser.SelectParser.p_expression_list_opt(self, p)

expression_list_opt : empty | expression | expression_list COMMA expression

Source code in beancount/query/query_parser.py
def p_expression_list_opt(self, p):
    """
    expression_list_opt : empty
                        | expression
                        | expression_list COMMA expression
    """
    p[0] = self.handle_comma_separated_list(p)

beancount.query.query_parser.SelectParser.p_expression_lt(self, p)

expression : expression LT expression

Source code in beancount/query/query_parser.py
def p_expression_lt(self, p):
    "expression : expression LT expression"
    p[0] = Less(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_lte(self, p)

expression : expression LTE expression

Source code in beancount/query/query_parser.py
def p_expression_lte(self, p):
    "expression : expression LTE expression"
    p[0] = LessEq(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_match(self, p)

expression : expression TILDE expression

Source code in beancount/query/query_parser.py
def p_expression_match(self, p):
    "expression : expression TILDE expression"
    p[0] = Match(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_mul(self, p)

expression : expression ASTERISK expression

Source code in beancount/query/query_parser.py
def p_expression_mul(self, p):
    "expression : expression ASTERISK expression"
    p[0] = Mul(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_ne(self, p)

expression : expression NE expression

Source code in beancount/query/query_parser.py
def p_expression_ne(self, p):
    "expression : expression NE expression"
    p[0] = Not(Equal(p[1], p[3]))

beancount.query.query_parser.SelectParser.p_expression_not(self, p)

expression : NOT expression

Source code in beancount/query/query_parser.py
def p_expression_not(self, p):
    "expression : NOT expression"
    p[0] = Not(p[2])

beancount.query.query_parser.SelectParser.p_expression_or(self, p)

expression : expression OR expression

Source code in beancount/query/query_parser.py
def p_expression_or(self, p):
    "expression : expression OR expression"
    p[0] = Or(p[1], p[3])

beancount.query.query_parser.SelectParser.p_expression_paren(self, p)

expression : LPAREN expression RPAREN

Source code in beancount/query/query_parser.py
def p_expression_paren(self, p):
    "expression : LPAREN expression RPAREN"
    p[0] = p[2]

beancount.query.query_parser.SelectParser.p_expression_sub(self, p)

expression : expression MINUS expression

Source code in beancount/query/query_parser.py
def p_expression_sub(self, p):
    "expression : expression MINUS expression"
    p[0] = Sub(p[1], p[3])

beancount.query.query_parser.SelectParser.p_flatten(self, p)

flatten : empty | FLATTEN

Source code in beancount/query/query_parser.py
def p_flatten(self, p):
    """
    flatten : empty
            | FLATTEN
    """
    p[0] = True if p[1] == 'FLATTEN' else None

beancount.query.query_parser.SelectParser.p_from(self, p)

from : empty | FROM opt_expression opt_open opt_close opt_clear

Source code in beancount/query/query_parser.py
def p_from(self, p):
    """
    from : empty
         | FROM opt_expression opt_open opt_close opt_clear
    """
    if len(p) != 2:
        if all(p[i] is None for i in range(2, 6)):
            raise ParseError("Empty FROM expression is not allowed")
        p[0] = From(p[2], p[3], p[4], p[5])
    else:
        p[0] = None

beancount.query.query_parser.SelectParser.p_from_subselect(self, p)

from_subselect : from | FROM LPAREN select_statement RPAREN

Source code in beancount/query/query_parser.py
def p_from_subselect(self, p):
    """
    from_subselect : from
                   | FROM LPAREN select_statement RPAREN
    """
    if len(p) == 2:
        p[0] = p[1]
    else:
        p[0] = p[3]

beancount.query.query_parser.SelectParser.p_group_by(self, p)

group_by : empty | GROUP BY expr_index_list having

Source code in beancount/query/query_parser.py
def p_group_by(self, p):
    """
    group_by : empty
             | GROUP BY expr_index_list having
    """
    p[0] = GroupBy(p[3], p[4]) if len(p) != 2 else None

beancount.query.query_parser.SelectParser.p_having(self, p)

having : empty | HAVING expression

Source code in beancount/query/query_parser.py
def p_having(self, p):
    """
    having : empty
           | HAVING expression
    """
    p[0] = p[2] if len(p) == 3 else None

beancount.query.query_parser.SelectParser.p_limit(self, p)

limit : empty | LIMIT INTEGER

Source code in beancount/query/query_parser.py
def p_limit(self, p):
    """
    limit : empty
          | LIMIT INTEGER
    """
    p[0] = p[2] if len(p) == 3 else None

beancount.query.query_parser.SelectParser.p_opt_clear(self, p)

opt_clear : empty | CLEAR

Source code in beancount/query/query_parser.py
def p_opt_clear(self, p):
    """
    opt_clear : empty
              | CLEAR
    """
    p[0] = True if (p[1] == 'CLEAR') else None

beancount.query.query_parser.SelectParser.p_opt_close(self, p)

opt_close : empty | CLOSE | CLOSE ON DATE

Source code in beancount/query/query_parser.py
def p_opt_close(self, p):
    """
    opt_close : empty
              | CLOSE
              | CLOSE ON DATE
    """
    p[0] = p[3] if len(p) == 4 else (True
                                     if (p[1] == 'CLOSE') else
                                     self.default_close_date)

beancount.query.query_parser.SelectParser.p_opt_expression(self, p)

opt_expression : empty | expression

Source code in beancount/query/query_parser.py
def p_opt_expression(self, p):
    """
    opt_expression : empty
                   | expression
    """
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_opt_open(self, p)

opt_open : empty | OPEN ON DATE

Source code in beancount/query/query_parser.py
def p_opt_open(self, p):
    """
    opt_open : empty
             | OPEN ON DATE
    """
    p[0] = p[3] if len(p) == 4 else None

beancount.query.query_parser.SelectParser.p_order_by(self, p)

order_by : empty | ORDER BY expr_index_list ordering

Source code in beancount/query/query_parser.py
def p_order_by(self, p):
    """
    order_by : empty
             | ORDER BY expr_index_list ordering
    """
    p[0] = None if len(p) == 2 else OrderBy(p[3], p[4])

beancount.query.query_parser.SelectParser.p_ordering(self, p)

ordering : empty | ASC | DESC

Source code in beancount/query/query_parser.py
def p_ordering(self, p):
    """
    ordering : empty
             | ASC
             | DESC
    """
    p[0] = p[1]

beancount.query.query_parser.SelectParser.p_pivot_by(self, p)

pivot_by : empty | PIVOT BY column_list

Source code in beancount/query/query_parser.py
def p_pivot_by(self, p):
    """
    pivot_by : empty
             | PIVOT BY column_list
    """
    p[0] = PivotBy(p[3]) if len(p) == 4 else None

beancount.query.query_parser.SelectParser.p_select_statement(self, p)

select_statement : SELECT distinct target_spec from_subselect where group_by order_by pivot_by limit flatten

Source code in beancount/query/query_parser.py
def p_select_statement(self, p):
    """
    select_statement : SELECT distinct target_spec from_subselect where \
                       group_by order_by pivot_by limit flatten
    """
    p[0] = Select(p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[2], p[10])

beancount.query.query_parser.SelectParser.p_target(self, p)

target : expression AS ID | expression

Source code in beancount/query/query_parser.py
def p_target(self, p):
    """
    target : expression AS ID
           | expression
    """
    p[0] = Target(p[1], p[3] if len(p) == 4 else None)

beancount.query.query_parser.SelectParser.p_target_list(self, p)

target_list : target | target_list COMMA target

Source code in beancount/query/query_parser.py
def p_target_list(self, p):
    """
    target_list : target
                | target_list COMMA target
    """
    p[0] = self.handle_comma_separated_list(p)

beancount.query.query_parser.SelectParser.p_target_spec(self, p)

target_spec : ASTERISK | target_list

Source code in beancount/query/query_parser.py
def p_target_spec(self, p):
    """
    target_spec : ASTERISK
                | target_list
    """
    p[0] = Wildcard() if p[1] == '*' else p[1]

beancount.query.query_parser.SelectParser.p_where(self, p)

where : empty | WHERE expression

Source code in beancount/query/query_parser.py
def p_where(self, p):
    """
    where : empty
          | WHERE expression
    """
    if len(p) == 3:
        assert p[2], "Empty WHERE clause is not allowed"
        p[0] = p[2]

beancount.query.query_parser.get_expression_name(expr)

Come up with a reasonable identifier for an expression.

Parameters:
  • expr – An expression node.

Source code in beancount/query/query_parser.py
def get_expression_name(expr):
    """Come up with a reasonable identifier for an expression.

    Args:
      expr: An expression node.
    """
    if isinstance(expr, Column):
        return expr.name.lower()

    elif isinstance(expr, Function):
        names = [expr.fname.lower()]
        for operand in expr.operands:
            names.append(get_expression_name(operand))
        return '_'.join(names)

    elif isinstance(expr, Constant):
        return 'c{}'.format(re.sub('[^a-z0-9]+', '_', str(expr.value)))

    elif isinstance(expr, UnaryOp):
        return '_'.join([type(expr).__name__.lower(),
                         get_expression_name(expr.operand)])

    elif isinstance(expr, BinaryOp):
        return '_'.join([type(expr).__name__.lower(),
                         get_expression_name(expr.left),
                         get_expression_name(expr.right)])

    else:
        assert False, "Unknown expression type."

beancount.query.query_parser_test

beancount.query.query_parser_test.QueryParserTestBase (TestCase)

beancount.query.query_parser_test.QueryParserTestBase.assertParse(self, expected, query, debug=False)

Assert parsed contents from 'query' is 'expected'.

Parameters:
  • expected – An expected AST to compare against the parsed value.

  • query – An SQL query to be parsed.

  • debug – A boolean, if true, print extra debugging information on the console.

Exceptions:
  • AssertionError – If the actual AST does not match the expected one.

Source code in beancount/query/query_parser_test.py
def assertParse(self, expected, query, debug=False):
    """Assert parsed contents from 'query' is 'expected'.

    Args:
      expected: An expected AST to compare against the parsed value.
      query: An SQL query to be parsed.
      debug: A boolean, if true, print extra debugging information on the console.
    Raises:
      AssertionError: If the actual AST does not match the expected one.
    """
    actual = self.parse(query)
    if debug:
        print()
        print()
        print(actual)
        print()
    self.assertEqual(expected, actual)
    return actual

beancount.query.query_parser_test.QueryParserTestBase.parse(self, query)

Parse one query.

Parameters:
  • query – An SQL query to be parsed.

Returns:
  • The AST.

Source code in beancount/query/query_parser_test.py
def parse(self, query):
    """Parse one query.

    Args:
      query: An SQL query to be parsed.
    Returns:
      The AST.
    """
    return self.parser.parse(query.strip())

beancount.query.query_parser_test.QueryParserTestBase.setUp(self)

Hook method for setting up the test fixture before exercising it.

Source code in beancount/query/query_parser_test.py
def setUp(self):
    self.parser = qp.Parser()

beancount.query.query_parser_test.TestSelectFromBase (QueryParserTestBase)

beancount.query.query_parser_test.TestSelectFromBase.setUp(self)

Hook method for setting up the test fixture before exercising it.

Source code in beancount/query/query_parser_test.py
def setUp(self):
    super().setUp()
    self.targets = [qp.Target(qp.Column('a'), None),
                    qp.Target(qp.Column('b'), None)]
    self.expr = qp.Equal(qp.Column('d'),
                         qp.And(
                             qp.Function('max', [qp.Column('e')]),
                             qp.Constant(17)))

beancount.query.query_parser_test.qSelect(target_spec=None, from_clause=None, where_clause=None, group_by=None, order_by=None, pivot_by=None, limit=None, distinct=None, flatten=None)

A convenience constructor for writing tests without having to provide all arguments.

Source code in beancount/query/query_parser_test.py
def qSelect(target_spec=None,
            from_clause=None, where_clause=None,
            group_by=None, order_by=None, pivot_by=None,
            limit=None, distinct=None, flatten=None):
    "A convenience constructor for writing tests without having to provide all arguments."
    return qp.Select(target_spec,
                     from_clause, where_clause,
                     group_by, order_by, pivot_by,
                     limit, distinct, flatten)

beancount.query.shell

An interactive command-line shell interpreter for the Beancount Query Language.

beancount.query.shell.BQLShell (DispatchingShell)

An interactive shell interpreter for the Beancount query language.

beancount.query.shell.BQLShell.on_Balances(self, balance)

Select balances of some subset of postings. This command is a convenience and converts into an equivalent Select statement, designed to extract the most sensible list of columns for the register of a list of entries as a table.

The general form of a JOURNAL statement loosely follows SQL syntax:

BALANCE [FROM_CLAUSE]

See the SELECT query help for more details on the FROM clause.

Source code in beancount/query/shell.py
def on_Balances(self, balance):
    """
    Select balances of some subset of postings. This command is a
    convenience and converts into an equivalent Select statement, designed
    to extract the most sensible list of columns for the register of a list
    of entries as a table.

    The general form of a JOURNAL statement loosely follows SQL syntax:

       BALANCE [FROM_CLAUSE]

    See the SELECT query help for more details on the FROM clause.
    """
    return self.on_Select(balance)

beancount.query.shell.BQLShell.on_Errors(self, errors_statement)

Print the errors that occurred during parsing.

Source code in beancount/query/shell.py
def on_Errors(self, errors_statement):
    """
    Print the errors that occurred during parsing.
    """
    if self.errors:
        printer.print_errors(self.errors)
    else:
        print('(No errors)', file=self.outfile)

beancount.query.shell.BQLShell.on_Explain(self, explain)

Compile and print a compiled statement for debugging.

Source code in beancount/query/shell.py
def on_Explain(self, explain):
    """
    Compile and print a compiled statement for debugging.
    """
    # pylint: disable=invalid-name
    pr = lambda *args: print(*args, file=self.outfile)
    pr("Parsed statement:")
    pr("  {}".format(explain.statement))
    pr()

    # Compile the select statement and print it uot.
    try:
        query = query_compile.compile(explain.statement,
                                      self.env_targets,
                                      self.env_postings,
                                      self.env_entries)
    except query_compile.CompilationError as exc:
        pr(str(exc).rstrip('.'))
        return

    pr("Compiled query:")
    pr("  {}".format(query))
    pr()
    pr("Targets:")
    for c_target in query.c_targets:
        pr("  '{}'{}: {}".format(
            c_target.name or '(invisible)',
            ' (aggregate)' if query_compile.is_aggregate(c_target.c_expr) else '',
            c_target.c_expr.dtype.__name__))
    pr()

beancount.query.shell.BQLShell.on_Journal(self, journal)

Select a journal of some subset of postings. This command is a convenience and converts into an equivalent Select statement, designed to extract the most sensible list of columns for the register of a list of entries as a table.

The general form of a JOURNAL statement loosely follows SQL syntax:

JOURNAL <account-regexp> [FROM_CLAUSE]

See the SELECT query help for more details on the FROM clause.

Source code in beancount/query/shell.py
def on_Journal(self, journal):
    """
    Select a journal of some subset of postings. This command is a
    convenience and converts into an equivalent Select statement, designed
    to extract the most sensible list of columns for the register of a list
    of entries as a table.

    The general form of a JOURNAL statement loosely follows SQL syntax:

       JOURNAL <account-regexp> [FROM_CLAUSE]

    See the SELECT query help for more details on the FROM clause.
    """
    return self.on_Select(journal)

beancount.query.shell.BQLShell.on_Print(self, print_stmt)

Print entries in Beancount format.

The general form of a PRINT statement includes an SQL-like FROM selector:

PRINT [FROM <from_expr> ...]

Where:

from_expr: A logical expression that matches on the attributes of the directives. See SELECT command for details (this FROM expression supports all the same expressions including its OPEN, CLOSE and CLEAR operations).

Source code in beancount/query/shell.py
def on_Print(self, print_stmt):
    """
    Print entries in Beancount format.

    The general form of a PRINT statement includes an SQL-like FROM
    selector:

       PRINT [FROM <from_expr> ...]

    Where:

      from_expr: A logical expression that matches on the attributes of
        the directives. See SELECT command for details (this FROM expression
        supports all the same expressions including its OPEN, CLOSE and
        CLEAR operations).

    """
    # Compile the print statement.
    try:
        c_print = query_compile.compile(print_stmt,
                                        self.env_targets,
                                        self.env_postings,
                                        self.env_entries)
    except query_compile.CompilationError as exc:
        print('ERROR: {}.'.format(str(exc).rstrip('.')), file=self.outfile)
        return

    if self.outfile is sys.stdout:
        query_execute.execute_print(c_print, self.entries, self.options_map,
                                    file=self.outfile)
    else:
        with self.get_pager() as file:
            query_execute.execute_print(c_print, self.entries, self.options_map, file)

beancount.query.shell.BQLShell.on_Reload(self, unused_statement=None)

Reload the input file without restarting the shell.

Source code in beancount/query/shell.py
def on_Reload(self, unused_statement=None):
    """
    Reload the input file without restarting the shell.
    """
    self.entries, self.errors, self.options_map = self.loadfun()
    if self.is_interactive:
        print_statistics(self.entries, self.options_map, self.outfile)

beancount.query.shell.BQLShell.on_RunCustom(self, run_stmt)

Run a custom query instead of a SQL command.

RUN <custom-query-name>

Where:

custom-query-name: Should be the name of a custom query to be defined in the Beancount input file.

Source code in beancount/query/shell.py
def on_RunCustom(self, run_stmt):
    """
    Run a custom query instead of a SQL command.

       RUN <custom-query-name>

    Where:

      custom-query-name: Should be the name of a custom query to be defined
        in the Beancount input file.

    """
    custom_query_map = create_custom_query_map(self.entries)
    name = run_stmt.query_name
    if name is None:
        # List the available queries.
        for name in sorted(custom_query_map):
            print(name)
    elif name == "*":
        for name, query in sorted(custom_query_map.items()):
            print('{}:'.format(name))
            self.run_parser(query.query_string, default_close_date=query.date)
            print()
            print()
    else:
        query = None
        if name in custom_query_map:
            query = custom_query_map[name]
        else:  # lookup best query match using name as prefix
            queries = [q for q in custom_query_map if q.startswith(name)]
            if len(queries) == 1:
                name = queries[0]
                query = custom_query_map[name]
        if query:
            statement = self.parser.parse(query.query_string)
            self.dispatch(statement)
        else:
            print("ERROR: Query '{}' not found".format(name))

beancount.query.shell.BQLShell.on_Select(self, statement)

Extract data from a query on the postings.

The general form of a SELECT statement loosely follows SQL syntax, with some mild and idiomatic extensions:

SELECT [DISTINCT][<targets>|*] [FROM <from_expr> [OPEN ON <date>] [CLOSE [ON <date>]] [CLEAR]] [WHERE <where_expr>] [GROUP BY <groups>] [ORDER BY <groups> [ASC|DESC]] [LIMIT num]

Where:

targets: A list of desired output attributes from the postings, and expressions on them. Some of the attributes of the parent transaction directive are made available in this context as well. Simple functions (that return a single value per row) and aggregation functions (that return a single value per group) are available. For the complete list of supported columns and functions, see help on "targets". You can also provide a wildcard here, which will select a reasonable default set of columns for rendering a journal.

from_expr: A logical expression that matches on the attributes of the directives (not postings). This allows you to select a subset of transactions, so the accounting equation is respected for balance reports. For the complete list of supported columns and functions, see help on "from".

where_expr: A logical expression that matches on the attributes of postings. The available columns are similar to those in the targets clause, without the aggregation functions.

OPEN clause: replace all the transactions before the given date by summarizing entries and transfer Income and Expenses balances to Equity.

CLOSE clause: Remove all the transactions after the given date and

CLEAR: Transfer final Income and Expenses balances to Equity.

Source code in beancount/query/shell.py
def on_Select(self, statement):
    """
    Extract data from a query on the postings.

    The general form of a SELECT statement loosely follows SQL syntax, with
    some mild and idiomatic extensions:

       SELECT [DISTINCT] [<targets>|*]
       [FROM <from_expr> [OPEN ON <date>] [CLOSE [ON <date>]] [CLEAR]]
       [WHERE <where_expr>]
       [GROUP BY <groups>]
       [ORDER BY <groups> [ASC|DESC]]
       [LIMIT num]

    Where:

      targets: A list of desired output attributes from the postings, and
        expressions on them. Some of the attributes of the parent transaction
        directive are made available in this context as well. Simple functions
        (that return a single value per row) and aggregation functions (that
        return a single value per group) are available. For the complete
        list of supported columns and functions, see help on "targets".
        You can also provide a wildcard here, which will select a reasonable
        default set of columns for rendering a journal.

      from_expr: A logical expression that matches on the attributes of
        the directives (not postings). This allows you to select a subset of
        transactions, so the accounting equation is respected for balance
        reports. For the complete list of supported columns and functions,
        see help on "from".

      where_expr: A logical expression that matches on the attributes of
        postings. The available columns are similar to those in the targets
        clause, without the aggregation functions.

      OPEN clause: replace all the transactions before the given date by
        summarizing entries and transfer Income and Expenses balances to
        Equity.

      CLOSE clause: Remove all the transactions after the given date and

      CLEAR: Transfer final Income and Expenses balances to Equity.

    """
    # Compile the SELECT statement.
    try:
        c_query = query_compile.compile(statement,
                                        self.env_targets,
                                        self.env_postings,
                                        self.env_entries)
    except query_compile.CompilationError as exc:
        print('ERROR: {}.'.format(str(exc).rstrip('.')), file=self.outfile)
        return

    # Execute it to obtain the result rows.
    rtypes, rrows = query_execute.execute_query(c_query,
                                                self.entries,
                                                self.options_map)

    # Output the resulting rows.
    if not rrows:
        print("(empty)", file=self.outfile)
    else:
        output_format = self.vars['format']
        if output_format == 'text':
            kwds = dict(boxed=self.vars['boxed'],
                        spaced=self.vars['spaced'],
                        expand=self.vars['expand'])
            if self.outfile is sys.stdout:
                with self.get_pager() as file:
                    query_render.render_text(rtypes, rrows,
                                             self.options_map['dcontext'],
                                             file,
                                             **kwds)
            else:
                query_render.render_text(rtypes, rrows,
                                         self.options_map['dcontext'],
                                         self.outfile,
                                         **kwds)

        elif output_format == 'csv':
            # Numberify CSV output if requested.
            if self.vars['numberify']:
                dformat = self.options_map['dcontext'].build()
                rtypes, rrows = numberify.numberify_results(rtypes, rrows, dformat)

            query_render.render_csv(rtypes, rrows,
                                    self.options_map['dcontext'],
                                    self.outfile,
                                    expand=self.vars['expand'])

        else:
            assert output_format not in _SUPPORTED_FORMATS
            print("Unsupported output format: '{}'.".format(output_format),
                  file=self.outfile)

beancount.query.shell.DispatchingShell (Cmd)

A usable convenient shell for interpreting commands, with history.

beancount.query.shell.DispatchingShell.__init__(self, is_interactive, parser, outfile, default_format, do_numberify) special

Create a shell with history.

Parameters:
  • is_interactive – A boolean, true if this serves an interactive tty.

  • parser – A command parser.

  • outfile – An output file object to write communications to.

  • default_format – A string, the default output format.

Source code in beancount/query/shell.py
def __init__(self, is_interactive, parser, outfile, default_format, do_numberify):
    """Create a shell with history.

    Args:
      is_interactive: A boolean, true if this serves an interactive tty.
      parser: A command parser.
      outfile: An output file object to write communications to.
      default_format: A string, the default output format.
    """
    super().__init__()
    if is_interactive and readline is not None:
        load_history(path.expanduser(HISTORY_FILENAME))
    self.is_interactive = is_interactive
    self.parser = parser
    self.initialize_vars(default_format, do_numberify)
    self.add_help()
    self.outfile = outfile

beancount.query.shell.DispatchingShell.add_help(self)

Attach help functions for each of the parsed token handlers.

Source code in beancount/query/shell.py
def add_help(self):
    "Attach help functions for each of the parsed token handlers."
    for attrname, func in list(self.__class__.__dict__.items()):
        match = re.match('on_(.*)', attrname)
        if not match:
            continue
        command_name = match.group(1)
        setattr(self.__class__, 'help_{}'.format(command_name.lower()),
                lambda _, fun=func: print(textwrap.dedent(fun.__doc__).strip(),
                                          file=self.outfile))

beancount.query.shell.DispatchingShell.cmdloop(self)

Override cmdloop to handle keyboard interrupts.

Source code in beancount/query/shell.py
def cmdloop(self):
    """Override cmdloop to handle keyboard interrupts."""
    while True:
        try:
            super().cmdloop()
            break
        except KeyboardInterrupt:
            print('\n(Interrupted)', file=self.outfile)

beancount.query.shell.DispatchingShell.default(self, line)

Default handling of lines which aren't recognized as native shell commands.

Parameters:
  • line – The string to be parsed.

Source code in beancount/query/shell.py
def default(self, line):
    """Default handling of lines which aren't recognized as native shell commands.

    Args:
      line: The string to be parsed.
    """
    self.run_parser(line)

beancount.query.shell.DispatchingShell.dispatch(self, statement)

Dispatch the given statement to a suitable method.

Parameters:
  • statement – An instance provided by the parser.

Returns:
  • Whatever the invoked method happens to return.

Source code in beancount/query/shell.py
def dispatch(self, statement):
    """Dispatch the given statement to a suitable method.

    Args:
      statement: An instance provided by the parser.
    Returns:
      Whatever the invoked method happens to return.
    """
    try:
        method = getattr(self, 'on_{}'.format(type(statement).__name__))
    except AttributeError:
        print("Internal error: statement '{}' is unsupported.".format(statement),
              file=self.outfile)
    else:
        return method(statement)

beancount.query.shell.DispatchingShell.do_EOF(self, _)

Exit the parser.

Source code in beancount/query/shell.py
def exit(self, _):
    """Exit the parser."""
    print('exit', file=self.outfile)
    return 1

beancount.query.shell.DispatchingShell.do_clear(self, _)

Clear the history.

Source code in beancount/query/shell.py
def do_clear(self, _):
    "Clear the history."
    readline.clear_history()

beancount.query.shell.DispatchingShell.do_exit(self, _)

Exit the parser.

Source code in beancount/query/shell.py
def exit(self, _):
    """Exit the parser."""
    print('exit', file=self.outfile)
    return 1

beancount.query.shell.DispatchingShell.do_help(self, command)

Strip superfluous semicolon.

Source code in beancount/query/shell.py
def do_help(self, command):
    """Strip superfluous semicolon."""
    super().do_help(command.rstrip('; \t'))

beancount.query.shell.DispatchingShell.do_history(self, _)

Print the command-line history statement.

Source code in beancount/query/shell.py
def do_history(self, _):
    "Print the command-line history statement."
    if readline is not None:
        for index, line in enumerate(get_history(self.max_entries)):
            print(line, file=self.outfile)

beancount.query.shell.DispatchingShell.do_lex(self, line)

Just run the lexer on the following command and print the output.

Source code in beancount/query/shell.py
def do_lex(self, line):
    "Just run the lexer on the following command and print the output."
    try:
        self.parser.tokenize(line)
    except query_parser.ParseError as exc:
        print(exc, file=self.outfile)

beancount.query.shell.DispatchingShell.do_parse(self, line)

Just run the parser on the following command and print the output.

Source code in beancount/query/shell.py
def do_parse(self, line):
    "Just run the parser on the following command and print the output."
    print("INPUT: {}".format(repr(line)), file=self.outfile)
    try:
        statement = self.parser.parse(line, True)
        print(statement, file=self.outfile)
    except (query_parser.ParseError,
            query_compile.CompilationError) as exc:
        print(exc, file=self.outfile)
    except Exception as exc:
        traceback.print_exc(file=self.outfile)

beancount.query.shell.DispatchingShell.do_quit(self, _)

Exit the parser.

Source code in beancount/query/shell.py
def exit(self, _):
    """Exit the parser."""
    print('exit', file=self.outfile)
    return 1

beancount.query.shell.DispatchingShell.do_set(self, line)

Get/set shell settings variables.

Source code in beancount/query/shell.py
def do_set(self, line):
    "Get/set shell settings variables."
    if not line:
        for varname, value in sorted(self.vars.items()):
            print('{}: {}'.format(varname, value), file=self.outfile)
    else:
        components = shlex.split(line)
        varname = components[0]
        if len(components) == 1:
            try:
                value = self.vars[varname]
                print('{}: {}'.format(varname, value), file=self.outfile)
            except KeyError:
                print("Variable '{}' does not exist.".format(varname),
                      file=self.outfile)
        elif len(components) == 2:
            value = components[1]
            try:
                converted_value = self.vars_types[varname](value)
                self.vars[varname] = converted_value
                print('{}: {}'.format(varname, converted_value), file=self.outfile)
            except KeyError:
                print("Variable '{}' does not exist.".format(varname),
                      file=self.outfile)
        else:
            print("Invalid number of arguments.", file=self.outfile)

beancount.query.shell.DispatchingShell.do_tokenize(self, line)

Just run the lexer on the following command and print the output.

Source code in beancount/query/shell.py
def do_lex(self, line):
    "Just run the lexer on the following command and print the output."
    try:
        self.parser.tokenize(line)
    except query_parser.ParseError as exc:
        print(exc, file=self.outfile)

beancount.query.shell.DispatchingShell.emptyline(self)

Do nothing on an empty line.

Source code in beancount/query/shell.py
def emptyline(self):
    """Do nothing on an empty line."""

beancount.query.shell.DispatchingShell.exit(self, _)

Exit the parser.

Source code in beancount/query/shell.py
def exit(self, _):
    """Exit the parser."""
    print('exit', file=self.outfile)
    return 1

beancount.query.shell.DispatchingShell.get_pager(self)

Create and return a context manager to write to, a pager subprocess if required.

Returns:
  • A pair of a file object to write to, and a pipe object to wait on (or

None if not necessary to wait).

Source code in beancount/query/shell.py
def get_pager(self):
    """Create and return a context manager to write to, a pager subprocess if required.

    Returns:
      A pair of a file object to write to, and a pipe object to wait on (or
    None if not necessary to wait).
    """
    if self.is_interactive:
        return pager.ConditionalPager(self.vars.get('pager', None),
                                      minlines=misc_utils.get_screen_height())
    else:
        file = (codecs.getwriter("utf-8")(sys.stdout.buffer)
                if hasattr(sys.stdout, 'buffer') else
                sys.stdout)
        return pager.flush_only(file)

beancount.query.shell.DispatchingShell.initialize_vars(self, default_format, do_numberify)

Initialize the setting variables of the interactive shell.

Source code in beancount/query/shell.py
def initialize_vars(self, default_format, do_numberify):
    """Initialize the setting variables of the interactive shell."""
    self.vars_types = {
        'pager': str,
        'format': str,
        'boxed': convert_bool,
        'spaced': convert_bool,
        'expand': convert_bool,
        'numberify': convert_bool,
        }
    self.vars = {
        'pager': os.environ.get('PAGER', None),
        'format': default_format,
        'boxed': False,
        'spaced': False,
        'expand': False,
        'numberify': do_numberify,
        }

beancount.query.shell.DispatchingShell.run_parser(self, line, default_close_date=None)

Handle statements via our parser instance and dispatch to appropriate methods.

Parameters:
  • line – The string to be parsed.

  • default_close_date – A datetimed.date instance, the default close date.

Source code in beancount/query/shell.py
def run_parser(self, line, default_close_date=None):
    """Handle statements via our parser instance and dispatch to appropriate methods.

    Args:
      line: The string to be parsed.
      default_close_date: A datetimed.date instance, the default close date.
    """
    try:
        statement = self.parser.parse(line,
                                      default_close_date=default_close_date)
        self.dispatch(statement)
    except query_parser.ParseError as exc:
        print(exc, file=self.outfile)
    except Exception as exc:
        traceback.print_exc(file=self.outfile)

beancount.query.shell.convert_bool(string)

Convert a string to a boolean.

Parameters:
  • string – A string representing a boolean.

Returns:
  • The corresponding boolean.

Source code in beancount/query/shell.py
def convert_bool(string):
    """Convert a string to a boolean.

    Args:
      string: A string representing a boolean.
    Returns:
      The corresponding boolean.
    """
    return not string.lower() in ('f', 'false', '0')

beancount.query.shell.create_custom_query_map(entries)

Extract a mapping of the custom queries from the list of entries.

Parameters:
  • entries – A list of entries.

Returns:
  • A map of query-name strings to Query directives.

Source code in beancount/query/shell.py
def create_custom_query_map(entries):
    """Extract a mapping of the custom queries from the list of entries.

    Args:
      entries: A list of entries.
    Returns:
      A map of query-name strings to Query directives.
    """
    query_map = {}
    for entry in entries:
        if not isinstance(entry, data.Query):
            continue
        if entry.name in query_map:
            logging.warning("Duplicate query: %s", entry.name)
        query_map[entry.name] = entry
    return query_map

beancount.query.shell.generate_env_attribute_list(env)

Generate a dictionary of rendered attribute lists for help.

Parameters:
  • env – An instance of an environment.

Returns:
  • A dict with keys 'columns', 'functions' and 'aggregates' to rendered and formatted strings.

Source code in beancount/query/shell.py
def generate_env_attribute_list(env):
    """Generate a dictionary of rendered attribute lists for help.

    Args:
      env: An instance of an environment.
    Returns:
      A dict with keys 'columns', 'functions' and 'aggregates' to rendered
      and formatted strings.
    """
    wrapper = textwrap.TextWrapper(initial_indent='  ',
                                   subsequent_indent='    ',
                                   drop_whitespace=True,
                                   width=80)

    str_columns = generate_env_attributes(
        wrapper, env.columns)
    str_simple = generate_env_attributes(
        wrapper, env.functions,
        lambda node: not issubclass(node, query_compile.EvalAggregator))
    str_aggregate = generate_env_attributes(
        wrapper, env.functions,
        lambda node: issubclass(node, query_compile.EvalAggregator))

    return dict(columns=str_columns,
                functions=str_simple,
                aggregates=str_aggregate)

beancount.query.shell.generate_env_attributes(wrapper, field_dict, filter_pred=None)

Generate a string of all the help functions of the attributes.

Parameters:
  • wrapper – A TextWrapper instance to format the paragraphs.

  • field_dict – A dict of the field-names to the node instances, fetch from an environment.

  • filter_pred – A predicate to filter the desired columns. This is applied to the evaluator node instances.

Returns:
  • A formatted multiline string, ready for insertion in a help text.

Source code in beancount/query/shell.py
def generate_env_attributes(wrapper, field_dict, filter_pred=None):
    """Generate a string of all the help functions of the attributes.

    Args:
      wrapper: A TextWrapper instance to format the paragraphs.
      field_dict: A dict of the field-names to the node instances, fetch from an
        environment.
      filter_pred: A predicate to filter the desired columns. This is applied to
        the evaluator node instances.
    Returns:
      A formatted multiline string, ready for insertion in a help text.
    """
    # Expand the name if its key has argument types.
    #
    # FIXME: Render the __intypes__ here nicely instead of the key.
    flat_items = []
    for name, column_cls in field_dict.items():
        if isinstance(name, tuple):
            name = name[0]

        if issubclass(column_cls, query_compile.EvalFunction):
            name = name.upper()
            args = []
            for dtypes in column_cls.__intypes__:
                if isinstance(dtypes, (tuple, list)):
                    arg = '|'.join(dtype.__name__ for dtype in dtypes)
                else:
                    arg = dtypes.__name__
                args.append(arg)
            name = "{}({})".format(name, ','.join(args))

        flat_items.append((name, column_cls))

    # Render each of the attributes.
    oss = io.StringIO()
    for name, column_cls in sorted(flat_items):
        if filter_pred and not filter_pred(column_cls):
            continue
        docstring = column_cls.__doc__ or "[See class {}]".format(column_cls.__name__)
        if issubclass(column_cls, query_compile.EvalColumn):
            docstring += " Type: {}.".format(column_cls().dtype.__name__)
            # if hasattr(column_cls, '__equivalent__'):
            #     docstring += " Attribute:{}.".format(column_cls.__equivalent__)

        text = re.sub('[ \t]+', ' ', docstring.strip().replace('\n', ' '))
        doc = "'{}': {}".format(name, text)
        oss.write(wrapper.fill(doc))
        oss.write('\n')

    return oss.getvalue().rstrip()

beancount.query.shell.get_history(max_entries)

Return the history in the readline buffer.

Parameters:
  • max_entries – An integer, the maximum number of entries to return.

Returns:
  • A list of string, the previous history of commands.

Source code in beancount/query/shell.py
def get_history(max_entries):
    """Return the history in the readline buffer.

    Args:
      max_entries: An integer, the maximum number of entries to return.
    Returns:
      A list of string, the previous history of commands.
    """
    num_entries = readline.get_current_history_length()
    assert num_entries >= 0
    start = max(0, num_entries - max_entries)
    return [readline.get_history_item(index+1)
            for index in range(start, num_entries)]

beancount.query.shell.load_history(filename)

Load the shell's past history.

Parameters:
  • filename – A string, the name of the file containing the shell history.

Source code in beancount/query/shell.py
def load_history(filename):
    """Load the shell's past history.

    Args:
      filename: A string, the name of the file containing the shell history.
    """
    readline.parse_and_bind("tab:complete")
    if hasattr(readline, "read_history_file"):
        try:
            readline.read_history_file(filename)
        except IOError:
            # Don't error on absent file.
            pass
        atexit.register(save_history, filename)

beancount.query.shell.print_statistics(entries, options_map, outfile)

Print summary statistics to stdout.

Parameters:
  • entries – A list of directives.

  • options_map – An options map. as produced by the parser.

  • outfile – A file object to write to.

Source code in beancount/query/shell.py
def print_statistics(entries, options_map, outfile):
    """Print summary statistics to stdout.

    Args:
      entries: A list of directives.
      options_map: An options map. as produced by the parser.
      outfile: A file object to write to.
    """
    num_directives, num_transactions, num_postings = summary_statistics(entries)
    if 'title' in options_map:
        print('Input file: "{}"'.format(options_map['title']), file=outfile)
    print("Ready with {} directives ({} postings in {} transactions).".format(
        num_directives, num_postings, num_transactions),
          file=outfile)

beancount.query.shell.save_history(filename)

Save the shell history. This should be invoked on exit.

Parameters:
  • filename – A string, the name of the file to save the history to.

Source code in beancount/query/shell.py
def save_history(filename):
    """Save the shell history. This should be invoked on exit.

    Args:
      filename: A string, the name of the file to save the history to.
    """
    readline.write_history_file(filename)

beancount.query.shell.summary_statistics(entries)

Calculate basic summary statistics to output a brief welcome message.

Parameters:
  • entries – A list of directives.

Returns:
  • A tuple of three integers, the total number of directives parsed, the total number of transactions and the total number of postings there in.

Source code in beancount/query/shell.py
def summary_statistics(entries):
    """Calculate basic summary statistics to output a brief welcome message.

    Args:
      entries: A list of directives.
    Returns:
      A tuple of three integers, the total number of directives parsed, the total number
      of transactions and the total number of postings there in.
    """
    num_directives = len(entries)
    num_transactions = 0
    num_postings = 0
    for entry in entries:
        if isinstance(entry, data.Transaction):
            num_transactions += 1
            num_postings += len(entry.postings)
    return (num_directives, num_transactions, num_postings)

beancount.query.shell_test

beancount.query.shell_test.TestShell (TestCase)

beancount.query.shell_test.TestShell.test_success(self, filename)

2013-01-01 open Assets:Account1 2013-01-01 open Assets:Account2 2013-01-01 open Assets:Account3 2013-01-01 open Equity:Unknown

2013-04-05 * Equity:Unknown Assets:Account1 5000 USD

2013-04-05 * Assets:Account1 -3000 USD Assets:Account2 30 BOOG {100 USD}

2013-04-05 * Assets:Account1 -1000 USD Assets:Account3 800 EUR @ 1.25 USD

Source code in beancount/query/shell_test.py
@test_utils.docfile
def test_success(self, filename):
    """
    2013-01-01 open Assets:Account1
    2013-01-01 open Assets:Account2
    2013-01-01 open Assets:Account3
    2013-01-01 open Equity:Unknown

    2013-04-05 *
      Equity:Unknown
      Assets:Account1     5000 USD

    2013-04-05 *
      Assets:Account1     -3000 USD
      Assets:Account2        30 BOOG {100 USD}

    2013-04-05 *
      Assets:Account1     -1000 USD
      Assets:Account3       800 EUR @ 1.25 USD
    """
    with test_utils.capture('stdout', 'stderr') as (stdout, _):
        test_utils.run_with_args(shell.main, [filename, "SELECT 1;"])
    self.assertTrue(stdout.getvalue())

beancount.query.shell_test.TestUseCases (TestCase)

Testing all the use cases from the proposal here. I'm hoping to replace reports by these queries instead.

beancount.query.shell_test.runshell(function)

Decorate a function to run the shell and return the output.

Source code in beancount/query/shell_test.py
def runshell(function):
    """Decorate a function to run the shell and return the output."""
    def test_function(self):
        def loadfun():
            return entries, errors, options_map
        with test_utils.capture('stdout') as stdout:
            shell_obj = shell.BQLShell(False, loadfun, sys.stdout)
            shell_obj.on_Reload()
            shell_obj.onecmd(function.__doc__)
        return function(self, stdout.getvalue())
    return test_function