From 9dddd5f514bc2b5f29311df02651229a2e56e5b0 Mon Sep 17 00:00:00 2001 From: Johannes Harms Date: Mon, 30 Apr 2018 20:01:38 +0200 Subject: [PATCH] refs #30 reads default value for known `account` from the importer instance. --- smart_importer/predict_postings.py | 14 ++++++ smart_importer/tests/predict_payees_test.py | 44 +++++++++---------- smart_importer/tests/predict_postings_test.py | 44 +++++++++---------- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/smart_importer/predict_postings.py b/smart_importer/predict_postings.py index 6fc2c68..a454e8d 100644 --- a/smart_importer/predict_postings.py +++ b/smart_importer/predict_postings.py @@ -76,14 +76,28 @@ def patched_extract_function(self, original_extract_function): @wraps(original_extract_function) def wrapper(self, file, existing_entries=None): + + # read the importer's existing entries, if provided as argument to its `extract` method: decorator.existing_entries = existing_entries + # read the importer's `extract`ed entries logger.debug(f"About to call the importer's extract function to receive entries to be imported...") if 'existing_entries' in inspect.signature(original_extract_function).parameters: decorator.imported_transactions = original_extract_function(self, file, existing_entries) else: decorator.imported_transactions = original_extract_function(self, file) + # read the importer's file_account, to be used as default value for the decorator's known `account`: + if inspect.ismethod(self.file_account) and not decorator.account: + logger.debug("Trying to read the importer's file_account, " + "to be used as default value for the decorator's `account` argument...") + file_account = self.file_account(file) + if file_account: + decorator.account = file_account + logger.debug(f"Read file_account {file_account} from the importer; " + f"using it as known account in the decorator.") + else: + logger.debug(f"Could not retrieve file_account from the importer.") return decorator.enhance_transactions() diff --git a/smart_importer/tests/predict_payees_test.py b/smart_importer/tests/predict_payees_test.py index 0195c3d..5235f44 100644 --- a/smart_importer/tests/predict_payees_test.py +++ b/smart_importer/tests/predict_payees_test.py @@ -7,6 +7,7 @@ from beancount.core.data import Transaction from beancount.ingest.importer import ImporterProtocol from beancount.parser import parser + from smart_importer import machinelearning_helpers as ml from smart_importer.predict_payees import PredictPayees @@ -97,10 +98,13 @@ class Testdata: ] -class BasicImporter(ImporterProtocol): +class BasicTestImporter(ImporterProtocol): def extract(self, file, existing_entries=None): return Testdata.test_data + def file_account(self, file): + return Testdata.known_account + class PredictPayeesTest(unittest.TestCase): ''' @@ -118,11 +122,11 @@ def setUp(self): account="Assets:US:BofA:Checking", overwrite_existing_payees=False ) - class DecoratedImporter(BasicImporter): + class DecoratedTestImporter(BasicTestImporter): pass - self.importerClass = DecoratedImporter - self.importer = DecoratedImporter() + self.importerClass = DecoratedTestImporter + self.importer = DecoratedTestImporter() def test_dummy_importer(self): ''' @@ -192,11 +196,11 @@ def test_class_decoration_with_arguments(self): training_data=Testdata.training_data, account=Testdata.known_account ) - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): pass - i = SmartImporter() - self.assertIsInstance(i, SmartImporter, + i = SmartTestImporter() + self.assertIsInstance(i, SmartTestImporter, 'The decorated importer shall still be an instance of the undecorated class.') transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_payees = [transaction.payee for transaction in transactions] @@ -210,24 +214,20 @@ def test_method_decoration_with_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) testcase = self - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): @PredictPayees( training_data=Testdata.training_data, account=Testdata.known_account ) def extract(self, file, existing_entries=None): - testcase.assertIsInstance(self, SmartImporter) + testcase.assertIsInstance(self, SmartTestImporter) return super().extract(file, existing_entries=existing_entries) - i = SmartImporter() + i = SmartTestImporter() transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_payees = [transaction.payee for transaction in transactions] self.assertEqual(predicted_payees, Testdata.correct_predictions) - # TODO: implement reasonable defaults to fix this test case: - @unittest.skip( - "smart imports without arguments currently fail " - "because the already known account is not filtered from the training data") def test_class_decoration_without_arguments(self): ''' Verifies that the decorator can be applied to importer classes, @@ -236,19 +236,15 @@ def test_class_decoration_without_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) @PredictPayees() - class SmartImporter(BasicImporter): pass + class SmartTestImporter(BasicTestImporter): pass - i = SmartImporter() - self.assertIsInstance(i, SmartImporter, + i = SmartTestImporter() + self.assertIsInstance(i, SmartTestImporter, 'The decorated importer shall still be an instance of the undecorated class.') transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_payees = [transaction.payee for transaction in transactions] self.assertEqual(predicted_payees, Testdata.correct_predictions) - # TODO: implement reasonable defaults to fix this test case: - @unittest.skip( - "smart imports without arguments currently fail " - "because the already known account is not filtered from the training data") def test_method_decoration_without_arguments(self): ''' Verifies that the decorator can be applied to an importer's extract method, @@ -257,13 +253,13 @@ def test_method_decoration_without_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) testcase = self - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): @PredictPayees() def extract(self, file, existing_entries=None): - testcase.assertIsInstance(self, SmartImporter) + testcase.assertIsInstance(self, SmartTestImporter) return super().extract(file, existing_entries=existing_entries) - i = SmartImporter() + i = SmartTestImporter() transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_payees = [transaction.payee for transaction in transactions] self.assertEqual(predicted_payees, Testdata.correct_predictions) diff --git a/smart_importer/tests/predict_postings_test.py b/smart_importer/tests/predict_postings_test.py index d3ba062..25b5c95 100644 --- a/smart_importer/tests/predict_postings_test.py +++ b/smart_importer/tests/predict_postings_test.py @@ -7,6 +7,7 @@ from beancount.core.data import Transaction from beancount.ingest.importer import ImporterProtocol from beancount.parser import parser + from smart_importer.predict_postings import PredictPostings LOG_LEVEL = logging.DEBUG @@ -93,10 +94,13 @@ class Testdata: ] -class BasicImporter(ImporterProtocol): +class BasicTestImporter(ImporterProtocol): def extract(self, file, existing_entries=None): return Testdata.test_data + def file_account(self, file): + return Testdata.known_account + class PredictPostingsTest(unittest.TestCase): ''' @@ -113,11 +117,11 @@ def setUp(self): training_data=Testdata.training_data, account="Assets:US:BofA:Checking" ) - class DecoratedImporter(BasicImporter): + class DecoratedTestImporter(BasicTestImporter): pass - self.importerClass = DecoratedImporter - self.importer = DecoratedImporter() + self.importerClass = DecoratedTestImporter + self.importer = DecoratedTestImporter() def test_unchanged_narrations(self): ''' @@ -178,11 +182,11 @@ def test_class_decoration_with_arguments(self): training_data=Testdata.training_data, account=Testdata.known_account ) - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): pass - i = SmartImporter() - self.assertIsInstance(i, SmartImporter, + i = SmartTestImporter() + self.assertIsInstance(i, SmartTestImporter, 'The decorated importer shall still be an instance of the undecorated class.') transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_accounts = [entry.postings[-1].account for entry in transactions] @@ -196,24 +200,20 @@ def test_method_decoration_with_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) testcase = self - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): @PredictPostings( training_data=Testdata.training_data, account=Testdata.known_account ) def extract(self, file, existing_entries=None): - testcase.assertIsInstance(self, SmartImporter) + testcase.assertIsInstance(self, SmartTestImporter) return super().extract(file, existing_entries=existing_entries) - i = SmartImporter() + i = SmartTestImporter() transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_accounts = [entry.postings[-1].account for entry in transactions] self.assertEqual(predicted_accounts, Testdata.correct_predictions) - # TODO: implement reasonable defaults to fix this test case: - @unittest.skip( - "smart imports without arguments currently fail " - "because the already known account is not filtered from the training data") def test_class_decoration_with_empty_arguments(self): ''' Verifies that the decorator can be applied to importer classes, @@ -222,19 +222,15 @@ def test_class_decoration_with_empty_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) @PredictPostings() - class SmartImporter(BasicImporter): pass + class SmartTestImporter(BasicTestImporter): pass - i = SmartImporter() - self.assertIsInstance(i, SmartImporter, + i = SmartTestImporter() + self.assertIsInstance(i, SmartTestImporter, 'The decorated importer shall still be an instance of the undecorated class.') transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_accounts = [transaction.postings[-1].account for transaction in transactions] self.assertEqual(predicted_accounts, Testdata.correct_predictions) - # TODO: implement reasonable defaults to fix this test case: - @unittest.skip( - "smart imports without arguments currently fail " - "because the already known account is not filtered from the training data") def test_method_decoration_with_empty_arguments(self): ''' Verifies that the decorator can be applied to an importer's extract method, @@ -243,13 +239,13 @@ def test_method_decoration_with_empty_arguments(self): logger.info("Running Test Case: {id}".format(id=self.id().split('.')[-1])) testcase = self - class SmartImporter(BasicImporter): + class SmartTestImporter(BasicTestImporter): @PredictPostings() def extract(self, file, existing_entries=None): - testcase.assertIsInstance(self, SmartImporter) + testcase.assertIsInstance(self, SmartTestImporter) return super().extract(file, existing_entries=existing_entries) - i = SmartImporter() + i = SmartTestImporter() transactions = i.extract('file', existing_entries=Testdata.training_data) predicted_accounts = [entry.postings[-1].account for entry in transactions] self.assertEqual(predicted_accounts, Testdata.correct_predictions)