books: All tools use books.Loader.dispatch() and LoadResult.
This is significant code deduplication, as seen in the diffstat.
This commit is contained in:
		
							parent
							
								
									3721226d17
								
							
						
					
					
						commit
						ee2bd6c096
					
				
					 10 changed files with 67 additions and 217 deletions
				
			
		|  | @ -371,23 +371,17 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|             for post in rec.statement_posts | ||||
|         ) + datetime.timedelta(days=1) | ||||
| 
 | ||||
|     returncode = os.EX_OK | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         date_fuzz = datetime.timedelta(days=args.date_fuzz) | ||||
|         entries, load_errors, options = books_loader.load_fy_range( | ||||
|             args.start_date - date_fuzz, args.stop_date + date_fuzz, | ||||
|         ) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
|     date_fuzz = datetime.timedelta(days=args.date_fuzz) | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), | ||||
|         args.start_date - date_fuzz, | ||||
|         args.stop_date + date_fuzz, | ||||
|     ) | ||||
|     books_load.print_errors(stderr) | ||||
|     returncode = books_load.returncode() | ||||
| 
 | ||||
|     date_range = DateRange(args.start_date, args.stop_date) | ||||
|     for bean_post in data.Posting.from_entries(entries): | ||||
|     for bean_post in books_load.iter_postings(): | ||||
|         if bean_post.account != args.account: | ||||
|             continue | ||||
|         paypal_post = PayPalPosting.from_books(bean_post) | ||||
|  |  | |||
|  | @ -406,19 +406,12 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|     rec_range = DateRange(args.start_date, args.stop_date) | ||||
|     post_range = DateRange(args.stop_date, args.stop_date + days_diff) | ||||
| 
 | ||||
|     returncode = os.EX_OK | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         entries, load_errors, options = books_loader.load_fy_range(pre_range.start, post_range.stop) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), pre_range.start, post_range.stop, | ||||
|     ) | ||||
|     books_load.load_account_metadata() | ||||
|     returncode = books_load.returncode() | ||||
| 
 | ||||
