def monthly_income_expenses_totals(self): month_tuples = self._month_tuples(self.entries) monthly_totals = [] for begin_date, end_date in month_tuples: entries, index = summarize.clamp_opt(self.entries, begin_date, end_date + timedelta(days=1), self.options_map) income_totals = self._table_totals(realization.get(realization.realize(entries, self.account_types), self.options_map['name_income'])) expenses_totals = self._table_totals(realization.get(realization.realize(entries, self.account_types), self.options_map['name_expenses'])) # FIXME find better way to only include relevant totals (lots of ZERO-ones at the beginning) sum_ = ZERO for currency, number in income_totals.items(): sum_ += number for currency, number in expenses_totals.items(): sum_ += number if sum_ != ZERO: monthly_totals.append({ 'begin_date': begin_date, 'end_date': end_date, 'income_totals': income_totals, 'expenses_totals': expenses_totals }) return monthly_totals
def _initialize(self, options_map): """Compute the list of filtered entries and realization trees.""" # Get the filtered list of entries. self.entries, self.begin_index, self.price_date = self.apply_filter( self.all_entries, options_map) # Compute the list of entries for the opening balances sheet. self.opening_entries = (self.entries[:self.begin_index] if self.begin_index is not None else []) # Compute the list of entries that includes transfer entries of the # income/expenses amounts to the balance sheet's equity (as "net # income"). This is used to render the end-period balance sheet, with # the current period's net income, closing the period. self.closing_entries = summarize.cap_opt(self.entries, options_map) # Realize the three sets of entries. account_types = options.get_account_types(options_map) with misc_utils.log_time('realize_opening', logging.info): self.opening_real_accounts = realization.realize(self.opening_entries, account_types) with misc_utils.log_time('realize', logging.info): self.real_accounts = realization.realize(self.entries, account_types) with misc_utils.log_time('realize_closing', logging.info): self.closing_real_accounts = realization.realize(self.closing_entries, account_types) assert self.real_accounts is not None assert self.closing_real_accounts is not None
def apply_filters(self): self.entries = self.all_entries if self.filters['time']: try: begin_date, end_date = parse_date(self.filters['time']) self.entries, _ = summarize.clamp_opt(self.entries, begin_date, end_date, self.options) except TypeError: raise FilterException('Failed to parse date string: {}'.format(self.filters['time'])) if self.filters['tag']: self.entries = [entry for entry in self.entries if isinstance(entry, Transaction) and entry.tags and (entry.tags & set(self.filters['tag']))] if self.filters['payee']: self.entries = [entry for entry in self.entries if (isinstance(entry, Transaction) and entry.payee and (entry.payee in self.filters['payee'])) or (isinstance(entry, Transaction) and not entry.payee and ('' in self.filters['payee']))] if self.filters['account']: self.entries = [entry for entry in self.entries if isinstance(entry, Transaction) and any(has_component(posting.account, self.filters['account']) for posting in entry.postings)] self.root_account = realization.realize(self.entries, self.account_types) self.all_accounts = self._account_components() self.all_accounts_leaf_only = self._account_components(leaf_only=True) self.closing_entries = summarize.cap_opt(self.entries, self.options) self.closing_real_accounts = realization.realize(self.closing_entries, self.account_types)
def interval_balances(self, interval, account_name, accumulate=False): """Balances by interval. Arguments: interval: An interval. account_name: An account name. accumulate: A boolean, ``True`` if the balances for an interval should include all entries up to the end of the interval. Returns: A list of RealAccount instances for all the intervals. """ min_accounts = [ account for account in self.accounts.keys() if account.startswith(account_name) ] interval_tuples = list( reversed(list(pairwise(self.interval_ends(interval))))) interval_balances = [ realization.realize( list( iter_entry_dates( self.entries, datetime.date.min if accumulate else begin_date, end_date, )), min_accounts, ) for begin_date, end_date in interval_tuples ] return interval_balances, interval_tuples
def test_dump(self, entries, _, __): """ 2012-01-01 open Assets:Bank1:Checking 2012-01-01 open Assets:Bank1:Savings 2012-01-01 open Assets:Bank2:Checking 2012-01-01 open Expenses:Restaurant 2012-01-01 open Expenses:Movie 2012-01-01 open Liabilities:CreditCard 2012-01-01 open Equity:Opening-Balances """ real_account = realization.realize(entries) lines = realization.dump(real_account) self.assertEqual([ ('|-- Assets ', '| | '), ('| |-- Bank1 ', '| | | '), ('| | |-- Checking ', '| | | '), ('| | `-- Savings ', '| | '), ('| `-- Bank2 ', '| | '), ('| `-- Checking ', '| '), ('|-- Equity ', '| | '), ('| `-- Opening-Balances', '| '), ('|-- Expenses ', '| | '), ('| |-- Movie ', '| | '), ('| `-- Restaurant ', '| '), ('`-- Liabilities ', ' | '), (' `-- CreditCard ', ' '), ], [(first_line, cont_line) for first_line, cont_line, _1 in lines])
def test_dump_balances(self, entries, _, options_map): """ 2012-01-01 open Expenses:Restaurant 2012-01-01 open Liabilities:US:CreditCard 2012-01-01 open Liabilities:CA:CreditCard 2014-05-30 * Liabilities:CA:CreditCard 123.45 CAD Expenses:Restaurant 2014-05-31 * Liabilities:US:CreditCard 123.45 USD Expenses:Restaurant """ real_account = realization.realize(entries) dformat = options_map['dcontext'].build( alignment=display_context.Align.DOT, reserved=2) self.assertLines( """ |-- Expenses | `-- Restaurant -123.45 CAD | -123.45 USD `-- Liabilities |-- CA | `-- CreditCard 123.45 CAD `-- US `-- CreditCard 123.45 USD """, realization.dump_balances(real_account, dformat))
def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): """Get a list of holdings by account (as Postings).""" simple_entries = [entry for entry in entries if (not isinstance(entry, Transaction) or entry.flag != flags.FLAG_UNREALIZED)] root_account = realization.realize(simple_entries) holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): account_type = account_types.get_account_type(real_account.account) if (included_account_types and account_type not in included_account_types): continue for pos in real_account.balance: price = None if pos.cost and price_map: base_quote = (pos.units.currency, pos.cost.currency) _, price = prices.get_price(price_map, base_quote, date) holdings.append(Posting(real_account.account, pos.units, pos.cost, price, None, None)) return holdings
def monthly_income_expenses_totals(self): month_tuples = self._month_tuples(self.entries) monthly_totals = [] for begin_date, end_date in month_tuples: entries = self._entries_in_inclusive_range(begin_date, end_date) realized = realization.realize(entries, self.account_types) income_totals = self._table_totals(realization.get(realized, self.account_types.income)) expenses_totals = self._table_totals(realization.get(realized, self.account_types.expenses)) # FIXME find better way to only include relevant totals (lots of ZERO-ones at the beginning) sum_ = ZERO for currency, number in income_totals.items(): sum_ += number for currency, number in expenses_totals.items(): sum_ += number if sum_ != ZERO: monthly_totals.append({ 'begin_date': begin_date, 'end_date': end_date, 'income_totals': income_totals, 'expenses_totals': expenses_totals }) return monthly_totals
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" with open(self.beancount_file_path, encoding='utf8') as f: self.source = f.read() self.entries, self._errors, self.options = loader.load_file(self.beancount_file_path) self.all_entries = self.entries self.price_map = prices.build_price_map(self.all_entries) self.title = self.options['title'] self.errors = [] for error in self._errors: self.errors.append({ 'file': error.source['filename'], 'line': error.source['lineno'], 'error': error.message, 'entry': error.entry # TODO render entry }) self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.account_types = options.get_account_types(self.options) self.real_accounts = realization.realize(self.entries, self.account_types) self.all_accounts = self._account_components()
def load_file(self, beancount_file_path=None): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" if beancount_file_path: self.beancount_file_path = beancount_file_path self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.dcontext = self.options['dcontext'] self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = self._entries_filter_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = self._all_accounts() self.all_accounts_leaf_only = self._all_accounts(leaf_only=True) self._apply_filters()
def do_linked(filename, args): """Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. """ from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit("Missing line number argument.") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. links = closest_entry.links if closest_entry is None: raise SystemExit("No entry could be found before {}:{}".format( filename, lineno)) if not links: return # Find all linked entries. linked_entries = [ entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links) ] # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) realization.dump_balances(real_root, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))
def test_dump_balances(self, entries, _, __): """ 2012-01-01 open Expenses:Restaurant 2012-01-01 open Liabilities:US:CreditCard 2012-01-01 open Liabilities:CA:CreditCard 2014-05-30 * Liabilities:CA:CreditCard 123.45 CAD Expenses:Restaurant 2014-05-31 * Liabilities:US:CreditCard 123.45 USD Expenses:Restaurant """ real_account = realization.realize(entries) self.assertLines(""" |-- Expenses | `-- Restaurant -123.45 CAD | -123.45 USD `-- Liabilities |-- CA | `-- CreditCard 123.45 CAD `-- US `-- CreditCard 123.45 USD """, realization.dump_balances(real_account))
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_leaf_only = _list_accounts(self.all_root_account, leaf_only=True) self.sidebar_links = _sidebar_links(self.all_entries) self._apply_filters() self.budgets = Budgets(self.entries) self.errors.extend(self.budgets.errors)
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self.format_string = '{:,f}' self.default_format_string = '{:,.2f}' else: self.format_string = '{:f}' self.default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_leaf_only = _list_accounts( self.all_root_account, leaf_only=True) self.sidebar_links = _sidebar_links(self.all_entries) self._apply_filters() self.budgets = Budgets(self.entries) self.errors.extend(self.budgets.errors)
def load_file(self) -> None: """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = _load( [(self.beancount_file_path, True)], None, None, None) else: self.all_entries, self.errors, self.options = load_file( self.beancount_file_path) self.get_filtered.cache_clear() self.account_types = get_account_types(self.options) self.price_map = build_price_map(self.all_entries) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_entries_by_type = group_entries_by_type(self.all_entries) self.accounts = AccountDict() for open_entry in self.all_entries_by_type.Open: self.accounts.setdefault(open_entry.account).meta = open_entry.meta for close in self.all_entries_by_type.Close: self.accounts.setdefault(close.account).close_date = close.date self.fava_options, errors = parse_options( self.all_entries_by_type.Custom) self.errors.extend(errors) if not self._is_encrypted: self._watcher.update(*self.paths_to_watch()) for mod in MODULES: getattr(self, mod).load_file()
def render_mini_balances(entries, options_map, conversion=None, price_map=None): """Render a treeified list of the balances for the given transactions. Args: entries: A list of selected transactions to render. options_map: The parsed options. conversion: Conversion method string, None, 'value' or 'cost'. price_map: A price map from the original entries. If this isn't provided, the inventories are rendered directly. If it is, their contents are converted to market value. """ # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(entries) dformat = options_map['dcontext'].build(alignment=Align.DOT, reserved=2) # TODO(blais): I always want to be able to convert at cost. We need # arguments capability. # # TODO(blais): Ideally this conversion inserts a new transactions to # 'Unrealized' to account for the difference between cost and market value. # Insert one and update the realization. Add an update() method to the # realization, given a transaction. acctypes = options.get_account_types(options_map) if conversion == 'value': assert price_map is not None # Warning: Mutate the inventories in-place, converting them to market # value. balance_diff = inventory.Inventory() for real_account in realization.iter_children(real_root): balance_cost = real_account.balance.reduce(convert.get_cost) balance_value = real_account.balance.reduce(convert.get_value, price_map) real_account.balance = balance_value balance_diff.add_inventory(balance_cost) balance_diff.add_inventory(-balance_value) if not balance_diff.is_empty(): account_unrealized = account.join(acctypes.income, options_map["account_unrealized_gains"]) unrealized = realization.get_or_create(real_root, account_unrealized) unrealized.balance.add_inventory(balance_diff) elif conversion == 'cost': for real_account in realization.iter_children(real_root): real_account.balance = real_account.balance.reduce(convert.get_cost) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))
def filter( self, force: bool = False, account: str | None = None, filter: str | None = None, # pylint: disable=redefined-builtin time: str | None = None, ) -> None: """Set and apply (if necessary) filters.""" changed = self.filters.set(account=account, filter=filter, time=time) if not (changed or force): return self.entries = self.filters.apply(self.all_entries) self.root_account = realization.realize(self.entries, self.account_types) self.root_tree = Tree(self.entries) self._date_first, self._date_last = get_min_max_dates( self.entries, (Transaction, Price)) if self._date_last: self._date_last = self._date_last + datetime.timedelta(1) if self.filters.time: self._date_first = self.filters.time.begin_date self._date_last = self.filters.time.end_date
def validate_leaf_only(entries, unused_options_map): """Check for non-leaf accounts that have postings on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. """ real_root = realization.realize(entries, compute_balance=False) default_meta = data.new_metadata('<leafonly>', 0) open_close_map = None # Lazily computed. errors = [] for real_account in realization.iter_children(real_root): if len(real_account) > 0 and real_account.txn_postings: if open_close_map is None: open_close_map = getters.get_account_open_close(entries) open_entry = open_close_map[real_account.account][0] errors.append( LeafOnlyError( open_entry.meta if open_entry else default_meta, "Non-leaf account '{}' has postings on it".format( real_account.account), open_entry)) return entries, errors
def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): """Get a list of holdings by account (as Postings).""" simple_entries = [ entry for entry in entries if (not isinstance(entry, Transaction) or entry.flag != flags.FLAG_UNREALIZED) ] root_account = realization.realize(simple_entries) holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): account_type = account_types.get_account_type(real_account.account) if (included_account_types and account_type not in included_account_types): continue for pos in real_account.balance: price = None if pos.cost and price_map: base_quote = (pos.units.currency, pos.cost.currency) _, price = prices.get_price(price_map, base_quote, date) holdings.append( Posting(real_account.account, pos.units, pos.cost, price, None, None)) return holdings
def build_interesting_realacc(entries, accounts): def is_included_account(realacc): for pattern in accounts: if re.match(pattern, realacc.account): if realacc.balance == inventory.Inventory(): return False # Remove empty accounts to "clean up" the tree return True return False realroot = realization.realize(entries) # first, filter out accounts that are not specified: realacc = realization.filter(realroot, is_included_account) if not realacc: sys.stderr.write( "No included accounts found. (Your --accounts <regex> failed to match any account)\n" ) sys.exit(1) # However, realacc includes all ancestor accounts of specified accounts, and their balances. For example, # if we specified 'Accounts:Investments:Brokerage', balances due to transactions on 'Accounts:Investments' # will also be included. We need to filter these out: for acc in realization.iter_children(realacc): if not is_included_account(acc): acc.balance = inventory.Inventory() return realacc
def load_file(self): """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) include_path = os.path.dirname(self.beancount_file_path) self._watcher.update(self.options['include'], [ os.path.join(include_path, path) for path in self.options['documents'] ]) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = get_account_types(self.options) self.all_root_account = realization.realize(self.all_entries, self.account_types) if self.options['render_commas']: self._format_string = '{:,f}' self._default_format_string = '{:,.2f}' else: self._format_string = '{:f}' self._default_format_string = '{:.2f}' self.fava_options, errors = parse_options( filter_type(self.all_entries, Custom)) self.errors.extend(errors) for mod in MODULES: getattr(self, mod).load_file() self.filter(True)
def interval_balances(self, interval, account_name, accumulate=False): """Balances by interval. Arguments: interval: An interval. account_name: An account name. accumulate: A boolean, ``True`` if the balances for an interval should include all entries up to the end of the interval. Returns: A list of RealAccount instances for all the intervals. """ min_accounts = [ account for account in self.accounts.keys() if account.startswith(account_name)] interval_tuples = list( reversed(list(pairwise(self.interval_ends(interval)))) ) interval_balances = [ realization.realize(list(iter_entry_dates( self.entries, datetime.date.min if accumulate else begin_date, end_date)), min_accounts) for begin_date, end_date in interval_tuples] return interval_balances, interval_tuples
def filter(self, force=False, **kwargs): """Set and apply (if necessary) filters.""" changed = False for filter_name, value in kwargs.items(): if self._filters[filter_name].set(value): changed = True if not (changed or force): return self.entries = self.all_entries for filter_class in self._filters.values(): self.entries = filter_class.apply(self.entries) self.root_account = realization.realize(self.entries, self.account_types) self.root_tree = Tree(self.entries) self._date_first, self._date_last = \ getters.get_min_max_dates(self.entries, (Transaction)) if self._date_last: self._date_last = self._date_last + datetime.timedelta(1) if self._filters['time']: self._date_first = self._filters['time'].begin_date self._date_last = self._filters['time'].end_date
def filter(self, force=False, **kwargs): """Set and apply (if necessary) filters.""" changed = False for filter_name, value in kwargs.items(): if self._filters[filter_name].set(value): changed = True if not (changed or force): return self.entries = self.all_entries for filter_class in self._filters.values(): self.entries = filter_class.apply(self.entries) self.root_account = realization.realize(self.entries, self.account_types) self.root_tree = Tree(self.entries) self._date_first, self._date_last = getters.get_min_max_dates( self.entries, (Transaction)) if self._date_last: self._date_last = self._date_last + datetime.timedelta(1) if self._filters["time"]: self._date_first = self._filters["time"].begin_date self._date_last = self._filters["time"].end_date
def _apply_filters(self): self.entries = self.all_entries for filter in self.filters.values(): self.entries = filter.apply(self.entries, self.options) self.root_account = realization.realize(self.entries, self.account_types)
def test_realize_min_accoumts(self): real_account = realization.realize([], account_types.DEFAULT_ACCOUNT_TYPES) self.assertTrue(isinstance(real_account, realization.RealAccount)) self.assertEqual(real_account.account, '') self.assertEqual(len(real_account), 5) self.assertEqual(set(account_types.DEFAULT_ACCOUNT_TYPES), real_account.keys())
def load_file(self): with open(self.beancount_file_path, encoding='utf8') as f: self._source = f.read() self.entries, self._errors, self.options_map = loader.load_file(self.beancount_file_path) self.all_entries = self.entries self.account_types = options.get_account_types(self.options_map) self.real_accounts = realization.realize(self.entries, self.account_types)
def hierarchy(self, account_name, begin=None, end=None): """An account tree.""" if begin: entries = iter_entry_dates(self.ledger.entries, begin, end) root_account = realization.realize(entries) else: root_account = self.ledger.root_account return _serialize_real_account( realization.get_or_create(root_account, account_name), end)
def forward_method(self, entries, errors, options_map, file, fwdfunc=value): account_types = options.get_account_types(options_map) real_root = realization.realize(entries, account_types) return fwdfunc(self, real_root, options_map, file)
def apply_filters(self): self.entries = self.all_entries if self.filters['time']: try: begin_date, end_date = parse_date(self.filters['time']) self.entries, _ = summarize.clamp_opt(self.entries, begin_date, end_date, self.options) except TypeError: raise FilterException('Failed to parse date string: {}'.format( self.filters['time'])) if self.filters['tag']: self.entries = [ entry for entry in self.entries if isinstance(entry, Transaction) and entry.tags and ( entry.tags & set(self.filters['tag'])) ] if self.filters['payee']: self.entries = [ entry for entry in self.entries if (isinstance(entry, Transaction) and entry.payee and (entry.payee in self.filters['payee'])) or ( isinstance(entry, Transaction) and not entry.payee and ('' in self.filters['payee'])) ] if self.filters['account']: self.entries = [ entry for entry in self.entries if isinstance(entry, Transaction) and any( has_component(posting.account, self.filters['account']) for posting in entry.postings) ] self.root_account = realization.realize(self.entries, self.account_types) self.all_accounts = self._account_components() self.all_accounts_leaf_only = self._account_components(leaf_only=True) self.closing_entries = summarize.cap_opt(self.entries, self.options) self.closing_real_accounts = realization.realize( self.closing_entries, self.account_types)
def create_report(entries, options_map): real_root = realization.realize(entries) # Find the institutions from the data. groups, ignored_accounts = find_institutions(entries, options_map) # List all the asset accounts which aren't included in the report. oc_map = getters.get_account_open_close(entries) open_map = {acc: open_entry for acc, (open_entry, _) in oc_map.items()} for acc in sorted(ignored_accounts): logging.info("Ignored account: %s", acc) # Gather missing fields and create a report object. institutions = [] for name, accounts in sorted(groups.items()): # Get the institution fields, which is the union of the fields for all # the accounts with the institution fields. institution_accounts = [ acc for acc in accounts if 'institution' in open_map[acc].meta ] institution_fields = {} for acc in institution_accounts: for key, value in open_map[acc].meta.items(): institution_fields.setdefault(key, value) institution_fields.pop('filename', None) institution_fields.pop('lineno', None) # Create infos for each account in this institution. account_reports = [] for acc in accounts: account_fields = {} for subacc in account.parents(acc): open_entry = open_map[subacc] if 'institution' in open_entry.meta: break account_fields.update(open_entry.meta) account_fields.pop('filename', None) account_fields.pop('lineno', None) for field in institution_fields: account_fields.pop(field, None) real_node = realization.get(real_root, acc) account_reports.append( AccountReport( acc, open_entry.date, real_node.balance, sum(1 for posting in real_node.txn_postings if isinstance(posting, data.TxnPosting)), account_fields)) # Create the institution report. institution = InstitutionReport(name, institution_fields, account_reports) institutions.append(institution) return Report(options_map['title'], institutions)
def test_tree_from_entries(example_ledger): tree = Tree(example_ledger.entries) real_account = realization.realize(example_ledger.entries) for account in realization.iter_children(real_account): name = account.account node = tree[name] _compare_inv_and_counter(account.balance, node.balance) _compare_inv_and_counter(realization.compute_balance(account), node.balance_children)
def filter(self, year=None, tag=None): if year: yv = YearView(self.all_entries, self.options_map, str(year), year) self.entries = yv.entries if tag: tv = TagView(self.all_entries, self.options_map, tag, set([tag])) self.entries = tv.entries self.real_accounts = realization.realize(self.entries, self.account_types)
def reload(self): if self.transaction is not None: self.log.warn("Discard transactions due to reload") self.log.warn(format_entry(self.transaction)) entries, errors, options_map = loader.load_file(self.filename) assert(len(errors) == 0) self.entries = entries self.options_map = options_map self.transaction = None # Commonly used transformations of the entries self.price_entries = prices.get_last_price_entries(entries, datetime.date.today()) self.accounts = realization.realize(self.entries)
def forward_method(self, entries, errors, options_map, file, fwdfunc=value): account_types = options.get_account_types(options_map) real_root = realization.realize(entries, account_types) price_map = prices.build_price_map(entries) # Note: When we forward, use the latest date (None). return fwdfunc(self, real_root, price_map, None, options_map, file)
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" # use the internal function to disable cache if not self.is_encrypted: self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) include_path = os.path.dirname(self.beancount_file_path) self.watcher.update(self.options['include'], [ os.path.join(include_path, path) for path in self.options['documents']]) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options['title'] if self.options['render_commas']: self._format_string = '{:,f}' self._default_format_string = '{:,.2f}' else: self._format_string = '{:f}' self._default_format_string = '{:.2f}' self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.custom_entries = _filter_entries_by_type(self.all_entries, Custom) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_active = _list_accounts( self.all_root_account, active_only=True) self.fava_options, errors = parse_options(self.custom_entries) self.errors.extend(errors) self.sidebar_links = _sidebar_links(self.custom_entries) self.upcoming_events = _upcoming_events( self.all_entries, self.fava_options['upcoming-events']) self.budgets, errors = parse_budgets(self.custom_entries) self.errors.extend(errors) self._apply_filters()
def _real_accounts(self, account_name, entries, begin_date=None, end_date=None): """ Returns the realization.RealAccount instances for account_name, and their entries clamped by the optional begin_date and end_date. Warning: For efficiency, the returned result does not include any added postings to account for balances at 'begin_date'. :return: realization.RealAccount instances """ entries_in_range = self._entries_in_inclusive_range(entries, begin_date=begin_date, end_date=end_date) real_accounts = realization.get(realization.realize(entries_in_range, [account_name]), account_name) return real_accounts
def load_file(self) -> None: """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = loader._load( [(self.beancount_file_path, True)], None, None, None ) else: self.all_entries, self.errors, self.options = loader.load_file( self.beancount_file_path ) self.account_types = get_account_types(self.options) self.price_map = build_price_map(self.all_entries) self.all_root_account = realization.realize( self.all_entries, self.account_types ) entries_by_type: DefaultDict[ Type[Directive], Entries ] = collections.defaultdict(list) for entry in self.all_entries: entries_by_type[type(entry)].append(entry) self.all_entries_by_type = entries_by_type self.accounts = AccountDict() for entry in entries_by_type[Open]: self.accounts.setdefault( cast(Open, entry).account ).meta = entry.meta for entry in entries_by_type[Close]: self.accounts.setdefault( cast(Close, entry).account ).close_date = entry.date self.fava_options, errors = parse_options( cast(List[Custom], entries_by_type[Custom]) ) self.errors.extend(errors) if not self._is_encrypted: self._watcher.update(*self.paths_to_watch()) for mod in MODULES: getattr(self, mod).load_file() self.filters = Filters(self.options, self.fava_options) self.filter(True)
def _apply_filters(self): self.entries = self.all_entries for filter_class in self.filters.values(): self.entries = filter_class.apply(self.entries, self.options) self.root_account = realization.realize(self.entries, self.account_types) self.date_first, self.date_last = getters.get_min_max_dates(self.entries, (Transaction)) if self.date_last: self.date_last = self.date_last + datetime.timedelta(1) if self.filters["time"]: self.date_first = self.filters["time"].begin_date self.date_last = self.filters["time"].end_date
def test_simple_realize(self, entries, errors, options_map): """ 2013-05-01 open Assets:US:Checking:Sub USD 2013-05-01 open Expenses:Stuff 2013-05-02 txn "Testing!" Assets:US:Checking:Sub 100 USD Expenses:Stuff -100 USD """ real_root = realization.realize(entries) for real_account in realization.iter_children(real_root): assert isinstance(real_account, realization.RealAccount) for account_name in ['Assets:US:Checking:Sub', 'Expenses:Stuff']: real_account = realization.get(real_root, account_name) self.assertEqual(account_name, real_account.account)
def _apply_filters(self): self.entries = self.all_entries for filter in self.filters.values(): self.entries = filter.apply(self.entries, self.options) self.root_account = realization.realize(self.entries, self.account_types) self.date_first, self.date_last = \ getters.get_min_max_dates(self.entries, (Transaction)) if self.filters['time']: self.date_first = self.filters['time'].begin_date self.date_last = self.filters['time'].end_date
def interval_balances(self, interval, account_name, accumulate=False): """accumulate is False for /changes and True for /balances""" min_accounts = [account for account in self.all_accounts if account.startswith(account_name)] interval_tuples = list(reversed(self._interval_tuples(interval))) interval_balances = [ realization.realize( list(iter_entry_dates(self.entries, self.date_first if accumulate else begin_date, end_date)), min_accounts, ) for begin_date, end_date in interval_tuples ] return interval_balances, interval_tuples
def test_tree_cap(example_ledger): closing_entries = summarize.cap_opt(example_ledger.entries, example_ledger.options) real_account = realization.realize(closing_entries) tree = Tree(example_ledger.entries) tree.cap(example_ledger.options, 'Unrealized') for account in realization.iter_children(real_account): name = account.account node = tree[name] if not name: continue if name.startswith('Expenses') or name.startswith('Income'): continue _compare_inv_and_counter(account.balance, node.balance)
def _real_account(account_name, entries, begin_date=None, end_date=None, min_accounts=None): """ Returns the realization.RealAccount instances for account_name, and their entries clamped by the optional begin_date and end_date. Warning: For efficiency, the returned result does not include any added postings to account for balances at 'begin_date'. :return: realization.RealAccount instances """ if begin_date: entries = list(iter_entry_dates(entries, begin_date, end_date)) if not min_accounts: min_accounts = [account_name] return realization.get(realization.realize(entries, min_accounts), account_name)
def interval_balances(self, interval, account_name, accumulate=False): """accumulate is False for /changes and True for /balances""" min_accounts = [account for account in self.all_accounts if account.startswith(account_name)] interval_tuples = list(reversed(self._interval_tuples(interval))) interval_balances = [ realization.realize(list(iter_entry_dates( self.entries, self.date_first if accumulate else begin_date, end_date)), min_accounts) for begin_date, end_date in interval_tuples] return interval_balances, interval_tuples
def test_is_account_active(self, entries, _, __): """ 2014-01-01 open Assets:Inactive 2014-01-01 open Assets:Active 2014-01-01 open Equity:Other 2014-07-04 * Assets:Active 1 USD Equity:Other """ real_root = realization.realize(entries) self.assertFalse(tree_table.is_account_active( realization.get(real_root, 'Assets:Inactive'))) self.assertTrue(tree_table.is_account_active( realization.get(real_root, 'Assets:Active')))
def load_file(self): """Load self.beancount_file_path and compute things that are independent of how the entries might be filtered later""" # use the internal function to disable cache if not self.is_encrypted: self.all_entries, self.errors, self.options = loader._load( [(self.beancount_file_path, True)], None, None, None ) include_path = os.path.dirname(self.beancount_file_path) self.watcher.update( self.options["include"], [os.path.join(include_path, path) for path in self.options["documents"]] ) else: self.all_entries, self.errors, self.options = loader.load_file(self.beancount_file_path) self.price_map = prices.build_price_map(self.all_entries) self.account_types = options.get_account_types(self.options) self.title = self.options["title"] if self.options["render_commas"]: self._format_string = "{:,f}" self._default_format_string = "{:,.2f}" else: self._format_string = "{:f}" self._default_format_string = "{:.2f}" self.active_years = list(getters.get_active_years(self.all_entries)) self.active_tags = list(getters.get_all_tags(self.all_entries)) self.active_payees = list(getters.get_all_payees(self.all_entries)) self.queries = _filter_entries_by_type(self.all_entries, Query) self.custom_entries = _filter_entries_by_type(self.all_entries, Custom) self.all_root_account = realization.realize(self.all_entries, self.account_types) self.all_accounts = _list_accounts(self.all_root_account) self.all_accounts_active = _list_accounts(self.all_root_account, active_only=True) self.fava_options, errors = parse_options(self.custom_entries) self.errors.extend(errors) self.sidebar_links = _sidebar_links(self.custom_entries) self.upcoming_events = _upcoming_events(self.all_entries, self.fava_options["upcoming-events"]) self.budgets, errors = parse_budgets(self.custom_entries) self.errors.extend(errors) self._apply_filters()
def monthly_income_expenses_totals(self): month_tuples = self._interval_tuples('month', self.entries) monthly_totals = [] for begin_date, end_date in month_tuples: entries = self._entries_in_inclusive_range(self.entries, begin_date, end_date) realized = realization.realize(entries, self.account_types) income_totals = self._table_totals(realization.get(realized, self.account_types.income)) expenses_totals = self._table_totals(realization.get(realized, self.account_types.expenses)) monthly_totals.append({ 'begin_date': begin_date, 'end_date': end_date, 'income_totals': income_totals, 'expenses_totals': expenses_totals }) return monthly_totals
def load_file(self): """Load the main file and all included files and set attributes.""" # use the internal function to disable cache if not self._is_encrypted: # pylint: disable=protected-access self.all_entries, self.errors, self.options = \ loader._load([(self.beancount_file_path, True)], None, None, None) self.account_types = get_account_types(self.options) self._watcher.update(*self.paths_to_watch()) else: self.all_entries, self.errors, self.options = \ loader.load_file(self.beancount_file_path) self.account_types = get_account_types(self.options) self.price_map = prices.build_price_map(self.all_entries) self.all_root_account = realization.realize(self.all_entries, self.account_types) entries_by_type = collections.defaultdict(list) for entry in self.all_entries: entries_by_type[type(entry)].append(entry) self.all_entries_by_type = entries_by_type self.accounts = _AccountDict() for entry in entries_by_type[Open]: self.accounts.setdefault(entry.account).meta = entry.meta for entry in entries_by_type[Close]: self.accounts.setdefault(entry.account).close_date = entry.date self.fava_options, errors = parse_options(entries_by_type[Custom]) self.errors.extend(errors) for mod in MODULES: getattr(self, mod).load_file() self._filters = { 'account': AccountFilter(self.options, self.fava_options), 'filter': AdvancedFilter(self.options, self.fava_options), 'time': TimeFilter(self.options, self.fava_options), } self.filter(True)
def _real_accounts(self, account_name, begin_date=None, end_date=None): """ Returns the realization.RealAccount instances for account_name, and their entries clamped by the optional begin_date and end_date. Returns: realization.RealAccount instances """ begin_date_, end_date_ = getters.get_min_max_dates(self.entries, (Transaction)) if begin_date: begin_date_ = begin_date if end_date: end_date_ = end_date entries, index = summarize.clamp_opt(self.entries, begin_date_, end_date_ + timedelta(days=1), self.options_map) real_accounts = realization.get(realization.realize(entries, self.account_types), account_name) return real_accounts
def closing_balances(self, account_name): closing_entries = summarize.cap_opt(self.entries, self.options) return realization.get_or_create(realization.realize(closing_entries), account_name)
def _real_account(account_name, entries, begin_date, end_date): if begin_date: entries = list(iter_entry_dates(entries, begin_date, end_date)) return realization.get_or_create(realization.realize(entries), account_name)