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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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.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.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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Exceptions: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
Returns: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
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: |
|
---|
Returns: |
|
---|
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