|     data.Account.load_from_books(entries, options) | ||||
|     real_accounts: Set[data.Account] = set() | ||||
|     for account_spec in args.accounts: | ||||
|         new_accounts = frozenset( | ||||
|  | @ -432,14 +425,10 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|             logger.critical("account %r did not match any open accounts", account_spec) | ||||
|             return 2 | ||||
| 
 | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
|     books_load.print_errors(stderr) | ||||
|     rt_wrapper = config.rt_wrapper() | ||||
|     if rt_wrapper is None: | ||||
|         logger.warning("could not initialize RT client; spreadsheet links will be broken") | ||||
|     postings = data.Posting.from_entries(entries) | ||||
|     for search_term in args.search_terms: | ||||
|         postings = search_term.filter_postings(postings) | ||||
| 
 | ||||
|     report = StatementReconciliation( | ||||
|         rt_wrapper, | ||||
|  | @ -450,7 +439,7 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|         args.statement_metadata_key, | ||||
|         args.id_metadata_key, | ||||
|     ) | ||||
|     report.write(postings) | ||||
|     report.write(books_load.iter_postings((), args.search_terms)) | ||||
|     if args.output_file is None: | ||||
|         out_dir_path = config.repository_path() or Path() | ||||
|         args.output_file = out_dir_path / 'ReconciliationReport_{}_{}.ods'.format( | ||||
|  |  | |||
|  | @ -624,15 +624,6 @@ class ReportType(enum.Enum): | |||
|     OUTGOINGS = OUTGOING | ||||
| 
 | ||||
| 
 | ||||
| def filter_search(postings: Iterable[data.Posting], | ||||
|                   search_terms: Iterable[cliutil.SearchTerm], | ||||
| ) -> Iterable[data.Posting]: | ||||
|     accounts = tuple(AccrualAccount.account_names()) | ||||
|     postings = (post for post in postings if post.account.is_under(*accounts)) | ||||
|     for query in search_terms: | ||||
|         postings = query.filter_postings(postings) | ||||
|     return postings | ||||
| 
 | ||||
| def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace: | ||||
|     parser = argparse.ArgumentParser(prog=PROGNAME) | ||||
|     cliutil.add_version_argument(parser) | ||||
|  | @ -705,37 +696,22 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|         config = configmod.Config() | ||||
|         config.load_file() | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, _ = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         load_since = None if args.report_type == ReportType.AGING else args.since | ||||
|         entries, load_errors, _ = books_loader.load_all(load_since) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     filters.remove_opening_balance_txn(entries) | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
| 
 | ||||
|     stop_date = args.stop_date or datetime.date(datetime.MAXYEAR, 12, 31) | ||||
|     postings_src: Iterator[data.Posting] = ( | ||||
|         posting | ||||
|         for posting in data.Posting.from_entries(entries) | ||||
|         if posting.meta.date < stop_date | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), | ||||
|         None if args.report_type is ReportType.AGING else args.since, | ||||
|     ) | ||||
|     books_load.print_errors(stderr) | ||||
|     returncode = books_load.returncode() | ||||
| 
 | ||||
|     filters.remove_opening_balance_txn(books_load.entries) | ||||
|     stop_date = args.stop_date or datetime.date(datetime.MAXYEAR, 12, 31) | ||||
|     accrual_accounts = tuple(AccrualAccount.account_names()) | ||||
|     postings = list( | ||||
|         post | ||||
|         for post in books_load.iter_postings(args.rewrite_rules, args.search_terms) | ||||
|         if post.meta.date < stop_date | ||||
|         and post.account.is_under(*accrual_accounts) | ||||
|     ) | ||||
|     for rewrite_path in args.rewrite_rules: | ||||
|         try: | ||||
|             ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path) | ||||
|         except ValueError as error: | ||||
|             logger.critical("failed loading rewrite rules from %s: %s", | ||||
|                             rewrite_path, error.args[0]) | ||||
|             return cliutil.ExitCode.RewriteRulesError | ||||
|         postings_src = ruleset.rewrite(postings_src) | ||||
|     postings = list(filter_search(postings_src, args.search_terms)) | ||||
|     if not postings: | ||||
|         logger.warning("no matching entries found to report") | ||||
|         returncode = returncode or cliutil.ExitCode.NoDataFiltered | ||||
|  |  | |||
|  | @ -523,34 +523,17 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|     if args.start_date is None: | ||||
|         args.start_date = cliutil.diff_year(args.stop_date, -1) | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options_map = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         start_fy = config.fiscal_year_begin().for_date(args.start_date) - 1 | ||||
|         entries, load_errors, options_map = books_loader.load_fy_range(start_fy, args.stop_date) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
| 
 | ||||
|     data.Account.load_from_books(entries, options_map) | ||||
|     postings = data.Posting.from_entries(entries) | ||||
|     for rewrite_path in args.rewrite_rules: | ||||
|         try: | ||||
|             ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path) | ||||
|         except ValueError as error: | ||||
|             logger.critical("failed loading rewrite rules from %s: %s", | ||||
|                             rewrite_path, error.args[0]) | ||||
|             return cliutil.ExitCode.RewriteRulesError | ||||
|         postings = ruleset.rewrite(postings) | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), | ||||
|         config.fiscal_year_begin().for_date(args.start_date) - 1, | ||||
|         args.stop_date, | ||||
|     ) | ||||
|     books_load.print_errors(stderr) | ||||
|     books_load.load_account_metadata() | ||||
|     returncode = books_load.returncode() | ||||
| 
 | ||||
|     balances = core.Balances( | ||||
|         postings, | ||||
|         books_load.iter_postings(args.rewrite_rules), | ||||
|         args.start_date, | ||||
|         args.stop_date, | ||||
|         'expense-type', | ||||
|  |  | |||
|  | @ -214,37 +214,15 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|         config = configmod.Config() | ||||
|         config.load_file() | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options_map = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         entries, load_errors, options_map = books_loader.load_fy_range( | ||||
|             args.start_date, args.stop_date, | ||||
|         ) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
| 
 | ||||
|     data.Account.load_from_books(entries, options_map) | ||||
|     postings = data.Posting.from_entries(entries) | ||||
|     for search_term in args.search_terms: | ||||
|         postings = search_term.filter_postings(postings) | ||||
|     for rewrite_path in args.rewrite_rules: | ||||
|         try: | ||||
|             ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path) | ||||
|         except ValueError as error: | ||||
|             logger.critical("failed loading rewrite rules from %s: %s", | ||||
|                             rewrite_path, error.args[0]) | ||||
|             return cliutil.ExitCode.RewriteRulesError | ||||
|         postings = ruleset.rewrite(postings) | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), args.start_date, args.stop_date, | ||||
|     ) | ||||
|     books_load.print_errors(stderr) | ||||
|     books_load.load_account_metadata() | ||||
|     returncode = books_load.returncode() | ||||
| 
 | ||||
|     balances = Balances( | ||||
|         postings, | ||||
|         books_load.iter_postings(args.rewrite_rules, args.search_terms), | ||||
|         args.start_date, | ||||
|         args.stop_date, | ||||
|         'expense-type', | ||||
|  |  | |||
|  | @ -383,36 +383,17 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|     if args.start_date is None: | ||||
|         args.start_date = cliutil.diff_year(args.stop_date, -1) | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         entries, load_errors, options = books_loader.load_fy_range(args.start_date, args.stop_date) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
| 
 | ||||
|     data.Account.load_from_books(entries, options) | ||||
|     postings = iter( | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), args.start_date, args.stop_date, | ||||
|     ) | ||||
|     books_load.print_errors(stderr) | ||||
|     returncode = books_load.returncode() | ||||
|     books_load.load_account_metadata() | ||||
|     postings = ( | ||||
|         post | ||||
|         for post in data.Posting.from_entries(entries) | ||||
|         for post in books_load.iter_postings(args.rewrite_rules, args.search_terms) | ||||
|         if post.meta.date < args.stop_date | ||||
|     ) | ||||
|     for rewrite_path in args.rewrite_rules: | ||||
|         try: | ||||
|             ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path) | ||||
|         except ValueError as error: | ||||
|             logger.critical("failed loading rewrite rules from %s: %s", | ||||
|                             rewrite_path, error.args[0]) | ||||
|             return cliutil.ExitCode.RewriteRulesError | ||||
|         postings = ruleset.rewrite(postings) | ||||
|     for search_term in args.search_terms: | ||||
|         postings = search_term.filter_postings(postings) | ||||
|     balances = core.Balances(postings, args.start_date, args.stop_date, 'project') | ||||
|     funds = sorted(balances.meta_values(), key=lambda s: locale.strxfrm(s.casefold())) | ||||
|     if not funds: | ||||
|  |  | |||
|  | @ -827,32 +827,12 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|     elif args.stop_date is None: | ||||
|         args.stop_date = cliutil.diff_year(args.start_date, 1) | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, options = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         entries, load_errors, options = books_loader.load_fy_range(args.start_date, args.stop_date) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
| 
 | ||||
|     data.Account.load_from_books(entries, options) | ||||
|     postings = data.Posting.from_entries(entries) | ||||
|     for rewrite_path in args.rewrite_rules: | ||||
|         try: | ||||
|             ruleset = rewrite.RewriteRuleset.from_yaml(rewrite_path) | ||||
|         except ValueError as error: | ||||
|             logger.critical("failed loading rewrite rules from %s: %s", | ||||
|                             rewrite_path, error.args[0]) | ||||
|             return cliutil.ExitCode.RewriteRulesError | ||||
|         postings = ruleset.rewrite(postings) | ||||
|     for search_term in args.search_terms: | ||||
|         postings = search_term.filter_postings(postings) | ||||
|     books_load = books.Loader.dispatch( | ||||
|         config.books_loader(), args.start_date, args.stop_date, | ||||
|     ) | ||||
|     returncode = books_load.returncode() | ||||
|     books_load.print_errors(stderr) | ||||
|     books_load.load_account_metadata() | ||||
| 
 | ||||
|     rt_wrapper = config.rt_wrapper() | ||||
|     if rt_wrapper is None: | ||||
|  | @ -889,7 +869,7 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|         logger.error("%s: %r", *error.args) | ||||
|         return 2 | ||||
|     report.set_common_properties(config.books_repo()) | ||||
|     report.write(postings) | ||||
|     report.write(books_load.iter_postings(args.rewrite_rules, args.search_terms)) | ||||
|     if not any(report.account_groups.values()): | ||||
|         logger.warning("no matching postings found to report") | ||||
|         returncode = returncode or cliutil.ExitCode.NoDataFiltered | ||||
|  |  | |||
|  | @ -162,22 +162,12 @@ def main(arglist: Optional[Sequence[str]]=None, | |||
|     if isinstance(args.as_of_date, int): | ||||
|         args.as_of_date = fy.first_date(args.as_of_date) | ||||
| 
 | ||||
|     returncode = 0 | ||||
|     books_loader = config.books_loader() | ||||
|     if books_loader is None: | ||||
|         entries, load_errors, _ = books.Loader.load_none(config.config_file_path()) | ||||
|         returncode = cliutil.ExitCode.NoConfiguration | ||||
|     else: | ||||
|         entries, load_errors, _ = books_loader.load_fy_range(0, args.as_of_date) | ||||
|         if load_errors: | ||||
|             returncode = cliutil.ExitCode.BeancountErrors | ||||
|         elif not entries: | ||||
|             returncode = cliutil.ExitCode.NoDataLoaded | ||||
|     for error in load_errors: | ||||
|         bc_printer.print_error(error, file=stderr) | ||||
|     books_load = books.Loader.dispatch(config.books_loader(), 0, args.as_of_date) | ||||
|     returncode = books_load.returncode() | ||||
|     books_load.print_errors(stderr) | ||||
| 
 | ||||
|     inventories: Mapping[AccountWithFund, Inventory] = collections.defaultdict(Inventory) | ||||
|     for post in Posting.from_entries(entries): | ||||
|     for post in books_load.iter_postings(): | ||||
|         if post.meta.date >= args.as_of_date: | ||||
|             continue | ||||
|         account = post.account | ||||
|  |  | |||
|  | @ -221,27 +221,6 @@ def check_aging_ods(ods_file, date, recv_rows=AGING_AR, pay_rows=AGING_AP): | |||
|     check_aging_sheet(sheets[0], recv_rows, date) | ||||
|     check_aging_sheet(sheets[1], pay_rows, date) | ||||
| 
 | ||||
| @pytest.mark.parametrize('search_terms,expect_count,check_func', [ | ||||
|     ([], ACCRUALS_COUNT, lambda post: post.account.is_under( | ||||
|         'Assets:Receivable:', 'Liabilities:Payable:', | ||||
|     )), | ||||
|     ([('rt-id', '^rt:505$')], 2, lambda post: post.meta['entity'] == 'DonorA'), | ||||
|     ([('invoice', r'^rt:\D+515/')], 1, lambda post: post.meta['entity'] == 'MatchingProgram'), | ||||
|     ([('entity', '^Lawyer$')], 3, lambda post: post.meta['rt-id'] == 'rt:510'), | ||||
|     ([('entity', '^Lawyer$'), ('contract', '^rt:510/')], 2, | ||||
|      lambda post: post.meta['invoice'].startswith('rt:510/')), | ||||
|     ([('rt-id', '^rt:510$'), ('approval', '.')], 0, lambda post: False), | ||||
| ]) | ||||
| def test_filter_search(accrual_postings, search_terms, expect_count, check_func): | ||||
|     search_terms = [cliutil.SearchTerm._make(query) for query in search_terms] | ||||
|     actual = list(accrual.filter_search(accrual_postings, search_terms)) | ||||
|     if expect_count < ACCRUALS_COUNT: | ||||
|         assert ACCRUALS_COUNT > len(actual) >= expect_count | ||||
|     else: | ||||
|         assert len(actual) == ACCRUALS_COUNT | ||||
|     for post in actual: | ||||
|         assert check_func(post) | ||||
| 
 | ||||
| @pytest.mark.parametrize('acct_name,invoice,day', testutil.combine_values( | ||||
|     INVOICE_ACCOUNTS, | ||||
|     ['FIXME', '', None, *testutil.NON_STRING_METADATA_VALUES], | ||||
|  |  | |||
|  | @ -238,7 +238,7 @@ class TestBooksLoader(books.Loader): | |||
|         self.source = source | ||||
| 
 | ||||
|     def load_all(self, from_year=None): | ||||
|         return bc_loader.load_file(self.source) | ||||
|         return books.LoadResult._make(bc_loader.load_file(self.source)) | ||||
| 
 | ||||
|     def load_fy_range(self, from_fy, to_fy=None): | ||||
|         return self.load_all() | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Brett Smith
						Brett Smith